Graphical Python Programming part 2: Write Your Own Screensaver - page 3
See the complete program on page 4
It works somewhat like an expose handler except that it's only called when the program isn't busy doing anything else. You do a little bit -- maybe just draw one line -- then return. Here's the outline of an idle handler:
def idle_handler(widget) : return True
The handler needs to return True every time it's called, or else it will be removed from the idle list and never called again.
Then, when you're setting up your window, set the idle handler:
import gobject idle_id = gobject.idle_add(idle_handler, drawing_area)
What should your real idle handler look like?
If you're only drawing one line at a time in the idle handler, that means a little bit of program restructuring. You have to store those colors and X and Y values more globally. Python offers several ways of doing this elegantly (to be explored in future articles), but for now let's just use the keyword global.
Normally, when you use a variable, like x1, in Python, it's only available inside a single function, and the next time you call the function Python won't remember its previous value. global lets you save the value even after you leave the idle function. So the idle handler (and any other function that needs access to the globals) might include a line like this:
global xgc, x1, x2, y1, y2, r, g, b, movesize, colorjump
Python also requires that you initialize all global variables outside of any function. So at the beginning of your program, you might have lines like this:
xgc = None x1 = None x2 = None y1 = None y2 = None r = random.randint(0, 65535) g = random.randint(0, 65535) b = random.randint(0, 65535) movesize = 20 colorjump = 2048
Notice that the colors and jump sizes are initialized here, but the other variables aren't. That's because there's no way to know the window size until the window is actually on screen.
If you're doing your drawing in the idle handler, you don't really need an expose handler all all. So get rid of it. You can move the line-drawing code into the idle handler: not the whole for loop, just the code to draw a single line.
But then what do you do about those values that used to be initialized in the expose handler -- xgc and the x and y coordinates?
The best solution is to initialize them just once. Remember that the global variables were initially set to None -- so you can test for that in the idle handler, and set them only if they're still none.
The idle handler looks like this:
def idle_handler(widget) : global xgc, w, h, x1, x2, y1, y2, r, g, b, movesize, colorjump # Get the window size every time, in case it's been resized: w, h = widget.window.get_size() # Initialize the GC and xy values if this is the first time through: if (xgc == None) : xgc = widget.window.new_gc() x1 = random.randint(0, w) x2 = random.randint(0, w) y1 = random.randint(0, h) y2 = random.randint(0, h) # Change the line boundaries a little bit: x1 = clip(x1 + random.randint(-movesize, movesize), 0, w) x2 = clip(x2 + random.randint(-movesize, movesize), 0, w) y1 = clip(y1 + random.randint(-movesize, movesize), 0, h) y2 = clip(y2 + random.randint(-movesize, movesize), 0, h) # Change the color a little bit: r = clip(r + random.randint(-colorjump, colorjump), 0, 65535) g = clip(g + random.randint(-colorjump, colorjump), 0, 65535) b = clip(b + random.randint(-colorjump, colorjump), 0, 65535) xgc.set_rgb_fg_color(gtk.gdk.Color(r, g, b)) # Draw the new line widget.window.draw_line(xgc, x1, y1, x2, y2) # Return True so we'll be called again: return True
You're done! Your whizzy program will keep running until you tell it to quit, filling the screen with lines of changing color (Figure 5).
Next up: although this global variable technique was an easy way of saving information in the idle handler to be used again, it's not considered good programming practice. In the next article I'll explore some of the reasons why, and how you can write more object-oriented Python to avoid those problems.