We now turn to the process of drawing on the screen. The widget
we use for this is the DrawingArea (see Chapter 12, Drawing Area) widget. A drawing area widget is
essentially an X window and nothing more. It is a blank canvas in which we
can draw whatever we like. A drawing area is created using the call:
darea = gtk.DrawingArea()
A default size for the widget can be specified by calling:
darea.set_size_request(width,height)
This default size can be overridden, as is true for all widgets,
by calling the set_size_request() method, and that,
in turn, can be overridden if the user manually resizes the the window
containing the drawing area.
It should be noted that when we create a
DrawingArea widget, we are completely responsible for
drawing the contents. If our window is obscured then uncovered, we get an
exposure event and must redraw what was previously hidden.
Having to remember everything that was drawn on the screen so we can properly redraw it can, to say the least, be a nuisance. In addition, it can be visually distracting if portions of the window are cleared, then redrawn step by step. The solution to this problem is to use an offscreen backing pixmap. Instead of drawing directly to the screen, we draw to an image stored in server memory but not displayed, then when the image changes or new portions of the image are displayed, we copy the relevant portions onto the screen.
To create an offscreen pixmap, we call the function:
pixmap = gtk.gdk.Pixmap(window,width,height,depth=-1)
The window parameter specifies a
gtk.gdk.Window that this pixmap takes some of its
properties from. width and
height specify the size of the
pixmap. depth specifies the
color depth, that is the number of bits per pixel, for the new window. If
the depth is specified as -1 or omitted, it will
match the depth of window.
We create the pixmap in our "configure_event" handler. This event is generated whenever the window changes size, including when it is originally created.
32 # Create a new backing pixmap of the appropriate size 33 def configure_event(widget, event): 34 global pixmap 35 36 x, y, width, height = widget.get_allocation() 37 pixmap = gtk.gdk.Pixmap(widget.window, width, height) 38 pixmap.draw_rectangle(widget.get_style().white_gc, 39 True, 0, 0, width, height) 40 41 return True
The call to draw_rectangle() clears the
pixmap initially to white. We'll say more about that in a moment.
Our exposure event handler then simply copies the relevant
portion of the pixmap onto the drawing area (widget) using the
draw_pixmap() method. (We determine the area we
need to redraw by using the event.area attribute of
the exposure event):
43 # Redraw the screen from the backing pixmap 44 def expose_event(widget, event): 45 x , y, width, height = event.area 46 widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL], 47 pixmap, x, y, x, y, width, height) 48 return False
We've now seen how to keep the screen up to date with our
pixmap, but how do we actually draw interesting stuff on our pixmap? There
are a large number of calls in PyGTK for drawing on drawables. A drawable is
simply something that can be drawn upon. It can be a window, a pixmap, or a
bitmap (a black and white image). We've already seen two such calls above,
draw_rectangle() and
draw_pixmap(). The complete list is:
drawable.draw_point(gc,x,y) drawable.draw_line(gc,x1,y1,x2,y2) drawable.draw_rectangle(gc,fill,x,y,width,height) drawable.draw_arc(gc,fill,x,y,width,height,angle1,angle2) drawable.draw_polygon(gc,fill,points) drawable.draw_drawable(gc,src,xsrc,ysrc,xdest,ydest,width,height) drawable.draw_points(gc,points) drawable.draw_lines(gc,points) drawable.draw_segments(gc,segments) drawable.draw_rgb_image(gc,x,y,width,height,dither,buffer,rowstride) drawable.draw_rgb_32_image(gc,x,y,width,height,dither,buffer,rowstride) drawable.draw_gray_image(gc,x,y,width,height,dither,buffer,rowstride)
The drawing area methods are the same as the drawable drawing
methods so you can use the methods described in Section 12.2, “Drawing Methods” for further details on these
methods. These methods all share the same first arguments. The first
argument is a graphics context (gc).
A graphics context encapsulates information about things such as
foreground and background color and line width. PyGTK has a full set of
functions for creating and modifying graphics contexts, but to keep things
simple we'll just use predefined graphics contexts. See Section 12.1, “Graphics Context” section for more information on
graphics contexts. Each widget has an associated style. (Which can be
modified in a gtkrc file, see Chapter 23, GTK's rc Files.) This, among other things, stores a number
of graphics contexts. Some examples of accessing these graphics contexts
are:
widget.get_style().white_gc widget.get_style().black_gc widget.get_style().fg_gc[STATE_NORMAL] widget.get_style().bg_gc[STATE_PRELIGHT]
The fields fg_gc, bg_gc,
dark_gc, and light_gc are indexed by a
parameter which can take on the values:
STATE_NORMAL, STATE_ACTIVE, STATE_PRELIGHT, STATE_SELECTED, STATE_INSENSITIVE
For instance, for STATE_SELECTED the default
foreground color is white and the default background color, dark
blue.
Our function draw_brush(), which does the
actual drawing on the pixmap, is then:
50 # Draw a rectangle on the screen 51 def draw_brush(widget, x, y): 52 rect = (int(x-5), int(y-5), 10, 10) 53 pixmap.draw_rectangle(widget.get_style().black_gc, True, 54 rect[0], rect[1], rect[2], rect[3]) 55 widget.queue_draw_area(rect[0], rect[1], rect[2], rect[3])
After we draw the rectangle representing the brush onto the pixmap, we call the function:
widget.queue_draw_area(x,y,width,height)
which notifies X that the area given needs to be updated. X will
eventually generate an expose event (possibly combining the areas passed in
several calls to draw()) which will cause our expose
event handler to copy the relevant portions to the screen.
We have now covered the entire drawing program except for a few mundane details like creating the main window.