Botones en un TreeView

Esto ha sido algo complicado de conseguir, pero después de algo de trial&error, he logrado añadir unos botones a un TreeView en MCM. En Internet es algo complicado encontrar información sobre esto y de hecho, no encontré nada por lo que ha tocado investigar distintas aproximaciones y al final estoy bastante contento con el resultado.

Así que, veamos como añadir un botón con imagen a un TreeView en PyGTK. Dado que no existe un CellRenderer para los botones -¿grave fallo?- hay que buscar otras soluciones como utilizar un CellRedererPixbuf o lo que es lo mismo, una imagen. En este caso voy a utilizar una del stock.

El primer método que nos interesa es para obtener la selección actual del TreeView para saber contra cual registro debemos actuar:

def get_selection(self):
    cursor = self.view.get_selection()
    model = self.view.get_model()
    (model, iter) = cursor.get_selected()
    if iter == None:
        return None
    alias = model.get_value(iter, 0)
    return alias

Es fácil ver lo que se hace aquí que es obtener a partir de la selección del TreeView los campos del registro. En este caso (MCM) el primer campo contiene el alias de la conexión el cual es único (podría haber sido un UID numérico) y es esto lo que el método va a devolver. Se puede escoger la columna deseada cambiando el valor de ese cero.

A continuación, añadimos a nuestro ListStore o TreeStore, una columna para contener imágenes, creamos la imagen que deseamos utilizar y la añadimos al ListStore junto con los demás campos del registro:

def connections_model(self):
    """Creates a ListStore with the Connections data"""
    store = gtk.ListStore(str, str, str, str, str, str, str, str, str, str, gtk.gdk.Pixbuf)
    img = self.dialog.render_icon(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)

    for cx in self.connections.values():
        cx_list = cx.to_list()
        cx_list.append(img)
        store.append(cx_list)
    return store

El ultimo argumento del ListStore es gtk.gdk.Pixbuf lo cual indica al nuevo objeto que debe contener objetos de éste tipo. En MCM cada objeto Connection, tiene un método llamado to_list() que devuelve las propiedades del objeto como una lista, añadimos la imagen a dicha lista y ésta al ListStore.

Con esto ya obtendríamos un TreeView con una imagen de stock al final de cada registro, pero ahora tenemos que hacer que dicha imagen haga algo al hacer doble-click en ella.

Para lograr esto -y aquí empieza lo complicado- vamos a utilizar el evento button-press-event del TreeView, por lo que debemos crear un método para el callback de éste evento y conectarlo con el TreeView al crearlo:

# Configure Tree Properties
view.set_headers_clickable(True)
view.connect('button-press-event', self.cell_click_event, store )

El callback de button-press-event debe recibir al widget, el evento y vamos además a añadir nuestro store ya que -aunque éste no sea el caso- nos puede ser útil:

def cell_click_event(self, widget, event, store):
    path = widget.get_path_at_pos(int(event.x), int(event.y))
    #row = int(path[0][0])
    column = path[1]
    col_title = column.get_title()
    if col_title == 'Delete' and event.type == gtk.gdk._2BUTTON_PRESS:
        alias = self.get_selection()
        dlg = UtilityDialogs()
        response = dlg.show_question_dialog(constants.deleting_connection_warning % alias, constants.are_you_sure)
        if response == gtk.RESPONSE_OK:
            del self.connections[alias]
            self.redraw_tree()

El elemento más útil aquí es el objeto event del tipo gtk.gdk.Event con el cual vamos a obtener algo como esto:

<gtk.gdk.Event at 0x1ed9b20: GDK_BUTTON_PRESS x=1000.00, y=31.00, button=1>

Eso dos campos (x,y) son las coordenadas donde se ha hecho el click y con esto podemos obtener un path con el cual obtener la columna y el registro sobre el que se está actuando. Dicho path contiene la siguiente información:

((3,), <gtk.TreeViewColumn object at 0x2684af0 (GtkTreeViewColumn at 0x27d09b0)>, 27, 13)

El primer valor de la tupla path es una tupla en la que el primer valor es el indice del registro sobre el que nos encontramos. Está comentado ya que gracias al metodo "get_selection()" no nos hace falta, pero sabemos que hemos actuado sobre el cuarto registro del TreeView. Lo que nos interesa de aquí es el TreeViewColumn del que vamos a obtener su titulo y si se cumplen las condiciones: doble-click sobre la columna con titulo Delete, actuamos, en este caso mostrando un dialogo de confirmación, borrando el registro y dibujando el TreeView.