[pygtk] How drawing, Drawable, ....work in pygtk !

Eric Raymond esr at thyrsus.com
Mon Dec 21 12:05:24 WST 2009


chuong nguyen vien <chuong_nguyenvien at yahoo.com>:
> Hello,
> 
> I have just played with pygtk a couple of day and everything seems ok but the drawing system. I dont know how it works basically and got confused, very confused !
> 
> I read somewhere on the internet it say that i have to follow a pattern to make it work, and the key to that pattern is : we need to implement the drawing code within the expose event handler. Why ? 
> 
> I tried to make some drawing code outside the expose event handler, and didnt write a expose event handler at all. It didnt work. It just appear a window without anything, or my drawing stuff didnt display.
> 
> So basically, how the expose event and drawing in GTK work or relate to each other ?
> 
> Thanks 
> 
> Chuong

Here's the canvas-management class I wrote for the GPSD test client. You
may be able to use it as a model.  Note how (a) all the actual drawing 
is done in the expose event, (b) redrawing is implemented with the
queue_draw() method, (c) the canvas's actual size allocation 
is picked up for later use in the on_size_allocate event.

class SkyView(gtk.DrawingArea):
    "Satellite skyview, encapsulates pygtk's draw-on-expose behavior."
    # See <http://faq.pygtk.org/index.py?req=show&file=faq18.008.htp>
    HORIZON_PAD = 20	# How much whitespace to leave around horizon
    SAT_RADIUS = 5	# Diameter of satellite circle
    GPS_PRNMAX = 32	# above this number are SBAS satellites
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.set_size_request(400, 400)
        self.gc = None  # initialized in realize-event handler
        self.width  = 0 # updated in size-allocate handler
        self.height = 0 # updated in size-allocate handler
        self.connect('size-allocate', self.on_size_allocate)
        self.connect('expose-event',  self.on_expose_event)
        self.connect('realize',       self.on_realize)
        self.pangolayout = self.create_pango_layout("")
        self.satellites = []

    def on_realize(self, widget):
        self.gc = widget.window.new_gc()
        self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID,
                                    gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)

    def on_size_allocate(self, widget, allocation):
        self.width = allocation.width
        self.height = allocation.height
        self.diameter = min(self.width, self.height) - SkyView.HORIZON_PAD

    def set_color(self, spec):
        "Set foreground color for drawing."
        self.gc.set_rgb_fg_color(gtk.gdk.color_parse(spec))

    def draw_circle(self, widget, x, y, diam, filled=False):
        "Draw a circle centered on the specified midpoint."
        widget.window.draw_arc(self.gc, filled,
                               x - diam / 2, y - diam / 2,
                               diam, diam, 0, 360 * 64)

    def draw_square(self, widget, x, y, diam, filled=False):
        "Draw a square centered on the specified midpoint."
        widget.window.draw_rectangle(self.gc, filled,
                                     x - diam / 2, y - diam / 2,
                                     diam, diam)

    def draw_string(self, widget, x, y, letter):
        "Draw a letter on the skyview."
        self.pangolayout.set_text(letter)
        # FIXME: When the layout object can report its size, use it
        self.window.draw_layout(self.gc, x-5, y-10, self.pangolayout)

    def pol2cart(self, az, el):
        "Polar to Cartesian coordinates within the horizon circle."
	az *= (math.pi/180)	# Degrees to radians
        # Exact spherical projection would be like this:
	# el = sin((90.0 - el) * DEG_2_RAD);
	el = ((90.0 - el) / 90.0);
	xout = int((self.width / 2) + math.sin(az) * el * (self.diameter / 2))
	yout = int((self.height / 2) - math.cos(az) * el * (self.diameter / 2))
        return (xout, yout)

    def on_expose_event(self, widget, event):
        self.set_color("white")
        widget.window.draw_rectangle(self.gc, True, 0,0, self.width,self.height)
        # The zenith marker
        self.set_color("gray")
        self.draw_circle(widget, self.width / 2, self.height / 2, 6)
        # The circle corresponding to 45 degrees elevation.
        # There are two ways we could plot this.  Projecting the sphere
        # on the display plane, the circle would have a diameter of
        # sin(45) ~ 0.7.  But the naive linear mapping, just splitting
        # the horizon diameter in half, seems to work better visually.
        self.draw_circle(widget, self.width / 2, self.height / 2,
                         int(self.diameter * 0.5))
        self.set_color("black")
        # The horizon circle
        self.draw_circle(widget, self.width / 2, self.height / 2,
                         self.diameter)
        # The compass-point letters
        (x, y) = self.pol2cart(0, 0)
        self.draw_string(widget, x, y+10, "N")
        (x, y) = self.pol2cart(90, 0)
        self.draw_string(widget, x-10, y, "E")
        (x, y) = self.pol2cart(180, 0)
        self.draw_string(widget, x, y-10, "S")
        (x, y) = self.pol2cart(270, 0)
        self.draw_string(widget, x+10, y, "W")
        # The satellites
        for sat in self.satellites:
            (x, y) = self.pol2cart(sat.azimuth, sat.elevation)
            if sat.ss < 10:
                self.set_color("Black")
            elif sat.ss < 30:
                self.set_color("Red")
            elif sat.ss < 35:
                self.set_color("Yellow");
            elif sat.ss < 40:
                self.set_color("Green3");
            else:
                self.set_color("Green1");
            if sat.PRN > SkyView.GPS_PRNMAX:
                self.draw_square(widget,
                                 x-SkyView.SAT_RADIUS, y-SkyView.SAT_RADIUS,
                                 2 * SkyView.SAT_RADIUS + 1, sat.used);
            else:
                self.draw_circle(widget,
                                 x-SkyView.SAT_RADIUS, y-SkyView.SAT_RADIUS,
                                 2 * SkyView.SAT_RADIUS + 1, sat.used);
            self.set_color("Black")
            self.draw_string(widget, x, y+10, str(sat.PRN))
    def redraw(self, satellites):
        "Redraw the skyview."
        self.satellites = satellites
        self.queue_draw()


-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>


More information about the pygtk mailing list