Hogar Mapa Indice Busqueda Noticias Arca Enlaces Sobre LF
[Top bar]
[Bottom bar]
Este documento está disponible en los siguientes idiomas: English  Castellano  Deutsch  Francais  Turkce  

convert to palmConvert to GutenPalm
or to PalmDoc

Hilaire Fernandes
por Hilaire Fernandes

Sobre el autor:

Hilaire Fernandes es el vice-presidente de OFSET, una organización dedicada a promover el desarrollo de software educacional libre para el escritorio Gnome. También es el autor de Dr.Geo, un programa interactivo de geometría que ha ganado algunos premios, y se encuentra en estos momentos trabajando en Dr.Genius, otro programa educativo sobre Matemáticas para el escritorio Gnome.


Contenidos:

Desarrolando aplicaciones Gnome con Python (Parte I)

Gnome

Resumen:

Esta serie de artículos está escrita pensada en principiantes a GNU/Linux y a la programación en Gnome. El lenguaje de programación escogido, Python, evita la sobrecarga habitual en lenguajes compilados como C. Es necesario cierto conocimiento previo de la programación en Python.



 

Herramientas necesarias

Para poder ejecutar el programa que se describe en este artículo es necesario disponer, por lo menos, de:

Para instalar Pyhton-Gnome y LibGlade a partir de las fuentes:
./configure
make
make install
será suficiente. Encontrarás una explicación más detallada en
http://www.linuxgazette.com/issue38/pollman.html ].

Debes verificar que la variable de entorno PYTHONPATH apunta hacia el lugar donde están instalados los módulos de Python-Gnome. Típicamente son /usr/local/lib/python1.5/site-packages o /usr/lib/python1.5/site-packages/. En estos lugares encontrarás todos los enlaces necesarios para Gnome y LibGlade, como puede ser el módulo libglade.py. Puedes fijar PYTHONPATH por el simple método de añadirla en tu .bash_profile:
PYTHONPATH=/usr/local/lib/python1.5/site-packages
export PYTHONPATH
No olvides que debes arrancar tu código Python desde un terminal para que esta variable esté fijada.


 

Glade, LibGlade & y la interacción con Python

Glade es un desarrollador de interfaces creado por Damon Chaplin. Permite construir de forma gráfica e interactiva interfaces de usuario gráficos para Gnome/Gtk. Desde Glade, el interfaz generado se puede archivar en formato xml o directamente como código C para ser incluido en un programa C convencional. Glade también permite definir los nombres de los handler (funciones) que se asociarán a cada uno de los eventos del interfaz. Por ejemplo, la función (name) que se llamará cuando se seleccione un determinado elemento del menú.

LibGlade es una librería escrita por James Henstridge para generar sobre la marcha un interfaz almacenado en un fighero xml de Glade. La aplicación necesita tan sólo el nombre del fichero xml, generalmente terminado con la extensión .glade, para generar el interfaz a partir de él. James Henstridge también ha escrito el enlance LibGlade Pyhton (entre otros) que está presente en el paquete Gnome-Python. LibGlade también permite auto-conexión de los handler definidos en el fichero .glade con funciones definidas en el código Python.

El gráfico adjunto muestra este mecanismo de forma general. Para entender cómo está implementado el enlace a Python, a veces es necesario echar un vistazo a los módulos de Gtk, Gnome, LibGlade y Python que se encuentran en PYTHONPATH para compararlos con la documentación de desarrollo en C sobre Gtk/Gnome.

 

Un ejemplo llamado couleur

Como primera aproximación a la programación bajo Gnome-Python, proponemos un juego en color, donde los niños tienen que reconocer piezas de un mismo color. Este ejemplo está directamente orientado a los gráficos y sirve para introducir varias características y elementos de programación como el Canvas Gnome y la ventana de aplicaciones Gnome. Las reglas del juego son sumamente simples: el tablero está formado por 16 piezas diferentes (círculos, estrellas y cuadrados) de diferentes colores. Estas 16 piezas se dividen en 8 pares de idéntico color. El juego acaba cuando se seleccionan de forma sucesiva estos 8 pares. Tal vez quieras echar un vistazo al código que se encuentra al final del artículo para tener una idea general, y luego retomar el artículo desde aquí.

 

Creando un interfaz con Glade

Los widgets

Tras arrancar Glade, nos aparecerán dos ventanas. Una de ellas, llamada Palette (paleta), contiene las herramientas para widgets. Con ella podemos seleccionar la categoría del widget entre GTK+ Basic, GTK+ Additional y Gnome. Si no te aparece el tipo Gnome, Glade se habrá compilado sin soporte para Gnome. Consulta el configure del paquete con las fuentes de Glade, configure --help explica las opciones de configuración.

La otra ventana contiene una lista de los widgets creados.

Lo primero que haremos con Glade será crear una ventana de aplicación Gnome. Este widget es una ventana con una barra de menú y una barra de herramientas. Ambas gestionadas por el handler dock. Debajo de la ventana de aplicación Gnome encontramos una barra de estatus. Tras crear la ventana de aplicación Gnome, podemos abrir el diálogo Widget Tree (que encontrarás en el menú de vistas bajo Glade). Ahora puedes mirar qué hay dentro de este widget.

Añadimos un canvas al área principal del widget de aplicación Gnome. Desde el diálogo de propiedades, fijamos sus coordenadas máximas a 400, y su altura y anchura máximas a 400.

Y ya podemos crear el diálogo Gnome About. Podemos cambiar su contenido con el diálogo propiedades en la hoja de widgets.

Todos estos widgets son de la categoría Gnome mostrada en la Paleta.

Ahora borramos los botones y entradas del menú que no usamos. En la barra de herramientas, quitamos los botones de Abrir y Salvar. Seguidamente, editamos la barra de menú (click derecho sobre ella, y seleccionamos edit menu) y borramos todos los menús y entradas de menú excepto File->New, File->Exit, Setting->Preferences y Help->About.

Fijando los widget y los nombres de handler

Para poder usarlos en Python, debemos aplicar ciertos nombres a los handler de los widgets:

Gnome Application Window:
colorApp
Gnome About Dialog:
about

Los handler son funciones especiales a las que se llama cuando ocurre algún evento particular. Esto quiere decir que definiremos las funciones en Python usando estos nombres, como veremos más adelante. Por ejemplo, cuando el usuario pulsa sobre el icono de "Nuevo", queremos llamar a una función que resetee el juego. Para hacer esto desde Glade, primero necesitamos seleccionar el widget, y luego ajustar su hoja de señales con el diálogo propiedades.

En nuestro ejemplo la señal es clicked y el handler es el nombre de la función. La siguiente tabla muestra todas las señales y handler usados:

En el diálogo about:

Nombre del widget Señal Handler
about clicked gtk_widget_destroy
about close gtk_widget_destroy
about destroy gtk_widget_destroy

El handler gtk_widget_destroy está predefinido en GTK. Simplemente, destruye el widget.

En la ventana colorApp lo que sucede es que Glade selecciona de forma automática las señales y handler para los elementos del menú. Puedes comprobar sus nombres, que se incluyen en la siguiente tabla. Observarás que el botón "Nuevo" y el elemento de menú "Nuevo" comparte un mismo handler, ya que tienen propósitos similares.

Nombre del widget Señal Handler
button1 (botón "Nuevo" de la barra de herramientas) clicked on_new_activate
new activate on_new_activate
colorApp destroy on_exit1_activate
exit1 activate on_exit1_activate
about1 activate on_about_activate

El toque final

Invocamos las Opciones de Proyecto desde el botón de Opciones en la barra de herramientas de Glade. EN la hoja General, ajustamos las entradas del proyecto como se describe debajo:

El fichero que representa a los widgets es color.glade. Cambiamos el path a nuestro propio directorio raíz.

Ahora salvamos el fichero desde el menú de Archivos. No compilamos el código fuente, ya que no usaremos esa característica.
Ahora, debemos cerrar Glade, y podemos empezar a trabajar con Python.

 

El código Python

Hemos incluído el código completo al final del artículo. Se debería almacenar en la misma localización que el fichero color.glade.

 

Inclusión de los módulos necesarios

from math import cos, sin, pi
from whrandom import randint
from gtk import *
from gnome.ui import *
from GDK import *
from libglade import *

Con los módulos math y whrandom, incluímos las funciones cos, sin, randint y la constante pi, no específicos de Gnome. Los módulos específicos d Gnome son gtk, GDK y gnome.ui. En C, al incluir gnome.h tendremos todas las cabeceras de Gnome. En Python, has de averiguar en qué modulo se encuentra el enlace a la función Gnome que quieres usar. Puedes, desde un terminal de texto con el shell, buscar el módulo que contenga la cadena "canvas" con el comando:
cd /usr/local/lib/python1.5/site-packages/gnome
grep canvas *.py
asumiendo que los enlaces Gnome se encuentran en el directorio /usr/local/lib/python1.5/site-packages.

 

Cargando el interfaz con Libglade

En este ejempleo usamos el Canvas Gnome para manipular las piezas (en este caso estrellas, círculos y cuadrados). Un canvas es un depósito para objetos gráficos (elipses, puntos, líneas, rectángulos , ...), objetos de texto e incluso widgets. De hecho, un canvas puede contener a su vez varios grupos de canvas. En estos últimos colocaremos nuestros elementos del canvas (nuestras piezas). Por defecto, un canvas contiene un grupo de canvas, llamado el grupo raíz, que es el que usaremos para colocar nuestras piezas.

En primer lugar definimos algunas variables globales:

La primera función que invocaremos (initColor) construye los widgets a partir del fichero color.glade y auto-conecta los handlers los widgets creados.
def initColor ():
    global rootGroup, canvas
    wTree = GladeXML ("color.glade",
                      "colorApp")
    dic = {"on_about_activate": on_about_activate,
           "on_exit1_activate": mainquit,
           "on_new_activate":on_new_activate}
    wTree.signal_autoconnect (dic)
    canvas = wTree.get_widget ("canvas")
    rootGroup = canvas.root ()

La construcción de los widgets se hace con la función GladeXML, aunque hay que ajustar la ruta hacial el fichero color.glade. Esta función construye y muestra la ventana de aplicación Gnome colorApp que definimos con Glade. Devuelve un objeto (en realidad una clase) con multitud de métodos útiles.

Seguidamente conectamos los handlers que hemos definido en Python (mas sobre ello luego) a los widgets definidos en el fichero color.glade. Para ello, debemos construir un diccionario que contenga entradas para los nombres de handler definidos en el fichero color.glade: on_about_activate, on_exit1_activate y on_new_activate. Los valores así obtenidos serán los nombres de las funciones definidas en Python.
Finalmente el método signal_autoconnect hará el resto del trabajo por nosotros.

Lo último que haremos será recuperar la referencia al canvas que se ha creado con la llamada a GladeXML (un objeto GnomeCanvas en Python) y el grupo de canvas raiz (el objeto GnomeCanvasGroup).

Consejos útiles

No existe ningún manual de referencia que cubra los enlaces de Gnome con Python. Sin embargo, existen un montón de documentación sobre programación en C para Gnome disponible en el sitio web de Gnome. Echar un vistazo a esta documentación puede ser útil, pero también necesitarás echar un vistazo al enlace de Gnome con Python para poder explotarlo:

El enlace está localizado en /usr/local/lib/python1.5/site-packages/gnome/ o /usr/lib/python1.5/site-packages/gnome/ y al hojear el enlace podemos observar varias cosas

  1. en el enlace libglade.py:
  2. En el enlacegnome/ui.py:

Para cada uso de Gnome con Python podemos hacer lo mismo con la documentación relacionada. Dejaremos al lector interesado bucear en la documentación de Gnome para aprender más sobre estas funciones.

 

Definiendo los handlers

Hay tres handlers para auto-conectar con el GUI. Son on_about_activate, on_new_activate y mainquit. El último de ellos es, de hecho, la función Python que para y sale de Python.

def on_about_activate(obj):
    "display the about dialog"
    about = GladeXML ("color.glade", "about").get_widget ("about")
    about.show ()

Este handler abre el diálogo about. Primero definimos una referencia al diálogo about (que construyó LibGlade a través de un objeto GladeXML). Recordemos que GladeXML es un objeto Python con un método (entre muchos otros) llamado get_widget. Este método devuelve un objeto GtkWidget que contiene al método show.

Pistas

Echemos un vistazo al objeto GtkWidget del enlace gtk.py. Veremos que este objeto tiene un método show. El handler anterior se podría haber escrito como:
GladeXML("color.glade","about").get_widget("about").show().

def on_new_activate (obj):
    global rootGroup, colorShape
    for item in colorShape:
        item.destroy ()
    del colorShape[0:]
    buildGameArea (rootGroup)

Este handler rehace el área de juego. En primer lugar, destruye las piezas existentes. Las piezas son objetos GnomeCanvasItem derivados de objetos GtkObject, que contienen un médoto destroy. Acto seguido, crea una nueva área de juego.

 

El GnomeCanvasItem

Definición de la forma

La función buildGameArea coordina la creación del área de juego en el grupo GnomeCanvasGroup. Las piezas (GnomeCanvasItem) se construyen con llamadas a la función buildShape. Las piezas pueden ser círculos, cuadrados o estrellas.

La creación de la pieza se hace con el siguiente código, según el tipo:
item = group.add ("ellipse", x1 = x - r, y1 = y - r,
                  x2 = x + r, y2 = y + r, fill_color = color,
                  outline_color = "black", width_units = 2.5)

[...]

item = group.add ("rect", x1 = x - a, y1 = y - a,
                  x2 = x + a, y2 = y + a, fill_color = color,
                  outline_color = "black", width_units = 2.5)

[...]

item = group.add ("polygon", points = pts, fill_color = color,
                  outline_color = "black", width_units = 2.5)

La variable group contiene la referencia al objeto GnomeCanvasGroup. Si miramos en el enlace ui.py, veremos que posee un método add. Su primer argumento, tp es una cadena que indica qué tipo de elemento hay que añadir. Los siguientes argumentos son pares de nombres y valores, que se contrastan con un diccionario. Para obtener una lista de posibles nombres, es necesario consultar la definición de los objetos GnomeCanvasRect, GnomeCanvasEllipse y GnomeCanvasPolygon dentro de ui.py.

Las definiciones de ellipse y rectangle son bastante similares, con dos pares de coordenadas que definen los puntos extremos de su caja (top-left (superior-izquierdo) y bottom-right (inferior-derecho)). El origen del canvas está colocado por defecto en su esquina superior-izquierda. El polygon espera como valor la palabra points, con una lista de pares de coordenadas que definen los puntos del polígono. El significado de los restantes argumentos es bastante fácil de entender.

Fijando eventos a las piezas

Ahor conectaremos un evento a cada una de las piezas que creemos. Esto se hace al final de la función buildShape
item.connect (évent', shapeEvent)
colorShape.append (item)

Nos limitamos a usar el método connect del objetoGtkObject, que es el originador del objeto GnomeCanvasItem. Su primer argumento es la señal. Como un objeto GnomeCanvasItem tiene una única señal event para cubrir todos los eventos posibles, nos limitamos a fijarlo a event. El segundo argumento es el nombre del handler que hemos escrito, en este caso shapeEvent. Opcionalmente, se le puede pasar un tercer argumento, pero nosotros no lo necesitaremos. ¡Y eso es todo!

 

Los eventos de las piezas

Y ahora la creación de los handler para las piezas:
def shapeEvent (item, event):
    global selectedItem, itemToSelect, colorShape
    if event.type == ENTER_NOTIFY and selectedItem != item:
        #highligh outline
        item.set(outline_color = 'white')
    elif event.type == LEAVE_NOTIFY and selectedItem != item:
        #unlight outline
        item.set(outline_color = 'black')
    elif event.type == BUTTON_PRESS:
        #select the item
        if not selectedItem:
            item.set (outline_color = 'white')
            selectedItem = item
        elif item['fill_color_gdk'] == selectedItem['fill_color_gdk'] \
             and item != selectedItem:
            #destroy both item
            item.destroy ()
            selectedItem.destroy ()
            colorShape.remove (item)
            colorShape.remove (selectedItem)
            selectedItem, itemToSelect = None, itemToSelect - 1
            if itemToSelect == 0:
                buildGameArea (rootGroup)
    return 1

Cuando se invoca este handler, la variable item debe contener una referencia a la pieza en la que se ha producido el evento, y event contendrá el evento. De entre todos los eventos contemplados en GdkEvent sólo nos interesan tres tipos:

Por último, el handler siempre devuelve TRUE (1). Esto quiere decir que la señal del evento no ha sido propagada a ningún otro elemento. No necesitamos hacer esto porque nuestras piezas nunca llegan a solaparse.

 

Unas palabras finales

He dejado de lado todo el código Python que no está relacionado con Gnome, aunque no debería ser difícil entenderlo. Mi principal objetivo en este simple tutorial es mostrar cómo puede uno deducir por sí mismo cómo funcionan las cosas: mirando en los enlaces para Python de Gnome, o en las cabeceras C de Gnome, o leyendo la documentación de Gnome para programación en C. Y por supuesto, mostrar cómo de sencillos y potentes son el canvas Gnome y Glade/LibGlade. A partir de ahora, hay muchas cosas que se pueden hacer con simples extensiones del código. (Las fuentes del artículo pueden encontrarse aquí)

 

Apéndice: Las fuentes completas

#!/usr/bin/python
# Couleur - Teo Serie
# Copyright Hilaire Fernandes 2000
# Release under the terms of the GPL licence version 2
# You can get a copy of the license at http://www.gnu.org
#
# Select shapes with same color
#
from math import cos, sin, pi
from whrandom import randint
from gtk import *
from gnome.ui import *
from GDK import *
from libglade import *

width, itemToSelect = 400, 8
selectedItem = rootGroup = canvas = None
# to keep trace of the canvas item
colorShape =[];

def on_about_activate(obj):
    "display the about dialog"
    about = GladeXML ("color.glade", "about").get_widget ("about")
    about.show ()

def on_new_activate (obj):
    global rootGroup, colorShape
    for item in colorShape:
        item.destroy ()
    del colorShape[0:]
    buildGameArea (rootGroup)

def shapeEvent (item, event):
    global selectedItem, itemToSelect, colorShape
    if event.type == ENTER_NOTIFY and selectedItem != item:
        #highligh outline
        item.set(outline_color = 'white')
    elif event.type == LEAVE_NOTIFY and selectedItem != item:
        #unlight outline
        item.set(outline_color = 'black')
    elif event.type == BUTTON_PRESS:
        #select the item
        if not selectedItem:
            item.set (outline_color = 'white')
            selectedItem = item
        elif item['fill_color_gdk'] == selectedItem['fill_color_gdk'] \
             and item != selectedItem:
            #destroy both item
            item.destroy ()
            selectedItem.destroy ()
            colorShape.remove (item)
            colorShape.remove (selectedItem)
            selectedItem, itemToSelect = None, itemToSelect - 1
            if itemToSelect == 0:
                buildGameArea (rootGroup)
    return 1

def buildShape (group, number, type, color):
    "build a shape of 'type' and 'color'"
    global colorShape
    w = width / 4
    x, y, r = (number % 4) * w + w / 2, (number / 4) * w + w / 2, w / 2 - 2
    if type == 'circle':
        item = buildCircle (group, x, y, r, color)
    elif type == 'squarre':
        item = buildSquare (group, x, y, r, color)
    elif type == 'star':
        item = buildStar (group, x, y, r, 0.4, randint (3, 15), color)
    elif type == 'star2':
        item = buildStar (group, x, y, r, 0.6, randint (3, 15), color)
    item.connect (évent', shapeEvent)
    colorShape.append (item)

def buildCircle (group, x, y, r, color):
    item = group.add ("ellipse", x1 = x - r, y1 = y - r,
                      x2 = x + r, y2 = y + r, fill_color = color,
                      outline_color = "black", width_units = 2.5)
    return item

def buildSquare (group, x, y, a, color):
    item = group.add ("rect", x1 = x - a, y1 = y - a,
                      x2 = x + a, y2 = y + a, fill_color = color,
                      outline_color = "black", width_units = 2.5)
    return item

def buildStar (group, x, y, r, k, n, color):
    "k: factor to get the internal radius"
    "n: number of branch"
    angleCenter = 2 * pi / n
    pts = []
    for i in range (n):
        #external points of the star
        pts.append (x + r * cos (i * angleCenter))
        pts.append (y + r * sin (i * angleCenter))
        #internal points of the star
        pts.append (x + r * k * cos (i * angleCenter + angleCenter / 2))
        pts.append (y + r * k * sin (i * angleCenter + angleCenter / 2))
    pts.append (pts[0])
    pts.append (pts[1])
    item = group.add ("polygon", points = pts, fill_color = color,
                      outline_color = "black", width_units = 2.5)
    return item

def getEmptyCell (l, n):
    "get the n-th non null element of l"
    length, i = len (l), 0
    while i < length:
        if l[i] == 0:
            n = n - 1
        if n < 0:
            return i
        i = i + 1
    return i

def buildGameArea (group):
    global itemToSelect, selectedItem
    itemColor = ['red', 'yellow', 'green', 'brown', 'blue', 'magenta',
                 'darkgreen', 'bisque1']
    itemShape = ['circle', 'squarre', 'star', 'star2']
    emptyCell = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    itemToSelect, i, selectedItem = 8, 15, None
    for color in itemColor:
        # two items of same color
        n = 2
        while n > 0:
            cellRandom = randint (0, i)
            cellNumber = getEmptyCell (emptyCell, cellRandom)
            emptyCell[cellNumber] = 1
            buildShape (group, cellNumber, itemShape[randint (0, 3)], color)
            i, n = i - 1, n - 1

def initColor ():
    global rootGroup, canvas
    wTree = GladeXML ("color.glade",
                      "colorApp")
    dic = {"on_about_activate": on_about_activate,
           "on_exit1_activate": mainquit,
           "on_new_activate":on_new_activate}
    wTree.signal_autoconnect (dic)
    canvas = wTree.get_widget ("canvas")
    rootGroup = canvas.root ()

initColor ()
buildGameArea (rootGroup)
mainloop ()
 

Formulario de "talkback" para este artículo

Cada artículo tiene su propia página de "talkback". A través de esa página puedes enviar un comentario o consultar los comentarios de otros lectores
 Ir a la página de "talkback" 

Contactar con el equipo de LinuFocus
© Hilaire Fernandes, FDL
LinuxFocus.org

Pinchar aquí para informar de algún problema o enviar comentarios a LinuxFocus
Información sobre la traducción:
fr -> -- Hilaire Fernandes
en -> es Javier Palacios

2001-03-30, generated by lfparser version 2.9