Skip to content

Commit 100cb30

Browse files
committed
Do a regular signal/slot connect if the argument is a slot object,
or a signal/signal connect, if the argument is a signal object. Only if the argument is a regular callable create a new receiver object. This will change the behavior of connects with regard to threads in some cases! Fixed #363 [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci
1 parent 88c313a commit 100cb30

1 file changed

Lines changed: 69 additions & 8 deletions

File tree

src/PythonQtSignal.cpp

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,18 +233,71 @@ static PyObject* PythonQtSignalFunction_typeName(PythonQtSignalFunctionObject* t
233233
return PythonQtMemberFunction_typeName(type->m_ml);
234234
}
235235

236+
// Find out if we can connect directly to the given Python object, without creating a separate receiver.
237+
// If yes, provide target object and signature for the connect call.
238+
static bool extractSignalTarget(PyObject* object, QObject*& targetObj, QByteArray& targetSignature)
239+
{
240+
static PyObject* qtSlots = PyUnicode_FromString("_qtSlots");
241+
if (PyObject_TypeCheck(object, &PythonQtSignalFunction_Type)) {
242+
PythonQtSignalFunctionObject* type = (PythonQtSignalFunctionObject*)object;
243+
PythonQtInstanceWrapper* self = (PythonQtInstanceWrapper*)type->m_self;
244+
if (self->_obj) {
245+
// connecting to another signal
246+
targetObj = self->_obj;
247+
targetSignature = QByteArray("2") + type->m_ml->signature();
248+
return true;
249+
}
250+
} else if (PyMethod_Check(object)) {
251+
PyObject* instance = PyMethod_Self(object);
252+
if (PyObject_TypeCheck(instance, &PythonQtInstanceWrapper_Type)) {
253+
PythonQtInstanceWrapper* typedInstance = (PythonQtInstanceWrapper*)instance;
254+
if (!typedInstance->_wrappedPtr) {
255+
// It's a QObject-derived class
256+
targetObj = typedInstance->_obj;
257+
PyObject* function = PyMethod_Function(object);
258+
if (PyObject_HasAttr(function, qtSlots)) {
259+
// connecting to a slot
260+
PyObject* signatures = PyObject_GetAttr(function, qtSlots);
261+
Py_ssize_t count = PyList_Size(signatures);
262+
// TODO: Find the best matching signature;
263+
// currently we only connect to the actual slot if only one slot signature is associated
264+
// with this callable - if there are more, we just connect to the callable and let
265+
// the callable figure the arguments out - but in this case Qt::DirectConnection is
266+
// used instead of Qt::AutoConnection, which can be suprising if threads are involved.
267+
if (count == 1) {
268+
PyObject* signature = PyList_GET_ITEM(signatures, 0);
269+
// Retrieve slot signature
270+
QByteArray sig = PyUnicode_AsUTF8(signature);
271+
targetSignature = QByteArray("1") + sig.split(' ')[1]; // include slot prefix
272+
return true;
273+
}
274+
}
275+
}
276+
}
277+
}
278+
return false;
279+
}
280+
236281
static PyObject* PythonQtSignalFunction_connect(PythonQtSignalFunctionObject* type, PyObject* args)
237282
{
238283
if (PyObject_TypeCheck(type->m_self, &PythonQtInstanceWrapper_Type)) {
239284
PythonQtInstanceWrapper* self = (PythonQtInstanceWrapper*)type->m_self;
240285
if (self->_obj) {
241286
Py_ssize_t argc = PyTuple_Size(args);
287+
QByteArray sourceSignature = QByteArray("2") + type->m_ml->signature();
242288
if (argc == 1) {
243289
// connect with Python callable
244290
PyObject* callable = PyTuple_GET_ITEM(args, 0);
245-
bool result =
246-
PythonQt::self()->addSignalHandler(self->_obj, QByteArray("2") + type->m_ml->signature(), callable);
247-
return PythonQtConv::GetPyBool(result);
291+
QObject* targetObj;
292+
QByteArray targetSignature;
293+
if (extractSignalTarget(callable, targetObj, targetSignature)) {
294+
// Do a regular signal/slot (or signal/signal) connect.
295+
QObject::connect(self->_obj, sourceSignature, targetObj, targetSignature, Qt::AutoConnection);
296+
return PythonQtConv::GetPyBool(true);
297+
} else {
298+
bool result = PythonQt::self()->addSignalHandler(self->_obj, sourceSignature, callable);
299+
return PythonQtConv::GetPyBool(result);
300+
}
248301
} else {
249302
PyErr_SetString(PyExc_ValueError, "Called connect with wrong number of arguments");
250303
}
@@ -259,15 +312,23 @@ static PyObject* PythonQtSignalFunction_disconnect(PythonQtSignalFunctionObject*
259312
PythonQtInstanceWrapper* self = (PythonQtInstanceWrapper*)type->m_self;
260313
if (self->_obj) {
261314
Py_ssize_t argc = PyTuple_Size(args);
262-
QByteArray signal = QByteArray("2") + type->m_ml->signature();
315+
QByteArray sourceSignature = QByteArray("2") + type->m_ml->signature();
263316
if (argc == 1) {
264317
// disconnect with Python callable
265318
PyObject* callable = PyTuple_GET_ITEM(args, 0);
266-
bool result = PythonQt::self()->removeSignalHandler(self->_obj, signal, callable);
267-
return PythonQtConv::GetPyBool(result);
319+
QObject* targetObj;
320+
QByteArray targetSignature;
321+
if (extractSignalTarget(callable, targetObj, targetSignature)) {
322+
// Do a regular signal/slot (or signal/signal) disconnect.
323+
QObject::disconnect(self->_obj, sourceSignature, targetObj, targetSignature);
324+
return PythonQtConv::GetPyBool(true);
325+
} else {
326+
bool result = PythonQt::self()->removeSignalHandler(self->_obj, sourceSignature, callable);
327+
return PythonQtConv::GetPyBool(result);
328+
}
268329
} else if (argc == 0) {
269-
bool result = PythonQt::self()->removeSignalHandler(self->_obj, signal, nullptr);
270-
result |= QObject::disconnect(self->_obj, signal, nullptr, nullptr);
330+
bool result = PythonQt::self()->removeSignalHandler(self->_obj, sourceSignature, nullptr);
331+
result |= QObject::disconnect(self->_obj, sourceSignature, nullptr, nullptr);
271332
return PythonQtConv::GetPyBool(result);
272333
} else {
273334
PyErr_SetString(PyExc_ValueError, "Called disconnect with wrong number of arguments");

0 commit comments

Comments
 (0)