Friday, July 5, 2013

Event Handling in wxPython

For the last several days I've been working on getting event handling working, so I figured I could tell you a bit about how event handling is implemented in wxPython.

wxWidgets Background


First of all some background about wxWidgets for those unfamilar.  (You can skip this section if you are familar at all with wxWidgets or wxPython)

Event handling in wxWidgets is more or less the same as in other GUI toolkit.  Events can be generated by the library, for example when the user clicks on a button or a timer fires, or can be created by the programmer.  The actual handling of the events occurs in wxEvtHandler, from which most of the widgets in wx are derive.  When a wxEvtHandler instance is created, a parent can be specified.  This will allow the parent to handle any events generated by the child (or its children) that it doesn't handle itself.  These events are manifested as C++ objects deriving from wxEvent and may hold details about the event that has occurred.

The programmer can specify an action for a wxEvtHandler to take when it encounters an event of a particular type (and optionally from a particular widget.)  This is described as connecting the handler to the event.  The action for the handler to take is specified in the form of a C++ callback.  Originally, the way to connect a handler to events was using compile time "event tables," which are limited to calling methods on the wxEvtHandler object they are defined on.  In modern versions of wxWidgets, it is possible to dynamically connect and disconnect a wxEvtHandler to an event at runtime. Additionally, arbitrary methods as well as functions and functors may be used as callbacks for these dynamic connections.

For a better/more detailed explanation see the wxWidgets documentation (or for the C++ averse the wxPython version)

In wxPython


For events to be useful to Python programmers, wxPython must these two things possible:  using arbitrary Python callables to handle events and creating new events.

The first feature is actually relatively straight forward for the existing wxPython bindings.  A C++ function is used as the callback that is passed to the library, which calls the Python callable. To get the pointer to the Python object to the C++ function, a C++ object that wraps the pointer is given as user data for the event.  wxWidgets makes this user data object available to the C++ callback that handles the event and takes ownership of the object, deleting it if/when the event is disconnected.

The second feature is a little more complex.  We want events created in Python to retain their Python attributes when they reach their callbacks.  (You can see an example of this here)  This sounds easy, but wxWidgets internally makes copies of the event objects. When the C++ event object is passed to the callback, it maybe at a different address than the original object, thus making it difficult to find the correct Python object to pass to the Python callable.

wxPython's solution to this is to have a PyEvent object that is Python aware and able to carry it's attributes through C++ unmolested.  It does this by storing a pointer to a Python dict object inside the C++ PyEvent object.  When the event object gets cloned, the pointer gets cloned as well (and its refcount gets incremented.)  The Python PyEvent  object defines __{set,get,del}attr__ methods that redirect to the aforementioned dict object. [1]  This way, even if the Python PyEvent objects are different and/or wrap different C++ objects, they will have the same attributes as far as the user is concerned.

Differences for wxPython-cffi


For the first feature, my solution is pretty much the same with one small twist:  PyPy doesn't have a C API that allows a Python object to be called from C++ like CPython does.  To cope with this, there are a couple of small changes that need to made.  First of all, instead of passing a pointer to the Python callable, we pass an handle created with ffi.new_handle()[2].  Second, we call a constant Python callback from the C++ callback that is connected to the event.  In this Python function we lookup the Python callable with ffi.from_handle(), create the event object, and then finally call the Python object.

This is an indication of what has been and I suspect will continue to be a theme in the project:  replacing C++ code with equivalent Python code


The second feature is a bit more difficult and I don't quite have a perfect solution right now.  What I have in place right now starts out similar to wxPython:  we hold a pointer inside the C++ PyEvent object.  Where its different is the pointer is in fact a wxSharedPointer and it points to a wrapper around a handle to a Python PyEvent object.  The idea here is that the wrapper has a destructor that will call a Python callback to release the reference to the handle so it can be garbage collected.  The wxSharedPointer is a refcounted autopointer and makes sure that the wrapper's dtor is only called when every PyEvent object pointing to it has been deleted.  The problem with this plan is the handle keeps the Python PyEvent object it represents alive, which in turn keeps the last C++ PyEvent object alive.  My thought process was that I wanted to be able to pass the original PyEvent object to the callbacks, bypassing the need to use a separate dictionary and the attribute access methods.

I'm still working out exactly how I'll handle this, but once I have it worked out, the only part of event handling left to work is releasing handles to Python callables once they've been disconnected.  I'll probably write another post once I know how that will work.


1. Its useful to note accessing the dictionary like this is possible because PyEvent, like the vast majority of wxPython types, is implemented in C and so can directly access the data members of the C++ objects it wraps.

2.  ffi.new_handle() is new in CFFI 0.7, which has yet to be released at the time of writing. See near the bottom of Misc methods on ffi in the CFFI documentation for more information.

Wednesday, June 19, 2013

Coding Begins...

Although coding begins officially this week, I've been doing some coding already the last couple of weeks already.  I've been writing mockups the generated bindings and working on the library code.  The library code what will provide functionality such as mapping C++ pointers to Python objects and overloading methods to the bindings.

Here's a screenshot of the first demo running on PyPy:
























The agenda for the next week is to get a second demo working that makes use of Python functions as event handlers.  Once have that demo working, I'll start on writing the generator itself.

Thursday, June 6, 2013

Introduction

So, this introduction is a little belated, but better late than never, right?

About Me

My name is Tyler Wade.  I'm a computer science major at Washburn University in Topeka, Kansas.  I will be a senior when classes start again in August and, baring any extraordinary circumstances, will graduate next Spring.  As far as I know, I'm the first student from my university to be chosen for GSoC, which is kind of cool.

About the project

Right now there isn't much support among GUI toolkits for PyPy.  Using cpyext, TKinter and (classic) wxPython work, but because of the complexity involved in emulating CPython's extension API they are buggy and the performance isn't what it could be.  So to improve this situation, this summer I will be extending wxPython Phoenix to create a set of bindings that use CFFI.

wxPython Phoenix

wxPython Phoenix is a work-in-progress rewrite of wxPython that seeks to clean up the API and, more importantly to this project improve maintainability.  It achieves this latter goal by partially automating the creation of the bindings via a set of 'etg' scripts.   The first set of scripts, called 'extractors,' collect API data from the Doxygen XML files generated from wxWidgets' header files.  This data is then processed through a set 'tweaker' scripts to make the API more pythonic.  Finally, the API information is fed to a 'generator' script that creates the actual bindings.  Currently, the only genreator script in place creates sip files.

CFFI

CFFI is a library for exposing C code to Python.  Its similar to ctypes in terms of functionality, but is higher level and has a much nicer API.  PyPy has specific optimizations for CFFI and recommends it as the preferred way to call native code from Python.

What I will be doing

I will be creating a generator script that will create the CFFI bindings.  Unfortunately, it not as simple as that sounds: more than just Python code needs to be generated.  First of all, CFFI is not meant to wrap C++ code directly, so the generator will have to create a C-callable wrapper around the C++ API.  Second, some of the functionality that is provided by sip, for example deriving Python types from C++ types, will have to be replicated.  Lastly, the tweaker scripts contain a bunch of sip and CPython specific code that will have to be modified too.

You can follow my coding progress throughout the summer at https://bitbucket.org/waedt/wxpython_cffi, though there's nothing there just yet.