Mostrando entradas con la etiqueta pygtk. Mostrar todas las entradas
Mostrando entradas con la etiqueta pygtk. Mostrar todas las entradas

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.

PyGTK Colores por defecto

Me he encontrado con un pequeño problema al intentar modificar los colores de los campos de MCM ya que es bastante complicado encontrar los colores por defecto utilizados por el tema. El problema se acentuaba al utilizar temas oscuros ya que se modificaban los colores de la aplicación para pintar los campos con fondo blanco.

Un poco de trial & error y esto es lo obtenido:

class DefaultColorSettings(object):
    def __init__(self):
        def_settings = gtk.settings_get_default()
        color_scheme = def_settings.get_property("gtk-color-scheme").strip()
        color_scheme = color_scheme.split("\n")

        settings = {}
        for property in color_scheme:
            # property is of the type key: value
            property = property.split(": ")
            settings[property[0]] = property[1]

        self.tooltip_fg_color = gtk.gdk.color_parse(settings["tooltip_fg_color"])
        self.selected_bg_color = gtk.gdk.color_parse(settings["selected_bg_color"])
        self.tooltip_bg_color = gtk.gdk.color_parse(settings["tooltip_bg_color"])
        self.base_color = gtk.gdk.color_parse(settings["base_color"])
        self.fg_color = gtk.gdk.color_parse(settings["fg_color"])
        self.text_color = gtk.gdk.color_parse(settings["text_color"])
        self.selected_fg_color = gtk.gdk.color_parse(settings["selected_fg_color"])
        self.bg_color = gtk.gdk.color_parse(settings["bg_color"]) 

Básicamente, todos los componentes que se configuran con los temas de GNOME llegan a nuestras aplicaciones a través de gtk.gdk.Screen. Con "settings_get_default" obtenemos distintos aspectos de la configuración del tema: colores, tamaños de iconos, sonidos, etc.

Para lo que nos incumbe ahora, obtenemos "gtk-color-scheme" el cual es un str con los distintos colores. En MCM mi solución fue la de crear una clase DefaultColorSettings y luego utilizar DefaultColorSettings.base_color para redibujar los campos a su estado original cada vez que cambiaba los colores de los mismos.

Glade3 y GtkBuilder

Glade es una herramienta que permite realizar RAD (Rapid Application Development) con una interfaz muy sencilla de utilizar para aplicaciones GTK+. Glade genera unos ficheros .glade que son xml y pueden ser leidos por las muchas implementaciones de GTK+ para Java, C, C++, Python, Perl y un largo etcétera.

Con Karmic Koala viene la nueva versión 3.6.7 de Glade que incluye un muy buen soporte para GtkBuilder.

GtkBuilder y Libglade

GtkBuilder va a reemplazar a Libglade en el futuro no muy lejano y GtkBuilder forma parte integral de GTK, por lo que no hace falta añadir más dependencias a nuestros proyectos. Además, al utilizar GtkBuilder, podemos crear nuevos objetos y acciones que antes se tenian que crear en el código. Por ejemplo, los ListStore para añadir a los Views facilitan mucho la creación de este tipo de widgets.

La razón por la cual se cambia de Libglade a GtkBuilder es que se ha convertido en algo muy común desarrollar la UI de forma dinámica en Glade por lo que no tenía sentido que el propio core de GTK no lo soportara. Aquí es donde entra GtkBuilder para además, añadir nuevas funcionalidades. La verdad es que no hay nada malo con Libglade, sólo que GtkBuilder extiende sobre Libglade y se añade directamente a GTK, haciendo el funcionamiento más sencillo y rápido.

PyGTK

Para utilizar GtkBuilder en Python, seguimos las mismas pautas que con libglade, es decir, cargar el fichero, conectar las señales y obtener los widgets para actuar sobre estos.

import gtk
x = gtk.Builder()
x.add_from_file("/tmp/test.glade")
y = x.get_object("window1")
y.show()

Cabe recalcar que MCM está desarrollada con Libglade y que poco a poco iré migrando a GtkBuilder viendo las posibilidades.