You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
653 lines
17 KiB
653 lines
17 KiB
/*
|
|
* The SIP-TQt library code that implements the interface to the optional module
|
|
* supplied TQt support.
|
|
*
|
|
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
|
|
*
|
|
* This file is part of SIP-TQt.
|
|
*
|
|
* This copy of SIP-TQt is licensed for use under the terms of the SIP License
|
|
* Agreement. See the file LICENSE for more details.
|
|
*
|
|
* This copy of SIP-TQt may also used under the terms of the GNU General Public
|
|
* License v2 or v3 as published by the Free Software Foundation which can be
|
|
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
|
|
*
|
|
* SIP-TQt is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
|
|
#include <Python.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "sip-tqt.h"
|
|
#include "sipint.h"
|
|
|
|
|
|
/* This is how TQt "types" signals and slots. */
|
|
#define isTQtSlot(s) (*(s) == '1')
|
|
#define isTQtSignal(s) (*(s) == '2')
|
|
|
|
|
|
static PyObject *getWeakRef(PyObject *obj);
|
|
static char *sipStrdup(const char *);
|
|
static void *createUniversalSlot(sipWrapper *txSelf, const char *sig,
|
|
PyObject *rxObj, const char *slot, const char **member, int flags);
|
|
static void *findSignal(void *txrx, const char **sig);
|
|
static void *newSignal(void *txrx, const char **sig);
|
|
|
|
|
|
/*
|
|
* Find an existing signal.
|
|
*/
|
|
static void *findSignal(void *txrx, const char **sig)
|
|
{
|
|
if (sipTQtSupport->tqt_find_universal_signal != NULL)
|
|
txrx = sipTQtSupport->tqt_find_universal_signal(txrx, sig);
|
|
|
|
return txrx;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return a usable signal, creating a new universal signal if needed.
|
|
*/
|
|
static void *newSignal(void *txrx, const char **sig)
|
|
{
|
|
void *new_txrx = findSignal(txrx, sig);
|
|
|
|
if (new_txrx == NULL && sipTQtSupport->tqt_create_universal_signal != NULL)
|
|
new_txrx = sipTQtSupport->tqt_create_universal_signal(txrx, sig);
|
|
|
|
return new_txrx;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create a universal slot. Returns a pointer to it or 0 if there was an
|
|
* error.
|
|
*/
|
|
static void *createUniversalSlot(sipWrapper *txSelf, const char *sig,
|
|
PyObject *rxObj, const char *slot, const char **member, int flags)
|
|
{
|
|
void *us = sipTQtSupport->tqt_create_universal_slot(txSelf, sig, rxObj, slot,
|
|
member, flags);
|
|
|
|
if (us && txSelf)
|
|
sipSetPossibleProxy((sipSimpleWrapper *)txSelf);
|
|
|
|
return us;
|
|
}
|
|
|
|
|
|
/*
|
|
* Invoke a single slot (TQt or Python) and return the result.
|
|
*/
|
|
PyObject *sip_api_invoke_slot(const sipSlot *slot, PyObject *sigargs)
|
|
{
|
|
PyObject *sa, *oxtype, *oxvalue, *oxtb, *sfunc, *sref;
|
|
|
|
/* Keep some compilers quiet. */
|
|
oxtype = oxvalue = oxtb = NULL;
|
|
|
|
/* Fan out TQt signals. (Only PyTQt will do this.) */
|
|
if (slot->name != NULL && slot->name[0] != '\0')
|
|
{
|
|
assert(sipTQtSupport->tqt_emit_signal);
|
|
|
|
if (sipTQtSupport->tqt_emit_signal(slot->pyobj, slot->name, sigargs) < 0)
|
|
return NULL;
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
/* Get the object to call, resolving any weak references. */
|
|
if (slot->weakSlot == Py_True)
|
|
{
|
|
/*
|
|
* The slot is guaranteed to be Ok because it has an extra reference or
|
|
* is None.
|
|
*/
|
|
sref = slot->pyobj;
|
|
Py_INCREF(sref);
|
|
}
|
|
else if (slot -> weakSlot == NULL)
|
|
sref = NULL;
|
|
else if ((sref = PyWeakref_GetObject(slot -> weakSlot)) == NULL)
|
|
return NULL;
|
|
else
|
|
Py_INCREF(sref);
|
|
|
|
if (sref == Py_None)
|
|
{
|
|
/*
|
|
* If the real object has gone then we pretend everything is Ok. This
|
|
* mimics the TQt behaviour of not caring if a receiving object has been
|
|
* deleted.
|
|
*/
|
|
Py_DECREF(sref);
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
if (slot -> pyobj == NULL)
|
|
{
|
|
PyObject *self = (sref != NULL ? sref : slot->meth.mself);
|
|
|
|
/*
|
|
* If the receiver wraps a C++ object then ignore the call if it no
|
|
* longer exists.
|
|
*/
|
|
if (PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type) &&
|
|
sip_api_get_address((sipSimpleWrapper *)self) == NULL)
|
|
{
|
|
Py_XDECREF(sref);
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
sfunc = PyMethod_New(slot->meth.mfunc, self);
|
|
|
|
if (sfunc == NULL)
|
|
{
|
|
Py_XDECREF(sref);
|
|
return NULL;
|
|
}
|
|
}
|
|
else if (slot -> name != NULL)
|
|
{
|
|
char *mname = slot -> name + 1;
|
|
PyObject *self = (sref != NULL ? sref : slot->pyobj);
|
|
|
|
if ((sfunc = PyObject_GetAttrString(self, mname)) == NULL || !PyCFunction_Check(sfunc))
|
|
{
|
|
/*
|
|
* Note that in earlier versions of SIP-TQt this error would be
|
|
* detected when the slot was connected.
|
|
*/
|
|
PyErr_Format(PyExc_NameError,"Invalid slot %s",mname);
|
|
|
|
Py_XDECREF(sfunc);
|
|
Py_XDECREF(sref);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sfunc = slot->pyobj;
|
|
Py_INCREF(sfunc);
|
|
}
|
|
|
|
/*
|
|
* We make repeated attempts to call a slot. If we work out that it failed
|
|
* because of an immediate type error we try again with one less argument.
|
|
* We keep going until we run out of arguments to drop. This emulates the
|
|
* TQt ability of the slot to accept fewer arguments than a signal provides.
|
|
*/
|
|
sa = sigargs;
|
|
Py_INCREF(sa);
|
|
|
|
for (;;)
|
|
{
|
|
PyObject *nsa, *xtype, *xvalue, *xtb, *resobj;
|
|
|
|
if ((resobj = PyObject_CallObject(sfunc, sa)) != NULL)
|
|
{
|
|
Py_DECREF(sfunc);
|
|
Py_XDECREF(sref);
|
|
|
|
/* Remove any previous exception. */
|
|
|
|
if (sa != sigargs)
|
|
{
|
|
Py_XDECREF(oxtype);
|
|
Py_XDECREF(oxvalue);
|
|
Py_XDECREF(oxtb);
|
|
PyErr_Clear();
|
|
}
|
|
|
|
Py_DECREF(sa);
|
|
|
|
return resobj;
|
|
}
|
|
|
|
/* Get the exception. */
|
|
PyErr_Fetch(&xtype,&xvalue,&xtb);
|
|
|
|
/*
|
|
* See if it is unacceptable. An acceptable failure is a type error
|
|
* with no traceback - so long as we can still reduce the number of
|
|
* arguments and try again.
|
|
*/
|
|
if (!PyErr_GivenExceptionMatches(xtype,PyExc_TypeError) ||
|
|
xtb != NULL ||
|
|
PyTuple_GET_SIZE(sa) == 0)
|
|
{
|
|
/*
|
|
* If there is a traceback then we must have called the slot and
|
|
* the exception was later on - so report the exception as is.
|
|
*/
|
|
if (xtb != NULL)
|
|
{
|
|
if (sa != sigargs)
|
|
{
|
|
Py_XDECREF(oxtype);
|
|
Py_XDECREF(oxvalue);
|
|
Py_XDECREF(oxtb);
|
|
}
|
|
|
|
PyErr_Restore(xtype,xvalue,xtb);
|
|
}
|
|
else if (sa == sigargs)
|
|
PyErr_Restore(xtype,xvalue,xtb);
|
|
else
|
|
{
|
|
/*
|
|
* Discard the latest exception and restore the original one.
|
|
*/
|
|
Py_XDECREF(xtype);
|
|
Py_XDECREF(xvalue);
|
|
Py_XDECREF(xtb);
|
|
|
|
PyErr_Restore(oxtype,oxvalue,oxtb);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* If this is the first attempt, save the exception. */
|
|
if (sa == sigargs)
|
|
{
|
|
oxtype = xtype;
|
|
oxvalue = xvalue;
|
|
oxtb = xtb;
|
|
}
|
|
else
|
|
{
|
|
Py_XDECREF(xtype);
|
|
Py_XDECREF(xvalue);
|
|
Py_XDECREF(xtb);
|
|
}
|
|
|
|
/* Create the new argument tuple. */
|
|
if ((nsa = PyTuple_GetSlice(sa,0,PyTuple_GET_SIZE(sa) - 1)) == NULL)
|
|
{
|
|
/* Tidy up. */
|
|
Py_XDECREF(oxtype);
|
|
Py_XDECREF(oxvalue);
|
|
Py_XDECREF(oxtb);
|
|
|
|
break;
|
|
}
|
|
|
|
Py_DECREF(sa);
|
|
sa = nsa;
|
|
}
|
|
|
|
Py_DECREF(sfunc);
|
|
Py_XDECREF(sref);
|
|
|
|
Py_DECREF(sa);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Compare two slots to see if they are the same.
|
|
*/
|
|
int sip_api_same_slot(const sipSlot *sp, PyObject *rxObj, const char *slot)
|
|
{
|
|
/* See if they are signals or TQt slots, ie. they have a name. */
|
|
if (slot != NULL)
|
|
{
|
|
if (sp->name == NULL || sp->name[0] == '\0')
|
|
return 0;
|
|
|
|
return (sipTQtSupport->tqt_same_name(sp->name, slot) && sp->pyobj == rxObj);
|
|
}
|
|
|
|
/* See if they are pure Python methods. */
|
|
if (PyMethod_Check(rxObj))
|
|
{
|
|
if (sp->pyobj != NULL)
|
|
return 0;
|
|
|
|
return (sp->meth.mfunc == PyMethod_GET_FUNCTION(rxObj)
|
|
&& sp->meth.mself == PyMethod_GET_SELF(rxObj)
|
|
);
|
|
}
|
|
|
|
/* See if they are wrapped C++ methods. */
|
|
if (PyCFunction_Check(rxObj))
|
|
{
|
|
if (sp->name == NULL || sp->name[0] != '\0')
|
|
return 0;
|
|
|
|
return (sp->pyobj == PyCFunction_GET_SELF(rxObj) &&
|
|
strcmp(&sp->name[1], ((PyCFunctionObject *)rxObj)->m_ml->ml_name) == 0);
|
|
}
|
|
|
|
/* The objects must be the same. */
|
|
return (sp->pyobj == rxObj);
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert a valid Python signal or slot to an existing universal slot.
|
|
*/
|
|
void *sipGetRx(sipSimpleWrapper *txSelf, const char *sigargs, PyObject *rxObj,
|
|
const char *slot, const char **memberp)
|
|
{
|
|
if (slot != NULL)
|
|
if (isTQtSlot(slot) || isTQtSignal(slot))
|
|
{
|
|
void *rx;
|
|
|
|
*memberp = slot;
|
|
|
|
if ((rx = sip_api_get_cpp_ptr((sipSimpleWrapper *)rxObj, sipTQObjectType)) == NULL)
|
|
return NULL;
|
|
|
|
if (isTQtSignal(slot))
|
|
rx = findSignal(rx, memberp);
|
|
|
|
return rx;
|
|
}
|
|
|
|
/*
|
|
* The slot was either a Python callable or PyTQt Python signal so there
|
|
* should be a universal slot.
|
|
*/
|
|
return sipTQtSupport->tqt_find_slot(sip_api_get_address(txSelf), sigargs, rxObj, slot, memberp);
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert a Python receiver (either a Python signal or slot or a TQt signal or
|
|
* slot) to a TQt receiver. It is only ever called when the signal is a TQt
|
|
* signal. Return NULL is there was an error.
|
|
*/
|
|
void *sip_api_convert_rx(sipWrapper *txSelf, const char *sigargs,
|
|
PyObject *rxObj, const char *slot, const char **memberp, int flags)
|
|
{
|
|
if (slot == NULL)
|
|
return createUniversalSlot(txSelf, sigargs, rxObj, NULL, memberp, flags);
|
|
|
|
if (isTQtSlot(slot) || isTQtSignal(slot))
|
|
{
|
|
void *rx;
|
|
|
|
*memberp = slot;
|
|
|
|
if ((rx = sip_api_get_cpp_ptr((sipSimpleWrapper *)rxObj, sipTQObjectType)) == NULL)
|
|
return NULL;
|
|
|
|
if (isTQtSignal(slot))
|
|
rx = newSignal(rx, memberp);
|
|
|
|
return rx;
|
|
}
|
|
|
|
/* The slot is a Python signal so we need a universal slot to catch it. */
|
|
return createUniversalSlot(txSelf, sigargs, rxObj, slot, memberp, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Connect a TQt signal or a Python signal to a TQt slot, a TQt signal, a Python
|
|
* slot or a Python signal. This is all possible combinations.
|
|
*/
|
|
PyObject *sip_api_connect_rx(PyObject *txObj, const char *sig, PyObject *rxObj,
|
|
const char *slot, int type)
|
|
{
|
|
/* Handle TQt signals. */
|
|
if (isTQtSignal(sig))
|
|
{
|
|
void *tx, *rx;
|
|
const char *member, *real_sig;
|
|
int res;
|
|
|
|
if ((tx = sip_api_get_cpp_ptr((sipSimpleWrapper *)txObj, sipTQObjectType)) == NULL)
|
|
return NULL;
|
|
|
|
real_sig = sig;
|
|
|
|
if ((tx = newSignal(tx, &real_sig)) == NULL)
|
|
return NULL;
|
|
|
|
if ((rx = sip_api_convert_rx((sipWrapper *)txObj, sig, rxObj, slot, &member, 0)) == NULL)
|
|
return NULL;
|
|
|
|
res = sipTQtSupport->tqt_connect(tx, real_sig, rx, member, type);
|
|
|
|
return PyBool_FromLong(res);
|
|
}
|
|
|
|
/* Handle Python signals. Only PyTQt will get this far. */
|
|
assert(sipTQtSupport->tqt_connect_py_signal);
|
|
|
|
if (sipTQtSupport->tqt_connect_py_signal(txObj, sig, rxObj, slot) < 0)
|
|
return NULL;
|
|
|
|
Py_INCREF(Py_True);
|
|
return Py_True;
|
|
}
|
|
|
|
|
|
/*
|
|
* Disconnect a signal to a signal or a TQt slot.
|
|
*/
|
|
PyObject *sip_api_disconnect_rx(PyObject *txObj,const char *sig,
|
|
PyObject *rxObj,const char *slot)
|
|
{
|
|
/* Handle TQt signals. */
|
|
if (isTQtSignal(sig))
|
|
{
|
|
sipSimpleWrapper *txSelf = (sipSimpleWrapper *)txObj;
|
|
void *tx, *rx;
|
|
const char *member;
|
|
int res;
|
|
|
|
if ((tx = sip_api_get_cpp_ptr(txSelf, sipTQObjectType)) == NULL)
|
|
return NULL;
|
|
|
|
if ((rx = sipGetRx(txSelf, sig, rxObj, slot, &member)) == NULL)
|
|
{
|
|
Py_INCREF(Py_False);
|
|
return Py_False;
|
|
}
|
|
|
|
/* Handle Python signals. */
|
|
tx = findSignal(tx, &sig);
|
|
|
|
res = sipTQtSupport->tqt_disconnect(tx, sig, rx, member);
|
|
|
|
/*
|
|
* Delete it if it is a universal slot as this will be it's only
|
|
* connection. If the slot is actually a universal signal then it
|
|
* should leave it in place.
|
|
*/
|
|
sipTQtSupport->tqt_destroy_universal_slot(rx);
|
|
|
|
return PyBool_FromLong(res);
|
|
}
|
|
|
|
/* Handle Python signals. Only PyTQt will get this far. */
|
|
assert(sipTQtSupport->tqt_disconnect_py_signal);
|
|
|
|
sipTQtSupport->tqt_disconnect_py_signal(txObj, sig, rxObj, slot);
|
|
|
|
Py_INCREF(Py_True);
|
|
return Py_True;
|
|
}
|
|
|
|
|
|
/*
|
|
* Free the resources of a slot.
|
|
*/
|
|
void sip_api_free_sipslot(sipSlot *slot)
|
|
{
|
|
if (slot->name != NULL)
|
|
{
|
|
sip_api_free(slot->name);
|
|
}
|
|
else if (slot->weakSlot == Py_True)
|
|
{
|
|
Py_DECREF(slot->pyobj);
|
|
}
|
|
|
|
/* Remove any weak reference. */
|
|
Py_XDECREF(slot->weakSlot);
|
|
}
|
|
|
|
|
|
/*
|
|
* Implement strdup() using sip_api_malloc().
|
|
*/
|
|
static char *sipStrdup(const char *s)
|
|
{
|
|
char *d;
|
|
|
|
if ((d = (char *)sip_api_malloc(strlen(s) + 1)) != NULL)
|
|
strcpy(d,s);
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialise a slot, returning 0 if there was no error. If the signal was a
|
|
* TQt signal, then the slot may be a Python signal or a Python slot. If the
|
|
* signal was a Python signal, then the slot may be anything.
|
|
*/
|
|
int sip_api_save_slot(sipSlot *sp, PyObject *rxObj, const char *slot)
|
|
{
|
|
sp -> weakSlot = NULL;
|
|
|
|
if (slot == NULL)
|
|
{
|
|
sp -> name = NULL;
|
|
|
|
if (PyMethod_Check(rxObj))
|
|
{
|
|
/*
|
|
* Python creates methods on the fly. We could increment the
|
|
* reference count to keep it alive, but that would keep "self"
|
|
* alive as well and would probably be a circular reference.
|
|
* Instead we remember the component parts and hope they are still
|
|
* valid when we re-create the method when we need it.
|
|
*/
|
|
sipSaveMethod(&sp -> meth,rxObj);
|
|
|
|
/* Notice if the class instance disappears. */
|
|
sp -> weakSlot = getWeakRef(sp -> meth.mself);
|
|
|
|
/* This acts a flag to say that the slot is a method. */
|
|
sp -> pyobj = NULL;
|
|
}
|
|
else
|
|
{
|
|
PyObject *self;
|
|
|
|
/*
|
|
* We know that it is another type of callable, ie. a
|
|
* function/builtin.
|
|
*/
|
|
|
|
if (PyCFunction_Check(rxObj) &&
|
|
(self = PyCFunction_GET_SELF(rxObj)) != NULL &&
|
|
PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type))
|
|
{
|
|
/*
|
|
* It is a wrapped C++ class method. We can't keep a copy
|
|
* because they are generated on the fly and we can't take a
|
|
* reference as that may keep the instance (ie. self) alive.
|
|
* We therefore treat it as if the user had specified the slot
|
|
* at "obj, TQ_SLOT('meth()')" rather than "obj.meth" (see below).
|
|
*/
|
|
|
|
const char *meth;
|
|
|
|
/* Get the method name. */
|
|
meth = ((PyCFunctionObject *)rxObj) -> m_ml -> ml_name;
|
|
|
|
if ((sp -> name = (char *)sip_api_malloc(strlen(meth) + 2)) == NULL)
|
|
return -1;
|
|
|
|
/*
|
|
* Copy the name and set the marker that it needs converting to
|
|
* a built-in method.
|
|
*/
|
|
sp -> name[0] = '\0';
|
|
strcpy(&sp -> name[1],meth);
|
|
|
|
sp -> pyobj = self;
|
|
sp -> weakSlot = getWeakRef(self);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Give the slot an extra reference to keep it alive and
|
|
* remember we have done so by treating weakSlot specially.
|
|
*/
|
|
Py_INCREF(rxObj);
|
|
sp->pyobj = rxObj;
|
|
|
|
Py_INCREF(Py_True);
|
|
sp->weakSlot = Py_True;
|
|
}
|
|
}
|
|
}
|
|
else if ((sp -> name = sipStrdup(slot)) == NULL)
|
|
return -1;
|
|
else if (isTQtSlot(slot))
|
|
{
|
|
/*
|
|
* The user has decided to connect a Python signal to a TQt slot and
|
|
* specified the slot as "obj, TQ_SLOT('meth()')" rather than "obj.meth".
|
|
*/
|
|
|
|
char *tail;
|
|
|
|
/* Remove any arguments. */
|
|
if ((tail = strchr(sp -> name,'(')) != NULL)
|
|
*tail = '\0';
|
|
|
|
/*
|
|
* A bit of a hack to indicate that this needs converting to a built-in
|
|
* method.
|
|
*/
|
|
sp -> name[0] = '\0';
|
|
|
|
/* Notice if the class instance disappears. */
|
|
sp -> weakSlot = getWeakRef(rxObj);
|
|
|
|
sp -> pyobj = rxObj;
|
|
}
|
|
else
|
|
/* It's a TQt signal. */
|
|
sp -> pyobj = rxObj;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return a weak reference to the given object.
|
|
*/
|
|
static PyObject *getWeakRef(PyObject *obj)
|
|
{
|
|
PyObject *wr;
|
|
|
|
if ((wr = PyWeakref_NewRef(obj,NULL)) == NULL)
|
|
PyErr_Clear();
|
|
|
|
return wr;
|
|
}
|