diff options
27 files changed, 417 insertions, 131 deletions
diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index adb67b56d..f54eb1ab3 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -198,6 +198,18 @@ instructions: property: target.compiler equals_value: ICC_18 - type: EnvironmentVariable + variableName: PYTHON3_PATH + variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}}" + enable_if: + condition: and + conditions: + - condition: property + property: host.os + equals_value: Windows + - condition: property + property: host.arch + equals_value: X86_64 + - type: EnvironmentVariable variableName: ICC64_18_PATH # Seems a bit hard to maintain variableValue: /opt/intel/compilers_and_libraries_2018.1.163/linux/bin/intel64:/opt/intel/bin enable_if: diff --git a/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml b/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml new file mode 100644 index 000000000..51bf3ef12 --- /dev/null +++ b/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtGraphs + +ApplicationWindow { + visible: true + width: 1000 + height: 800 + title: "Data from the microphone (" + device_name + ")" + + GraphsView { + id: graph + anchors.fill: parent + + LineSeries { + id: audio_series + width: 2 + color: "#007acc" + } + + axisX: ValueAxis { + min: 0 + max: 2000 + tickInterval : 500 + labelFormat: "%g" + titleText: "Samples" + } + + axisY: ValueAxis { + min: -1 + max: 1 + tickInterval : 0.5 + labelFormat: "%0.1f" + titleText: "Audio level" + } + } + + Connections { + target: audio_bridge + function onDataUpdated(buffer) { + audio_series.clear() + for (let i = 0; i < buffer.length; ++i) { + audio_series.append(buffer[i]) + } + } + } +} diff --git a/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir b/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir new file mode 100644 index 000000000..cc5408a66 --- /dev/null +++ b/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir @@ -0,0 +1,2 @@ +module GraphsAudio +Main 1.0 Main.qml diff --git a/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst b/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst new file mode 100644 index 000000000..f19b28caf --- /dev/null +++ b/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst @@ -0,0 +1,8 @@ +GraphsAudio Example +=================== + +This example shows the drawing of dynamic data (microphone input) using QtGraphs and Qml. + +.. image:: graphsaudio.webp + :width: 400 + :alt: GraphsAudio Screenshot diff --git a/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp Binary files differnew file mode 100644 index 000000000..bb57b18e5 --- /dev/null +++ b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp diff --git a/examples/graphs/2d/graphsaudio/graphsaudio.pyproject b/examples/graphs/2d/graphsaudio/graphsaudio.pyproject new file mode 100644 index 000000000..eff791919 --- /dev/null +++ b/examples/graphs/2d/graphsaudio/graphsaudio.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "GraphsAudio/Main.qml", "GraphsAudio/qmldir"] +} diff --git a/examples/graphs/2d/graphsaudio/main.py b/examples/graphs/2d/graphsaudio/main.py new file mode 100644 index 000000000..239aee036 --- /dev/null +++ b/examples/graphs/2d/graphsaudio/main.py @@ -0,0 +1,80 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations + +import sys +from pathlib import Path +from PySide6.QtCore import QObject, QPointF, Slot, Signal +from PySide6.QtMultimedia import QAudioFormat, QAudioSource, QMediaDevices +from PySide6.QtWidgets import QMessageBox +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtGui import QGuiApplication + + +SAMPLE_COUNT = 2000 +RESOLUTION = 4 + + +class Audio(QObject): + dataUpdated = Signal(list) + + def __init__(self, device): + super().__init__() + + format_audio = QAudioFormat() + format_audio.setSampleRate(8000) + format_audio.setChannelCount(1) + format_audio.setSampleFormat(QAudioFormat.UInt8) + + self.device_name = device.description() + + self._audio_input = QAudioSource(device, format_audio, self) + self._io_device = self._audio_input.start() + self._io_device.readyRead.connect(self._readyRead) + + self._buffer = [QPointF(x, 0) for x in range(SAMPLE_COUNT)] + + def closeEvent(self, event): + if self._audio_input is not None: + self._audio_input.stop() + event.accept() + + @Slot() + def _readyRead(self): + data = self._io_device.readAll() + available_samples = data.size() // RESOLUTION + start = 0 + if (available_samples < SAMPLE_COUNT): + start = SAMPLE_COUNT - available_samples + for s in range(start): + self._buffer[s].setY(self._buffer[s + available_samples].y()) + + data_index = 0 + for s in range(start, SAMPLE_COUNT): + value = (ord(data[data_index]) - 128) / 128 + self._buffer[s].setY(value) + data_index = data_index + RESOLUTION + + self.dataUpdated.emit(self._buffer) + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + engine = QQmlApplicationEngine() + + input_devices = QMediaDevices.audioInputs() + if not input_devices: + QMessageBox.warning(None, "audio", "There is no audio input device available.") + sys.exit(-1) + + audio_bridge = Audio(input_devices[0]) + engine.rootContext().setContextProperty("audio_bridge", audio_bridge) + + device = input_devices[0] + device_name = device.description() + engine.rootContext().setContextProperty("device_name", device_name) + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("GraphsAudio", "Main") + + sys.exit(app.exec()) diff --git a/sources/pyside-tools/project_lib/pyproject_toml.py b/sources/pyside-tools/project_lib/pyproject_toml.py index fafe0d67d..6da7b455e 100644 --- a/sources/pyside-tools/project_lib/pyproject_toml.py +++ b/sources/pyside-tools/project_lib/pyproject_toml.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only from __future__ import annotations +import os import sys # TODO: Remove this import when Python 3.11 is the minimum supported version if sys.version_info >= (3, 11): @@ -48,19 +49,19 @@ def _parse_toml_content(content: str) -> dict: return result -def _write_toml_content(data: dict) -> str: +def _write_base_toml_content(data: dict) -> str: """ Write minimal TOML content with project and tool.pyside6-project sections. """ lines = [] - if 'project' in data and data['project']: + if data.get('project'): lines.append('[project]') for key, value in sorted(data['project'].items()): if isinstance(value, str): lines.append(f'{key} = "{value}"') - if 'tool' in data and 'pyside6-project' in data['tool']: + if data.get("tool") and data['tool'].get('pyside6-project'): lines.append('\n[tool.pyside6-project]') for key, value in sorted(data['tool']['pyside6-project'].items()): if isinstance(value, list): @@ -115,7 +116,7 @@ def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult: def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]): """ - Create or update a pyproject.toml file with the specified content. + Create or overwrite a pyproject.toml file with the specified content. """ data = { "project": {"name": project_name}, @@ -124,13 +125,33 @@ def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: } } + content = _write_base_toml_content(data) try: - content = _write_toml_content(data) pyproject_file.write_text(content, encoding='utf-8') except Exception as e: raise ValueError(f"Error writing TOML file: {str(e)}") +def robust_relative_to_posix(target_path: Path, base_path: Path) -> str: + """ + Calculates the relative path from base_path to target_path. + Uses Path.relative_to first, falls back to os.path.relpath if it fails. + Returns the result as a POSIX path string. + """ + # Ensure both paths are absolute for reliable calculation, although in this specific code, + # project_folder and paths in output_files are expected to be resolved/absolute already. + abs_target = target_path.resolve() if not target_path.is_absolute() else target_path + abs_base = base_path.resolve() if not base_path.is_absolute() else base_path + + try: + return abs_target.relative_to(abs_base).as_posix() + except ValueError: + # Fallback to os.path.relpath which is more robust for paths that are not direct subpaths. + relative_str = os.path.relpath(str(abs_target), str(abs_base)) + # Convert back to Path temporarily to get POSIX format + return Path(relative_str).as_posix() + + def migrate_pyproject(pyproject_file: Path | str = None) -> int: """ Migrate a project *.pyproject JSON file to the new pyproject.toml format. @@ -170,7 +191,7 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int: project_name = project_files[0].stem # The project files that will be written to the pyproject.toml file - output_files = set() + output_files: set[Path] = set() for project_file in project_files: project_data = parse_pyproject_json(project_file) if project_data.errors: @@ -185,39 +206,58 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int: project_name = project_folder.name pyproject_toml_file = project_folder / "pyproject.toml" - if pyproject_toml_file.exists(): - already_existing_file = True + + relative_files = sorted( + robust_relative_to_posix(p, project_folder) for p in output_files + ) + + if not (already_existing_file := pyproject_toml_file.exists()): + # Create new pyproject.toml file + data = { + "project": {"name": project_name}, + "tool": { + "pyside6-project": {"files": relative_files} + } + } + updated_content = _write_base_toml_content(data) + else: + # For an already existing file, append our tool.pyside6-project section + # If the project section is missing, add it try: content = pyproject_toml_file.read_text(encoding='utf-8') - data = _parse_toml_content(content) except Exception as e: - raise ValueError(f"Error parsing TOML: {str(e)}") - else: - already_existing_file = False - data = {"project": {}, "tool": {"pyside6-project": {}}} + print(f"Error processing existing TOML file: {str(e)}", file=sys.stderr) + return 1 - # Update project name if not present - if "name" not in data["project"]: - data["project"]["name"] = project_name + append_content = [] - # Update files list - data["tool"]["pyside6-project"]["files"] = sorted( - p.relative_to(project_folder).as_posix() for p in output_files - ) + if '[project]' not in content: + # Add project section if needed + append_content.append('\n[project]') + append_content.append(f'name = "{project_name}"') + + if '[tool.pyside6-project]' not in content: + # Add tool.pyside6-project section + append_content.append('\n[tool.pyside6-project]') + items = [f'"{item}"' for item in relative_files] + append_content.append(f'files = [{", ".join(items)}]') - # Generate TOML content - toml_content = _write_toml_content(data) + if append_content: + updated_content = content.rstrip() + '\n' + '\n'.join(append_content) + else: + # No changes needed + print("pyproject.toml already contains [project] and [tool.pyside6-project] sections") + return 0 - if already_existing_file: print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"") print("The file will be updated with the following content:") - print(toml_content) + print(updated_content) response = input("Proceed? [Y/n] ") if response.lower().strip() not in {"yes", "y"}: return 0 try: - pyproject_toml_file.write_text(toml_content) + pyproject_toml_file.write_text(updated_content, encoding='utf-8') except Exception as e: print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr) return 1 diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index c073c8bc1..e58d54998 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -45,7 +45,14 @@ QMetaType QVariant_resolveMetaType(PyTypeObject *type) // that has added any python fields or slots to its object layout. // See https://mail.python.org/pipermail/python-list/2009-January/520733.html if (type->tp_bases) { - for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) { + const auto size = PyTuple_Size(type->tp_bases); + Py_ssize_t i = 0; + // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject; + // it needs to use always QGraphicsItem as a QVariant type for + // QGraphicsItem::itemChange() to work. + if (qstrcmp(typeName, "QGraphicsObject*") == 0) + ++i; + for ( ; i < size; ++i) { auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i)); const QMetaType derived = QVariant_resolveMetaType(baseType); if (derived.isValid()) diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 509459ec6..a15527c03 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -3358,7 +3358,7 @@ <enum-type name="PixmapPadMode"/> </object-type> - <object-type name="QGraphicsObject" default-superclass="QGraphicsItem"/> + <object-type name="QGraphicsObject"/> <object-type name="QGraphicsOpacityEffect"/> <object-type name="QGraphicsRotation"/> <object-type name="QGraphicsScale"/> diff --git a/sources/pyside6/doc/building_from_source/windows.rst b/sources/pyside6/doc/building_from_source/windows.rst index d2510a1b6..737d045b3 100644 --- a/sources/pyside6/doc/building_from_source/windows.rst +++ b/sources/pyside6/doc/building_from_source/windows.rst @@ -19,8 +19,14 @@ Requirements .. _OpenSSL: https://sourceforge.net/projects/openssl/ .. _`Qt for Windows`: https://doc.qt.io/qt-6/windows.html -Building from source on Windows 10 ----------------------------------- +Building from source on Windows +------------------------------- + +Creating a Dev Drive +~~~~~~~~~~~~~~~~~~~~ + +We recommend using a `Dev Drive`_ for development work on Windows. This is a +special partition with a fast file system that is excluded from virus scanning. Creating a virtual environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -143,3 +149,4 @@ Remember to properly set the environment variables for Qt and PySide:: python examples\widgets\widgets\tetrix\tetrix.py .. _`uv`: https://docs.astral.sh/uv/ +.. _`Dev Drive`: https://learn.microsoft.com/en-us/windows/dev-drive/ diff --git a/sources/pyside6/libpyside/feature_select.cpp b/sources/pyside6/libpyside/feature_select.cpp index a60dd3319..2af02beca 100644 --- a/sources/pyside6/libpyside/feature_select.cpp +++ b/sources/pyside6/libpyside/feature_select.cpp @@ -283,6 +283,7 @@ static inline void SelectFeatureSetSubtype(PyTypeObject *type, int select_id) } if (!moveToFeatureSet(type, select_id)) { if (!createNewFeatureSet(type, select_id)) { + PyErr_Print(); Py_FatalError("failed to create a new feature set!"); return; } diff --git a/sources/pyside6/tests/QtWidgets/CMakeLists.txt b/sources/pyside6/tests/QtWidgets/CMakeLists.txt index 01b7d08ea..9bb2fad67 100644 --- a/sources/pyside6/tests/QtWidgets/CMakeLists.txt +++ b/sources/pyside6/tests/QtWidgets/CMakeLists.txt @@ -84,6 +84,7 @@ PYSIDE_TEST(qapp_issue_585.py) PYSIDE_TEST(qapp_test.py) PYSIDE_TEST(qapplication_test.py) PYSIDE_TEST(qapplication_exit_segfault_test.py) +PYSIDE_TEST(pyside3069.py) PYSIDE_TEST(qdialog_test.py) PYSIDE_TEST(qdynamic_signal.py) # TODO: This passes, but requires manual button clicking (at least on mac) diff --git a/sources/pyside6/tests/QtWidgets/pyside3069.py b/sources/pyside6/tests/QtWidgets/pyside3069.py new file mode 100644 index 000000000..62ad73038 --- /dev/null +++ b/sources/pyside6/tests/QtWidgets/pyside3069.py @@ -0,0 +1,51 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +from __future__ import annotations + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths # noqa: E402 +init_test_paths(False) + +from PySide6.QtCore import Qt # noqa: E402 +from PySide6.QtWidgets import QApplication, QComboBox, QGraphicsScene, QGraphicsView # noqa: E402 + +from helper.usesqapplication import UsesQApplication # noqa: E402 + + +class BugTest(UsesQApplication): + """PYSIDE-3069: Test that the conversion of an element of a list + QGraphicsItem* to QGraphicsProxyWidget* (inheriting QObject/QGraphicsItem) + works correctly without crash. + + For this, we need a QGraphicsProxyWidget for which no wrapper exists, + created in C++. So, we populate a combo, add it to the scene and show its + popup, which creates a top level that is automatically wrapped by + another QGraphicsProxyWidget. This, we print from the list of items(). + + See also PYSIDE-86, PYSIDE-1887.""" + def test(self): + qApp.setEffectEnabled(Qt.UI_AnimateCombo, False) # noqa: F821 + cb = QComboBox() + cb.addItem("i1") + cb.addItem("i2") + scene = QGraphicsScene() + scene.addWidget(cb) + view = QGraphicsView(scene) + view.show() + cb.showPopup() + while not view.windowHandle().isExposed(): + QApplication.processEvents() + items = scene.items() + self.assertEqual(len(items), 2) # Combo and its popup, created in C++ + for i in items: + print(i) + view.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py b/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py index 71aba9941..91b405aaf 100644 --- a/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py +++ b/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py @@ -50,6 +50,7 @@ class QGraphicsObjectReimpl(UsesQApplication): # and then the QVariant was not associated with # a QGraphicsItem but a QObjectItem because the base # class was a QObject. + # See also PYSIDE-1887, PYSIDE-3069 gobjA = GObjA() gobjA.setParentItem(w) self.assertIs(type(w), type(gobjA.parentItem())) diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml index 1ceb0ac0b..b6f16e698 100644 --- a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml +++ b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml @@ -2,4 +2,4 @@ name = "subproject" [tool.pyside6-project] -files = ["subproject_button.py"] +files = ["subproject_button.py", "../main.py"] diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject index abfa1f461..688f07c33 100644 --- a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject +++ b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject @@ -1,3 +1,3 @@ { - "files": ["subproject_button.py"] + "files": ["subproject_button.py", "../main.py"] } diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml index be8aa949f..c0adb0c76 100644 --- a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml +++ b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml @@ -15,8 +15,9 @@ target-version = ["py38"] # Another comment -[tool.pyside6-project] -files = ["main.py", "zzz.py"] [build-system] requires = ["setuptools >=42"] build-backend = "setuptools.build_meta" + +[tool.pyside6-project] +files = ["main.py", "zzz.py"] diff --git a/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py b/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py index d66395251..cbacc4e48 100644 --- a/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py +++ b/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py @@ -1,5 +1,7 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations + import contextlib import difflib import importlib diff --git a/sources/shiboken6/ApiExtractor/typesystem_enums.h b/sources/shiboken6/ApiExtractor/typesystem_enums.h index c0c3da1f6..2b876efc4 100644 --- a/sources/shiboken6/ApiExtractor/typesystem_enums.h +++ b/sources/shiboken6/ApiExtractor/typesystem_enums.h @@ -36,6 +36,7 @@ enum CodeSnipPosition { CodeSnipPositionEnd, CodeSnipPositionDeclaration, CodeSnipPositionPyOverride, + CodeSnipPositionWrapperDeclaration, CodeSnipPositionAny }; diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp index 1d747419f..8b2ad08de 100644 --- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp @@ -341,7 +341,8 @@ ENUM_LOOKUP_BEGIN(TypeSystem::CodeSnipPosition, Qt::CaseInsensitive, {u"beginning", TypeSystem::CodeSnipPositionBeginning}, {u"end", TypeSystem::CodeSnipPositionEnd}, {u"declaration", TypeSystem::CodeSnipPositionDeclaration}, - {u"override", TypeSystem::CodeSnipPositionPyOverride} + {u"override", TypeSystem::CodeSnipPositionPyOverride}, + {u"wrapper-declaration", TypeSystem::CodeSnipPositionWrapperDeclaration} }; ENUM_LOOKUP_LINEAR_SEARCH diff --git a/sources/shiboken6/doc/typesystem_codeinjection.rst b/sources/shiboken6/doc/typesystem_codeinjection.rst index 03d5f4b16..e50a4ca7c 100644 --- a/sources/shiboken6/doc/typesystem_codeinjection.rst +++ b/sources/shiboken6/doc/typesystem_codeinjection.rst @@ -29,80 +29,83 @@ into the **C++ Wrapper** or the **Python Wrapper** (see The ``position`` attribute specifies the location of the custom code in the function. - -+---------------+------+-----------+--------------------------------------------------------------+ -|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. | -| | +-----------+--------------------------------------------------------------+ -| | |override |Used only for virtual functions. The code is injected before | -| | | |the code calling the Python override. | -| | +-----------+--------------------------------------------------------------+ -| | |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. | -| | +-----------+--------------------------------------------------------------+ -| | |declaration|Insert code into module header. | -+---------------+------+-----------+--------------------------------------------------------------+ ++---------------+------+---------------------+--------------------------------------------------------------+ +|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``. | +| | +---------------------+--------------------------------------------------------------+ +| | | wrapper-declaration |Write into the declaration of the wrapper class, right after | +| | | |the ``public:`` keyword. This can be used for importing base | +| | | |class members via ``using``. | +| +------+---------------------+--------------------------------------------------------------+ +| |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. | +| | +---------------------+--------------------------------------------------------------+ +| | |override |Used only for virtual functions. The code is injected before | +| | | |the code calling the Python override. | +| | +---------------------+--------------------------------------------------------------+ +| | |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. | +| | +---------------------+--------------------------------------------------------------+ +| | |declaration |Insert code into module header. | ++---------------+------+---------------------+--------------------------------------------------------------+ Anatomy of Code Injection @@ -174,7 +177,7 @@ In other words, use .. code-block:: xml <inject-code class="target" position="beginning | end"> - %CPPSELF.originalMethodName(); + %CPPSELF.%FUNCTION_NAME(); </inject-code> @@ -184,7 +187,7 @@ instead of .. code-block:: xml <inject-code class="target" position="beginning | end"> - %CPPSELF.%FUNCTION_NAME(); + %CPPSELF.originalMethodName(); </inject-code> diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index e27a768a5..965d80dad 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -240,6 +240,10 @@ void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s, << " : public " << metaClass->qualifiedCppName() << "\n{\npublic:\n" << indent; + writeClassCodeSnips(s, metaClass->typeEntry()->codeSnips(), + TypeSystem::CodeSnipPositionWrapperDeclaration, + TypeSystem::NativeCode, classContext); + writeProtectedEnums(s, classContext); writeSpecialFunctions(s, wrapperName, classContext); diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index a65359a1e..3e26b4605 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -761,6 +761,28 @@ PyObject *Sbk_ReturnFromPython_Self(PyObject *self) } //extern "C" +// Determine name of a Python override of a virtual method according to features +// and populate name cache. +static PyObject *overrideMethodName(PyObject *pySelf, const char *methodName, + PyObject **nameCache) +{ + // PYSIDE-1626: Touch the type to initiate switching early. + auto *obType = Py_TYPE(pySelf); + SbkObjectType_UpdateFeature(obType); + + const int flag = currentSelectId(obType); + const int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0; + const bool is_snake = flag & 0x01; + PyObject *pyMethodName = nameCache[is_snake]; // borrowed + if (pyMethodName == nullptr) { + if (propFlag) + methodName += 2; // skip the propFlag and ':' + pyMethodName = Shiboken::String::getSnakeCaseName(methodName, is_snake); + nameCache[is_snake] = pyMethodName; + } + return pyMethodName; +} + // The virtual function call PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject, Shiboken::GilState &gil, const char *funcName, @@ -773,9 +795,13 @@ PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject, SbkObject *wrapper = bindingManager.retrieveWrapper(voidThis, typeObject); // The refcount can be 0 if the object is dieing and someone called // a virtual method from the destructor - if (wrapper == nullptr || Py_REFCNT(reinterpret_cast<const PyObject *>(wrapper)) == 0) + if (wrapper == nullptr) + return nullptr; + auto *pySelf = reinterpret_cast<PyObject *>(wrapper); + if (Py_REFCNT(pySelf) == 0) return nullptr; - pyOverride = Shiboken::BindingManager::getOverride(wrapper, nameCache, funcName); + PyObject *pyMethodName = overrideMethodName(pySelf, funcName, nameCache); + pyOverride = Shiboken::BindingManager::getOverride(wrapper, pyMethodName); if (pyOverride == nullptr) { resultCache = true; gil.release(); diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp index 3652e4a4a..25cc5c00a 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.cpp +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -366,23 +366,8 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr, PyTypeObject *typeO return it != m_d->wrapperMapper.cend() ? it->second : nullptr; } -PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *nameCache[], - const char *methodName) -{ - // PYSIDE-1626: Touch the type to initiate switching early. - SbkObjectType_UpdateFeature(Py_TYPE(wrapper)); - - int flag = currentSelectId(Py_TYPE(wrapper)); - int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0; - bool is_snake = flag & 0x01; - PyObject *pyMethodName = nameCache[is_snake]; // borrowed - if (pyMethodName == nullptr) { - if (propFlag) - methodName += 2; // skip the propFlag and ':' - pyMethodName = Shiboken::String::getSnakeCaseName(methodName, is_snake); - nameCache[is_snake] = pyMethodName; - } - +PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName) +{ auto *obWrapper = reinterpret_cast<PyObject *>(wrapper); auto *wrapper_dict = SbkObject_GetDict_NoRef(obWrapper); if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) { diff --git a/sources/shiboken6/libshiboken/bindingmanager.h b/sources/shiboken6/libshiboken/bindingmanager.h index 4615bfb11..e2a4ac8ea 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.h +++ b/sources/shiboken6/libshiboken/bindingmanager.h @@ -44,7 +44,7 @@ public: SbkObject *retrieveWrapper(const void *cptr, PyTypeObject *typeObject) const; SbkObject *retrieveWrapper(const void *cptr) const; - static PyObject *getOverride(SbkObject *wrapper, PyObject *nameCache[], const char *methodName); + static PyObject *getOverride(SbkObject *wrapper, PyObject *pyMethodName); void addClassInheritance(Module::TypeInitStruct *parent, Module::TypeInitStruct *child); /// Try to find the correct type of cptr via type discovery knowing that it's at least diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py index bfb68a1b6..c5dc44644 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py @@ -241,7 +241,7 @@ FROM_IMPORTS = [ (None, ["os"]), (None, ["enum"]), (None, ["typing"]), - (None, ["collections"]), + (None, ["collections.abc"]), ("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]), ("shiboken6", ["Shiboken"]), ] |