Acabo de lanzar la versión 2.1.0 de Cronopete, que añade una pequeña modificación en el efecto zoom para intentar que vaya mejor en ordenadores lentos.
Para entender el problema, lo primero es explicar como realicé el efecto de zoom. Al principio quería usar clutter, porque se supone que permite hacer cosas como que una ventana se mueva en tres dimensiones y demás, que es justo lo que pretendía. Por desgracia, para poder meter dentro de un actor de clutter un elemento de GTK (en este caso el navegador de ficheros), tenía que trabajar con GTK3, porque en GTK2 no está soportado.
Probé a usar ventanas auténticas, pero el cambio de tamaño para hacer el efecto era lentísimo, por lo que no resultaba. Después de muchas pruebas, me lié la manta a la cabeza e intenté hacerlo de una forma radicalmente diferente. La solución que encontré era tan «simple» como usar la función gdk_pixbuf_get_from_drawable para hacer una captura de la ventana, y usar el pixmap resultante para hacer el efecto de scroll mediante Cairo. Al ser una imagen estática en lugar de un conjunto de widgets, el redimensionado debería ser rápido, incluso haciéndolo por software.
Pero las cosas, por desgracia, no son tan sencillas en la práctica. Cada vez que hacía una captura, me salía un pixmap negro, o con la ventana tal y como estaba antes de cualquier cambio que quisiese reflejar. El motivo es que el redibujado de una ventana en GTK es asíncrono: yo puedo dar la orden de mostrar algo, de cambiar un texto, o de añadir más elementos a una lista, pero hasta que no retorne al bucle principal de GTK, no se llevará a cabo esa acción.
La primera solución que encontré fue aprovechar el temporizador que uso para las animaciones para sacar la captura: así, tras dar la orden de cambiar de carpeta, por ejemplo, marcaba un flag que indicaba que había que hacer una nueva captura, activaba el temporizador y salía del callback, volviendo al bucle principal. En ese momento GTK repintaba toda la ventana, que quedaba lista para ser capturada en cuanto venciese el temporizador.
Esto funcionaba en mi ordenador sin problemas, así que publiqué la versión 2.0.0; pero más tarde, al tener la oportunidad de probarlo en un equipo lento, me encontré con que no iba bien, pues capturaba antes de tiempo. Era obvio que GTK le daba más prioridad a los temporizadores que al redibujado de ventanas, por lo que, en ese equipo, no le daba tiempo a repintar todo.
Pregunté en los foros si GTK emitía alguna señal cuando terminaba de redibujar una ventana, pero nadie respondió, así que decidí probar yo mismo todas las señales que emite un widget, a ver cual me podía ser útil. Probé con damage-event, composited-changed, expose-event, realize, screen-changed y show, antes de encontrar la que parecía la solución a mis problemas: map-event. Pero aunque en el código de ejemplo que hice funcionaba, cuando la probé en el código de cronópete también falló.
Hasta que, al final, revisando otras funciones, encontré la solución en las funciones idle. Estas se ejecutan cuando no queda ninguna tarea por hacer en el bucle principal, así que la solución fue tan sencilla como añadir una función idle que haga la captura: ésta no será llamada hasta que la cola esté vacía, lo que será cuando se hayan procesado todos los eventos de repintado de widgets dentro de la ventana que queremos capturar, o sea, cuando ésta ya esté completamente lista.