Using Event Trackers to Simplify Tool Implementation

The Widget Construction Kit
(WCK) uses different base classes for the code that draws a widget,
and the code that implement the interactive behaviour. The latter is
called controller, and is used to handle incoming events, and
update the widget accordingly.

Here’s a minimal example. Here, the controller responds to button
clicks by printing a message to the console, and the widget doesn’t do
much at all. (Besides providing a place for the user to click, that
is.)

To save resources, the WCK framework shares controller instances,
so there will only be one MyController instance in memory, even
if you create multiple instances of the MyWidget widget. If
you need to access the widget in which an event originated, you can
use the event.widget attribute:

defclick(self, event):
print"CLICK!", event.widget

Other event attributes include event.x and event.y
for mouse events, and event.char for keyboard events.

Controllers can be used for a lot more than just clicking and
typing. Here’s a controller that allows the user to drag a line
across a widget:

This controller uses rubberband and add_line methods
on the widget. Note that you can use this controller with any widget
that implements these methods; the controller is not dependent on any
specific widget class.

However, if you set out to implement a full-fledged drawing editor,
you will want more than just a single line tool. You probably want a
tool palette from which the user can pick a tool, and you may also
need to implement tools with a lot more options built into a single
tool. In earlier versions of the WCK, the easiest way to implement
this would be to install a dispatching controller, which captured all
events needed by your tools, and passed them on to the current tool.
Here’s an example:

You can also use this mechanism to simplify the code for more
complex tools. Instead of creating a single tool controller that
handles all possible cases, break the tool into smaller parts, called
trackers. For a rectangle drawing tool, you could use one
tracker to activate the tool, another to draw arbitrary rectangles,
a third one to select existing objects, a fourth one to draw squares,
etc.

To illustrate the tracker pattern, let’s look at a larger example.
Here’s the widget I’m going to use for this example:

This widget creates a 500x500 pixel drawing area, and can
optionally draw a red rectangle on it. To draw the rectangle, set the
xy attribute and call ui_damage() to refresh the
widget.

Let’s attach a simple tool to this widget. This tool allows the
you to draw a new rectangle, or move an existing rectangle around. To
move the existing rectangle, click the mouse on the rectangle, and
drag it to the new location. If you click outside, a new rectangle is
drawn.

Here’s the main tool controller (which is also a tracker, of
course). If you click on the rectangle (that is, if the mouse
coordinates are inside the xy rectangle), the controller hands
control over to the MoveTracker controller. Otherwise, it hands
control over to the DrawTracker.

And finally, the DrawTracker uses the anchor and the current
position to determine the location of the rectangle, and updates the
widget accordingly. Also note that both controllers restore the
original RectangleTool when the mouse button is released.