[review] [PyKDE] Extraction of Qt objects from a PyObject
Albert Strasheim
13640887 at sun.ac.za
Sat Feb 14 21:19:01 GMT 2004
Hello,
Using sipForceConvertTo_*(), I was able to convert a boost::python::object
returned via the Boost.Python wrapper around the base class (from which I
derive a Python class) into a Qt object.
The code to do this looks like this:
// sip function definitions
extern QEvent* sipForceConvertTo_QEvent(PyObject*, int*);
extern QKeyEvent* sipForceConvertTo_QKeyEvent(PyObject*, int*);
QEvent* inject()
{
object o = call_method<object>(self, "inject");
int iserr = 0;
QEvent* e = sipForceConvertTo_QEvent(o.ptr(), &iserr);
if ( !iserr )
{
switch ( e->type() )
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
QKeyEvent* k = sipForceConvertTo_QKeyEvent(o.ptr(), &iserr);
if ( !iserr )
{
return new QKeyEvent(*k);
}
break;
}
// more events
default:
{
// unsupported event type returned from Python
}
}
}
return 0;
}
Now I would also like to pass a Qt object to a Python function implemented
in a class which derives from a C++ class.
To do this, one creates a wrapper class for the base class from which you
want to derive in Python. The wrapper class dispatches each pure virtual
function to Python.
The Boost.Python module declaration looks as follows:
BOOST_PYTHON_MODULE(boostqt)
{
class_<MyUnitTest_t::EventHandlerBase_t, EventHandlerBaseWrap,
boost::noncopyable>("EventHandlerBase");
to_python_converter<QEvent, QEvent_to_sipqtQEvent>();
}
EventHandlerBase_t is the base class from which we wish to derive in
Python and EventHandlerBaseWrap is the wrapper class.
The to_python_converter<...>() statement takes as first a C++ type and as
second argument a class implementing a static method called convert()
which takes a const reference to the aforementioned C++ type and converts
it to a PyObject*. It is here that we have to translate the Qt object into
a PyObject containing an instance of the SIP class wrapping the Qt class
in question.
struct QEvent_to_sipqtQEvent
{
static PyObject* convert(const QEvent& e)
{
// return something...
}
};
So my question is: what is the right way to convert a Qt type to a
PyObject*? Since I'm only interested in QEvent's derived types here, I can
use QEvent::type() with a switch to figure out which derived type I want
to convert. But what SIP function do I call?
Let's look at QKeyEvent as an example. In sipqtQKeyEvent.h (which is
a generated file), there is a constructor for sipQKeyEvent that takes a
QKeyEvent as its only argument. Can I convert this sipQKeyEvent instance
to a PyObject*?
There is also a sipNew_QKeyEvent() function which returns a PyObject*,
taking two PyObject* arguments. What should I pass to this function? And
how do I copy the data from my existing QKeyEvent instance into this new
object (I'm hoping I don't have to write code to do this copying on a
per-event basis).
By the way, should packages like Red Hat/Fedora's PyQt-devel include
headers like sipqtQKeyEvent.h? Is there a way to generate these headers
files given the .sip files (which the -devel package does include)?
Cheers,
Albert
On Sun, 08 Feb 2004, Jim Bublitz wrote:
> On Sunday February 8 2004 08:23, Albert Strasheim wrote:
> > Hello PyKDE mailing list,
> >
> > I am writing some code that intercepts the events received by
> > a Qt program by installing an event filter on the QApplication
> > instance.
> >
> > When events are intercepted, they are passed on to another
> > object via a call to a function. This function is pure
> > virtual, so you can derive a C++ or Python class from it to
> > specify a function to do with the events what you want.
> > Alternatively, the event filter can call another function
> > which returns an object, the class of which is derived from
> > QEvent, to inject new events which the program must handle.
> >
> > I'm using Boost.Python to wrap the "event handler" base class,
> > but I would like to use the existing SIP Qt bindings instead
> > of writing my own. I'm currently having a problem extracting
> > the events (i.e. instances of the classes derived from QEvent)
> > from the PyObject* returned by the latter function mentioned
> > above.
> >
> > The Python code looks like this:
> >
> > from qt import *
> > from pyqttest import *
> >
> > class PyEventHandler(EventHandlerBase):
> > def __init__(self):
> > EventHandlerBase.__init__(self)
> > pass
> > def inject(self):
> > return QKeyEvent(QEvent.KeyRelease, 0, 32, 0, 'h',
> > False, 0)
> >
> > def main():
> > eh = PyEventHandler()
> > myQtAppTest = MyQtAppTest(TestType.INJECT, eh)
> >
> > The C++ wrapper around the base class that PyEventHandler is
> > deriving from looks like this:
> >
> > struct EventHandlerBaseWrap : MyUnitTest_t::EventHandlerBase_t
> > {
> > EventHandlerBaseWrap(PyObject* self_) :
> > self(self_)
> > {
> > }
> >
> > virtual QEvent* inject()
> > {
> > try
> > {
> > // Get a Python object
> > boost::python::object o = call_method<object>(self,
> > "inject");
> >
> > // more code here...
> > }
> > catch (...)
> > {
> > // something has gone badly wrong...
> > }
> > }
> > }
> >
> > MyUnitTest_t::EventHandlerBase_t::inject() is a pure virtual
> > function.
> >
> > boost::python::object is a Boost.Python type that wraps around
> > a PyObject*. You can get at the underlying PyObject* by
> > calling the object's ptr() method.
> >
> > If I print the QKeyEvent instantiated in the Python inject()
> > above (i.e. the __repr__ method is called?), the address
> > displayed corresponds to the address contained in the pointer
> > returned by object's ptr() method, so I'm pretty sure the
> > return is working. Now I need to get at the event in the
> > PyObject so that I can copy it and return the address of the
> > copy:
> >
> > boost::python::object o = call_method<object>(self, "inject");
> > int iserrp = 0;
> > PyObject* attr_name = PyString_FromString("__class__");
> > PyObject* baseclass = PyObject_GetAttr(o.ptr(), attr_name);
> > delete attr_name;
> > attr_name = 0;
> >
> > const QEvent* e = reinterpret_cast<const QEvent*>(
> > sipConvertToCpp(o.ptr(), baseclass, &iserrp));
> >
> > I obtained this way of figuring out the base class from a
> > thread on this mailing list in July 2003. I'm not sure this is
> > exactly what I want to do here, though. Now that we have the
> > QEvent (or maybe it's a sipQEvent? of course, the
> > reinterpret_cast isn't going to care), we call the type()
> > method to figure out the derived type so that we may copy all
> > its information:
> >
> > switch ( e->type() )
> > {
> > case QEvent::KeyPress:
> > case QEvent::KeyRelease:
> > {
> > std::cout << "QEvent* = " << e << std::endl;
> > std::cout << typeid(e).name() << std::endl;
> > std::cout << typeid(*e).name() << std::endl;
> > const QKeyEvent* k = dynamic_cast<const
> > QKeyEvent*>(e); std::cout << "casted const QKeyEvent* = " << k
> > << std::endl; }
> >
> > // ...
> > }
> >
> > When run, this code prints out the following:
> >
> > QEvent* = 0x923c108
> > PK6QEvent
> > 12sipQKeyEvent
> > casted const QKeyEvent* = 0
> >
> > The interesting one is the third line. Apparently the
> > underlying object is a sipQKeyEvent, and not a QKeyEvent.
> > Also, the dynamic_cast fails, so apparently sipQKeyEvent is
> > not derived from QKeyEvent. From what I can see, sipQKeyEvent
> > is not declared in any public headers.
> >
> > How do I go about extracting the underlying QKeyEvent so that
> > I can do with it what I want? Am I going about the whole the
> > wrong way? Is sipConvertToCpp() the function I want to call
> > here? Am I using the right value for baseclass?
>
> The method you want is:
>
> QKeyEvent *sipForceConvertTo_QKeyEvent (PyObject *, int *);
>
> and is found in sipqtQKeyEvent.h in PyQt*/qt, where PyQt* is the
> directory you build PyQt in. You also need to link to
> libqtcmodule.so (-L<install dir> -lqtcmodule). Usage, where 'p'
> is a ptr to the PyObject to convert, is:
>
> // should eliminate having to do the #include
> extern QKeyEvent *sipForceConvertTo_QKeyEvent (PyObject *,
> int *);
>
> int isErr = 0;
> QKeyEvent *qk = sipForceConvertTo_QKeyEvent (p, &isErr);
> if isErr:
> <error occurred>
>
> I don't know anything about the boost related stuff, so can't
> comment on that.
More information about the PyQt
mailing list