Webcomic - Deseo

miércoles, diciembre 30, 2009

El comic de hoy va dedicado a mi pana Renata Franco, sé que se sentirá muy identificada xDDDD. Enjoy it.

Click para agrandar


Webcomic - Jamón Endiablado

martes, diciembre 29, 2009

Empezando con una faceta que tenía tieeeempo con ganas de cristalizar pero que había estado procrastinando, finalmente se ha materializado... mi primer webcomic xD. Ahí les va

Click para agrandar



Si te gustó, comentalo... sino también xD

Codificar y decodificar entidades HTML en Python

jueves, diciembre 24, 2009

Aunque es una tarea vulgar y silvestre, en Python no existe algo "obvio" para codificar y decodificar entidades HTML. Me gustaría encontrar cosas como htmllib.decode() o htmllib.encode(), pero no existen. He visto en la web muchos (entiéndase MUCHOS) códigos y hasta expresiones regulares para completar esta tarea, pero son soluciones rebuscadas y que requieren invertir mucho tiempo.

Lo mío es a la Python-way: sencillo, elegante y limpio, y saxutils hace el trabajo a la perfección.

Basta con importar la librería y usar las funciones escape (para codificar) y unescape (para decodificar):

import xml.sax.saxutils as saxutils
print saxutils.escape("Codificando entidades HTML: & > <")

print saxutils.unescape("Decodificando entidades HTML: &gt; &amp; &lt;")


Esto imprimirá en pantalla algo como:
Codificando entidades HTML: &amp; &gt; &lt;
Decodificando entidades HTML: > & <


Simple, pero a veces no lo vemos a la primera. Espero que sea de utilidad este consejo

Tooltip en PyGTK para un IconView (insertado en un ScrolledWindow)

jueves, diciembre 03, 2009

Luego de pasar toda una tarde reventándome la cabeza y tomando psicotrópicos leyendo la documentación oficial de PyGTK y haciendo pruebas, logré conseguir mi objetivo: Mostrar un miserable tooltip sensible en un IconView usando la nueva API para Tooltips de PyGTK >= 2.12.

La forma antigua y tradicional de hacer un Tooltip era más rudimentaria; incluso habían formas que involucraban conectarse al evento 'motion-notify-event' del widget, obtener la celda en cuestión en base a la posición del ratón y crear la ventana a pie para luego mostrarla. Sin mencionar que era necesario manejar el tiempo de aparición del tooltip usando un timer y la desaparición del tooltip actual antes de mostrar uno nuevo. Nada trivial en realidad, pero así lo implementé en varias ocasiones.

La nueva API reduce todo el proceso a 3 simples pasos:
  • Establecer la propiedad has-tooltip del widget a True para que GTK pueda monitorear los eventos de movimiento relacionados con el tooltip
  • Conectarse a la señal 'query-tooltip' del widget. Esta señal será emitida cuando el tooltip deba ser mostrado. Uno de los argumentos pasados a la señal es un objeto Tooltip correspondiente al widget. Solo queda de nuestra parte modificarlo
  • Retornar True desde el callback que maneja la señal 'query-tooltip' para mostrar el tooltip o False para que no se muestre

Sencillo, ¿eh?. La API es realmente buena y facilita un montón la creación de un Tooltip, de hecho es tan sencillo que parace increible xD. Pero... se les pasó un pequeño detalle... las ventanas de desplazamiento (ScrolledWindow).

Cuando se usa un widget que es o será más grande que el espacio disponible para dibujarlo (TreeView, TextView, IconView, etc) se debe emplear un ventana con scroll (ScrolledWindow) e insertar el widget dentro de ella. La ScrolledWindow se encargará de manejar eso de los scrollbars, el viewport, etc, etc, etc (si no entiendes de que estoy hablando te recomiendo leer este apartado del tutorial de PyGTK). Muy bonito todo... hasta ahora.

El problema aparece cuando queremos mostrar un tooltip sensible para un IconView/TreeView (es decir, que el tooltip mostrará información diferente para cada elemento del contenedor) pues la famosa señal 'query-tooltip' dentro de sus argumentos pasa la posición RELATIVA del cursor mediante x e y:

def callback(widget, x, y, keyboard_mode, tooltip, user_param1, ...)

¿Qué significa la 'posición relativa del cursor'? Pues, en un widget sin scroll es la posición exácta del cursor sobre ese widget, pero en un widget con scroll ocurre lo siguiente:



La señal 'query-tooltip' nos devuelve lo que en la imagen se ve como X e Y, es decir la posición del elemento relativa al ScrolledWindow. Bien.

Ahora, para saber a cual elemento del IconView estamos haciendo referencia hace falta conocer las coordenadas del elemento (relativas al IconView) y ubicar en el modelo el registro correspondiente.

¿Cómo demonios vamos a obtener el elemento del IconView si la señal nos devuelve unas coordenadas que no nos sirven? ¿Cómo rayos obtenemos los valores de Z y W para referenciar al objeto correctamente?

Pues he aquí la solución, y es más fácil de lo que parece. El código se explica con los comentarios.

# -*- coding: utf-8 -*-

import gtk

class PruebaTooltip:
    def __init__(self):
        # Creamos nuestro modelo con 2 campos, uno para la imagen y otro para 
        # la descripción
        self.model = gtk.ListStore(gtk.gdk.Pixbuf, str)
        
        # Creamos el IconView
        self.iconview = gtk.IconView(self.model)
        # Le decimos que la imagen la sacará de la primera columna
        self.iconview.set_pixbuf_column(0)
        # Habilitamos el nuevo soporte de la API para tooltips
        self.iconview.set_has_tooltip(True)
        self.iconview.set_orientation(gtk.ORIENTATION_VERTICAL)
        self.iconview.set_selection_mode(gtk.SELECTION_SINGLE)
        self.iconview.set_column_spacing(10)
        self.iconview.set_columns(6)
        self.iconview.set_item_width(50)
        # Nos conectamos a la señal 'query-tooltip'
        self.iconview.connect("query-tooltip", self.show_tooltip)
        
        # Creamos el ScrolledWindow y le insertamos el IconView
        self.scrollwin = gtk.ScrolledWindow()
        self.scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.scrollwin.set_shadow_type(gtk.SHADOW_IN)
        self.scrollwin.add(self.iconview)
        
        vbox = gtk.VBox(False, 5)
        vbox.pack_start(self.scrollwin, True, True, 0)
        
        # Creamos una ventana simple y le agregamos la caja que contiene la
        # ScrolledWindow y todo lo demás
        self.window = gtk.Window()
        self.window.set_title('Tooltip de in IconView como debe ser')
        self.window.set_default_size(300, 300)
        self.window.set_position(gtk.WIN_POS_CENTER)
        self.window.connect('destroy', gtk.main_quit)
        self.window.add(vbox)
        self.window.show_all()
        
        # Creamos unos cuantos elementos dentro del modelo (esto es solo con 
        # fines ilustrativos, pues en teoría debería llenarse desde otra parte)
        for i in range(30):
            label = 'Tooltip del Elemento %i' % (i + 1)
            pix = self.window.render_icon(gtk.STOCK_ABOUT, gtk.ICON_SIZE_DIALOG)
            self.model.append([pix, label])
        del pix
    
    # Esta es la parte ruda xD
    # Nuestro callback para la señal 'query-tooltip'
    def show_tooltip(self, widget, x, y, keyboard_mode, tooltip):
        # Calculamos el offset (w y x), es decir la diferencia entre el origen 
        # del ScrolledWindow y el IconView. Para eso usamos el valor de cada uno
        # de los scrollbar. Simple ¿no?. Pues después de los psicotrópicos lo
        # ví muy sencillo :P
        w = self.scrollwin.get_property('hadjustment').value
        z = self.scrollwin.get_property('vadjustment').value
        
        # Ubicamos la ruta del elemento según la posición 'exácta' del cursor
        # sobre el IconView
        path = widget.get_path_at_pos(int(x + w), int(y + z))
        if path is None: return False
        
        model = widget.get_model()
        
        # Obtenemos el elemento mediante el modelo y la ruta
        iter = model.get_iter(path)
        
        # Obtenemos la imagen y la descripción guardada en el modelo
        pix = model.get_value(iter, 0)
        desc = model.get_value(iter, 1)
        # Establecemos la imagen del tooltip
        tooltip.set_icon(pix)
        # Establecemos el texto del tooltip (con soporte para marcado pango :D)
        tooltip.set_markup(desc)
        # Borramos la imagen para no dejar basura regada
        del pix
        
        # Devolvemos True para que se muestre el Tooltip y seamos felices weee!
        return True
        
if __name__ == "__main__":
    PruebaTooltip()
    gtk.main()

Al final veremos algo así:



Pues sí, eso es todo... una simple suma. Lo que me reventó el coco fue saber de donde diablos sacar los valores de W y Z (los offset). Espero que hayan podido leer este post antes de pensar en implementar los Tooltips a la Old-Fashion Way e incluso antes de pensar en el suicidio xD.

Cambio y fuera