aboutsummaryrefslogtreecommitdiffstats
path: root/sources
diff options
context:
space:
mode:
Diffstat (limited to 'sources')
-rw-r--r--sources/pyside6/doc/CMakeLists.txt4
-rw-r--r--sources/pyside6/doc/building_from_source/index.rst2
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/add_chart.rst14
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst8
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst33
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject3
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py13
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject3
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py8
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py13
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py16
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject3
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py41
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py13
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py16
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject3
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py95
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py14
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py17
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/filter_data.rst7
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst12
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/read_data.rst2
-rw-r--r--sources/pyside6/libpyside/CMakeLists.txt1
-rw-r--r--sources/pyside6/libpyside/dynamicqmetaobject.cpp11
-rw-r--r--sources/pyside6/libpyside/pysideproperty.cpp302
-rw-r--r--sources/pyside6/libpyside/pysideproperty.h8
-rw-r--r--sources/pyside6/libpyside/pysideproperty_p.h46
-rw-r--r--sources/pyside6/libpyside/pysidepropertybase_p.h95
-rw-r--r--sources/pyside6/libpyside/signalmanager.cpp2
-rw-r--r--sources/pyside6/libpysideqml/pysideqmllistproperty.cpp134
-rw-r--r--sources/pyside6/tests/pysidetest/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/pysidetest/snake_case_imported.py25
-rw-r--r--sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py (renamed from sources/pyside6/tests/pysidetest/snake_case_sub.py)2
-rw-r--r--sources/pyside6/tests/pysidetest/snake_case_test.py18
-rw-r--r--sources/shiboken6/libshiboken/signature/signature.cpp23
-rw-r--r--sources/shiboken6/tests/libsample/spaceship.cpp22
-rw-r--r--sources/shiboken6/tests/libsample/spaceship.h15
-rw-r--r--sources/shiboken6/tests/samplebinding/CMakeLists.txt1
-rw-r--r--sources/shiboken6/tests/samplebinding/spaceship_test.py18
-rw-r--r--sources/shiboken6/tests/samplebinding/typesystem_sample.xml1
-rw-r--r--sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp88
-rw-r--r--sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h2
-rw-r--r--sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp43
-rw-r--r--sources/shiboken6_generator/ApiExtractor/abstractmetalang.h2
-rw-r--r--sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp1
-rw-r--r--sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp30
-rw-r--r--sources/shiboken6_generator/ApiExtractor/parser/codemodel.h6
-rw-r--r--sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h1
-rw-r--r--sources/shiboken6_generator/ApiExtractor/typedatabase.cpp11
49 files changed, 839 insertions, 410 deletions
diff --git a/sources/pyside6/doc/CMakeLists.txt b/sources/pyside6/doc/CMakeLists.txt
index e234b7ce8..62a7a430c 100644
--- a/sources/pyside6/doc/CMakeLists.txt
+++ b/sources/pyside6/doc/CMakeLists.txt
@@ -247,6 +247,8 @@ set(CODE_SNIPPET_ROOT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/codesnippets"
if (FULLDOCSBUILD)
shiboken_get_tool_shell_wrapper(shiboken tool_wrapper)
+set(RHI_INCLUDE_DIR ${QT_INCLUDE_DIR}/QtGui/${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}/QtGui)
+
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/QtCore/index.rst"
COMMAND
${tool_wrapper}
@@ -254,7 +256,7 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/Q
--generator-set=qtdoc
${global_header}
--enable-pyside-extensions
- --include-paths="${QT_INCLUDE_DIR}${PATH_SEP}${pyside6_SOURCE_DIR}${PATH_SEP}${TS_ROOT}"
+ --include-paths="${QT_INCLUDE_DIR}${PATH_SEP}${RHI_INCLUDE_DIR}${PATH_SEP}${pyside6_SOURCE_DIR}${PATH_SEP}${TS_ROOT}"
--api-version=${SUPPORTED_QT_VERSION}
--typesystem-paths="${QDOC_TYPESYSTEM_PATH}"
--library-source-dir=${QT_SRC_DIR}
diff --git a/sources/pyside6/doc/building_from_source/index.rst b/sources/pyside6/doc/building_from_source/index.rst
index 3899f4361..a20143726 100644
--- a/sources/pyside6/doc/building_from_source/index.rst
+++ b/sources/pyside6/doc/building_from_source/index.rst
@@ -484,7 +484,7 @@ The target executes several steps:
#. ``sphinx`` is run to produce the documentation in HTML format.
Re-running the command will not execute step 1 unless the file
-``qdoc-output/webxml/qtcore-index.webxml`` is removed from the build tree.
+``qdoc-output/qtcore/webxml/qtcore-index.webxml`` is removed from the build tree.
Similarly, step 2 will not be executed unless the file ``base/PySide6/QtCore/index.rst``
is removed.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst b/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst
index acffce40d..87e012021 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst
+++ b/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst
@@ -5,17 +5,17 @@ Chapter 5 - Add a chart view
=============================
A table is nice to present data, but a chart is even better. For this, you
-need the QtCharts module that provides many types of plots and options to
+need the QtGraphs module that provides many types of plots and options to
graphically represent data.
-The placeholder for a plot is a QChartView, and inside that Widget you can
-place a QChart. As a first step, try including only this without any data to
-plot.
+The relevant class for a plot is the GraphsView QML type, to which axes and
+data series can be added. As a first step, try including then without any data
+to plot.
Make the following highlighted changes to :code:`main_widget.py` from the
-previous chapter to add a QChartView:
+previous chapter to add a chart:
.. literalinclude:: datavisualize5/main_widget.py
:linenos:
- :lines: 3-
- :emphasize-lines: 2-3,6,22-36,47-49
+ :lines: 5-
+ :emphasize-lines: 4,27-39,53
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst
index ab5468cd6..9073e679a 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst
+++ b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst
@@ -4,7 +4,8 @@
Chapter 3 - Create an empty QMainWindow
==========================================
-You can now think of presenting your data in a UI. A QMainWindow provides a
+You can now think of presenting your data in a UI. A
+class:`~PySide6.QtWidgets.QMainWindow` provides a
convenient structure for GUI applications, such as a menu bar and status bar.
The following image shows the layout that QMainWindow offers out-of-the box:
@@ -24,12 +25,13 @@ the resolution you currently have. In the following snippet, you will see how
window size is defined based on available screen width (80%) and height (70%).
.. note:: You can achieve a similar structure using other Qt elements like
- QMenuBar, QWidget, and QStatusBar. Refer the QMainWindow layout for
+ class:`~PySide6.QtWidgets.QMenuBar`, class:`~PySide6.QtWidgets.QWidget`,
+ and class:`~PySide6.QtWidgets.QStatusBar`. Refer the QMainWindow layout for
guidance.
.. literalinclude:: datavisualize3/main_window.py
:language: python
:linenos:
- :lines: 4-
+ :lines: 5-
Try running the script to see what output you get with it.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst
index 5b7e5e735..b3041349c 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst
+++ b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst
@@ -8,21 +8,22 @@ Now that you have a QMainWindow, you can include a centralWidget to your
interface. Usually, a QWidget is used to display data in most data-driven
applications. Use a table view to display your data.
-The first step is to add a horizontal layout with just a QTableView. You
-can create a QTableView object and place it inside a QHBoxLayout. Once the
+The first step is to add a horizontal layout with just a
+class:`~PySide6.QtWidgets.QTableView`. You can create a QTableView object
+and place it inside a class:`~PySide6.QtWidgets.QHBoxLayout`. Once the
QWidget is properly built, pass the object to the QMainWindow as its central
widget.
Remember that a QTableView needs a model to display information. In this case,
-you can use a QAbstractTableModel instance.
+you can use a class:`~PySide6.QtCore.QAbstractTableModel` instance.
.. note:: You could also use the default item model that comes with a
- QTableWidget instead. QTableWidget is a convenience class that reduces
- your codebase considerably as you don't need to implement a data model.
- However, it's less flexible than a QTableView, as QTableWidget cannot be
- used with just any data. For more insight about Qt's model-view framework,
- refer to the
- `Model View Programming <https://doc.qt.io/qt-5/model-view-programming.html>`
+ class:`~PySide6.QtWidgets.QTableWidget` instead. QTableWidget is a
+ convenience class that reduces your codebase considerably as you don't need
+ to implement a data model. However, it's less flexible than a QTableView,
+ as QTableWidget cannot be used with just any data. For more insight about
+ Qt's model-view framework, refer to the
+ `Model View Programming <https://doc.qt.io/qt-6/model-view-programming.html>`
documentation.
Implementing the model for your QTableView, allows you to:
@@ -42,7 +43,7 @@ Here is a script that implements the CustomTableModel:
.. literalinclude:: datavisualize4/table_model.py
:language: python
:linenos:
- :lines: 3-
+ :lines: 5-
Now, create a QWidget that has a QTableView, and connect it to your
CustomTableModel.
@@ -50,8 +51,8 @@ CustomTableModel.
.. literalinclude:: datavisualize4/main_widget.py
:language: python
:linenos:
- :emphasize-lines: 12-17
- :lines: 3-
+ :emphasize-lines: 12-12
+ :lines: 5-
You also need minor changes to the :code:`main_window.py` and
:code:`main.py` from chapter 3 to include the Widget inside the
@@ -62,11 +63,11 @@ In the following snippets you'll see those changes highlighted:
.. literalinclude:: datavisualize4/main_window.py
:language: python
:linenos:
- :lines: 3-
- :emphasize-lines: 8,11
+ :lines: 5-
+ :emphasize-lines: 9
.. literalinclude:: datavisualize4/main.py
:language: python
:linenos:
- :lines: 3-
- :emphasize-lines: 46-47
+ :lines: 5-
+ :emphasize-lines: 45-46
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject
new file mode 100644
index 000000000..1bd31f959
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "main_window.py"]
+}
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
index 6ee8fa61b..79a4afd36 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
@@ -2,25 +2,22 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
from PySide6.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
- QMainWindow.__init__(self)
+ super().__init__()
self.setWindowTitle("Eartquakes information")
# Menu
self.menu = self.menuBar()
- self.file_menu = self.menu.addMenu("File")
+ file_menu = self.menu.addMenu("File")
# Exit QAction
- exit_action = QAction("Exit", self)
- exit_action.setShortcut(QKeySequence.Quit)
- exit_action.triggered.connect(self.close)
-
- self.file_menu.addAction(exit_action)
+ file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+ "Exit", QKeySequence.StandardKey.Quit, self.close)
# Status Bar
self.status = self.statusBar()
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject
new file mode 100644
index 000000000..f54969728
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "main_widget.py", "main_window.py", "table_model.py"]
+}
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py
index 85e24833f..5d8e6ade3 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py
@@ -10,7 +10,7 @@ from table_model import CustomTableModel
class Widget(QWidget):
def __init__(self, data):
- QWidget.__init__(self)
+ super().__init__()
# Getting the Model
self.model = CustomTableModel(data)
@@ -22,13 +22,13 @@ class Widget(QWidget):
# QTableView Headers
self.horizontal_header = self.table_view.horizontalHeader()
self.vertical_header = self.table_view.verticalHeader()
- self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents)
- self.vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents)
+ self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
+ self.vertical_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
self.horizontal_header.setStretchLastSection(True)
# QWidget Layout
self.main_layout = QHBoxLayout()
- size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
# Left layout
size.setHorizontalStretch(1)
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py
index ded7fdf5c..600af6503 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py
@@ -2,25 +2,22 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
from PySide6.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self, widget):
- QMainWindow.__init__(self)
+ super().__init__()
self.setWindowTitle("Eartquakes information")
self.setCentralWidget(widget)
# Menu
self.menu = self.menuBar()
- self.file_menu = self.menu.addMenu("File")
+ file_menu = self.menu.addMenu("File")
# Exit QAction
- exit_action = QAction("Exit", self)
- exit_action.setShortcut(QKeySequence.Quit)
- exit_action.triggered.connect(self.close)
-
- self.file_menu.addAction(exit_action)
+ file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+ "Exit", QKeySequence.StandardKey.Quit, self.close)
# Status Bar
self.status = self.statusBar()
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py
index cc2ac12ab..9a2871c22 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py
@@ -25,27 +25,27 @@ class CustomTableModel(QAbstractTableModel):
return self.column_count
def headerData(self, section, orientation, role):
- if role != Qt.DisplayRole:
+ if role != Qt.ItemDataRole.DisplayRole:
return None
- if orientation == Qt.Horizontal:
+ if orientation == Qt.Orientation.Horizontal:
return ("Date", "Magnitude")[section]
else:
return f"{section}"
- def data(self, index, role=Qt.DisplayRole):
+ def data(self, index, role=Qt.ItemDataRole.DisplayRole):
column = index.column()
row = index.row()
- if role == Qt.DisplayRole:
+ if role == Qt.ItemDataRole.DisplayRole:
if column == 0:
date = self.input_dates[row].toPython()
return str(date)[:-3]
elif column == 1:
magnitude = self.input_magnitudes[row]
return f"{magnitude:.2f}"
- elif role == Qt.BackgroundRole:
- return QColor(Qt.white)
- elif role == Qt.TextAlignmentRole:
- return Qt.AlignRight
+ elif role == Qt.ItemDataRole.BackgroundRole:
+ return QColor(Qt.GlobalColor.white)
+ elif role == Qt.ItemDataRole.TextAlignmentRole:
+ return Qt.AlignmentFlag.AlignRight
return None
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject
new file mode 100644
index 000000000..f54969728
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "main_widget.py", "main_window.py", "table_model.py"]
+}
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
index 77ea4e776..fca09b059 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
@@ -2,17 +2,17 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtGui import QPainter
from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
QSizePolicy)
-from PySide6.QtCharts import QChart, QChartView
+from PySide6.QtQuickWidgets import QQuickWidget
+from PySide6.QtGraphs import QLineSeries, QDateTimeAxis, QValueAxis, QGraphsTheme
from table_model import CustomTableModel
class Widget(QWidget):
def __init__(self, data):
- QWidget.__init__(self)
+ super().__init__()
# Getting the Model
self.model = CustomTableModel(data)
@@ -24,21 +24,27 @@ class Widget(QWidget):
# QTableView Headers
self.horizontal_header = self.table_view.horizontalHeader()
self.vertical_header = self.table_view.verticalHeader()
- self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents)
- self.vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents)
+ self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
+ self.vertical_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
self.horizontal_header.setStretchLastSection(True)
- # Creating QChart
- self.chart = QChart()
- self.chart.setAnimationOptions(QChart.AllAnimations)
-
- # Creating QChartView
- self.chart_view = QChartView(self.chart)
- self.chart_view.setRenderHint(QPainter.Antialiasing)
+ # Create QGraphView via QML
+ self.series = QLineSeries()
+ self.axis_x = QDateTimeAxis()
+ self.axis_y = QValueAxis()
+ self.quick_widget = QQuickWidget(self)
+ self.quick_widget.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView)
+ self.theme = QGraphsTheme()
+ initial_properties = {"theme": self.theme,
+ "axisX": self.axis_x,
+ "axisY": self.axis_y,
+ "seriesList": self.series}
+ self.quick_widget.setInitialProperties(initial_properties)
+ self.quick_widget.loadFromModule("QtGraphs", "GraphsView")
# QWidget Layout
- self.main_layout = QHBoxLayout()
- size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ self.main_layout = QHBoxLayout(self)
+ size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
# Left layout
size.setHorizontalStretch(1)
@@ -47,8 +53,5 @@ class Widget(QWidget):
# Right Layout
size.setHorizontalStretch(4)
- self.chart_view.setSizePolicy(size)
- self.main_layout.addWidget(self.chart_view)
-
- # Set the layout to the QWidget
- self.setLayout(self.main_layout)
+ self.quick_widget.setSizePolicy(size)
+ self.main_layout.addWidget(self.quick_widget)
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py
index ded7fdf5c..600af6503 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py
@@ -2,25 +2,22 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
from PySide6.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self, widget):
- QMainWindow.__init__(self)
+ super().__init__()
self.setWindowTitle("Eartquakes information")
self.setCentralWidget(widget)
# Menu
self.menu = self.menuBar()
- self.file_menu = self.menu.addMenu("File")
+ file_menu = self.menu.addMenu("File")
# Exit QAction
- exit_action = QAction("Exit", self)
- exit_action.setShortcut(QKeySequence.Quit)
- exit_action.triggered.connect(self.close)
-
- self.file_menu.addAction(exit_action)
+ file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+ "Exit", QKeySequence.StandardKey.Quit, self.close)
# Status Bar
self.status = self.statusBar()
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py
index cc2ac12ab..9a2871c22 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py
@@ -25,27 +25,27 @@ class CustomTableModel(QAbstractTableModel):
return self.column_count
def headerData(self, section, orientation, role):
- if role != Qt.DisplayRole:
+ if role != Qt.ItemDataRole.DisplayRole:
return None
- if orientation == Qt.Horizontal:
+ if orientation == Qt.Orientation.Horizontal:
return ("Date", "Magnitude")[section]
else:
return f"{section}"
- def data(self, index, role=Qt.DisplayRole):
+ def data(self, index, role=Qt.ItemDataRole.DisplayRole):
column = index.column()
row = index.row()
- if role == Qt.DisplayRole:
+ if role == Qt.ItemDataRole.DisplayRole:
if column == 0:
date = self.input_dates[row].toPython()
return str(date)[:-3]
elif column == 1:
magnitude = self.input_magnitudes[row]
return f"{magnitude:.2f}"
- elif role == Qt.BackgroundRole:
- return QColor(Qt.white)
- elif role == Qt.TextAlignmentRole:
- return Qt.AlignRight
+ elif role == Qt.ItemDataRole.BackgroundRole:
+ return QColor(Qt.GlobalColor.white)
+ elif role == Qt.ItemDataRole.TextAlignmentRole:
+ return Qt.AlignmentFlag.AlignRight
return None
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject
new file mode 100644
index 000000000..f54969728
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "main_widget.py", "main_window.py", "table_model.py"]
+}
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py
index f987689ea..336afacd8 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py
@@ -2,18 +2,20 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtCore import QDateTime, Qt
-from PySide6.QtGui import QPainter
+from math import floor, ceil
+
+from PySide6.QtCore import QDateTime, QTime, QTimeZone
from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
QSizePolicy)
-from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
+from PySide6.QtQuickWidgets import QQuickWidget
+from PySide6.QtGraphs import QLineSeries, QDateTimeAxis, QValueAxis, QGraphsTheme
from table_model import CustomTableModel
class Widget(QWidget):
def __init__(self, data):
- QWidget.__init__(self)
+ super().__init__()
# Getting the Model
self.model = CustomTableModel(data)
@@ -23,25 +25,29 @@ class Widget(QWidget):
self.table_view.setModel(self.model)
# QTableView Headers
- resize = QHeaderView.ResizeToContents
+ resize = QHeaderView.ResizeMode.ResizeToContents
self.horizontal_header = self.table_view.horizontalHeader()
self.vertical_header = self.table_view.verticalHeader()
self.horizontal_header.setSectionResizeMode(resize)
self.vertical_header.setSectionResizeMode(resize)
self.horizontal_header.setStretchLastSection(True)
- # Creating QChart
- self.chart = QChart()
- self.chart.setAnimationOptions(QChart.AllAnimations)
- self.add_series("Magnitude (Column 1)", [0, 1])
-
- # Creating QChartView
- self.chart_view = QChartView(self.chart)
- self.chart_view.setRenderHint(QPainter.Antialiasing)
+ # Create QGraphView via QML
+ self.populate_series()
+ self.quick_widget = QQuickWidget(self)
+ self.quick_widget.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView)
+ self.theme = QGraphsTheme()
+ self.theme.setTheme(QGraphsTheme.Theme.BlueSeries)
+ initial_properties = {"theme": self.theme,
+ "axisX": self.axis_x,
+ "axisY": self.axis_y,
+ "seriesList": self.series}
+ self.quick_widget.setInitialProperties(initial_properties)
+ self.quick_widget.loadFromModule("QtGraphs", "GraphsView")
# QWidget Layout
- self.main_layout = QHBoxLayout()
- size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ self.main_layout = QHBoxLayout(self)
+ size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
# Left layout
size.setHorizontalStretch(1)
@@ -50,46 +56,51 @@ class Widget(QWidget):
# Right Layout
size.setHorizontalStretch(4)
- self.chart_view.setSizePolicy(size)
- self.main_layout.addWidget(self.chart_view)
+ self.quick_widget.setSizePolicy(size)
+ self.main_layout.addWidget(self.quick_widget)
- # Set the layout to the QWidget
- self.setLayout(self.main_layout)
+ def populate_series(self):
+ def seconds(qtime: QTime):
+ return qtime.minute() * 60 + qtime.second()
- def add_series(self, name, columns):
- # Create QLineSeries
self.series = QLineSeries()
- self.series.setName(name)
+ self.series.setName("Magnitude (Column 1)")
# Filling QLineSeries
+ time_min = QDateTime(2100, 1, 1, 0, 0, 0)
+ time_max = QDateTime(1970, 1, 1, 0, 0, 0)
+ time_zone = QTimeZone(QTimeZone.Initialization.UTC)
+ y_min = 1e37
+ y_max = -1e37
+ date_fmt = "yyyy-MM-dd HH:mm:ss.zzz"
for i in range(self.model.rowCount()):
- # Getting the data
t = self.model.index(i, 0).data()
- date_fmt = "yyyy-MM-dd HH:mm:ss.zzz"
-
- x = QDateTime().fromString(t, date_fmt).toSecsSinceEpoch()
+ time = QDateTime.fromString(t, date_fmt)
+ time.setTimeZone(time_zone)
y = float(self.model.index(i, 1).data())
-
- if x > 0 and y > 0:
- self.series.append(x, y)
-
- self.chart.addSeries(self.series)
+ if time.isValid() and y > 0:
+ if time > time_max:
+ time_max = time
+ if time < time_min:
+ time_min = time
+ if y > y_max:
+ y_max = y
+ if y < y_min:
+ y_min = y
+ self.series.append(time.toMSecsSinceEpoch(), y)
# Setting X-axis
self.axis_x = QDateTimeAxis()
- self.axis_x.setTickCount(10)
- self.axis_x.setFormat("dd.MM (h:mm)")
+ self.axis_x.setLabelFormat("dd.MM (h:mm)")
self.axis_x.setTitleText("Date")
- self.chart.addAxis(self.axis_x, Qt.AlignBottom)
- self.series.attachAxis(self.axis_x)
+ self.axis_x.setMin(time_min.addSecs(-seconds(time_min.time())))
+ self.axis_x.setMax(time_max.addSecs(3600 - seconds(time_max.time())))
+ self.series.setAxisX(self.axis_x)
+
# Setting Y-axis
self.axis_y = QValueAxis()
- self.axis_y.setTickCount(10)
self.axis_y.setLabelFormat("%.2f")
self.axis_y.setTitleText("Magnitude")
- self.chart.addAxis(self.axis_y, Qt.AlignLeft)
- self.series.attachAxis(self.axis_y)
-
- # Getting the color from the QChart to use it on the QTableView
- color_name = self.series.pen().color().name()
- self.model.color = f"{color_name}"
+ self.axis_y.setMin(floor(y_min))
+ self.axis_y.setMax(ceil(y_max))
+ self.series.setAxisY(self.axis_y)
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
index f37268df8..6a9eaea8e 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
@@ -2,26 +2,22 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
from PySide6.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self, widget):
- QMainWindow.__init__(self)
+ super().__init__()
self.setWindowTitle("Eartquakes information")
# Menu
self.menu = self.menuBar()
- self.file_menu = self.menu.addMenu("File")
+ file_menu = self.menu.addMenu("File")
# Exit QAction
- exit_action = QAction("Exit", self)
- exit_action.setShortcut(QKeySequence.Quit)
- exit_action.triggered.connect(self.close)
-
- self.file_menu.addAction(exit_action)
-
+ file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+ "Exit", QKeySequence.StandardKey.Quit, self.close)
# Status Bar
self.status = self.statusBar()
self.status.showMessage("Data loaded and plotted")
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py
index 3201e5887..9a2871c22 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py
@@ -9,7 +9,6 @@ from PySide6.QtGui import QColor
class CustomTableModel(QAbstractTableModel):
def __init__(self, data=None):
QAbstractTableModel.__init__(self)
- self.color = None
self.load_data(data)
def load_data(self, data):
@@ -26,27 +25,27 @@ class CustomTableModel(QAbstractTableModel):
return self.column_count
def headerData(self, section, orientation, role):
- if role != Qt.DisplayRole:
+ if role != Qt.ItemDataRole.DisplayRole:
return None
- if orientation == Qt.Horizontal:
+ if orientation == Qt.Orientation.Horizontal:
return ("Date", "Magnitude")[section]
else:
return f"{section}"
- def data(self, index, role=Qt.DisplayRole):
+ def data(self, index, role=Qt.ItemDataRole.DisplayRole):
column = index.column()
row = index.row()
- if role == Qt.DisplayRole:
+ if role == Qt.ItemDataRole.DisplayRole:
if column == 0:
date = self.input_dates[row].toPython()
return str(date)[:-3]
elif column == 1:
magnitude = self.input_magnitudes[row]
return f"{magnitude:.2f}"
- elif role == Qt.BackgroundRole:
- return (QColor(Qt.white), QColor(self.color))[column]
- elif role == Qt.TextAlignmentRole:
- return Qt.AlignRight
+ elif role == Qt.ItemDataRole.BackgroundRole:
+ return QColor(Qt.GlobalColor.white)
+ elif role == Qt.ItemDataRole.TextAlignmentRole:
+ return Qt.AlignmentFlag.AlignRight
return None
diff --git a/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst
index bef134e5b..4edde69c1 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst
+++ b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst
@@ -17,7 +17,8 @@ be done by filtering the data that follows the condition, "magnitude > 0", to
avoid faulty data or unexpected behavior.
The Date column provides data in UTC format (for example,
-2018-12-11T21:14:44.682Z), so you could easily map it to a QDateTime object
+2018-12-11T21:14:44.682Z), so you could easily map it to a
+class:`~PySide6.QtCore.QDateTime` object
defining the structure of the string. Additionally, you can adapt the time
based on the timezone you are in, using QTimeZone.
@@ -26,7 +27,7 @@ The following script filters and formats the CSV data as described earlier:
.. literalinclude:: datavisualize2/main.py
:language: python
:linenos:
- :lines: 3-
+ :lines: 5-
-Now that you have a tuple of QDateTime and float data, try improving the
+Now that you have a tuple of ``QDateTime`` and float data, try improving the
output further. That's what you'll learn in the following chapters.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst
index e4374e861..47d12a7c4 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst
+++ b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst
@@ -1,23 +1,23 @@
.. _tutorial_plot_datapoints:
-Chapter 6 - Plot the data in the ChartView
+Chapter 6 - Plot the data in the GraphsView
===========================================
-The last step of this tutorial is to plot the CSV data inside our QChart. For
-this, you need to go over our data and include the data on a QLineSeries.
+The last step of this tutorial is to plot the CSV data inside our GraphsView.
+For this, you need to go over our data and include the data on a QLineSeries.
After adding the data to the series, you can modify the axis to properly
display the QDateTime on the X-axis, and the magnitude values on the Y-axis.
Here is the updated :code:`main_widget.py` that includes an additional
-function to plot data using a QLineSeries:
+function to plot data using a :class:`~PySide6.QtGraphs.QLineSeries`:
.. literalinclude:: datavisualize6/main_widget.py
:language: python
:linenos:
- :lines: 3-
- :emphasize-lines: 33,56-91
+ :lines: 5-
+ :emphasize-lines: 31-42, 68-102
Now, run the application to visualize the earthquake magnitudes
data at different times.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/read_data.rst b/sources/pyside6/doc/tutorials/datavisualize/read_data.rst
index 8be0e1c2f..d083a05ee 100644
--- a/sources/pyside6/doc/tutorials/datavisualize/read_data.rst
+++ b/sources/pyside6/doc/tutorials/datavisualize/read_data.rst
@@ -21,7 +21,7 @@ The following python script, :code:`main.py`, demonstrates how to do it:
.. literalinclude:: datavisualize1/main.py
:language: python
:linenos:
- :lines: 3-
+ :lines: 5-
The Python script uses the :code:`argparse` module to accept and parse input
from the command line. It then uses the input, which in this case is the filename,
diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt
index 6aa4d7580..7640d1cce 100644
--- a/sources/pyside6/libpyside/CMakeLists.txt
+++ b/sources/pyside6/libpyside/CMakeLists.txt
@@ -29,6 +29,7 @@ set(libpyside_HEADERS # installed below
pyside_p.h
pysideglobals_p.h
pysideproperty.h
+ pysidepropertybase_p.h
pysideproperty_p.h
pysideqapp.h
pysideqenum.h
diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp
index db8e0c5ae..46bc3ace9 100644
--- a/sources/pyside6/libpyside/dynamicqmetaobject.cpp
+++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp
@@ -275,7 +275,7 @@ void MetaObjectBuilder::removeMethod(QMetaMethod::MethodType mtype, int index)
int MetaObjectBuilderPrivate::getPropertyNotifyId(PySideProperty *property) const
{
int notifyId = -1;
- if (property->d->notify) {
+ if (property->d->notify()) {
if (const char *signalNotify = PySide::Property::getNotifyName(property))
notifyId = indexOfMethod(QMetaMethod::Signal, signalNotify);
}
@@ -316,13 +316,14 @@ QMetaPropertyBuilder
}
}
}
- const auto metaType = QMetaType::fromName(property->d->typeName);
+ const QByteArray &typeName = property->d->typeName();
+ const auto metaType = QMetaType::fromName(typeName);
if (!metaType.isValid()) {
const auto &msg = msgInvalidPropertyType(m_builder->className(), propertyName,
- property->d->typeName);
+ typeName);
PyErr_WarnEx(PyExc_RuntimeWarning, msg.constData(), 0);
}
- return builder->addProperty(propertyName, property->d->typeName, metaType, propertyNotifyId);
+ return builder->addProperty(propertyName, typeName, metaType, propertyNotifyId);
}
int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName,
@@ -336,7 +337,7 @@ int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName,
auto newProperty = createProperty(property, propertyName);
// Adding property attributes
- const auto &flags = property->d->flags;
+ const auto flags = property->d->flags();
newProperty.setReadable(flags.testFlag(PySide::Property::PropertyFlag::Readable));
newProperty.setWritable(flags.testFlag(PySide::Property::PropertyFlag::Writable));
newProperty.setResettable(flags.testFlag(PySide::Property::PropertyFlag::Resettable));
diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp
index 3d1968045..0207a7320 100644
--- a/sources/pyside6/libpyside/pysideproperty.cpp
+++ b/sources/pyside6/libpyside/pysideproperty.cpp
@@ -16,6 +16,8 @@
#include <sbktypefactory.h>
#include <signature.h>
+#include <utility>
+
using namespace Shiboken;
using namespace Qt::StringLiterals;
@@ -101,9 +103,40 @@ PyTypeObject *PySideProperty_TypeF(void)
return type;
}
+PySidePropertyBase::PySidePropertyBase(Type t) : m_type(t)
+{
+}
+
+PySidePropertyBase::PySidePropertyBase(const PySidePropertyBase &rhs) = default;
+
+void PySidePropertyBase::tp_clearBase()
+{
+ Py_CLEAR(m_notify);
+ Py_CLEAR(m_pyTypeObject);
+}
+
+int PySidePropertyBase::tp_traverseBase(visitproc visit, void *arg)
+{
+ Py_VISIT(m_notify);
+ Py_VISIT(m_pyTypeObject);
+ return 0;
+}
+
+void PySidePropertyBase::increfBase()
+{
+ Py_XINCREF(m_notify);
+ Py_XINCREF(m_pyTypeObject);
+}
+
+PySidePropertyBase *PySidePropertyBase::clone() const
+{
+ Q_UNIMPLEMENTED();
+ return nullptr;
+}
+
// Helper to check a callable function passed to a property instance.
-bool PySidePropertyPrivate::assignCheckCallable(PyObject *source, const char *name,
- PyObject **target)
+bool PySidePropertyBase::assignCheckCallable(PyObject *source, const char *name,
+ PyObject **target)
{
if (source != nullptr && source != Py_None) {
if (PyCallable_Check(source) == 0) {
@@ -117,8 +150,32 @@ bool PySidePropertyPrivate::assignCheckCallable(PyObject *source, const char *na
return true;
}
-PySidePropertyPrivate::PySidePropertyPrivate() noexcept = default;
-PySidePropertyPrivate::~PySidePropertyPrivate() = default;
+void PySidePropertyPrivate::tp_clear()
+{
+ PySidePropertyBase::tp_clearBase();
+ Py_CLEAR(fget);
+ Py_CLEAR(fset);
+ Py_CLEAR(freset);
+ Py_CLEAR(fdel);
+}
+
+int PySidePropertyPrivate::tp_traverse(visitproc visit, void *arg)
+{
+ Py_VISIT(fget);
+ Py_VISIT(fset);
+ Py_VISIT(freset);
+ Py_VISIT(fdel);
+ return PySidePropertyBase::tp_traverseBase(visit, arg);
+}
+
+void PySidePropertyPrivate::incref()
+{
+ PySidePropertyBase::increfBase();
+ Py_XINCREF(fget);
+ Py_XINCREF(fset);
+ Py_XINCREF(freset);
+ Py_XINCREF(fdel);
+}
PyObject *PySidePropertyPrivate::getValue(PyObject *source) const
{
@@ -165,6 +222,13 @@ int PySidePropertyPrivate::reset(PyObject *source)
return -1;
}
+PySidePropertyPrivate *PySidePropertyPrivate::clone() const
+{
+ auto *result = new PySidePropertyPrivate(*this);
+ result->incref();
+ return result;
+}
+
void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, void **args)
{
switch (call) {
@@ -172,13 +236,13 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v
AutoDecRef value(getValue(source));
if (value.isNull())
return;
- if (typeName == "PyObject"_ba) {
+ if (typeName() == "PyObject"_ba) {
// Manual conversion, see PyObjectWrapper converter registration
auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]);
pw->reset(value.object());
return;
}
- if (Conversions::SpecificConverter converter(typeName); converter) {
+ if (Conversions::SpecificConverter converter(typeName()); converter) {
converter.toCpp(value.object(), args[0]);
return;
}
@@ -188,7 +252,7 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v
break;
case QMetaObject::WriteProperty: {
- Conversions::SpecificConverter converter(typeName);
+ Conversions::SpecificConverter converter(typeName());
if (converter) {
AutoDecRef value(converter.toPython(args[0]));
setValue(source, value);
@@ -213,7 +277,7 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v
static const char dataCapsuleName[] = "PropertyPrivate";
static const char dataCapsuleKeyName[] = "_PropertyPrivate"; // key in keyword args
-static PySidePropertyPrivate *getDataFromKwArgs(PyObject *kwds)
+static PySidePropertyBase *getDataFromKwArgs(PyObject *kwds)
{
if (kwds != nullptr && PyDict_Check(kwds) != 0) {
static PyObject *key = PyUnicode_InternFromString(dataCapsuleKeyName);
@@ -221,19 +285,27 @@ static PySidePropertyPrivate *getDataFromKwArgs(PyObject *kwds)
Shiboken::AutoDecRef data(PyDict_GetItem(kwds, key));
if (PyCapsule_CheckExact(data.object()) != 0) {
if (void *p = PyCapsule_GetPointer(data.object(), dataCapsuleName))
- return reinterpret_cast<PySidePropertyPrivate *>(p);
+ return reinterpret_cast<PySidePropertyBase *>(p);
}
}
}
return nullptr;
}
-static void addDataCapsuleToKwArgs(const AutoDecRef &kwds, PySidePropertyPrivate *data)
+static void addDataCapsuleToKwArgs(const AutoDecRef &kwds, PySidePropertyBase *data)
{
auto *capsule = PyCapsule_New(data, dataCapsuleName, nullptr);
PyDict_SetItemString(kwds.object(), dataCapsuleKeyName, capsule);
}
+static inline PySidePropertyPrivate *propertyPrivate(PyObject *self)
+{
+ auto *data = reinterpret_cast<PySideProperty *>(self);
+ Q_ASSERT(data->d != nullptr);
+ Q_ASSERT(data->d->type() == PySidePropertyBase::Type::Property);
+ return static_cast<PySidePropertyPrivate *>(data->d);
+}
+
static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject *kwds)
{
auto *me = PepExt_TypeCallAlloc<PySideProperty>(subtype, 0);
@@ -245,8 +317,10 @@ static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, Py
static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds)
{
- auto *data = reinterpret_cast<PySideProperty *>(self);
- PySidePropertyPrivate *pData = data->d;
+ auto *pData = propertyPrivate(self);
+
+ if (!pData->typeName().isEmpty()) // Cloned copy, already initialized
+ return 0;
static const char *kwlist[] = {"type", "fget", "fset", "freset", "fdel", "doc", "notify",
"designable", "scriptable", "stored",
@@ -274,27 +348,24 @@ static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds)
|| !PySidePropertyPrivate::assignCheckCallable(fset, "fset", &pData->fset)
|| !PySidePropertyPrivate::assignCheckCallable(freset, "freset", &pData->freset)
|| !PySidePropertyPrivate::assignCheckCallable(fdel, "fdel", &pData->fdel)) {
- pData->fget = pData->fset = pData->freset = pData->fdel = pData->notify = nullptr;
+ pData->fget = pData->fset = pData->freset = pData->fdel = nullptr;
+ pData->setNotify(nullptr);
return -1;
}
if (notify != nullptr && notify != Py_None)
- pData->notify = notify;
+ pData->setNotify(notify);
// PYSIDE-1019: Fetching the default `__doc__` from fget would fail for inherited functions
// because we don't initialize the mro with signatures (and we will not!).
// But it is efficient and in-time to do that on demand in qPropertyDocGet.
pData->getter_doc = false;
- if (doc)
- pData->doc = doc;
- else
- pData->doc.clear();
+ pData->setDoc(doc != nullptr ? QByteArray(doc) : QByteArray{});
- pData->pyTypeObject = type;
- Py_XINCREF(pData->pyTypeObject);
- pData->typeName = PySide::Signal::getTypeName(type);
+ pData->setPyTypeObject(type);
+ pData->setTypeName(PySide::Signal::getTypeName(type));
- auto &flags = pData->flags;
+ PySide::Property::PropertyFlags flags;
flags.setFlag(PySide::Property::PropertyFlag::Readable, pData->fget != nullptr);
flags.setFlag(PySide::Property::PropertyFlag::Writable, pData->fset != nullptr);
flags.setFlag(PySide::Property::PropertyFlag::Resettable, pData->freset != nullptr);
@@ -304,24 +375,22 @@ static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds)
flags.setFlag(PySide::Property::PropertyFlag::User, user);
flags.setFlag(PySide::Property::PropertyFlag::Constant, constant);
flags.setFlag(PySide::Property::PropertyFlag::Final, finalProp);
+ pData->setFlags(flags);
- if (type == Py_None || pData->typeName.isEmpty())
+ if (type == Py_None || pData->typeName().isEmpty())
PyErr_SetString(PyExc_TypeError, "Invalid property type or type name.");
else if (constant && pData->fset != nullptr)
PyErr_SetString(PyExc_TypeError, "A constant property cannot have a WRITE method.");
- else if (constant && pData->notify != nullptr)
+ else if (constant && pData->notify() != nullptr)
PyErr_SetString(PyExc_TypeError, "A constant property cannot have a NOTIFY signal.");
if (PyErr_Occurred() != nullptr) {
- pData->fget = pData->fset = pData->freset = pData->fdel = pData->notify = nullptr;
+ pData->fget = pData->fset = pData->freset = pData->fdel = nullptr;
+ pData->setNotify(nullptr);
return -1;
}
- Py_XINCREF(pData->fget);
- Py_XINCREF(pData->fset);
- Py_XINCREF(pData->freset);
- Py_XINCREF(pData->fdel);
- Py_XINCREF(pData->notify);
+ pData->incref();
return 0;
}
@@ -336,88 +405,80 @@ static void qpropertyDeAlloc(PyObject *self)
}
// Create a copy of the property to prevent the @property.setter from modifying
-// the property in place and avoid strange side effects in derived classes
-// (cf https://bugs.python.org/issue1620).
-static PyObject *
-_property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *reset, PyObject *del)
+// the property in place and avoid strange side effects when modifying the
+// property in derived classes (cf https://bugs.python.org/issue1620,
+// pysidetest/property_python_test.py).
+static PyObject *copyProperty(PyObject *old)
{
- auto *pold = reinterpret_cast<PySideProperty *>(old);
- PySidePropertyPrivate *pData = pold->d;
-
AutoDecRef type(PyObject_Type(old));
- QByteArray doc{};
- if (type.isNull())
- return nullptr;
-
- if (get == nullptr || get == Py_None) {
- Py_XDECREF(get);
- get = pData->fget ? pData->fget : Py_None;
- }
- if (set == nullptr || set == Py_None) {
- Py_XDECREF(set);
- set = pData->fset ? pData->fset : Py_None;
- }
- if (reset == nullptr || reset == Py_None) {
- Py_XDECREF(reset);
- reset = pData->freset ? pData->freset : Py_None;
- }
- if (del == nullptr || del == Py_None) {
- Py_XDECREF(del);
- del = pData->fdel ? pData->fdel : Py_None;
- }
- // make _init use __doc__ from getter
- if ((pData->getter_doc && get != Py_None) || pData->doc.isEmpty())
- doc.clear();
- else
- doc = pData->doc;
-
- auto *notify = pData->notify ? pData->notify : Py_None;
-
- const auto &flags = pData->flags;
- PyObject *obNew =
- PyObject_CallFunction(type, "OOOOOsO" "bbb" "bbb",
- pData->pyTypeObject, get, set, reset, del, doc.data(), notify,
- flags.testFlag(PySide::Property::PropertyFlag::Designable),
- flags.testFlag(PySide::Property::PropertyFlag::Scriptable),
- flags.testFlag(PySide::Property::PropertyFlag::Stored),
- flags.testFlag(PySide::Property::PropertyFlag::User),
- flags.testFlag(PySide::Property::PropertyFlag::Constant),
- flags.testFlag(PySide::Property::PropertyFlag::Final));
-
- return obNew;
+ Shiboken::AutoDecRef kwds(PyDict_New());
+ addDataCapsuleToKwArgs(kwds, propertyPrivate(old)->clone());
+ Shiboken::AutoDecRef args(PyTuple_New(0));
+ return PyObject_Call(type.object(), args.object(), kwds.object());
}
static PyObject *qPropertyGetter(PyObject *self, PyObject *getter)
{
- return _property_copy(self, getter, nullptr, nullptr, nullptr);
+ PyObject *result = copyProperty(self);
+ if (result != nullptr) {
+ auto *data = propertyPrivate(result);
+ auto *old = std::exchange(data->fget, getter);
+ Py_XINCREF(data->fget);
+ Py_XDECREF(old);
+ data->setFlag(PySide::Property::PropertyFlag::Readable);
+ }
+ return result;
}
static PyObject *qPropertySetter(PyObject *self, PyObject *setter)
{
- return _property_copy(self, nullptr, setter, nullptr, nullptr);
+ PyObject *result = copyProperty(self);
+ if (result != nullptr) {
+ auto *data = propertyPrivate(result);
+ auto *old = std::exchange(data->fset, setter);
+ Py_XINCREF(data->fset);
+ Py_XDECREF(old);
+ data->setFlag(PySide::Property::PropertyFlag::Writable);
+ }
+ return result;
}
static PyObject *qPropertyResetter(PyObject *self, PyObject *resetter)
{
- return _property_copy(self, nullptr, nullptr, resetter, nullptr);
+ PyObject *result = copyProperty(self);
+ if (result != nullptr) {
+ auto *data = propertyPrivate(result);
+ auto *old = std::exchange(data->freset, resetter);
+ Py_XINCREF(data->freset);
+ Py_XDECREF(old);
+ data->setFlag(PySide::Property::PropertyFlag::Resettable);
+ }
+ return result;
}
static PyObject *qPropertyDeleter(PyObject *self, PyObject *deleter)
{
- return _property_copy(self, nullptr, nullptr, nullptr, deleter);
+ PyObject *result = copyProperty(self);
+ if (result != nullptr) {
+ auto *data = propertyPrivate(result);
+ auto *old = std::exchange(data->fdel, deleter);
+ Py_XINCREF(data->fdel);
+ Py_XDECREF(old);
+ }
+ return result;
}
static PyObject *qPropertyCall(PyObject *self, PyObject *args, PyObject * /* kw */)
{
PyObject *getter = PyTuple_GetItem(args, 0);
- return _property_copy(self, getter, nullptr, nullptr, nullptr);
+ return qPropertyGetter(self, getter);
}
// PYSIDE-1019: Provide the same getters as Pythons `PyProperty`.
static PyObject *qProperty_fget(PyObject *self, void *)
{
- auto *func = reinterpret_cast<PySideProperty *>(self)->d->fget;
+ auto *func = propertyPrivate(self)->fget;
if (func == nullptr)
Py_RETURN_NONE;
Py_INCREF(func);
@@ -426,7 +487,7 @@ static PyObject *qProperty_fget(PyObject *self, void *)
static PyObject *qProperty_fset(PyObject *self, void *)
{
- auto *func = reinterpret_cast<PySideProperty *>(self)->d->fset;
+ auto *func = propertyPrivate(self)->fset;
if (func == nullptr)
Py_RETURN_NONE;
Py_INCREF(func);
@@ -435,7 +496,7 @@ static PyObject *qProperty_fset(PyObject *self, void *)
static PyObject *qProperty_freset(PyObject *self, void *)
{
- auto *func = reinterpret_cast<PySideProperty *>(self)->d->freset;
+ auto *func = propertyPrivate(self)->freset;
if (func == nullptr)
Py_RETURN_NONE;
Py_INCREF(func);
@@ -444,7 +505,7 @@ static PyObject *qProperty_freset(PyObject *self, void *)
static PyObject *qProperty_fdel(PyObject *self, void *)
{
- auto *func = reinterpret_cast<PySideProperty *>(self)->d->fdel;
+ auto *func = propertyPrivate(self)->fdel;
if (func == nullptr)
Py_RETURN_NONE;
Py_INCREF(func);
@@ -454,16 +515,15 @@ static PyObject *qProperty_fdel(PyObject *self, void *)
static PyObject *qPropertyDocGet(PyObject *self, void *)
{
auto *data = reinterpret_cast<PySideProperty *>(self);
- PySidePropertyPrivate *pData = data->d;
+ if (!data->d->doc().isEmpty() || data->d->type() != PySidePropertyBase::Type::Property)
+ return PyUnicode_FromString(data->d->doc());
- QByteArray doc(pData->doc);
- if (!doc.isEmpty())
- return PyUnicode_FromString(doc);
+ auto *pData = static_cast<PySidePropertyPrivate *>(data->d);
if (pData->fget != nullptr) {
// PYSIDE-1019: Fetch the default `__doc__` from fget. We do it late.
AutoDecRef get_doc(PyObject_GetAttr(pData->fget, PyMagicName::doc()));
if (!get_doc.isNull() && get_doc.object() != Py_None) {
- pData->doc = String::toCString(get_doc);
+ pData->setDoc(String::toCString(get_doc));
pData->getter_doc = true;
if (Py_TYPE(self) == PySideProperty_TypeF())
return qPropertyDocGet(self, nullptr);
@@ -486,10 +546,8 @@ static PyObject *qPropertyDocGet(PyObject *self, void *)
static int qPropertyDocSet(PyObject *self, PyObject *value, void *)
{
auto *data = reinterpret_cast<PySideProperty *>(self);
- PySidePropertyPrivate *pData = data->d;
-
if (String::check(value)) {
- pData->doc = String::toCString(value);
+ data->d->setDoc(String::toCString(value));
return 0;
}
PyErr_SetString(PyExc_TypeError, "String argument expected.");
@@ -498,34 +556,20 @@ static int qPropertyDocSet(PyObject *self, PyObject *value, void *)
static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg)
{
- PySidePropertyPrivate *data = reinterpret_cast<PySideProperty *>(self)->d;
- if (!data)
- return 0;
-
- Py_VISIT(data->fget);
- Py_VISIT(data->fset);
- Py_VISIT(data->freset);
- Py_VISIT(data->fdel);
- Py_VISIT(data->notify);
- Py_VISIT(data->pyTypeObject);
- return 0;
+ auto *pData = propertyPrivate(self);
+ return pData != nullptr ? pData->tp_traverse(visit, arg) : 0;
}
static int qpropertyClear(PyObject *self)
{
- PySidePropertyPrivate *data = reinterpret_cast<PySideProperty *>(self)->d;
- if (!data)
+ auto *data = reinterpret_cast<PySideProperty *>(self);
+ if (data->d == nullptr)
return 0;
- Py_CLEAR(data->fget);
- Py_CLEAR(data->fset);
- Py_CLEAR(data->freset);
- Py_CLEAR(data->fdel);
- Py_CLEAR(data->notify);
- Py_CLEAR(data->pyTypeObject);
-
- delete data;
- reinterpret_cast<PySideProperty *>(self)->d = nullptr;
+ auto *baseData = std::exchange(data->d, nullptr);
+ Q_ASSERT(baseData->type() == PySidePropertyBase::Type::Property);
+ static_cast<PySidePropertyPrivate *>(baseData)->tp_clear();
+ delete baseData;
return 0;
}
@@ -588,22 +632,22 @@ bool checkType(PyObject *pyObj)
PyObject *getValue(PySideProperty *self, PyObject *source)
{
- return self->d->getValue(source);
+ return static_cast<PySidePropertyPrivate *>(self->d)->getValue(source);
}
int setValue(PySideProperty *self, PyObject *source, PyObject *value)
{
- return self->d->setValue(source, value);
+ return static_cast<PySidePropertyPrivate *>(self->d)->setValue(source, value);
}
int reset(PySideProperty *self, PyObject *source)
{
- return self->d->reset(source);
+ return static_cast<PySidePropertyPrivate *>(self->d)->reset(source);
}
const char *getTypeName(const PySideProperty *self)
{
- return self->d->typeName;
+ return self->d->typeName().constData();
}
PySideProperty *getObject(PyObject *source, PyObject *name)
@@ -624,28 +668,28 @@ PySideProperty *getObject(PyObject *source, PyObject *name)
const char *getNotifyName(PySideProperty *self)
{
- if (self->d->notifySignature.isEmpty()) {
- AutoDecRef str(PyObject_Str(self->d->notify));
- self->d->notifySignature = Shiboken::String::toCString(str);
+ if (self->d->notifySignature().isEmpty()) {
+ AutoDecRef str(PyObject_Str(self->d->notify()));
+ self->d->setNotifySignature(Shiboken::String::toCString(str));
}
- return self->d->notifySignature.isEmpty()
- ? nullptr : self->d->notifySignature.constData();
+ return self->d->notifySignature().isEmpty()
+ ? nullptr : self->d->notifySignature().constData();
}
void setTypeName(PySideProperty *self, const char *typeName)
{
- self->d->typeName = typeName;
+ self->d->setTypeName(typeName);
}
PyObject *getTypeObject(const PySideProperty *self)
{
- return self->d->pyTypeObject;
+ return self->d->pyTypeObject();
}
PyObject *create(const char *typeName, PyObject *getter,
PyObject *setter, PyObject *notifySignature,
- PySidePropertyPrivate *data)
+ PySidePropertyBase *data)
{
Shiboken::AutoDecRef kwds(PyDict_New());
PyDict_SetItemString(kwds.object(), "type", PyUnicode_FromString(typeName));
@@ -669,7 +713,7 @@ PyObject *create(const char *typeName, PyObject *getter,
PyObject *create(const char *typeName, PyObject *getter,
PyObject *setter, const char *notifySignature,
- PySidePropertyPrivate *data)
+ PySidePropertyBase *data)
{
PyObject *obNotifySignature = notifySignature != nullptr
diff --git a/sources/pyside6/libpyside/pysideproperty.h b/sources/pyside6/libpyside/pysideproperty.h
index 90c40e174..c2ce006a9 100644
--- a/sources/pyside6/libpyside/pysideproperty.h
+++ b/sources/pyside6/libpyside/pysideproperty.h
@@ -10,7 +10,7 @@
#include <QtCore/qmetaobject.h>
-class PySidePropertyPrivate;
+class PySidePropertyBase;
extern "C"
{
@@ -19,7 +19,7 @@ extern "C"
struct PYSIDE_API PySideProperty
{
PyObject_HEAD
- PySidePropertyPrivate* d;
+ PySidePropertyBase* d;
};
};
@@ -71,12 +71,12 @@ PYSIDE_API void setTypeName(PySideProperty *self, const char *typeName);
/// Create a property from type, getter, setter and notification signature.
PYSIDE_API PyObject *create(const char *typeName, PyObject *getter,
PyObject *setter, PyObject *notifySignature,
- PySidePropertyPrivate *data = nullptr);
+ PySidePropertyBase *data = nullptr);
/// Create a property from type, getter, optional setter and notification signature.
PYSIDE_API PyObject *create(const char *typeName, PyObject *getter,
PyObject *setter = nullptr,
const char *notifySignature = nullptr,
- PySidePropertyPrivate *data = nullptr);
+ PySidePropertyBase *data = nullptr);
} //namespace PySide::Property
diff --git a/sources/pyside6/libpyside/pysideproperty_p.h b/sources/pyside6/libpyside/pysideproperty_p.h
index bd223d0be..baf0df178 100644
--- a/sources/pyside6/libpyside/pysideproperty_p.h
+++ b/sources/pyside6/libpyside/pysideproperty_p.h
@@ -7,6 +7,7 @@
#include <sbkpython.h>
#include "pysideproperty.h"
+#include "pysidepropertybase_p.h"
#include <pysidemacros.h>
#include <QtCore/qbytearray.h>
@@ -16,53 +17,34 @@
struct PySideProperty;
-namespace PySide::Property {
-
-enum class PropertyFlag {
- Readable = 0x001,
- Writable = 0x002,
- Resettable = 0x004,
- Designable = 0x008,
- Scriptable = 0x010,
- Stored = 0x020,
- User = 0x040,
- Constant = 0x080,
- Final = 0x100
-};
-Q_DECLARE_FLAGS(PropertyFlags, PropertyFlag)
-
-} // namespace PySide::Property
-
-class PYSIDE_API PySidePropertyPrivate
+class PYSIDE_API PySidePropertyPrivate : public PySidePropertyBase
{
public:
+ PySidePropertyPrivate(const PySidePropertyPrivate &) = default;
+ PySidePropertyPrivate &operator=(const PySidePropertyPrivate &) = delete;
+ PySidePropertyPrivate(PySidePropertyPrivate &&) = delete;
+ PySidePropertyPrivate &operator=(PySidePropertyPrivate &&) = delete;
- Q_DISABLE_COPY_MOVE(PySidePropertyPrivate)
+ PySidePropertyPrivate() : PySidePropertyBase(Type::Property) {}
+ ~PySidePropertyPrivate() override = default;
- PySidePropertyPrivate() noexcept;
- virtual ~PySidePropertyPrivate();
+ [[nodiscard]] PySidePropertyPrivate *clone() const override;
- virtual void metaCall(PyObject *source, QMetaObject::Call call, void **args);
+ void metaCall(PyObject *source, QMetaObject::Call call, void **args) override;
+
+ void tp_clear();
+ int tp_traverse(visitproc visit, void *arg);
+ void incref();
PyObject *getValue(PyObject *source) const;
int setValue(PyObject *source, PyObject *value);
int reset(PyObject *source);
- static bool assignCheckCallable(PyObject *source, const char *name, PyObject **target);
-
- QByteArray typeName;
- // Type object: A real PyTypeObject ("@Property(int)") or a string
- // "@Property('QVariant')".
- PyObject *pyTypeObject = nullptr;
PyObject *fget = nullptr;
PyObject *fset = nullptr;
PyObject *freset = nullptr;
PyObject *fdel = nullptr;
- PyObject *notify = nullptr;
bool getter_doc = false;
- QByteArray notifySignature;
- QByteArray doc;
- PySide::Property::PropertyFlags flags;
};
namespace PySide::Property {
diff --git a/sources/pyside6/libpyside/pysidepropertybase_p.h b/sources/pyside6/libpyside/pysidepropertybase_p.h
new file mode 100644
index 000000000..c8ef778ca
--- /dev/null
+++ b/sources/pyside6/libpyside/pysidepropertybase_p.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_PROPERTYBASE_P_H
+#define PYSIDE_PROPERTYBASE_P_H
+
+#include <sbkpython.h>
+
+#include <pysidemacros.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qtclasshelpermacros.h>
+#include <QtCore/qflags.h>
+#include <QtCore/qmetaobject.h>
+
+struct PySideProperty;
+
+namespace PySide::Property {
+
+enum class PropertyFlag {
+ Readable = 0x001,
+ Writable = 0x002,
+ Resettable = 0x004,
+ Designable = 0x008,
+ Scriptable = 0x010,
+ Stored = 0x020,
+ User = 0x040,
+ Constant = 0x080,
+ Final = 0x100
+};
+Q_DECLARE_FLAGS(PropertyFlags, PropertyFlag)
+
+} // namespace PySide::Property
+
+// Base class for meta-callable properties (Normal properties, QmlListProperty)
+class PYSIDE_API PySidePropertyBase
+{
+public:
+ PySidePropertyBase &operator=(const PySidePropertyBase &) = delete;
+ PySidePropertyBase(PySidePropertyBase &&) = delete;
+ PySidePropertyBase &operator=(PySidePropertyBase &&) = delete;
+
+ enum class Type : unsigned char { Property, ListProperty };
+
+ virtual ~PySidePropertyBase() = default;
+
+ // For handling decorator like "@property.getter"
+ [[nodiscard]] virtual PySidePropertyBase *clone() const;
+
+ virtual void metaCall(PyObject *source, QMetaObject::Call call, void **args) = 0;
+
+ [[nodiscard]] Type type() const { return m_type; }
+
+ [[nodiscard]] const QByteArray &typeName() const { return m_typeName; }
+ void setTypeName(const QByteArray &newTypeName) { m_typeName = newTypeName; }
+
+ [[nodiscard]] PyObject *pyTypeObject() const { return m_pyTypeObject; }
+ void setPyTypeObject(PyObject *pt) { m_pyTypeObject = pt; }
+
+ [[nodiscard]] PyObject *notify() const { return m_notify; }
+ void setNotify(PyObject *n) { m_notify = n; }
+
+ [[nodiscard]] const QByteArray &notifySignature() const { return m_notifySignature; }
+ void setNotifySignature(const QByteArray &s) { m_notifySignature = s; }
+
+ [[nodiscard]] const QByteArray &doc() const { return m_doc; }
+ void setDoc(const QByteArray &doc) { m_doc = doc; }
+
+ [[nodiscard]] PySide::Property::PropertyFlags flags() const { return m_flags; }
+ void setFlags(PySide::Property::PropertyFlags f) { m_flags = f; }
+ void setFlag(PySide::Property::PropertyFlag f) { m_flags.setFlag(f); }
+
+ static bool assignCheckCallable(PyObject *source, const char *name, PyObject **target);
+
+protected:
+ explicit PySidePropertyBase(Type t);
+ PySidePropertyBase(const PySidePropertyBase &rhs);
+
+ void tp_clearBase();
+ int tp_traverseBase(visitproc visit, void *arg);
+ void increfBase();
+
+private:
+ QByteArray m_typeName;
+ // Type object: A real PyTypeObject ("@Property(int)") or a string
+ // "@Property('QVariant')".
+ PyObject *m_pyTypeObject = nullptr;
+ PyObject *m_notify = nullptr;
+ QByteArray m_notifySignature;
+ QByteArray m_doc;
+ PySide::Property::PropertyFlags m_flags;
+ Type m_type;
+};
+
+#endif // PYSIDE_PROPERTYBASE_P_H
diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp
index 211588eea..01b08e283 100644
--- a/sources/pyside6/libpyside/signalmanager.cpp
+++ b/sources/pyside6/libpyside/signalmanager.cpp
@@ -404,7 +404,7 @@ int SignalManagerPrivate::qtPropertyMetacall(QObject *object,
PyErr_WarnFormat(PyExc_RuntimeWarning, 0,
ign ? "Unknown property type '%s' of QObject '%s' used in fset"
: "Unknown property type '%s' of QObject '%s' used in fget with %R",
- pp->d->typeName.constData(), metaObject->className(), errorStash.getException());
+ pp->d->typeName().constData(), metaObject->className(), errorStash.getException());
if (PyErr_Occurred())
Shiboken::Errors::storeErrorOrPrint();
errorStash.release();
diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp
index de3e6b501..fd2014a35 100644
--- a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp
+++ b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp
@@ -15,16 +15,27 @@
#include <sbktypefactory.h>
#include <pysideproperty.h>
-#include <pysideproperty_p.h>
+#include <pysidepropertybase_p.h>
#include <pysideqobject.h>
#include <QtCore/qobject.h>
#include <QtQml/qqmllist.h>
+#include <utility>
+
+using namespace Qt::StringLiterals;
+
// This is the user data we store in the property.
-class QmlListPropertyPrivate : public PySidePropertyPrivate, public QmlListPropertyMixin
+class QmlListPropertyPrivate : public PySidePropertyBase, public QmlListPropertyMixin
{
public:
+ QmlListPropertyPrivate(const QmlListPropertyPrivate &) = delete;
+ QmlListPropertyPrivate& operator=(const QmlListPropertyPrivate &) = delete;
+ QmlListPropertyPrivate(QmlListPropertyPrivate &&) = delete;
+ QmlListPropertyPrivate& operator=(QmlListPropertyPrivate &&) = delete;
+
+ QmlListPropertyPrivate() : PySidePropertyBase(Type::ListProperty) {}
+
void metaCall(PyObject *source, QMetaObject::Call call, void **args) override
{ handleMetaCall(source, call, args); }
@@ -36,7 +47,11 @@ public:
void replace(QQmlListProperty<QObject> *propList, qsizetype index, QObject *value) override;
void removeLast(QQmlListProperty<QObject> *propList) override;
- PyTypeObject *elementType = nullptr;
+ void tp_clear();
+ int tp_traverse(visitproc visit, void *arg);
+ void incref();
+
+ PyObject *obElementType = nullptr;
PyObject *obAppend = nullptr;
PyObject *obCount = nullptr;
PyObject *obAt = nullptr;
@@ -45,6 +60,50 @@ public:
PyObject *obRemoveLast = nullptr;
};
+void QmlListPropertyPrivate::tp_clear()
+{
+ PySidePropertyBase::tp_clearBase();
+ Py_CLEAR(obElementType);
+ Py_CLEAR(obAppend);
+ Py_CLEAR(obCount);
+ Py_CLEAR(obAt);
+ Py_CLEAR(obClear);
+ Py_CLEAR(obReplace);
+ Py_CLEAR(obRemoveLast);
+}
+
+int QmlListPropertyPrivate::tp_traverse(visitproc visit, void *arg)
+{
+ Py_VISIT(obElementType);
+ Py_VISIT(obAppend);
+ Py_VISIT(obCount);
+ Py_VISIT(obAt);
+ Py_VISIT(obClear);
+ Py_VISIT(obReplace);
+ Py_VISIT(obRemoveLast);
+ return PySidePropertyBase::tp_traverseBase(visit, arg);
+}
+
+void QmlListPropertyPrivate::incref()
+{
+ PySidePropertyBase::increfBase();
+ Py_XINCREF(obElementType);
+ Py_XINCREF(obAppend);
+ Py_XINCREF(obCount);
+ Py_XINCREF(obAt);
+ Py_XINCREF(obClear);
+ Py_XINCREF(obReplace);
+ Py_XINCREF(obRemoveLast);
+}
+
+static inline QmlListPropertyPrivate *qmlListProperty(PyObject *self)
+{
+ auto *data = reinterpret_cast<PySideProperty *>(self);
+ Q_ASSERT(data->d != nullptr);
+ Q_ASSERT(data->d->type() == PySidePropertyBase::Type::ListProperty);
+ return static_cast<QmlListPropertyPrivate *>(data->d);
+}
+
extern "C"
{
@@ -75,7 +134,7 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds)
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O|OOOOOOsObbbbbb:QtQml.ListProperty",
const_cast<char **>(kwlist),
- &data->elementType,
+ &data->obElementType,
&append, &count, &at, &clear, &replace, &removeLast,
/*s*/ &doc,
/*O*/ &notify, // PySideProperty
@@ -84,12 +143,12 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds)
return -1;
}
- if (!PySidePropertyPrivate::assignCheckCallable(append, "append", &data->obAppend)
- || !PySidePropertyPrivate::assignCheckCallable(count, "count", &data->obCount)
- || !PySidePropertyPrivate::assignCheckCallable(at, "at", &data->obAt)
- || !PySidePropertyPrivate::assignCheckCallable(clear, "clear", &data->obClear)
- || !PySidePropertyPrivate::assignCheckCallable(replace, "replace", &data->obReplace)
- || !PySidePropertyPrivate::assignCheckCallable(removeLast, "removeLast", &data->obRemoveLast)) {
+ if (!PySidePropertyBase::assignCheckCallable(append, "append", &data->obAppend)
+ || !PySidePropertyBase::assignCheckCallable(count, "count", &data->obCount)
+ || !PySidePropertyBase::assignCheckCallable(at, "at", &data->obAt)
+ || !PySidePropertyBase::assignCheckCallable(clear, "clear", &data->obClear)
+ || !PySidePropertyBase::assignCheckCallable(replace, "replace", &data->obReplace)
+ || !PySidePropertyBase::assignCheckCallable(removeLast, "removeLast", &data->obRemoveLast)) {
return -1;
}
@@ -101,24 +160,22 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds)
data->setMethodFlag(QmlListPropertyMixin::MethodFlag::RemoveLast, data->obRemoveLast != nullptr);
if (notify != nullptr && notify != Py_None)
- data->notify = notify;
+ data->setNotify(notify);
- if (doc)
- data->doc = doc;
- else
- data->doc.clear();
+ data->setDoc(doc != nullptr ? QByteArray(doc) : QByteArray{});
PyTypeObject *qobjectType = PySide::qObjectType();
- if (!PySequence_Contains(data->elementType->tp_mro, reinterpret_cast<PyObject *>(qobjectType))) {
+ auto *elementType = reinterpret_cast<PyTypeObject *>(data->obElementType);
+ if (!PySequence_Contains(elementType->tp_mro, reinterpret_cast<PyObject *>(qobjectType))) {
PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.",
- qobjectType->tp_name, data->elementType->tp_name);
+ qobjectType->tp_name, elementType->tp_name);
return -1;
}
- data->typeName = QByteArrayLiteral("QQmlListProperty<QObject>");
+ data->setTypeName("QQmlListProperty<QObject>"_ba);
- auto &flags = data->flags;
+ PySide::Property::PropertyFlags flags;
flags.setFlag(PySide::Property::PropertyFlag::Readable, true);
flags.setFlag(PySide::Property::PropertyFlag::Designable, designable);
flags.setFlag(PySide::Property::PropertyFlag::Scriptable, scriptable);
@@ -126,15 +183,51 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds)
flags.setFlag(PySide::Property::PropertyFlag::User, user);
flags.setFlag(PySide::Property::PropertyFlag::Constant, constant);
flags.setFlag(PySide::Property::PropertyFlag::Final, finalProp);
+ data->setFlags(flags);
+
+ data->incref();
return 0;
}
+static int tp_propListTraverse(PyObject *self, visitproc visit, void *arg)
+{
+ auto *pData = qmlListProperty(self);
+ return pData != nullptr ? pData->tp_traverse(visit, arg) : 0;
+}
+
+static int tp_propListClear(PyObject *self)
+{
+ auto *data = reinterpret_cast<PySideProperty *>(self);
+ if (data->d == nullptr)
+ return 0;
+
+ auto *baseData = std::exchange(data->d, nullptr);
+ Q_ASSERT(baseData->type() == PySidePropertyBase::Type::ListProperty);
+ static_cast<QmlListPropertyPrivate *>(baseData)->tp_clear();
+ delete baseData;
+ return 0;
+}
+
+static void tp_propListDeAlloc(PyObject *self)
+{
+ tp_propListClear(self);
+ // PYSIDE-939: Handling references correctly.
+ // This was not needed before Python 3.8 (Python issue 35810)
+ Py_DECREF(Py_TYPE(self));
+ PyObject_GC_UnTrack(self);
+ PepExt_TypeCallFree(self);
+}
+
static PyTypeObject *createPropertyListType()
{
PyType_Slot PropertyListType_slots[] = {
{Py_tp_new, reinterpret_cast<void *>(propList_tp_new)},
{Py_tp_init, reinterpret_cast<void *>(propListTpInit)},
+ {Py_tp_dealloc, reinterpret_cast<void *>(tp_propListDeAlloc)},
+ {Py_tp_traverse, reinterpret_cast<void *>(tp_propListTraverse)},
+ {Py_tp_clear, reinterpret_cast<void *>(tp_propListClear)},
+ {Py_tp_del, reinterpret_cast<void *>(PyObject_GC_Del)},
{0, nullptr}
};
@@ -142,7 +235,7 @@ static PyTypeObject *createPropertyListType()
"2:PySide6.QtQml.ListProperty",
sizeof(PySideProperty),
0,
- Py_TPFLAGS_DEFAULT,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
PropertyListType_slots,
};
@@ -219,6 +312,7 @@ QObject *QmlListPropertyPrivate::at(QQmlListProperty<QObject> *propList, qsizety
Shiboken::AutoDecRef retVal(PyObject_CallObject(obAt, args));
QObject *result = nullptr;
+ auto *elementType = reinterpret_cast<PyTypeObject *>(obElementType);
if (PyErr_Occurred())
PyErr_Print();
else if (PyType_IsSubtype(Py_TYPE(retVal), elementType))
diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt
index 4a7e2e1d1..8b4de5d8e 100644
--- a/sources/pyside6/tests/pysidetest/CMakeLists.txt
+++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt
@@ -156,7 +156,6 @@ PYSIDE_TEST(new_inherited_functions_test.py)
PYSIDE_TEST(notify_id.py)
PYSIDE_TEST(properties_test.py)
PYSIDE_TEST(property_python_test.py)
-PYSIDE_TEST(snake_case_sub.py)
PYSIDE_TEST(snake_case_test.py)
PYSIDE_TEST(true_property_test.py)
PYSIDE_TEST(qapp_like_a_macro_test.py)
diff --git a/sources/pyside6/tests/pysidetest/snake_case_imported.py b/sources/pyside6/tests/pysidetest/snake_case_imported.py
new file mode 100644
index 000000000..c79966e1e
--- /dev/null
+++ b/sources/pyside6/tests/pysidetest/snake_case_imported.py
@@ -0,0 +1,25 @@
+# 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
+
+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 __feature__ import snake_case # noqa
+
+"""
+PYSIDE-3250: Tests that snake_case works when used in several files
+"""
+
+from PySide6.QtWidgets import QWidget # noqa: E402
+
+
+def test():
+ print(__name__)
+ widget = QWidget()
+ return widget.size_hint()
diff --git a/sources/pyside6/tests/pysidetest/snake_case_sub.py b/sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py
index e423542a6..a5ce14694 100644
--- a/sources/pyside6/tests/pysidetest/snake_case_sub.py
+++ b/sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py
@@ -20,4 +20,4 @@ from PySide6.QtWidgets import QWidget # noqa: E402
def test_no_snake_case():
print(__name__)
widget = QWidget()
- check = widget.sizeHint # noqa
+ return widget.sizeHint()
diff --git a/sources/pyside6/tests/pysidetest/snake_case_test.py b/sources/pyside6/tests/pysidetest/snake_case_test.py
index bdcd996c4..4667e584a 100644
--- a/sources/pyside6/tests/pysidetest/snake_case_test.py
+++ b/sources/pyside6/tests/pysidetest/snake_case_test.py
@@ -21,18 +21,26 @@ if not is_pypy:
from __feature__ import snake_case # noqa
from helper.usesqapplication import UsesQApplication
-import snake_case_sub
+import snake_case_imported
+import snake_case_imported_no_snake_case
@unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy")
class SnakeCaseNoPropagateTest(UsesQApplication):
- def testSnakeCase(self):
- # this worked
+ def testSnakeCaseImport(self):
+ """PYSIDE-3250: Test that snake case works when using it in imported modules."""
widget = QWidget()
- check = widget.size_hint # noqa
+ r1 = widget.size_hint()
+ r2 = snake_case_imported.test()
+ self.assertEqual(r1, r2)
- snake_case_sub.test_no_snake_case()
+ def testSnakeCaseImportNoSnakeCase(self):
+ """PYSIDE-2029: Tests that snake_case is isolated from imported modules."""
+ widget = QWidget()
+ r1 = widget.size_hint()
+ r2 = snake_case_imported_no_snake_case.test_no_snake_case()
+ self.assertEqual(r1, r2)
if __name__ == '__main__':
diff --git a/sources/shiboken6/libshiboken/signature/signature.cpp b/sources/shiboken6/libshiboken/signature/signature.cpp
index 9a8d5080a..85cc60e30 100644
--- a/sources/shiboken6/libshiboken/signature/signature.cpp
+++ b/sources/shiboken6/libshiboken/signature/signature.cpp
@@ -310,27 +310,30 @@ static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject
if (origImportFunc == nullptr) {
Py_FatalError("libshiboken: builtins has no \"__orig_import__\" function");
}
- // PYSIDE-3054: Instead of just calling the original import, we temporarily
- // reset the whole import function to the previous version.
- // This prevents unforeseen recursions like in settrace.
- PyObject *featureImportFunc = PyDict_GetItemString(builtins.object(), "__import__");
- Py_INCREF(origImportFunc);
- Py_INCREF(featureImportFunc);
- PyDict_SetItemString(builtins.object(), "__import__", origImportFunc);
ret = PyObject_Call(origImportFunc, args, kwds);
if (ret) {
+ // PYSIDE-3054: Instead of just calling the original import, we temporarily
+ // reset the whole import function to the previous version.
+ // This prevents unforeseen recursions like in settrace.
+ PyObject *featureImportFunc = PyDict_GetItemString(builtins.object(), "__import__");
+ Py_INCREF(origImportFunc);
+ Py_INCREF(featureImportFunc);
+ PyDict_SetItemString(builtins.object(), "__import__", origImportFunc);
+
// PYSIDE-2029: Intercept after the import to search for PySide usage.
PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func,
ret, nullptr);
Py_XDECREF(post);
+
+ PyDict_SetItemString(builtins.object(), "__import__", featureImportFunc);
+ Py_DECREF(origImportFunc);
+ Py_DECREF(featureImportFunc);
+
if (post == nullptr) {
Py_DECREF(ret);
ret = nullptr;
}
}
- PyDict_SetItemString(builtins.object(), "__import__", featureImportFunc);
- Py_DECREF(origImportFunc);
- Py_DECREF(featureImportFunc);
return ret;
}
diff --git a/sources/shiboken6/tests/libsample/spaceship.cpp b/sources/shiboken6/tests/libsample/spaceship.cpp
index b30f2f30f..c883f7c2f 100644
--- a/sources/shiboken6/tests/libsample/spaceship.cpp
+++ b/sources/shiboken6/tests/libsample/spaceship.cpp
@@ -8,6 +8,13 @@ SpaceshipComparisonTester::SpaceshipComparisonTester(int v) noexcept
{
}
+#if __cplusplus >= 202002 || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002)
+std::strong_ordering SpaceshipComparisonTester::operator<=>(int rhs) const
+{
+ return m_value <=> rhs;
+}
+#endif // C++ 20
+
FreeSpaceshipComparisonTester::FreeSpaceshipComparisonTester(int v) noexcept
: m_value(v)
{
@@ -25,3 +32,18 @@ std::strong_ordering operator<=>(FreeSpaceshipComparisonTester lhs,
return lhs.value() <=> rhs.value();
}
#endif // C++ 20
+
+NonEqualityComparisonTester::NonEqualityComparisonTester(int v) noexcept
+ : m_value(v)
+{
+}
+
+int NonEqualityComparisonTester::value() const
+{
+ return m_value;
+}
+
+bool NonEqualityComparisonTester::operator==(NonEqualityComparisonTester rhs) const
+{
+ return m_value == rhs.m_value;
+}
diff --git a/sources/shiboken6/tests/libsample/spaceship.h b/sources/shiboken6/tests/libsample/spaceship.h
index 0d0854fe6..26f636009 100644
--- a/sources/shiboken6/tests/libsample/spaceship.h
+++ b/sources/shiboken6/tests/libsample/spaceship.h
@@ -17,6 +17,7 @@ public:
#if __cplusplus >= 202002 || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002)
auto operator<=>(const SpaceshipComparisonTester &rhs) const = default;
+ std::strong_ordering operator<=>(int rhs) const;
enum Enabled { HasSpaceshipOperator = 1 };
#else
@@ -44,4 +45,18 @@ LIBSAMPLE_API std::strong_ordering operator<=>(FreeSpaceshipComparisonTester lhs
FreeSpaceshipComparisonTester rhs);
#endif // C++ 20
+
+class LIBSAMPLE_API NonEqualityComparisonTester
+{
+public:
+ explicit NonEqualityComparisonTester(int v) noexcept;
+
+ int value() const;
+
+ bool operator==(NonEqualityComparisonTester rhs) const;
+
+private:
+ int m_value;
+};
+
#endif // SPACESHIP_H
diff --git a/sources/shiboken6/tests/samplebinding/CMakeLists.txt b/sources/shiboken6/tests/samplebinding/CMakeLists.txt
index c9e4f601f..01f51fc2d 100644
--- a/sources/shiboken6/tests/samplebinding/CMakeLists.txt
+++ b/sources/shiboken6/tests/samplebinding/CMakeLists.txt
@@ -64,6 +64,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/sample/moveonly_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/moveonlyhandler_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/noimplicitconversion_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/nondefaultctor_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/sample/nonequalitycomparisontester_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/objectmodel_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/objecttype_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/objecttypebyvalue_wrapper.cpp
diff --git a/sources/shiboken6/tests/samplebinding/spaceship_test.py b/sources/shiboken6/tests/samplebinding/spaceship_test.py
index 92d65d4ee..95d2506ce 100644
--- a/sources/shiboken6/tests/samplebinding/spaceship_test.py
+++ b/sources/shiboken6/tests/samplebinding/spaceship_test.py
@@ -14,7 +14,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
from shiboken_paths import init_paths
init_paths()
-from sample import FreeSpaceshipComparisonTester, SpaceshipComparisonTester
+from sample import (FreeSpaceshipComparisonTester, SpaceshipComparisonTester,
+ NonEqualityComparisonTester)
class SpaceshipTest(unittest.TestCase):
@@ -31,6 +32,12 @@ class SpaceshipTest(unittest.TestCase):
self.assertFalse(t1 > t2)
@unittest.skipUnless(SpaceshipComparisonTester.Enabled.HasSpaceshipOperator, "< C++ 20")
+ def testNonHomogeneousSpaceshipOperator(self):
+ t = SpaceshipComparisonTester(42)
+ self.assertTrue(t < 43)
+ self.assertTrue(t > 41)
+
+ @unittest.skipUnless(SpaceshipComparisonTester.Enabled.HasSpaceshipOperator, "< C++ 20")
def testFreeSpaceshipOperator(self):
"""Test a free operator<=>(). It does not provide equality
as it is not defaulted."""
@@ -39,6 +46,15 @@ class SpaceshipTest(unittest.TestCase):
self.assertTrue(t1 < t2)
self.assertFalse(t1 > t2)
+ @unittest.skipUnless(SpaceshipComparisonTester.Enabled.HasSpaceshipOperator, "< C++ 20")
+ def testNonEqualSynthetization(self):
+ ne_a = NonEqualityComparisonTester(1)
+ ne_b = NonEqualityComparisonTester(1)
+ self.assertTrue(ne_a == ne_b)
+ # Verify that different instances with same value are not reported as "not equal",
+ # (fooling the FallbackRichCompare() function which is generated for missing operators).
+ self.assertFalse(ne_a != ne_b)
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
index fed84ba0d..05798a6ce 100644
--- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
+++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
@@ -160,6 +160,7 @@
<enum-type name="Enabled"/>
</value-type>
<value-type name="FreeSpaceshipComparisonTester"/>
+ <value-type name="NonEqualityComparisonTester"/>
<primitive-type name="PStr">
<include file-name="str.h" location="global"/>
diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp
index 2413cc1ad..bf8d3246c 100644
--- a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp
+++ b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp
@@ -278,8 +278,35 @@ void AbstractMetaBuilderPrivate::registerToStringCapability(const FunctionModelI
}
}
+// Find "operator!=" matching an "operator==" in a scope.
+static bool hasOperatorNotEqual(const ScopeModelItem &scopeItem, const FunctionModelItem &operatorEqual)
+{
+ auto pred = [&operatorEqual](const FunctionModelItem &f) {
+ return f->isOperatorNotEqual() && operatorEqual->hasEquivalentArguments(*f);
+ };
+ return std::any_of(scopeItem->functions().cbegin(), scopeItem->functions().cend(), pred);
+}
+
+static ComparisonOperators synthesizedSpaceshipComparison(const AbstractMetaClassCPtr &currentClass,
+ const FunctionModelItem &item)
+{
+ const auto te = currentClass->typeEntry();
+ // operator "<", ">" not for non-pair type containers
+ if (te->isContainer()) {
+ auto cTe = std::static_pointer_cast<const ContainerTypeEntry>(te);
+ if (cTe->containerKind() != ContainerTypeEntry::PairContainer)
+ return ComparisonOperatorType::EqualityMask;
+ }
+
+ // An == operator function is declared implicitly for each operator<=>
+ // defined as defaulted.
+ return item->attributes().testFlag(FunctionAttribute::Defaulted)
+ ? ComparisonOperatorType::AllMask : ComparisonOperatorType::OrderingMask;
+}
+
// Traverse free operator functions (global/namespace)
void AbstractMetaBuilderPrivate::traverseFreeOperatorFunction(const FunctionModelItem &item,
+ const ScopeModelItem &scope,
const AbstractMetaClassPtr &currentClass)
{
Q_ASSERT(!currentClass || currentClass->isNamespace());
@@ -315,12 +342,6 @@ void AbstractMetaBuilderPrivate::traverseFreeOperatorFunction(const FunctionMode
return;
}
- if (item->isSpaceshipOperator() && !item->isDeleted()) {
- AbstractMetaClass::addSynthesizedComparisonOperators(baseoperandClass,
- InternalFunctionFlag::OperatorCpp20Spaceship);
- return;
- }
-
// Do not synthesize reverse comparison operators. CPython swaps the
// arguments for them by itself in Py_tp_richcompare.
const bool reverseOperator = !firstArgumentIsSelf && !unaryOperator;
@@ -359,6 +380,27 @@ void AbstractMetaBuilderPrivate::traverseFreeOperatorFunction(const FunctionMode
if (metaFunction->isComparisonOperator())
metaFunction->setConstant(true);
metaFunction->setAccess(Access::Public);
+ if (item->isSpaceshipOperator()) {
+ // For spaceship, the traverse mechanism is only used to handle rejections
+ // and get the argument type.
+ const auto ops = synthesizedSpaceshipComparison(baseoperandClass, item);
+ flags.setFlag(InternalFunctionFlag::OperatorCpp20Spaceship);
+ AbstractMetaClass::addSynthesizedComparisonOperators(baseoperandClass,
+ metaFunction->arguments(),
+ ops, flags);
+ return;
+ }
+
+ // C++20: Synthesize "!=" from "=="
+ if (clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20
+ && item->isOperatorEqual()
+ && !item->hasPointerArguments() && !hasOperatorNotEqual(scope, item)) {
+ AbstractMetaClass::addSynthesizedComparisonOperators(
+ baseoperandClass, metaFunction->arguments(),
+ ComparisonOperatorType::OperatorNotEqual,
+ flags | InternalFunctionFlag::OperatorCpp20NonEquality);
+ }
+
AbstractMetaClass::addFunction(baseoperandClass, metaFunction);
ReportHandler::addGeneralMessage(msgSynthesizedFunction(metaFunction, item));
if (!metaFunction->arguments().isEmpty()) {
@@ -676,11 +718,11 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom,
case CodeModel::ArithmeticOperator:
case CodeModel::BitwiseOperator:
case CodeModel::LogicalOperator:
- traverseFreeOperatorFunction(func, {});
+ traverseFreeOperatorFunction(func, dom, {});
break;
case CodeModel::ShiftOperator:
if (!traverseStreamOperator(func, {}))
- traverseFreeOperatorFunction(func, {});
+ traverseFreeOperatorFunction(func, dom, {});
default:
break;
}
@@ -1465,7 +1507,7 @@ void AbstractMetaBuilderPrivate::traverseNameSpaceFunctions(const ScopeModelItem
functions.reserve(scopeFunctionList.size());
for (const FunctionModelItem &function : scopeFunctionList) {
if (function->isOperator()) {
- traverseFreeOperatorFunction(function, currentClass);
+ traverseFreeOperatorFunction(function, scopeItem, currentClass);
} else if (auto metaFunction = traverseFunction(function, currentClass)) {
metaFunction->setCppAttribute(FunctionAttribute::Static);
functions.append(metaFunction);
@@ -1548,8 +1590,27 @@ void AbstractMetaBuilderPrivate::traverseClassFunction(const ScopeModelItem& sco
const AbstractMetaFunctionPtr &metaFunction,
const AbstractMetaClassPtr &metaClass) const
{
- Q_UNUSED(scopeItem)
- Q_UNUSED(function)
+ if (function->isSpaceshipOperator()) {
+ // For spaceship, the traverse mechanism is only used to handle rejections
+ // and get the argument type.
+ if (!function->isDeleted()) {
+ const auto ops = synthesizedSpaceshipComparison(metaClass, function);
+ AbstractMetaClass::addSynthesizedComparisonOperators(metaClass,
+ metaFunction->arguments(),
+ ops, InternalFunctionFlag::OperatorCpp20Spaceship);
+ }
+ return;
+ }
+
+ // C++20: Synthesize "!=" from "=="
+ if (clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20
+ && function->isOperatorEqual() && !hasOperatorNotEqual(scopeItem, function)) {
+ AbstractMetaClass::addSynthesizedComparisonOperators(
+ metaClass, metaFunction->arguments(),
+ ComparisonOperatorType::OperatorNotEqual,
+ InternalFunctionFlag::OperatorCpp20NonEquality);
+ }
+
traverseClassFunction(metaFunction, metaClass);
}
@@ -1559,10 +1620,7 @@ void AbstractMetaBuilderPrivate::traverseClassFunctions(const ScopeModelItem& sc
Q_ASSERT(metaClass);
AbstractMetaClass::Attributes constructorAttributes;
for (const FunctionModelItem &function : scopeItem->functions()) {
- if (function->isSpaceshipOperator() && !function->isDeleted()) {
- AbstractMetaClass::addSynthesizedComparisonOperators(metaClass,
- InternalFunctionFlag::OperatorCpp20Spaceship);
- } else if (auto metaFunction = traverseFunction(function, metaClass)) {
+ if (auto metaFunction = traverseFunction(function, metaClass)) {
traverseClassFunction(scopeItem, function, metaFunction, metaClass);
} else if (!function->isDeleted() && function->functionType() == CodeModel::Constructor) {
// traverseFunction() failed: mark rejected constructors
diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h
index b503f4b33..0890eb752 100644
--- a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h
+++ b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h
@@ -116,7 +116,7 @@ public:
void traverseFields(const ScopeModelItem &item, const AbstractMetaClassPtr &parent);
bool traverseStreamOperator(const FunctionModelItem &functionItem,
const AbstractMetaClassPtr &currentClass);
- void traverseFreeOperatorFunction(const FunctionModelItem &item,
+ void traverseFreeOperatorFunction(const FunctionModelItem &item, const ScopeModelItem &scope,
const AbstractMetaClassPtr &currentClass);
AbstractMetaFunctionPtr
traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc,
diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp
index 837ce0d1a..14ed79644 100644
--- a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp
+++ b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp
@@ -920,35 +920,30 @@ static AbstractMetaType boolType()
return result;
}
-// Helper to synthesize comparison operators from a spaceship operator. Since
-// shiboken also generates code for comparing to different types, this fits
-// better than of handling it in the generator code.
+// Helper to synthesize comparison operators from a spaceship operator and equality operators.
+// Since shiboken also generates code for comparing to different types, this fits
+// better than handling it in the generator code.
void AbstractMetaClass::addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c,
+ const AbstractMetaArgumentList &arguments,
+ ComparisonOperators ops,
InternalFunctionFlags flags)
{
static const auto returnType = boolType();
- AbstractMetaType selfType(c->typeEntry());
- selfType.setConstant(true);
- selfType.setReferenceType(LValueReference);
- selfType.decideUsagePattern();
- AbstractMetaArgument selfArgument;
- selfArgument.setType(selfType);
- selfArgument.setName(u"rhs"_s);
- AbstractMetaArgumentList arguments(1, selfArgument);
-
- static const char *operators[]
- = {"operator==", "operator!=", "operator<", "operator<=", "operator>", "operator>="};
- for (const auto *op : operators) {
- auto *f = AbstractMetaClassPrivate::createFunction(QLatin1StringView(op),
- AbstractMetaFunction::ComparisonOperator,
- Access::Public, arguments,
- returnType, c);
- f->setFlags(f->flags() | flags);
- f->setConstant(true);
- AbstractMetaFunctionCPtr newFunction(f);
- c->d->addFunction(newFunction);
- ReportHandler::addGeneralMessage(msgSynthesizedFunction(newFunction));
+ for (int mask = 0x1; (mask & int(ComparisonOperatorType::AllMask)) != 0; mask <<= 1) {
+ const auto op = ComparisonOperatorType(mask);
+ if (ops.testFlag(op)) {
+ const QString name = "operator"_L1 + QLatin1StringView(AbstractMetaFunction::cppComparisonOperator(op));
+ auto *f = AbstractMetaClassPrivate::createFunction(name,
+ AbstractMetaFunction::ComparisonOperator,
+ Access::Public, arguments,
+ returnType, c);
+ f->setFlags(f->flags() | flags);
+ f->setConstant(true);
+ AbstractMetaFunctionCPtr newFunction(f);
+ c->d->addFunction(newFunction);
+ ReportHandler::addGeneralMessage(msgSynthesizedFunction(newFunction));
+ }
}
}
diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h
index 8cc2f71f8..fd9b89443 100644
--- a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h
+++ b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h
@@ -128,6 +128,8 @@ public:
bool isCopyConstructible() const;
static void addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c,
+ const AbstractMetaArgumentList &arguments,
+ ComparisonOperators ops,
InternalFunctionFlags flags);
bool generateExceptionHandling() const;
diff --git a/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp
index 407a7a9e7..8e262d6d8 100644
--- a/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp
+++ b/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp
@@ -397,6 +397,7 @@ FunctionModelItem BuilderPrivate::createMemberFunction(const CXCursor &cursor,
result->setAttribute(FunctionAttribute::Static, clang_CXXMethod_isStatic(cursor) != 0);
result->setAttribute(FunctionAttribute::Virtual, clang_CXXMethod_isVirtual(cursor) != 0);
result->setAttribute(FunctionAttribute::Abstract, clang_CXXMethod_isPureVirtual(cursor) != 0);
+ result->setAttribute(FunctionAttribute::Defaulted, clang_CXXMethod_isDefaulted(cursor) != 0);
return result;
}
diff --git a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp
index 8cbca7bc9..bfccacbac 100644
--- a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp
+++ b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp
@@ -846,6 +846,13 @@ void _ArgumentModelItem::setScopeResolution(bool v)
m_scopeResolution = v;
}
+bool _ArgumentModelItem::isEquivalent(const _ArgumentModelItem &rhs) const
+{
+ return m_scopeResolution == rhs.m_scopeResolution && m_defaultValue == rhs.m_defaultValue
+ && m_defaultValueExpression == rhs.m_defaultValueExpression
+ && m_type == rhs.m_type;
+}
+
#ifndef QT_NO_DEBUG_STREAM
void _ArgumentModelItem::formatDebug(QDebug &d) const
{
@@ -991,6 +998,16 @@ bool _FunctionModelItem::isOperator() const
return result;
}
+static bool isPointerArgument(const ArgumentModelItem &a)
+{
+ return !a->type().indirectionsV().isEmpty();
+}
+
+bool _FunctionModelItem::hasPointerArguments() const
+{
+ return std::any_of(m_arguments.cbegin(), m_arguments.cend(), isPointerArgument);
+}
+
ExceptionSpecification _FunctionModelItem::exceptionSpecification() const
{
return m_exceptionSpecification;
@@ -1075,6 +1092,19 @@ QString _FunctionModelItem::typeSystemSignature() const // For dumping out type
return result;
}
+static inline bool equivalentArguments(const ArgumentModelItem &lhs,
+ const ArgumentModelItem &rhs)
+{
+ return lhs->isEquivalent(*rhs);
+}
+
+bool _FunctionModelItem::hasEquivalentArguments(const _FunctionModelItem &rhs) const
+{
+ return m_arguments.size() == rhs.m_arguments.size()
+ && std::equal(m_arguments.cbegin(), m_arguments.cend(), rhs.m_arguments.cbegin(), rhs.m_arguments.cend(),
+ equivalentArguments);
+}
+
using NameFunctionTypeHash = QHash<QStringView, CodeModel::FunctionType>;
static const NameFunctionTypeHash &nameToOperatorFunction()
diff --git a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h
index 8d757e635..7ff0a88e2 100644
--- a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h
+++ b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h
@@ -380,6 +380,8 @@ public:
bool scopeResolution() const;
void setScopeResolution(bool v);
+ bool isEquivalent(const _ArgumentModelItem &rhs) const; // Compare all except name
+
#ifndef QT_NO_DEBUG_STREAM
void formatDebug(QDebug &d) const override;
#endif
@@ -502,6 +504,7 @@ public:
bool isSpaceshipOperator() const;
bool isOperatorEqual() const;
bool isOperatorNotEqual() const;
+ bool hasPointerArguments() const;
bool isSimilar(const FunctionModelItem &other) const;
@@ -515,6 +518,9 @@ public:
QString classQualifiedSignature() const;
QString typeSystemSignature() const; // For dumping out type system files
+ // Compare all except names
+ bool hasEquivalentArguments(const _FunctionModelItem &rhs) const;
+
// Private, for usage by the clang builder.
void _determineType();
diff --git a/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h b/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h
index 358195799..272140ae3 100644
--- a/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h
+++ b/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h
@@ -53,6 +53,7 @@ enum class FunctionAttribute : std::uint8_t {
Final = 0x00000010,
Deprecated = 0x00000020, // Code annotation
Explicit = 0x00000040, // Constructor
+ Defaulted = 0x00000080
};
Q_DECLARE_FLAGS(FunctionAttributes, FunctionAttribute)
diff --git a/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp b/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp
index 91d39f835..ae06fb140 100644
--- a/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp
+++ b/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp
@@ -1739,6 +1739,17 @@ void TypeDatabasePrivate::addBuiltInPrimitiveTypes()
root, rootPackage,
pyUnicodeCustomEntry);
}
+
+ // Prevent rejection of operator<=>() due to mismatched return type.
+ if (clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20) {
+ for (const QString &ordering : {u"std::strong_ordering"_s, u"std::partial_ordering"_s}) {
+ if (!m_entries.contains(ordering)) {
+ auto entry = std::make_shared<CustomTypeEntry>(ordering, QVersionNumber{}, root);
+ entry->setTargetLangPackage(rootPackage);
+ m_entries.insert(ordering, entry);
+ }
+ }
+ }
}
QDebug operator<<(QDebug d, const TypeDatabase &db)