Skip to content

Commit 44cbed8

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
1 parent 88c313a commit 44cbed8

1 file changed

Lines changed: 70 additions & 8 deletions

File tree

src/PythonQtSignal.cpp

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,18 +233,72 @@ 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+
}
251+
else if (PyMethod_Check(object)) {
252+
PyObject* instance = PyMethod_Self(object);
253+
if (PyObject_TypeCheck(instance, &PythonQtInstanceWrapper_Type)) {
254+
PythonQtInstanceWrapper* typedInstance = (PythonQtInstanceWrapper*)instance;
255+
if (!typedInstance->_wrappedPtr) {
256+
// It's a QObject-derived class
257+
targetObj = typedInstance->_obj;
258+
PyObject* function = PyMethod_Function(object);
259+
if (PyObject_HasAttr(function, qtSlots)) {
260+
// connecting to a slot
261+
PyObject* signatures = PyObject_GetAttr(function, qtSlots);
262+
Py_ssize_t count = PyList_Size(signatures);
263+
// TODO: Find the best matching signature;
264+
// currently we only connect to the actual slot if only one slot signature is associated
265+
// with this callable - if there are more, we just connect to the callable and let
266+
// the callable figure the arguments out - but in this case Qt::DirectConnection is
267+
// used instead of Qt::AutoConnection, which can be suprising if threads are involved.
268+
if (count == 1) {
269+
PyObject* signature = PyList_GET_ITEM(signatures, 0);
270+
// Retrieve slot signature
271+
QByteArray sig = PyUnicode_AsUTF8(signature);
272+
targetSignature = QByteArray("1") + sig.split(' ')[1]; // include slot prefix
273+
return true;
274+
}
275+
}
276+
}
277+
}
278+
}
279+
return false;
280+
}
281+
236282
static PyObject* PythonQtSignalFunction_connect(PythonQtSignalFunctionObject* type, PyObject* args)
237283
{
238284
if (PyObject_TypeCheck(type->m_self, &PythonQtInstanceWrapper_Type)) {
239285
PythonQtInstanceWrapper* self = (PythonQtInstanceWrapper*)type->m_self;
240286
if (self->_obj) {
241287
Py_ssize_t argc = PyTuple_Size(args);
288+
QByteArray sourceSignature = QByteArray("2") + type->m_ml->signature();
242289
if (argc == 1) {
243290
// connect with Python callable
244291
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);
292+
QObject* targetObj;
293+
QByteArray targetSignature;
294+
if (extractSignalTarget(callable, targetObj, targetSignature)) {
295+
// Do a regular signal/slot (or signal/signal) connect.
296+
QObject::connect(self->_obj, sourceSignature, targetObj, targetSignature, Qt::AutoConnection);
297+
return PythonQtConv::GetPyBool(true);
298+
} else {
299+
bool result = PythonQt::self()->addSignalHandler(self->_obj, sourceSignature, callable);
300+
return PythonQtConv::GetPyBool(result);
301+
}
248302
} else {
249303
PyErr_SetString(PyExc_ValueError, "Called connect with wrong number of arguments");
250304
}
@@ -259,15 +313,23 @@ static PyObject* PythonQtSignalFunction_disconnect(PythonQtSignalFunctionObject*
259313
PythonQtInstanceWrapper* self = (PythonQtInstanceWrapper*)type->m_self;
260314
if (self->_obj) {
261315
Py_ssize_t argc = PyTuple_Size(args);
262-
QByteArray signal = QByteArray("2") + type->m_ml->signature();
316+
QByteArray sourceSignature = QByteArray("2") + type->m_ml->signature();
263317
if (argc == 1) {
264318
// disconnect with Python callable
265319
PyObject* callable = PyTuple_GET_ITEM(args, 0);
266-
bool result = PythonQt::self()->removeSignalHandler(self->_obj, signal, callable);
267-
return PythonQtConv::GetPyBool(result);
320+
QObject* targetObj;
321+
QByteArray targetSignature;
322+
if (extractSignalTarget(callable, targetObj, targetSignature)) {
323+
// Do a regular signal/slot (or signal/signal) disconnect.
324+
QObject::disconnect(self->_obj, sourceSignature, targetObj, targetSignature);
325+
return PythonQtConv::GetPyBool(true);
326+
} else {
327+
bool result = PythonQt::self()->removeSignalHandler(self->_obj, sourceSignature, callable);
328+
return PythonQtConv::GetPyBool(result);
329+
}
268330
} else if (argc == 0) {
269-
bool result = PythonQt::self()->removeSignalHandler(self->_obj, signal, nullptr);
270-
result |= QObject::disconnect(self->_obj, signal, nullptr, nullptr);
331+
bool result = PythonQt::self()->removeSignalHandler(self->_obj, sourceSignature, nullptr);
332+
result |= QObject::disconnect(self->_obj, sourceSignature, nullptr, nullptr);
271333
return PythonQtConv::GetPyBool(result);
272334
} else {
273335
PyErr_SetString(PyExc_ValueError, "Called disconnect with wrong number of arguments");

0 commit comments

Comments
 (0)