original in fr Hilaire Fernandes
en to es Javier Palacios
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.
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.
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 |
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 |
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.
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í.
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:
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
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.
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.
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 |
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
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.
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.
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 ('event', 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!
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:
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í)
#!/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 ('event', 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 () |