Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--coin/instructions/common_environment.yaml12
-rw-r--r--examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml50
-rw-r--r--examples/graphs/2d/graphsaudio/GraphsAudio/qmldir2
-rw-r--r--examples/graphs/2d/graphsaudio/doc/graphsaudio.rst8
-rw-r--r--examples/graphs/2d/graphsaudio/doc/graphsaudio.webpbin0 -> 12908 bytes
-rw-r--r--examples/graphs/2d/graphsaudio/graphsaudio.pyproject3
-rw-r--r--examples/graphs/2d/graphsaudio/main.py80
-rw-r--r--sources/pyside-tools/project_lib/pyproject_toml.py90
-rw-r--r--sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp9
-rw-r--r--sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml2
-rw-r--r--sources/pyside6/doc/building_from_source/windows.rst11
-rw-r--r--sources/pyside6/libpyside/feature_select.cpp1
-rw-r--r--sources/pyside6/tests/QtWidgets/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/QtWidgets/pyside3069.py51
-rw-r--r--sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py1
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml2
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject2
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml5
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py2
-rw-r--r--sources/shiboken6/ApiExtractor/typesystem_enums.h1
-rw-r--r--sources/shiboken6/ApiExtractor/typesystemparser.cpp3
-rw-r--r--sources/shiboken6/doc/typesystem_codeinjection.rst155
-rw-r--r--sources/shiboken6/generator/shiboken/headergenerator.cpp4
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.cpp30
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.cpp19
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.h2
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py2
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
new file mode 100644
index 000000000..bb57b18e5
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp
Binary files differ
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"]),
]