diff options
Diffstat (limited to 'sources/pyside6/libpyside/qobjectconnect.cpp')
-rw-r--r-- | sources/pyside6/libpyside/qobjectconnect.cpp | 104 |
1 files changed, 93 insertions, 11 deletions
diff --git a/sources/pyside6/libpyside/qobjectconnect.cpp b/sources/pyside6/libpyside/qobjectconnect.cpp index 2d97a68d1..3c5b75953 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,10 @@ #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) { Shiboken::AutoDecRef methodName(PyObject_GetAttr(method, Shiboken::PyMagicName::name())); @@ -29,6 +34,8 @@ static bool isMethodDecorator(PyObject *method, bool is_pymethod, PyObject *self function1 = PyMethod_GET_FUNCTION(otherMethod.object()); } else { function1 = PyObject_GetAttr(otherMethod.object(), Shiboken::PyName::im_func()); + if (function1 == nullptr) + return false; Py_DECREF(function1); // Not retaining a reference in line with what PyMethod_GET_FUNCTION does. } @@ -61,12 +68,31 @@ QDebug operator<<(QDebug d, const GetReceiverResult &r) d.noquote(); d.nospace(); d << "GetReceiverResult(receiver=" << r.receiver << ", self=" << r.self - << ", sig=" << r.callbackSig << "slotIndex=" << r.slotIndex + << ", sig=\"" << r.callbackSig << "\", slotIndex=" << r.slotIndex << ", usingGlobalReceiver=" << r.usingGlobalReceiver << ')'; return d; } #endif // QT_NO_DEBUG_STREAM +static const char *getQualifiedName(PyObject *ob) +{ + Shiboken::AutoDecRef qualNameP(PyObject_GetAttr(ob, Shiboken::PyMagicName::qualname())); + return qualNameP.isNull() + ? nullptr : Shiboken::String::toCString(qualNameP.object()); +} + +// Determine whether a method is declared in a class using qualified name lookup. +static bool isDeclaredIn(PyObject *method, const char *className) +{ + bool result = false; + if (auto *qualifiedNameC = getQualifiedName(PyMethod_Function(method))) { + std::string_view qualifiedName(qualifiedNameC); + if (const auto dot = qualifiedName.rfind('.'); dot != std::string::npos) + result = qualifiedName.substr(0, dot) == className; + } + return result; +} + static GetReceiverResult getReceiver(QObject *source, const char *signal, PyObject *callback) { @@ -99,31 +125,40 @@ static GetReceiverResult getReceiver(QObject *source, const char *signal, result.usingGlobalReceiver = !result.receiver || forceGlobalReceiver; - // Check if this callback is a overwrite of a non-virtual Qt slot. + // Check if this callback is a overwrite of a non-virtual Qt slot (pre-Jira bug 1019). + // Make it possible to connect to a MyWidget.show() although QWidget.show() + // is a non-virtual slot which would be found by QMetaObject search. + // FIXME PYSIDE7: This is arguably a bit of a misguided "feature", remove? if (!result.usingGlobalReceiver && result.receiver && result.self) { result.callbackSig = PySide::Signal::getCallbackSignature(signal, result.receiver, callback, - result.usingGlobalReceiver).toLatin1(); + result.usingGlobalReceiver); const QMetaObject *metaObject = result.receiver->metaObject(); result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); - if (result.slotIndex != -1 && result.slotIndex < metaObject->methodOffset() - && PyMethod_Check(callback)) { - result.usingGlobalReceiver = true; - } + if (PyMethod_Check(callback) != 0 && result.slotIndex != -1 + && result.slotIndex < metaObject->methodOffset()) { + // Find the class in which the slot is declared. + while (result.slotIndex < metaObject->methodOffset()) + metaObject = metaObject->superClass(); + // If the Python callback is not declared in the same class, assume it is + // a Python override. Resort to global receiver (PYSIDE-2418). + if (!isDeclaredIn(callback, metaObject->className())) + result.usingGlobalReceiver = true; + } } const auto receiverThread = result.receiver ? result.receiver->thread() : nullptr; if (result.usingGlobalReceiver) { PySide::SignalManager &signalManager = PySide::SignalManager::instance(); - result.receiver = signalManager.globalReceiver(source, callback); + result.receiver = signalManager.globalReceiver(source, callback, result.receiver); // PYSIDE-1354: Move the global receiver to the original receivers's thread // so that autoconnections work correctly. if (receiverThread && receiverThread != result.receiver->thread()) result.receiver->moveToThread(receiverThread); result.callbackSig = PySide::Signal::getCallbackSignature(signal, result.receiver, callback, - result.usingGlobalReceiver).toLatin1(); + result.usingGlobalReceiver); const QMetaObject *metaObject = result.receiver->metaObject(); result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); } @@ -208,7 +243,50 @@ QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *sign } } - auto connection = QMetaObject::connect(source, signalIndex, receiver.receiver, slotIndex, type); + QMetaObject::Connection connection{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() + connection = QMetaObject::connect(source, signalIndex, receiver.receiver, slotIndex, 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; +} + +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); @@ -237,7 +315,11 @@ bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *ca const int signalIndex = source->metaObject()->indexOfSignal(signal + 1); const int slotIndex = receiver.slotIndex; - if (!QMetaObject::disconnectOne(source, signalIndex, receiver.receiver, slotIndex)) + bool ok{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with disconnectNotify() + ok = QMetaObject::disconnectOne(source, signalIndex, receiver.receiver, slotIndex); + Py_END_ALLOW_THREADS + if (!ok) return false; Q_ASSERT(receiver.receiver); |