3
votes

I am just getting started using pycairo, and I ran into the following interesting error. The program I write creates a simple gtk window, draws a rectangle on it, and then has a callback to draw a random line on any kind of keyboard input. However, it seems that with each keyboard input, I have to create a new context, or I get an error at the moment the program receives first keyboard input (specifically, on the .stroke() line). Error is as follows, if it matters. 'BadDrawable (invalid Pixmap or Window parameter)'. (Details: serial 230 error_code 9 request_code 53 minor_code 0)

#! /usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo, math, random
# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):
# Draw in response to an expose-event
  __gsignals__ = { "expose-event": "override" }

  # Handle the expose-event by drawing
  def do_expose_event(self, event):
    # Create the cairo context
    self.cr = self.window.cairo_create()
    # Restrict Cairo to the exposed area; avoid extra work
    self.cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
    self.cr.clip()

    self.draw(*self.window.get_size())

  def key_press_event(self, *args):
    # print args
    self.cr = self.window.cairo_create() # This is the line I have to add
    # in order to make this function not throw the error. Note that cr is only
    # given as attribute of self in order to stop it going out of scope when this line
    # doesn't exist
    self.cr.set_source_rgb(random.random(), random.random(), random.random())
    self.cr.move_to(*[z/2.0 for z in self.window.get_size()])
    self.cr.line_to(*[z*random.random() for z in self.window.get_size()])
    self.cr.stroke()

  def draw(self, width, height):
    # Fill the background with gray
    self.cr.set_source_rgb(.5,.5,.5)
    self.cr.rectangle(0, 0, width,height)
    self.cr.fill()

    self.cr.set_source_rgb(1,0,0)
    self.cr.arc(width/2.0, height/2.0, min(width,height)/2.0 - 20.0, 0.0, 2.0*math.pi)
    self.cr.stroke()

#create a gtk window, attach to exit button, and whatever is passed as arg becomes the body of the window. AWESOME
def run(Widget):
  window = gtk.Window()
  widget = Widget()
  window.connect("delete-event", gtk.main_quit)
  window.connect('key-press-event',widget.key_press_event)
  widget.show()
  window.add(widget)
  window.present()
  gtk.main()

if __name__ == "__main__":
  run(Screen)

Thanks for your help!

(Update: I was playing around, and I realized the following: when I resize the window, all new objects that were added get deleted (or at least don't appear anymore?) )

3
You might be interested in wiki.github.com/tbaugis/hamster_experiments - it provides a quite helpful abstraction on top of Cairo. Even if you don't use the full library, the included tweener is certainly worth looking at.Donald Harvey

3 Answers

2
votes

Cairo drawings don't persist at all. (It's best not to think of them as "objects" -- it's not like a canvas library where you can move them around or transform them after you've drawn them.) You have to do all drawing in the expose handler, or it will, as you have found out, disappear whenever the window is redrawn.

The cairo context doesn't persist because of double buffering: see the note in the C documentation, which unfortunately I couldn't find anywhere in the PyGTK documentation.

In the above code, you should generate the coordinates and color of your random line in the keypress handler and save them in an array. Then in the expose handler, draw each line in the array in order.

1
votes

while you have to create context on every run, you can achieve the persistence you are looking for by disabling the double buffering of the widget.

here's an example using hamster graphics library that just does that:

https://github.com/projecthamster/experiments/blob/master/many_lines.py

0
votes

Many flavors of persistence to discuss:

Drawings on some surfaces don't persist: GUI surfaces. You should redraw them in the expose callback.

PyCairo objects shouldn't be treated as persistent objects, only as an interface to functions of the Cairo library in C.

The contents (paths and fills) of Cairo contexts don't persist beyond a stroke() or fill() operation.

A context for a GUI surface doesn't persist between expose events (because of double buffering?) (I don't know whether a context persists for other surfaces i.e. devices.) So you can't use a cairo context to store the attributes of a viewport (a window onto a document i.e. model in user coordinates.)

Visual persistence is the tendency of the human eye to see light after it has ceased. Ghosts and flicker are its symptoms in animation or video. Disabling double buffering lets you see things as they are drawn, that is, enabling animation within one expose event ( the simulation of the symptoms of visual persistence.) Disabling double buffering doesn't make a context on a GUI surface persistent between expose events.

The persistence of memory is the ur real persistence, or should I say surreal.