Nueva versión de GAG

A menos de 15 horas de irme de viaje a Bilbao (¡merecidas vacaciones, proclamo!) acabo de subir la versión 4.8 de GAG.

La verdad es que ha sido una sesión maratoniana para terminar el trabajo de casi tres semanas, durante las cuales hice algunas cosas que tenía que haber hecho hace mucho, mucho tiempo.

Para empezar he escrito una excelente documentación de instalación y uso en HTML (y con multitud de capturas de pantalla), por lo que ahora es mucho más fácil entender como se instala y configura GAG.

También actualicé la FAQ, añadiendo entre otras cosas que también se puede usar GRUB, así como la manera de recuperar el arranque de Linux si se instala GAG antes de meter LILO o GRUB en el superblock de la partición raiz, y algunos detalles más.

Otra sección de la documentación es la de detalles técnicos. Ahí explico el formato de los datos de configuración, así como el formato de los iconos, fuentes de letra, etc. La idea es que otros puedan crear iconos y fuentes de letra para GAG (por ejemplo, para añadir soporte realmente nativo a lenguas como el japonés o el griego).

Respecto a la configuración, la he cambiado de sitio y ahora está siempre al principio del segundo sector del disco duro. Esto significa que ya es posible escribir programas de configuración externos (para, por ejemplo, cambiar la configuración directamente desde un instalador de Linux).

Otra gran novedad es el instalador nativo desde Linux: un simple gag-install /dev/sda instalará GAG en la primera unidad (haciendo las comprobaciones pertinentes para evitar pisar una partición que empiece en donde no deba, claro). También se puede especificar desde la línea de comandos la lengua y el tipo de teclado a instalar.

Y ya por último corregí un par de detalles en los ficheros de lenguas Bable y Turco.

Ahora sólo queda que se me pase el miedo que siempre acompaña a cualquier lanzamiento de GAG: miedo a haber metido la gamba con cualquier tontería y destrozar los datos de medio planeta… O:-)

Trabajando con GtkTreeView en Python

Esta semana tuve que trabajar en serio con el widget GtkTreeView en Python y se me hizo completamente cuesta arriba hasta que, después de buscar en múltiples tutoriales y pelearme con el código, me vino una epifanía y entendí su complejo pero potente esquema de funcionamiento; así que, en base a la experiencia adquirida, voy a escribir un pequeño tutorial, acompañado de algo de código para ejemplificar.

En GtkTreeView tenemos una clara división entre los datos, la presentación y el motor. Esto es lo que hace que sea un poco lioso al principio, pero también le da una potencia y versatilidad sorprendente.

Lo primero que necesitamos es añadir los módulos gtk, pygtk, gtk.glade y gobject. El último es necesario para los tipos de datos que vamos a manejar.

A continuación tenemos el widget GtkTreeView. Este elemento es el que mostrará nuestra lista de datos. Obtenemos un puntero a dicho objeto mediante GLADE:

treeview = arbol_xml.get_widget("nuestro_gtktreeview")

Ahora vamos a definir los tipos de datos que contendrá cada fila. A ésto se le denomina modelo. Es importante entender que no todos los campos del modelo tienen por qué ser visibles. En este ejemplo vamos a reservar un entero para saber, cuando el usuario marque una opción, cual es la que está escogiendo. También habrá tres cadenas, que contendrán el texto que queremos mostrar en el TreeView y el color de una de ellas.

modelo = gtk.ListStore (gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)

Existen dos tipos de modelos: ListStore y TreeStore. Ambos se manejan prácticamente igual, salvo que el segundo permite tener elementos anidados en forma de arbol. La diferencia principal se da a la hora de añadir los datos en sí, como veremos luego.

El siguiente paso es asociar el modelo al TreeView, de manera que éste sepa qué datos almacenar en cada fila:

treeview.set_model(modelo)

Hasta aquí hemos definido los tipos de datos (mediante el modelo), y tenemos el motor (el TreeView), por lo que sólo nos falta la presentación. Para ello tenemos que usar dos clases más para definir cada una de las columnas que mostraremos:

render=gtk.CellRendererText() # renderer para la primera columna
columna = gtk.TreeViewColumn ("titulo", render, text=1, background=2) # primera columna de datos
treeview.append_column (columna)

render2=gtk.CellRendererText()
columna2 = gtk.TreeViewColumn ("otro titulo", render2, text=3) # segunda columna de datos
treeview.append_column(columna2)

CellRendererText es una clase encargada de mostrar un texto en una columna de un TreeView. En el ejemplo vemos que creamos dos de ellos, uno para cada columna que vamos a mostrar. Luego, creamos una TreeViewColumn que tendrá varios parámetros: el primero es el título que se mostrará en la parte superior del TreeView para dicha columna (si estamos mostrando los nombres de personas, el título podría ser nombre, por ejemplo), y el segundo es la referencia al tipo de CellRenderer que vamos a utilizar para esa columna (en este caso, como es texto, un CellRendererText, pero hay más tipos). El resto de parámetros depende del CellRenderer que estemos utilizando. En el caso del CellRendererText tenemos, entre otros, el parámetro text, que contendrá el texto que queremos mostrar, y el parámetro background, que contiene el color del texto en formato de cadena de texto.

Vemos que en la primera columna le asociamos el valor 1 para el texto y el 2 para el color de fondo, y para la segunda columna usamos el valor 3 para el texto. Estos valores se refieren a la variable correspondiente del modelo TreeStore que generamos unos pasos antes: la variable 0 es el entero, la 1 y la 2 son los dos strings que contendrán, respectivamente, el texto y el color de la primera columna, y la 3 el texto de la segunda columna.

Tras crear cada una de las columnas la añadimos a nuestro TreeView mediante el método append_column, con lo que nuestro TreeView estará listo para ser rellenado con los datos que deseemos. Esto lo hacemos de la siguiente manera:

iterador = modelo.insert(posicion) # el iterador es el objeto que contiene todos los valores de una fila concreta
modelo.set_value(iterador,0,0) # el segundo cero es el dato que metemos (un entero)
modelo.set_value(iterador,1,"Un texto")
modelo.set_value(iterador,2,"#00FFE0")
modelo.set_value(iterador,3,"Otro texto")

Estas llamadas se repetirán tantas veces como filas queramos insertar en el TreeView. La variable posicion contendrá el número de fila en la que se insertarán estos datos.

En el caso de utilizar un TreeStore en lugar de un ListStore, el método insert precisaría de un parámetro adicional, antes de la posición, para indicar qué elemento (referenciado mediante un TreeIter) es el padre. Si se pone None, se supondrá que cuelga del padre.

Con ésto ya tenemos nuestro TreeView listo, pero aún nos faltan dos cosas: borrarlo para meter nuevas filas, y saber qué fila es la que un usuario ha escogido. Lo primero es tan simple como llamar al método clear del modelo, con lo que podemos volver al último paso (con los set_values) para introducir las nuevas columnas. Para lo segundo sólo tenemos que hacer:

seleccion,iterador = treeview.get_selection().get_selected()

Si iterador es None, significa que no hay ninguna fila seleccionada. En caso contrario podemos utilizar

seleccion.get_value(iterador,VARIABLE)

para obtener el valor de la columna indicada por VARIABLE. Si usamos el valor 1 nos devolvería el texto de la primera columna; si usamos el valor 2, el color de fondo de la primera columna, y con el valor 3, el texto de la segunda columna. Con el valor 0 nos devolvería un entero, que hasta ahora no hemos utilizado para nada; pero si a dicho entero le asignamos valores consecutivos al crear las filas, nos dirá cual es el número de fila que escogió el usuario.

El silencio

Comentaba en mi anterior entrada «¿Termoqué?» la dificultad que estaba teniendo para encontrar un disipador adecuado para mi nuevo equipo. Y no es para menos: paso mucho tiempo delante de la pantalla y lo último que quiero es una turbina de avión destrozándome el oído.

Después de muchas pruebas y mediciones (de tamaño: la nueva caja tiene un travesaño que limita la altura del disipador) me decanté por un Zalman CNPS8000. Este disipador tiene un tamaño adecuado para entrar en la caja y es muy silencioso (a baja velocidad, claro). El único defecto es que la regulación de velocidad es manual. Afortunadamente mi nueva placa es una ASUS con tecnología Q-Fan. Este sistema, propio de ASUS, permite a la BIOS controlar la velocidad de giro de cualquier ventilador (no tiene por qué tener cable de control de velocidad) en base a la temperatura de la CPU, exactamente igual que hacía mi viejo Artic Cooling. El resultado es justo el que quiero: trabajar con mi ordenador disfrutando en todo momento del máximo silencio posible, y sin tener que preocuparme de si el procesador se puede estar sobrecalentando.

Nueva versión de DeVeDe

DeVeDe 2.9 está en la calle por fin. La gran novedad es que ahora se puede ejecutar también en Windows, aunque hacen falta unas cuantas dependencias: además de Mplayer, Mencoder, DVDAuthor, VCDImager y MKisoFS para windows es necesario Python, PyGTK, GTK, Pyglade y LibGlade para windows. Ahí es nada. Por supuesto tengo que agradecer a Peter Gill el trabajo realizado, pues ha sido todo obra suya.

Otro cambio ha sido el detectar cuando se produce un error en MKISOFS. En efecto, aunque el código estaba ahí, faltaba poner a TRUE una variable, por lo que si MKISOFS fallaba por cualquier razón, DeVeDe devolvía un mensaje de éxito en lugar de error.

También añadí un aviso sobre el uso de particiones FAT32/VFAT para el directorio de destino: los DVDs de vídeo tienen una estructura de directorios con sus nombres en mayúsculas. Desgraciadamente las particiones FAT32/VFAT no distinguen entre ambas y MKISOFS se hace un lío y falla miserablemente (cinco céntimos de euro a quien adivine qué relación tiene este bug con el anterior 🙂

Por último, y aparte de un fallito tonto en el Drag&Drop, también corregí un problema con ciertos vídeos provenientes de capturadoras de vídeo. Estos vídeos provocan una avalancha de líneas ID cuando se usa la opción -identify de MPlayer, necesaria para conocer la longitud, resolución, etc. del vídeo. En el código original esperaba a que terminase la ejecución de MPlayer y sólo entonces leía el buffer de STDOUT para ver los valores ID, con lo que si había demasiados de éstos el buffer se llenaba y no seguía la ejecución. Ahora voy leyendo las líneas a la vez que se ejecuta, por lo que ya no falla.

Un remake de UFO: Enemy Unknown

Cuando era joven me gustaba jugar al UFO: Enemy Unknown. En él, tu misión consistía en defender a la tierra del ataque de diversas hordas alien, creando bases, investigando nuevas tecnologías, capturando naves y fabricando nuevas armas. El juego tenía dos modos: uno de pura estrategia, en el que se controlan las distintas bases y se decide qué temas se investigan, qué dispositivos se fabrican, qué nuevos soldados o científicos se contratan, etc. También es este modo se detectan los ovnis y otras amenazas y se pueden lanzar ataques contra ellos.

El otro modo, al que se pasa cada vez que se derriba un ovni o se llega a una ciudad atacada o base alienígena, está basado en un sistema de combate por turnos. En él debes localizar a todos los aliens que hay y matarlos o capturarlos. Si lo consigues, te haces con toda la tecnología que hayan dejado, la cual se puede investigar (para replicarla) o vender (para ganar más dinero).

El juego era endiabladamente bueno, y gracias a sus excelentes gráficos e increíble música conseguía sumergirte de lleno en la historia, así como hacerte dar más de un respingo cuando, en alguna fase de combate por turnos, aparecía de pronto uno de los aliens y atacaba a tus hombres.

Todo ésto viene a cuento porque hace tres días me reencontré UFO: Alien Invasion, un remake del UFO original basado en el motor gráfico del Quake 2. Ya lo había visto hace un año, pero de aquella sólo estaba lista la parte de las misiones de combate. Ahora, sin embargo, toda la mecánica de estrategia está lista también, así como la música, y sólo falta pulir algunos detalles, añadir la UFOpaedia (u OVNIpedia) e implementar el hilo conductor de la historia original. Estuve jugando estos días y el resultado no puede ser mejor.

En la página hay versiones para Windows y para Linux, estas últimas en forma de paquete .DEB (para Debian testing e inestable). En Ubuntu, en principio no se puede instalar porque el paquete depende de la libasound2 versión 1.0.12 o superior, mientras que Ubuntu dispone de la versión 1.0.11. Sin embargo, basta con bajarse la libasound de Debian (añadiendo también, si es necesaria, la libasound2-dev de Debian) e instalarlas con DPKG.

Python, MySQL, UTF-8 y la madre que los parió

Errar es humano, pero para liar las cosas de verdad se necesita un ordenador… que trabaje con UTF-8.

Para los que no lo conozcan, UTF-8 es una codificación multibyte para la tabla de caracteres UNICODE. Un caracter UNICODE está representado por un número entre 0 y 1,114,111, y UTF-8 es un sistema para representar dicho número mediante una secuencia de bytes. Su característica más atractiva es que los primeros 128 caracteres se corresponden con los de la tabla ASCII y, además, ocupan un solo byte, lo que significa que un texto en ASCII estándar (de 7 bits) es, a la vez, un texto UTF-8; sin embargo, tan pronto comenzamos a usar otros caracteres más raros (como nuestra querida letra Ñ, o nuestras vocales acentuadas), ocuparemos dos, tres o hasta cuatro bytes por caracter.

En efecto, hagamos una pequeña prueba: abramos nuestro interprete de Python y escribamos:

Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
[GCC 4.1.2 (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> cadena = u"Papá"
>>> print len(cadena)
4

Hasta aquí nada raro: hemos definido una cadena de tipo UTF-8 (la letra u antes de las comillas sirve para eso) y le hemos pedido al interprete que nos diga su longitud, que, efectivamente, es cuatro. Pero si cambiamos un poco el experimento llegan las cosas «raras»:

>>> cadena2 = "Papá"
>>> print len(cadena2)
5

Ahora la longitud es cinco, lo que no supone ningún misterio si nos damos cuenta de que la cadena la estamos definiendo como cadena normal, no cadena UTF-8, y por tanto nos devuelve el número de bytes que ocupa; y como la letra á (con tilde) necesita dos bytes (pues mi sistema utiliza UTF-8), el misterio queda resuelto.

Por desgracia las cosas se complican cuando uno tiene que usar Python para transmitir y recibir cadenas en UTF-8, byte a byte, por la red; cadenas de las que tomará trozos y compondrá con ellos peticiones para MySQL. Entonces es cuando llega la pesadilla… ¿Y por qué? Pues porque por defecto MySQL espera y entrega sus datos en Latin-1, y también los almacena en dicho formato; pero si yo meto una cadena UTF-8 sin decirle que es UTF-8, el la considera una secuencia el Latin-1, con lo que los datos que me entrega llegan bien, y los que yo le paso también… si no fuese porque el módulo de Python para trabajar con MySQL sabe que éste espera los datos en Latin-1 en intenta convertirlos, soltando a veces una excepción, o metiendo basura en la base de datos el resto.

Por todo ello debemos ser muy cuidadosos a la hora de trabajar con MySQL y Python en UTF-8, y aquí van las pistas que he ido recolectando después de varios días de sufrimientos varios:

Convertir las tablas a UTF-8 Es el primer paso: si creaste tus tablas en formato Latin-1, lo mejor es pasarlas a UTF-8 para evitarse problemas. Para ello usaremos

mysqldump --opt --password=miclave --user=miuser mibasededatos > archivo.sql

A continuación editaremos el código y cambiaremos la palabra latin1 por utf8, con lo que al recuperar los datos, las tablas se crearán en el nuevo formato. Ahora sólo queda volver a grabarlas con

mysql -u miuser -p miclave mibasededatos < archivo.sql

y listo, ya tenemos nuestras tablas en UTF-8.

Acceder a MySQL mediante UTF-8 Esta parte es sencilla también, pero la documentación es escasa y sólo la encontré después de bucear por muchas páginas de Internet y juntar resultados. Tenemos que usar la línea en python:

mibase=MySQLdb.connect(host="mihost", user="usuario", passwd="clave", db="basedatos", charset="utf8", init_command="set names utf8")

(atención a la negrita). Justo a continuación tenemos que añadir:

mibase.names="utf8"

y con ésto ya tendremos garantizado el acceso a la base de datos mediante UTF-8.

En principio con esto ya tendríamos todo listo para poder trabajar cómodamente con MySQL, Python y UTF-8, pero aún falta algo, y es que la cadena que contenga las peticiones no basta con que contenga secuencias UTF-8, sino que tiene que ser un string de tipo UTF-8. Sin embargo, puede ocurrir que tengamos las secuencias UTF-8 almacenadas en cadenas normales. En estos casos, el método encode(«utf-8») nos sacará del apuro. También nos será util si necesitamos mezclar cadenas normales con los resultados que nos devuelva, en UTF-8, la base de datos.

Panel desplazable

Una de las limitaciones del .NET Compact Framework es que, al contrario que el Framework completo, no dispone de un panel con barras de desplazamiento. El único control que se le parece es el Panel (así, a secas), que consiste en un contenedor de tamaño variable en el que se pueden insertar tantos widgets como se desee, en las coordenadas que se quiera.

Sin embargo, para el trabajo que estoy haciendo necesitaba un panel con barras desplazables, así que busqué en la red y encontré un truco: hacer un panel del tamaño que se necesite, colocar dentro los elementos deseados, añadir dos barras de desplazamiento fuera del panel y hacer que éstas cambien las coordenadas X e Y de éste, de manera que se mueva por toda la pantalla. Esta solución es aceptable cuando sólo se desea un único panel que ocupe toda la pantalla, pero en mi caso no servía porque yo necesitaba poder colocar más elementos fijos, con lo que el panel sólo ocuparía una parte.

La solución, un tanto enrevesada, consiste en crear una nueva clase que contenga dos widgets Panel, uno dentro del otro: el externo, que definirá la zona de visión y será del tamaño que desee que se muestre en pantalla, y el interno, que será más grande y contendrá todos los widgets que necesitemos. Para conseguir el desplazamiento basta con añadir barras de desplazamiento.

En mi caso sólo necesitaba desplazamiento vertical, así que añadí una sola a la derecha. El evento scrolled ajusta la propiedad Top del panel interno, usando el negativo de su valor (al aumentar el valor de la barra hay que desplazar el panel negativamente). El parámetro Maximum de la barra toma el valor de la altura del panel interno menos uno, y el parámetro LargeChange la altura del panel externo. De esta manera el tamaño de la barra se ajusta automáticamente a la relación de tamaños de ambos paneles.

El código de ejemplo del panel desplazable se puede bajar aquí.

Electrónica analógica

Hace unos días me pasaron un enlace a un libro titulado Designing analog chips. Este libro, disponible en formato PDF, fue escrito por Hans Camenzind. Para los que no le conozcan, diré que es el diseñador de uno de los más famosos cirtuitos integrados analógicos (situado justo después del amplificador operacional): el 555.

El libro comienza con una introducción teoría básica de los semiconductores y su funcionamiento en los principales componentes analógicos (diodo, transistor bipolar, FET…). A continuación entra de lleno en el tema de la simulación dedicándole un capítulo entero, y ya sin más dilación comienza a explicar los principales montajes analógicos: espejos de corriente, amplificadores operacionales, temporizadores…

Estoy empezando a leerlo con calma, pero después de echarle un ojo por encima debo reconocer que tiene una pinta impresionante. Lo más interesante es que se centra sobre todo en el aspecto práctico, y además mediante un lenguage muy asequible que lo hace todavía más interesante.

Creo que hace unos años habría matado por tener este libro, cuando me tuve que enfrentar a Dispositivos Electrónicos (1 y 2) y a Electrónica Analógica.

¿Termoqué?

Llevo una temporada pensando en cambiarme de ordenador. A fin de cuentas, un Durón a 1’3GHz es algo que ya empieza a estar obsoleto. Por otro lado debo reconocer que en el fondo es un capricho, pues salvo porque los vídeos de YouTube van algo justitos, el resto me va perfectamente; por eso mi intención era no gastarme mucho dinero.

Decidí tirar por un Sempron 3000+, pues las últimas versiones tienen soporte de 64 bits y, al estar fabricados con tecnología de 90nm, disipan muy poco, y como soy un fanático del silencio es algo que me viene genial, pues eso significa menor velocidad en el ventilador del disipador. Por supuesto eso implicaba un ventilador termorregulado, similar al modelo de Artic Cooling que tengo ahora, el cual ajusta su velocidad automáticamente en función de la temperatura del procesador.

¡Cual sería mi sorpresa al encontrarme con que no hay disipadores termorregulados para AMD! Bueno, miento: supuestamente, los disipadores que vienen de fábrica con el propio procesador sí son termorregulados, de manera que al combinarlo con la tecnología Cool’n’Quiet se consigue una reducción efectiva del ruído. Pero los disipadores de terceros, como Zalman o Termaltake, parecen dividirse solamente en dos categorías: velocidad regulada manualmente (son disipadores muy poco más grandes de lo normal y que incluyen un potenciómetro para que ajustes tú mismo la velocidad) o «de bajo ruido» (disipadores gigantescos con ventiladores que funcionan a baja velocidad, pero siempre a la misma). No tienen ninguno termorregulado (o, al menos, yo no he encontrado ninguno).

Así pues, sólo tengo tres opciones, y todas tienen inconvenientes, en concreto:

  • El disipador «de fábrica»: no se hasta qué punto será silencioso, sobre todo si tenemos en cuenta que algunas placas de Asus incorporan una tecnología que monitoriza la temperatura del procesador y baja aún más la velocidad del ventilador (bajándole el voltaje).
  • El disipador de ajuste manual: ni de coña. En pleno siglo XXI no pienso ajustar yo mismo la velocidad cuando es algo que se puede hacer automáticamente y por muy poco dinero. Además, un elemento mecánico como un potenciómetro es mucho menos fiable que un componente 100% electrónico, como es un sensor de estado sólido.
  • El disipador «de bajo ruido»: no sirve cualquier placa ni cualquier caja, pues pueden tener diámetros de hasta 15 cm y podrían tropezar con algún condensador. Además suelen pesar más de 600 gramos, cuando las especificaciones de AMD indican que el disipador no debería superar los 450 gramos; esto puede ser un problema al trasladar el equipo en un coche, por ejemplo.

Probablemente lo que haga sea probar el disipador «de fábrica», y si veo que no me convence ir a por uno «de bajo ruido».

Los Pentium 4, por su parte, han resuelto ésto de otra manera: según leí en Tom’s Hardware hace tiempo (lo siento, no tengo el enlace) las últimas versiones llevan ventiladores con cuatro hilos en lugar de tres, usándose el cuarto para controlar la velocidad de giro desde la propia placa (supongo que basándose en la temperatura medida por el sensor interno del procesador). De todas maneras, un Core Duo se sale de presupuesto, y un Prescott se calienta demasiado, así que será AMD.

Primeras diferencias del Compact Framework

Actualizado. Empiezan a aparecer las primeras diferencias y limitaciones del Compact Framework .NET. La primera es que el widget TrackBar no incluye el evento Scroll, que se dispara cuando el usuario mueve el desplazador. El único disponible es ValueChanged, que se dispara tanto cuando es el usuario quien mueve el desplazador como cuando lo hace el programa al cambiar el valor del TrackBar.

La segunda diferencia está en el widget Label, en concreto en la propiedad TextAlign, la cual sólo admite TopLeft, TopCenter y TopRight, haciendo caso omiso de MiddleXXXX y BottomXXXX.

Mañana más.

Actualización a 22/10. Otra diferencia más: la clase Stack, que implementa una pila, no dispone del método Clear.