10 May

Soon I will have to answer the question, "How do I render a video timeline using Python & Glade / PyGTK?". Their are obviously no widgets that are made for this function, and I can't exactly assemble buttons, images, and labels on the screen and make it look very nice or believable.

The answer is Cairo. Not the city in Egypt, but rather the open-source, multi-platform 2D graphics library, with a special set of bindings for Python (PyCairo).

So, given my limited knowledge so far, the plan is to use Glade for almost all of the interface, dialogs, and use Cairo only to draw the video / audio timeline on the screen. Cairo should give me all the control I need to allow the user to drag clips around, snap them to the timeline, trim clips, etc... And best of all, it will give me 100% control over how I want that experience to look & feel.

Not that it matters, but the failed Diva project & the Jokosher project also used Cairo to render their timelines. That adds to my confidence that this is the right direction to go.

Here is a simple Python example of some Cairo code, which draws a happy face on the screen:

#! /usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
from math import pi

# 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
cr = self.window.cairo_create()

# Restrict Cairo to the exposed area; avoid extra work
cr.rectangle(event.area.x, event.area.y,
event.area.width, event.area.height)
cr.clip()
self.draw(cr, *self.window.get_size() )

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

# draw a rectangle
cr.set_source_rgb(1.0, 1.0, 1.0)
cr.rectangle(10, 10, width - 20, height - 20)
cr.fill()

# draw lines
cr.set_source_rgb(0.0, 0.0, 0.8)
cr.move_to(width / 3.0, height / 3.0)
cr.rel_line_to(0, height / 6.0)
cr.move_to(2 * width / 3.0, height / 3.0)
cr.rel_line_to(0, height / 6.0)
cr.stroke()

# and a circle
cr.set_source_rgb(1.0, 0.0, 0.0)
radius = min(width, height)
cr.arc(width / 2.0, height / 2.0, radius / 2.0 - 20, 0, 2 * pi)
cr.stroke()
cr.arc(width / 2.0, height / 2.0, radius / 3.0 - 10, pi / 3, 2 * pi / 3)
cr.stroke()

# GTK mumbo-jumbo to show the widget in a window and quit when it's closed
def run(Widget):
window = gtk.Window()
window.connect("delete-event", gtk.main_quit)
widget = Widget()
widget.show()
window.add(widget)
window.present()
gtk.main()

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