In the past I’ve put off writing documentation for Chaco and Enable because every time I had faced the task, it seemed so daunting: there are over a hundred classes, a dozen different patterns and conventions, and so many areas for improvement and exploration. It frequently felt much easier just to work on code.
But I recognize that the situation is pretty dire, and something needs to be done about it. The reality of the situation is that the only way this documentation will materialize is if someone actually sits down and writes it, and this someone is basically going to have to be me, along with much-appreciated help from Janet. I’m going to take a slightly different approach, though. In the past, I’ve been stumped by the question of organization and structure; this time, I will just talk about all the pieces that comprise Enable and Chaco and essentially narrate a tour of the packages, without trying to outline a detailed structure beforehand.
Doing this as a series of blog posts makes the task seem somewhat less daunting, and it also allows for reader feedback as I’m writing things up.
So let’s get started!
Day 1: An Overview of Kiva and Enable
Chaco, Enable, and Kiva are three packages in the Enthought Tool Suite. They have been there for a long time now, since almost the beginning of Enthought as a company. Enthought has delivered many applications using these toolkits. The Kiva and Enable packages are bundled together in the “Enable” project.
Kiva is a 2D vector drawing library for python. It serves a purpose similar to Cairo. It allows us to compose vector graphics for display on the screen or for saving to a variety of vector and image file formats. To use Kiva, a program instantiates a Kiva GraphicsContext object of an appropriate type, and then makes drawing calls on it like gc.draw_image(), gc.line_to(), and gc.show_text(). Kiva integrates with windowing toolkits like wxWindows and QT, and it has an OpenGL backend as well. For wxPython and QT, Kiva actually performs a high-quality, fast software rasterization using the Anti-Grain Geometry (AGG) library. For OpenGL, Kiva has a python extension that makes native OpenGL calls from C++.
Kiva provides a GraphicsContext for drawing onto the screen or saving out to disk, but it provides no mechanism for user input and control. For this “control” layer, it would be convenient to only have to write one set of event callbacks or handlers for all the platforms we support, and all the toolkits on each platform. Enable provides us this layer. It insulates all the rendering and event handling code in Chaco from needing to worry about the minutiae of each GUI toolkit. Additionally, and to some extent more importantly, Enable defines the concept of “components” and “containers” that form the foundation of Chaco’s architecture. In the Enable model, the top-most Window object is responsible for dispatching events and drawing a single component. Usually, this component is a container with other containers and components inside it. The container can perform layout on its internal components, and it controls how events are subsequently dispatched to its set of components.
Almost every single graphical component in Chaco is an instance of an Enable component or container. We’re currently trying to push more of the layout system (implemented as the various different kinds of Chaco plot containers) down into Enable, but as things currently stand, you have to use Chaco containers if you want to get layout. The general trend has been that we implement some nifty new thing in Chaco, and then realize that it is a more general tool or overlay that will be useful for other non-plotting visual applications. We then move it into Enable, and if there are plotting-specific aspects of it, we will create an appropriate subclass in Chaco to encapsulate that behavior.
The sorts of applications that can and should be done at the Enable level include things like a visual programming canvas or a vector drawing tool. There is nothing at the Enable level that understands the concept of mapping between a data space to screen space and vice versa. Although there has been some debate about the incorporating rudimentary mapping into Enable, for the time being, any kind of canvas-like thing that wants to model more than just pixel space on the screen should be implemented using the mechanisms in Chaco.
The way that Enable hooks up to the underlying GUI toolkit system is via an enable.Window object. Each toolkit has its own implementation of this object, and they all subclass from enable.AbstractWindow. They usually contain an instance of the GUI toolkit’s specific window object, whether it’s a wx.Window or Qt.QWidget or pyglet.window.Window. This instance is created upon initialization of the enable.Window and stored as .control on the enable Window. From the perspective of the GUI toolkit, an opaque widget gets created and stuck inside a parent control (or dialog or frame or window). This instance serves as a proxy between the GUI toolkit and the world of Enable. When the user clicks inside the widget area, the .control widget calls a method on the enable.Window object, which then in turn can dispatch the event down the stack of Enable containers and components. When the system tells the widget to draw itself (e.g., as the result of a PAINT or EXPOSE event from the OS), the enable.Window is responsible for creating an appropriate Kiva GraphicsContext (GC), then passing it down through the object hierarchy so that everyone gets a chance to draw. After all the components have drawn onto the GC, for the Agg-based bitmap backends, the enable.Window object is responsible for blitting the rastered off-screen buffer of the GC into the actual widget’s space on the screen. (For Kiva’s OpenGL backend, there is no final blit since calls to the gc render in immediate mode in the window’s active OpenGL context, but the idea is the same, and the enable.Window object does perform initialization on the GL GraphicsContext.)
Some of the advantages to using Enable is that it makes mouse and key events from disparate windowing systems all share the same kind of signature, and be accessible via the same name. So, if you write bare wxPython and handle a key_pressed event in wx, this might generate a value of wx.WXK_BACK. Using Enable, you would just get a “key” back and its value would be the string “Backspace”, and this would hold true on Qt4 and pyglet. Almost all of the event handling and rendering code in Chaco is identical under all of the backends; there are very few backend-specific changes that need to be handled at the Chaco level.
The enable.Window object has a reference to a single top-level graphical component (which includes containers, since they are subclasses of component). Whenever it gets user input events, it recursively dispatches all the way down the potentially-nested stack of components. Whenever a components wants to signal that it needs to be redrawn, it calls self.request_redraw(), which ultimately reaches the enable.Window, which can then make sure it schedules a PAINT event with the OS. The nice thing about having the enable.Window object between the GUI toolkits and our apps, and sitting at the very top of event dispatch, is that we can easily interject new kinds of events; this is precisely what we did to enable all of our tools to work with Multitouch.
The basic things to remember about Enable are that:
- Any place that your GUI toolkit allows you stick a generic widget, you can stick an Enable component (and this extends to Chaco components, as well). Dave Morrill had a neat demonstration of this by embedding small chaco plots as cells in a wx Table control.
- If you have some new GUI toolkit, and you want to provide an Enable backend for it, all you have to do is implement a new Window class for that backend. You will also need to make sure that Kiva can actually create a GraphicsContext for that toolkit. Once the kiva_gl branch is committed to the trunk, Kiva will be able to render into any GL context, and so if your newfangled unsupported GUI toolkit has a GLWindow type of thing, then you will be able to use Kiva, Enable, and Chaco inside it. This is a pretty major improvement to interoperability, if only because users now don’t have to download and install wxPython just to play with Chaco.
I’m not going to cover the rest of the parts of Enable for now, because this entry is meant to be just a basic overview of what it does.
Coming up tomorrow: Chaco architectural overview