Cairo, PyWebKit y PyGTK: Semana de Pruebas

jueves, octubre 22, 2009

Esta ha sido una semana de pruebas, pruebas y más pruebas. Estoy trabajando en eso de mejorar las interfaces en PyGTK y pretendo apoyarme en Cairo y WebKit. He investigado un poco de ambos; los resultados han sido alentadores.

Primera prueba de la semana: Cairo

Cairo es una librería que permite dibujar sobre un widget (canvas o lienzo). Y cuando digo dibujar, me refiero a dibujar. Trazar líneas, rectángulos, arcos y cosas por el estilo.

Lo primero que se me ocurrió hacer para probar Cairo fue un medidor. Para esto solo me haría falta un slider vertical y el canvas para dibujar. La idea es que el medidor se llene o se vacíe según el deslizamiento de la barra.

El código del medidor quedó así (se explica con los comentarios):
#!/usr/bin/python

# Ejemplo de widget con Cairo
#
# Author: Wil Alvarez (aka Satanas)
# Oct 19, 2009

import gtk
import cairo

# Creamos una clase que herede de gtk.DrawingArea para usarla como canvas
class Cpu(gtk.DrawingArea):
    def __init__(self, parent):
        self.par = parent
        gtk.DrawingArea.__init__(self)
        # Nos conectamos al evento expose, pues allí es donde ocurre toda 
        # la diversión
        self.connect('expose-event', self.expose)
        self.set_size_request(130, 200)
    
    # Este evento se ejecuta cada vez que la aplicación necesita redibujarse
    # o cuando cambiamos un valor y mandamos a redibujarla. Aquí se pintará
    # y se le dará forma al widget
    def expose(self, widget, event):
        # Aquí obtenemos el contexto de cairo
        cr = widget.window.cairo_create()
        cr.set_line_width(0.8)
        
        # Definimos un rectángulo para limitar el proceso de dibujado y así
        # optimizar la operación
        cr.rectangle(event.area.x, event.area.y, 
            event.area.width, event.area.height)
        cr.clip()
        
        cr.rectangle(0,0,130,200)
        cr.set_source_rgb(0, 0, 0)  # Establecemos el color de la brocha/pincel
        cr.fill()
        
        # Obtenemos el valor actual del slider
        x = (self.par.cur_value * 34) / 100
        
        # Dibujamos 34 barritas para el medidor y según el valor de 'x'
        # decidimos si está 'encendida' o no
        for i in range(34):
            if (i < 34 - x):
                cr.set_source_rgb(0.53, 0, 0)
            else:
                cr.set_source_rgb(1, 0, 0)
            
            h = 15 + (i*5)
            cr.rectangle(15,h,49,4)
            cr.fill()
            
            cr.rectangle(67,h,49,4)
            cr.fill()

# Creamos una ventana sencilla en PyGTK con el slider y el canvas
class PyApp(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        
        self.set_title('CPU Meter')
        self.set_size_request(200, 200)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect('destroy', gtk.main_quit)

        self.cur_value = 10
       
        vbox = gtk.VBox(False, 2)
        
        scale = gtk.VScale()
        scale.set_range(0, 100)
        scale.set_digits(0)
        scale.set_size_request(35, 160)
        scale.set_value(self.cur_value)
        scale.set_inverted(True)
        scale.connect('value-changed', self.on_changed)
        
        self.cpu = Cpu(self)
        
        hbox = gtk.HBox(False)
        hbox.pack_start(self.cpu)
        hbox.pack_start(scale)
        
        vbox.pack_start(hbox, True, True, 2)

        self.add(vbox)
        self.show_all()
        
    # Programamos el evento 'value-changed' de la barra para que con cada
    # cambio mande a redibujar al widget del medidor
    def on_changed(self, widget):
        self.cur_value = widget.get_value()
        self.cpu.queue_draw()


    def get_cur_value(self):
        return self.cur_value

PyApp()
gtk.main()
Al final la aplicación quedó así:



Bastante aceptable para mi gusto. El código pueden descargarlo aquí


Segunda prueba de la semana: PyWebKit

La segunda prueba fue con PyWebKit. Esta prueba me ha frustrado un poco por la dificultad de conseguir documentación o referencias sobre la API de PyWebKit. Fue una labor árdua. Tuve que descargar varios códigos fuentes; entre ellos el de Gwibber (cliente de Twitter) y hasta el del mismísimo PyWebKit para lograr acercame a algo vagamente funcional.

El único ejemplo que traía PyWebKit era el de un navegador que soporta pestañas y otro centenar de características, por lo que no era para nada sencillo comprender su código (¿a quién se le ocurriría poner ese ejemplo? BIG FAIL! Es solo un demo, ¡rayos!... No el próximo Firefox, Opera o Safari).

El código de gwibber era de lejos más comprensible, aunque no menos complejo (se lo justifico por ser una aplicación de verdad no un ejemplo). Después de mucho leer, digerir implementaciones de controles GTK, fumarme unas cuantas lumpias y otros esoterismos, pude dar con el método que permite insertar código HTML directamente sobre el widget... el famoso load_string.

La cuestión con PyWebKit es relativamente simple, porque después que dominamos el load_string lo demás es carpintería HTML y CSS.

El código de la prueba a continuación:

#!/usr/bin/python

# Ejemplo de widget con WebKit
#
# Author: Wil Alvarez (aka Satanas)
# Oct 20, 2009

import gtk
import webkit
import gobject
gobject.threads_init()

# Codigo HTML que insertaremos al control para que lo muestre
ABOUT_PAGE = """
<html><head><title>PyWebKitGtk</title></head><body>
<h1>Mi primera prueba con PyWebKit</h1>
<p><a href="http://code.google.com/p/pywebkitgtk/">http://code.google.com/p/pywebkitgtk/</a><br/>
</p>
<div style="border: 1px solid #000; width:300px; height: 100px; background-color:#aaa;">
  zOMG! This is fucking awesome<br/><br/>
  No se que más poner en este div con estilos css  XDDD
</div>
</body></html>
"""

# Clase donde sobreecribimos el widget WebView de WebKit para implementar
# nuestro código y hacer uso del load_string para inyectar HTML directamente
# sobre el control (sin usar URI o algo similar)
class MessageStreamView(webkit.WebView):
    def __init__(self):
        webkit.WebView.__init__(self)
        self.connect("navigation-requested", self.on_click_link)
        
        self.settings = webkit.WebSettings()
        self.set_settings(self.settings)
        
        # Recibe como parámetros el código HTML, el mime-type de la página,
        # la codificación y un URI
        self.load_string(ABOUT_PAGE, "text/html", "iso-8859-15", "about")
        
    def on_click_link(self, view, frame, req):
        uri = req.get_uri()
        print uri
        return True

# Creamos una ventana simple en PyGTK con el control que acabamos de crear y 
# voilá! Tenemos nuestro widget que renderiza páginas web con el motor WebKit
class Simulador(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_title('Pruebas de Gwibber, Webkit y otras shits')
        self.set_default_size(400, 400)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect('destroy', gtk.main_quit)
        
        messages = MessageStreamView()
        
        vbox = gtk.VBox(False, 5)
        vbox.pack_start(messages, True, True, 0)
        
        self.add(vbox)
        self.show_all()
    
Simulador()
gtk.main()
La ventana luce así:



Y el código pueden descargarlo aquí.

Aún no estoy muy satisfecho con la información que tengo de PyWebKit, lo mejor que he conseguido es una lista con los nombres de los métodos de la API y más nada, así que seguiré buscando.


Conslusiones

Cada librería tiene un ámbito diferente, por lo que la idea no es establecer una comparación uno a uno entre ellas, sino destacar los pro y los contra para forjar una idea de hasta donde podemos llegar con ellas.

PyWebKit

La mayor desventaja de PyWebKit es la falta de documentación. No me importaría tener una documentación vulgar, chapucera y hasta con errores ortográficos, con tal de al menos tener una! Sin embargo, para compensar eso podemos decir que PyWebKit nos otorga todo el poder y la flexibilidad de un motor de renderizado como WebKit. El límite lo pone nuestro manejo de HTML/CSS y el nivel de implementación de PyWebKit.

Cairo

Cairo tampoco tiene documentación ofical (o no la he encontrado aún) pero al menos hay unos cuantos tutoriales en la red que te dan luces acerca de su funcionamiento y sus métodos básicos. La principal ventaja de Cairo es que nos da la oportunidad de ser artistas sobre un widget xD, incluso creo que se puede usar OpenGL para acelerar el renderizado y aplicar efectos 3D... así que el límite lo pone nuestra imaginación. Pero como diría el abuelo de Peter Parker:

Un gran poder trae consigo una gran responsabilidad

Y esto es porque Cairo se utiliza en controles GTK que hacen las veces de lienzo; podemos imaginarlo como un control más de GTK pero que podemos dibujar a nuestro antojo. Esto trae una consecuencia, pensar en una aplicación desarrollada completamente con Cairo requiere una inversión de trabajo enorme (y quizás injustificada e innecesaria) pues Cairo como tal no tiene widgets, así que nos tocaría implementar desde cero cada control que queramos utilizar; entiéndase cajas de texto, etiquetas, botones y ni hablar de las listas y elementos con scroll... sería una verdadera pesadilla.

Ambas librerías tienen un enorme potencial, cada una en su ámbito y creo que la fórmula ganadora está en una buena combinación de ambas. Ni más ni menos.

Para la próxima entrega postearé las segundas pruebas con Cairo y las pruebas con DBus y el sistema de notificaciones de Ubuntu, NotifyOSD.

Referencias
[1] http://www.zetcode.com/tutorials/pygtktutorial/customwidget/
[2] http://zetcode.com/tutorials/cairographicstutorial/customgtkwidget/
[3] http://www.pygtk.org/articles/cairo-pygtk-widgets/cairo-pygtk-widgets.htm
[4] http://www.tortall.net/mu/wiki/CairoTutorial
[5] http://jackvalmadre.wordpress.com/2008/09/21/resizable-image-control/
[6] http://www.pygtk.org/articles/cairo-pygtk-widget-signals-es/cairo-pygtk-widget-signals.html
[7] http://trac.webkit.org/attachment/wiki/HackingGtk/webkit.api

2 comentarios:

Anónimo dijo...

Hola, quisiera saber como instalar los archivos descomprimidos de pywebkitgtk en windows, por favor?

Wil Alvarez dijo...

Hola,

Hasta donde pude investigar no hay un binding de pywebkitgtk para Windows así que creo que no será posible correr los archivos en esa plataforma.

Saludos