diff options
author | Cristián Maureira-Fredes <Cristian.Maureira-Fredes@qt.io> | 2024-02-08 14:24:44 +0100 |
---|---|---|
committer | Cristián Maureira-Fredes <Cristian.Maureira-Fredes@qt.io> | 2024-02-08 14:24:44 +0100 |
commit | 4bb71d324c7281bcb34c7625cd4f82ad090c95ff (patch) | |
tree | 9c9b6737cc3da150ebf771e0a48d7d769344f68e | |
parent | f0b06aa4946f0ffa7bac59e2ed86ebd5c3d9f252 (diff) | |
parent | bf0f779125afae2c6c995cea5dc5553da8e9da77 (diff) |
Merge branch '6.6' into 6.6.2
Change-Id: I6478a1b61b328f1ab4ad0606ae36974c37f97012
-rw-r--r-- | build_scripts/platforms/windows_desktop.py | 3 | ||||
-rw-r--r-- | examples/qml/usingmodel/usingmodel.py | 48 | ||||
-rw-r--r-- | examples/qml/usingmodel/view.qml | 12 | ||||
-rw-r--r-- | sources/pyside6/PySide6/QtCore/typesystem_core_common.xml | 25 | ||||
-rw-r--r-- | sources/pyside6/PySide6/glue/qtcore.cpp | 56 | ||||
-rw-r--r-- | sources/pyside6/libpyside/CMakeLists.txt | 4 | ||||
-rw-r--r-- | sources/pyside6/libpyside/pysideqslotobject_p.cpp | 36 | ||||
-rw-r--r-- | sources/pyside6/libpyside/pysideqslotobject_p.h | 39 | ||||
-rw-r--r-- | sources/pyside6/libpyside/qobjectconnect.cpp | 43 | ||||
-rw-r--r-- | sources/pyside6/libpyside/qobjectconnect.h | 5 | ||||
-rw-r--r-- | sources/pyside6/plugins/designer/designercustomwidgets.cpp | 1 | ||||
-rw-r--r-- | sources/pyside6/tests/QtCore/qtimer_singleshot_test.py | 11 | ||||
-rw-r--r-- | sources/pyside6/tests/signals/qobject_sender_test.py | 7 |
13 files changed, 222 insertions, 68 deletions
diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index 543ee9880..c3b19d5a1 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -120,7 +120,8 @@ def prepare_packages_win32(pyside_build, _vars): src = f"{{install_dir}}/bin/{script_dir}" target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script_dir}" # Exclude subdirectory tests - copydir(src, target, _filter=["*.py", "*.spec"], recursive=False, _vars=_vars) + copydir(src, target, _filter=["*.py", "*.spec", "*.jpg", "*.icns", "*.ico"], + recursive=False, _vars=_vars) # <install>/bin/*.exe,*.dll -> {st_package_name}/ filters = ["pyside*.exe", "pyside*.dll"] diff --git a/examples/qml/usingmodel/usingmodel.py b/examples/qml/usingmodel/usingmodel.py index 6f8ea5a21..008a1b94b 100644 --- a/examples/qml/usingmodel/usingmodel.py +++ b/examples/qml/usingmodel/usingmodel.py @@ -2,24 +2,37 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import os +from dataclasses import dataclass from pathlib import Path import sys from PySide6.QtCore import QAbstractListModel, Qt, QUrl, QByteArray from PySide6.QtGui import QGuiApplication from PySide6.QtQuick import QQuickView -from PySide6.QtQml import qmlRegisterSingletonType +from PySide6.QtQml import QmlElement, QmlSingleton +QML_IMPORT_NAME = "PersonModel" +QML_IMPORT_MAJOR_VERSION = 1 + + +@dataclass +class Person: + name: str + myrole: str + + +@QmlElement +@QmlSingleton class PersonModel (QAbstractListModel): MyRole = Qt.UserRole + 1 - def __init__(self, parent=None): - QAbstractListModel.__init__(self, parent) - self._data = [] + def __init__(self, data, parent=None): + super().__init__(parent) + self._data = data def roleNames(self): roles = { - PersonModel.MyRole: QByteArray(b'modelData'), + PersonModel.MyRole: QByteArray(b'myrole'), Qt.DisplayRole: QByteArray(b'display') } return roles @@ -29,26 +42,18 @@ class PersonModel (QAbstractListModel): def data(self, index, role): d = self._data[index.row()] - if role == Qt.DisplayRole: - return d['name'] - elif role == Qt.DecorationRole: + return d.name + if role == Qt.DecorationRole: return Qt.black - elif role == PersonModel.MyRole: - return d['myrole'] + if role == PersonModel.MyRole: + return d.myrole return None - def populate(self, data=None): - for item in data: - self._data.append(item) - - -def model_callback(engine): - my_model = PersonModel() - data = [{'name': 'Qt', 'myrole': 'role1'}, - {'name': 'PySide', 'myrole': 'role2'}] - my_model.populate(data) - return my_model + @staticmethod + def create(engine): + data = [Person('Qt', 'myrole'), Person('PySide', 'role2')] + return PersonModel(data) if __name__ == '__main__': @@ -56,7 +61,6 @@ if __name__ == '__main__': view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) - qmlRegisterSingletonType(PersonModel, "PersonModel", 1, 0, "MyModel", model_callback) qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) if view.status() == QQuickView.Error: diff --git a/examples/qml/usingmodel/view.qml b/examples/qml/usingmodel/view.qml index c5aa7e0fc..e8b1fb2fb 100644 --- a/examples/qml/usingmodel/view.qml +++ b/examples/qml/usingmodel/view.qml @@ -8,21 +8,13 @@ ListView { width: 100 height: 100 anchors.fill: parent - model: MyModel + model: PersonModel delegate: Component { Rectangle { height: 25 width: 100 Text { - function displayText() { - var result = "" - if (typeof display !== "undefined") - result = display + ": " - result += modelData - return result - } - - text: displayText() + text: display + ": " + myrole } } } diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index bdaef41f4..5168373fd 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -1731,16 +1731,10 @@ <modify-argument index="1" pyi-type="Optional[PySide6.QtCore.QObject]"/> </modify-function> <modify-function signature="connect(const QObject*,const char*,const char*,Qt::ConnectionType)const"> - <modify-argument index="4"> - <rename to="type"/> - </modify-argument> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-1"/> </modify-function> <!-- static version --> <modify-function signature="connect(const QObject*,QMetaMethod,const QObject*,QMetaMethod,Qt::ConnectionType)"> - <modify-argument index="5"> - <rename to="type"/> - </modify-argument> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-2"/> </modify-function> <modify-function signature="connect(const QObject*,const char*,const QObject*,const char*,Qt::ConnectionType)"> @@ -1750,16 +1744,20 @@ <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-3"/> </modify-function> <inject-code class="native" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect"/> - <add-function signature="connect(const QObject*,const char*,PyCallable*,Qt::ConnectionType@type@=Qt::AutoConnection)" + <add-function signature="connect(const QObject*@sender@,const char*@signal@,PyCallable*@functor@,Qt::ConnectionType@type@=Qt::AutoConnection)" return-type="QMetaObject::Connection" static="yes"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-4"/> </add-function> + <add-function signature="connect(const QObject*@sender@,const char*@signal@,const QObject*@context@,PyCallable*@functor@,Qt::ConnectionType@type@=Qt::AutoConnection)" + return-type="QMetaObject::Connection" static="yes"> + <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-4-context"/> + </add-function> <!-- static version --> - <add-function signature="connect(const char*,PyCallable*,Qt::ConnectionType@type@=Qt::AutoConnection)" + <add-function signature="connect(const char*@signal@,PyCallable*@functor@,Qt::ConnectionType@type@=Qt::AutoConnection)" return-type="QMetaObject::Connection"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-5"/> </add-function> - <add-function signature="connect(const char*,const QObject*,const char*,Qt::ConnectionType@type@=Qt::AutoConnection)" + <add-function signature="connect(const char*@signal@,const QObject*@receiver@,const char*@method@,Qt::ConnectionType@type@=Qt::AutoConnection)" return-type="QMetaObject::Connection"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-connect-6"/> </add-function> @@ -1767,12 +1765,17 @@ <add-function signature="emit(const char*,...)" return-type="bool"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-emit"/> </add-function> - <add-function signature="disconnect(const char*,PyCallable*)" return-type="bool"> + <add-function signature="disconnect(const char*@signal@,PyCallable*@functor@)" return-type="bool"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-disconnect-1"/> </add-function> - <add-function signature="disconnect(const QObject*,const char*,PyCallable*)" return-type="bool" static="yes"> + <add-function signature="disconnect(const QObject*@sender@,const char*@signal@,PyCallable*@functor@)" return-type="bool" static="yes"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-disconnect-2"/> </add-function> + <modify-function signature="disconnect(QMetaObject::Connection)"> + <modify-argument index="1"> + <rename to="connection"/> + </modify-argument> + </modify-function> <add-function signature="findChild(PyTypeObject*@type@,const QString&@name@={},Qt::FindChildOptions@options@=Qt::FindChildrenRecursively)" diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index a687d8447..94dc915a0 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -321,6 +321,12 @@ PyModule_AddStringConstant(module, "__version__", qVersion()); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); // @snippet qobject-connect-4 +// @snippet qobject-connect-4-context +// %FUNCTION_NAME() - disable generation of function call. +%RETURN_TYPE %0 = PySide::qobjectConnectCallback(%1, %2, %3, %PYARG_4, %5); +%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); +// @snippet qobject-connect-4-context + // @snippet qobject-connect-5 // %FUNCTION_NAME() - disable generation of function call. %RETURN_TYPE %0 = PySide::qobjectConnectCallback(%CPPSELF, %1, %PYARG_2, %3); @@ -1053,28 +1059,9 @@ timer->start(%1); // @snippet qtimer-singleshot-2 // @snippet qtimer-singleshot-functor-context -Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); -if (PyObject_TypeCheck(%3, PySideSignalInstance_TypeF())) { - auto *timerType = Shiboken::SbkType<QTimer>(); - auto *pyTimer = timerType->tp_new(Shiboken::SbkType<QTimer>(), emptyTuple, nullptr); - timerType->tp_init(pyTimer, emptyTuple, nullptr); - QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); - timer->setSingleShot(true); - PySideSignalInstance *signalInstance = reinterpret_cast<PySideSignalInstance *>(%2); - Shiboken::AutoDecRef signalSignature(Shiboken::String::fromFormat("2%s", PySide::Signal::getSignature(signalInstance))); - Shiboken::AutoDecRef result( - PyObject_CallFunction(PySide::PySideName::qtConnect(), "OsOO", - pyTimer, - SIGNAL(timeout()), - %3, - PySide::Signal::getObject(signalInstance), - signalSignature.object()) - ); - timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); - Shiboken::Object::releaseOwnership(reinterpret_cast<SbkObject *>(pyTimer)); - Py_XDECREF(pyTimer); - timer->start(%1); -} else { +auto msec = %1; +if (msec == 0) { + Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); auto *callable = %PYARG_3; auto cppCallback = [callable]() { @@ -1085,7 +1072,30 @@ if (PyObject_TypeCheck(%3, PySideSignalInstance_TypeF())) { }; Py_INCREF(callable); - %CPPSELF.%FUNCTION_NAME(%1, %2, cppCallback); + %CPPSELF.%FUNCTION_NAME(msec, %2, cppCallback); +} else { + Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); + auto *timerType = Shiboken::SbkType<QTimer>(); + auto newFunc = timerType->tp_new; + auto initFunc = timerType->tp_init; + auto *pyTimer = newFunc(Shiboken::SbkType<QTimer>(), emptyTuple, nullptr); + initFunc(pyTimer, emptyTuple, nullptr); + + QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); + timer->setSingleShot(true); + + Shiboken::AutoDecRef result( + PyObject_CallMethod(pyTimer, "connect", "OsOO", + pyTimer, + SIGNAL(timeout()), + %PYARG_2, + %PYARG_3) + ); + + timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); + Shiboken::Object::releaseOwnership(reinterpret_cast<SbkObject *>(pyTimer)); + Py_XDECREF(pyTimer); + timer->start(msec); } // @snippet qtimer-singleshot-functor-context diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index 01a0a66b0..e9fd0d56e 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -5,7 +5,10 @@ project(libpyside) set(libpyside_libraries Qt::Core Qt::CorePrivate) +set(CMAKE_AUTOMOC ON) + set(libpyside_HEADERS # installed below + pysideqslotobject_p.h class_property.h dynamicqmetaobject.h feature_select.h @@ -41,6 +44,7 @@ set(libpyside_HEADERS # installed below ) set(libpyside_SRC + pysideqslotobject_p.cpp class_property.cpp dynamicqmetaobject.cpp feature_select.cpp diff --git a/sources/pyside6/libpyside/pysideqslotobject_p.cpp b/sources/pyside6/libpyside/pysideqslotobject_p.cpp new file mode 100644 index 000000000..914be898a --- /dev/null +++ b/sources/pyside6/libpyside/pysideqslotobject_p.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "pysideqslotobject_p.h" + +#include <autodecref.h> +#include <gilstate.h> + +namespace PySide +{ + +void PySideQSlotObject::impl(int which, QSlotObjectBase *this_, QObject *receiver, + void **args, bool *ret) +{ + auto self = static_cast<PySideQSlotObject *>(this_); + switch (which) { + case Destroy: + delete self; + break; + case Call: + { + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(0)); + Shiboken::AutoDecRef ret(PyObject_CallObject(self->callable, arglist)); + break; + } + case Compare: + case NumOperations: + Q_UNUSED(receiver); + Q_UNUSED(args); + Q_UNUSED(ret); + break; + } +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/pysideqslotobject_p.h b/sources/pyside6/libpyside/pysideqslotobject_p.h new file mode 100644 index 000000000..d7d258505 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqslotobject_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef PYSIDEQSLOTOBJECT_P_H +#define PYSIDEQSLOTOBJECT_P_H + +#include "pysidemacros.h" +#include <sbkpython.h> + +#include <QtCore/QObject> +#include <QtCore/qobjectdefs.h> + +namespace PySide +{ + +class PySideQSlotObject : public QtPrivate::QSlotObjectBase +{ + PyObject *callable; + + static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **args, bool *ret); + +public: + PySideQSlotObject(PyObject *callable) : QtPrivate::QSlotObjectBase(&impl), callable(callable) + { + Py_INCREF(callable); + } + + ~PySideQSlotObject() + { + auto gstate = PyGILState_Ensure(); + Py_DECREF(callable); + PyGILState_Release(gstate); + } +}; + + +} // namespace PySide + +#endif // PYSIDEQSLOTOBJECT_P_H diff --git a/sources/pyside6/libpyside/qobjectconnect.cpp b/sources/pyside6/libpyside/qobjectconnect.cpp index 15b71766c..c47c96eb8 100644 --- a/sources/pyside6/libpyside/qobjectconnect.cpp +++ b/sources/pyside6/libpyside/qobjectconnect.cpp @@ -3,6 +3,7 @@ #include "qobjectconnect.h" #include "pysideqobject.h" +#include "pysideqslotobject_p.h" #include "pysidesignal.h" #include "pysideutils.h" #include "signalmanager.h" @@ -15,6 +16,8 @@ #include <QtCore/QMetaMethod> #include <QtCore/QObject> +#include <QtCore/private/qobject_p.h> + #include <string_view> static bool isMethodDecorator(PyObject *method, bool is_pymethod, PyObject *self) @@ -259,6 +262,46 @@ QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *sign return connection; } +QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, QObject *context, + PyObject *callback, Qt::ConnectionType type) +{ + if (!signal || !PySide::Signal::checkQtSignal(signal)) + return {}; + + const int signalIndex = + PySide::SignalManager::registerMetaMethodGetIndex(source, signal + 1, + QMetaMethod::Signal); + if (signalIndex == -1) + return {}; + + // Extract receiver from callback + const GetReceiverResult receiver = getReceiver(source, signal + 1, callback); + if (receiver.receiver == nullptr && receiver.self == nullptr) + return {}; + + PySide::SignalManager &signalManager = PySide::SignalManager::instance(); + + PySideQSlotObject *slotObject = new PySideQSlotObject(callback); + + QMetaObject::Connection connection{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() + connection = QObjectPrivate::connect(source, signalIndex, context, slotObject, type); + Py_END_ALLOW_THREADS + if (!connection) { + if (receiver.usingGlobalReceiver) + signalManager.releaseGlobalReceiver(source, receiver.receiver); + return {}; + } + + Q_ASSERT(receiver.receiver); + if (receiver.usingGlobalReceiver) + signalManager.notifyGlobalReceiver(receiver.receiver); + + const QMetaMethod signalMethod = receiver.receiver->metaObject()->method(signalIndex); + static_cast<FriendlyQObject *>(source)->connectNotify(signalMethod); + return connection; +} + bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *callback) { if (!PySide::Signal::checkQtSignal(signal)) diff --git a/sources/pyside6/libpyside/qobjectconnect.h b/sources/pyside6/libpyside/qobjectconnect.h index 70b862233..c99b8006e 100644 --- a/sources/pyside6/libpyside/qobjectconnect.h +++ b/sources/pyside6/libpyside/qobjectconnect.h @@ -33,6 +33,11 @@ PYSIDE_API QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, PyObject *callback, Qt::ConnectionType type); +/// Helpers for QObject::connect(): Make a connection to a Python callback and a context object +PYSIDE_API QMetaObject::Connection + qobjectConnectCallback(QObject *source, const char *signal, QObject *context, + PyObject *callback, Qt::ConnectionType type); + /// Helpers for QObject::disconnect(): Disconnect a Python callback PYSIDE_API bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *callback); diff --git a/sources/pyside6/plugins/designer/designercustomwidgets.cpp b/sources/pyside6/plugins/designer/designercustomwidgets.cpp index 6491251b9..ccff635a9 100644 --- a/sources/pyside6/plugins/designer/designercustomwidgets.cpp +++ b/sources/pyside6/plugins/designer/designercustomwidgets.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2021 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 +#undef slots #include <Python.h> // Include before Qt headers due to 'slots' macro definition #include "designercustomwidgets.h" diff --git a/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py b/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py index 1392281fa..9718a2427 100644 --- a/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py +++ b/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py @@ -85,6 +85,17 @@ class TestSingleShot(UsesQApplication): self.assertTrue(thread.called) self.assertEqual(self.qthread, thread.qthread) + def testSingleShotWithContextZero(self): + thread = ThreadForContext() + thread.start() + thread.context.moveToThread(thread) + QTimer.singleShot(0, thread.context, self.callback) + self.app.exec() + thread.wait() + self.assertTrue(self.called) + self.assertTrue(thread.called) + self.assertEqual(self.qthread, thread.qthread) + class SigEmitter(QObject): diff --git a/sources/pyside6/tests/signals/qobject_sender_test.py b/sources/pyside6/tests/signals/qobject_sender_test.py index a7cda69f7..485584ec2 100644 --- a/sources/pyside6/tests/signals/qobject_sender_test.py +++ b/sources/pyside6/tests/signals/qobject_sender_test.py @@ -73,6 +73,12 @@ class ObjectSenderWithQAppTest(UsesQApplication): self.app.exec() self.assertTrue(isinstance(recv.the_sender, QObject)) + def testSenderCppSignalSingleShotTimerWithContext(self): + recv = Receiver() + QTimer.singleShot(10, recv, recv.callback) + self.app.exec() + self.assertTrue(isinstance(recv.the_sender, QObject)) + def testSenderCppSignalWithPythonExtendedClass(self): sender = ExtQTimer() recv = Receiver() @@ -105,4 +111,3 @@ class ObjectSenderWithQAppCheckOnReceiverTest(UsesQApplication): if __name__ == '__main__': unittest.main() - |