Archivo por días: 20 junio, 2007

Version 4.9 de GAG

Hace un par de días tuve que sacar una nueva versión de GAG, la 4.9. La razón fue que, al cambiar toda la parte de configuración al principio del código, cometí un pequeño fallo: la posición de memoria en donde se almacena el tipo de teclado también quedó desplazada, pero el código de GAG seguía leyendo de la antigua. Para colmo, el instalador metía el valor del teclado en la posición antigua, con lo que el resultado era que, al arrancar GAG desde el instalador, el teclado funcionaba perfectamente, pero en cuanto se instalaba en disco duro y se reiniciaba, pasaba a usar un teclado aleatorio. En mi caso, por puro azar fue siempre QWERTY, por lo que no advertí el error; pero a varios usuarios les tocó el teclado ruso, con lo que se encontrarón con que, una vez instalado, no era posible modificar la configuración (sí podían arrancar los sistemas operativos previamente configurados, porque los números están en las mismas teclas en todas las lenguas).

Trabajando con PyCairo y GTK

En la nueva versión de DeVeDe (que espero sacar en un par de días) he tenido que trabajar con Cairo para poder generar menús para el disco. La información la saqué de este tutorial sobre PyCairo (en inglés), en donde viene mucha información interesante. Sin embargo, faltaba un detalle importante: como mostrar el dibujo hecho en cairo en una ventana GTK.

En efecto, esto era fundamental porque el usuario tiene que poder ver el resultado final del menú antes de generar el disco (para, por ejemplo, asegurarse de que los títulos no son demasiado largos), y para eso necesito pintarlo en una ventana y no sólo volcarlo a un PNG en disco. Después de mucho buscar y de hacer varias pruebas encontré por fin como hacerlo.

Lo primero es crear un widget GtkDrawingArea, en el que mostraremos el dibujo generado con Cairo, y crear un callback para el evento expose-event. Este evento se produce cada vez que hay que redibujar la ventana (bien porque estaba tapada por otra y la hemos destapado, bien porque estaba minimizada y la hemos maximizado…). En dicho callback será donde hagamos el pintado. Para ello, lo primero que tenemos que hacer es conseguir un contexto de Cairo para dicho widget. Así, si el widget se llama MiDibujo, y arbol es el objeto Glade con nuestras ventanas, haríamos:

# asignamos el callback a nuestra funcion de repintado
w=arbol.get_widget("MiDibujo")
w.connect("expose-event",repintado)

# y definimos el callback. Tiene dos argumentos
def repintado(dwidget,evento):
    cr=dwidget.window.cairo_create()
    [funciones y métodos de Cairo para pintar]

Y ya podremos usar ese contexto para pintar y trazar todo lo que queramos.

Sin embargo, si el dibujo es estático no tiene sentido que lo tracemos una y otra vez cada vez que se produzca un evento, porque desperdiciamos tiempo de proceso. Lo mejor es tenerlo pintado desde el principio y limitarnos a copiar el bitmapa final en el callback de repintado. Para ello sólo tenemos que crear primero una superficie de Cairo y obtener un contexto para ella:

sf=cairo.ImageSurface(cairo.FORMAT_ARGB32,ResX,Resy)
cr1=cairo.Context(sf)

Ahora pintaremos nuestro dibujo en dicha superficie con ese contexto, y una vez que hayamos terminado, almacenaremos el objeto superficie de alguna manera que nos permita acceder a él desde nuestro callback (una variable global es la solución más rápida y menos elegante). Allí sólo tenemos que usar los métodos set_source_surface y paint para transferir la superficie a nuestro widget GtkDrawingArea, ahorrando mucho tiempo de proceso.

def repintado(dwidget,evento):
    cr=dwidget.window.cairo_create()
    # sf es la que pintamos en el paso anterior
    cr.set_source_surface(sf)

    # transferimos la superficie con el dibujo al Widget
    cr.paint()

Este truco nos permite también pegar cualquier imagen en la superficie en la que estamos trabajando, e incluso redimensionarla en el proceso. Un ejemplo: queremos crear una imagen de tamaño X,Y, usando como fondo un PNG de tamaño cualquiera, pero de manera que éste se expanda o encoja para ocupar exactamente la superficie de nuestra imagen final. Sólo tendríamos que hacer:

sf_base= cairo.ImageSurface.create_from_png("imagen.png")
sf_final=cairo.ImageSurface(cairo.FORMAT_ARGB32,X,Y)

# creamos un contexto para pintar
cr_final=cairo.Context(sf_final)

# cogemos el ancho y alto de la imagen.
# Importante: en coma flotante
xbase=float(sf_base.get_width())
ybase=float(sf_base.get_height())

# aplicamos el escalado para que la imagen de fondo
# pase a tener el mismo tamaño que la imagen final
cr_final.scale(float(X)/xbase,float(Y)/ybase)

# y estampamos la imagen de origen en la superficie final
cr_final.set_source_surface(sf_base)
cr_final.paint()

# podemos usar el método identity para
#restaurar la escala a 1:1
cr.identity_matrix()