Página siguiente Página anterior Índice general

2. Comenzando

Por supuesto lo primero que hay que hacer es descargar las fuentes de GTK e instalarlas. La última versión siempre se puede obtener de ftp.gtk.org (en el directorio /pub/gtk). En http://www.gtk.org/ hay más información sobre GTK.

Para configurar GTK hay que usar GNU autoconf. Una vez descomprimido se puede obtener las opciones usando ./configure --help. El código de GTK además contiene las fuentes completas de todos los ejemplos usados en este manual, así como los makefiles para compilarlos.

Para comenzar nuestra introducción a GTK vamos a empezar con el programa más sencillo posible. Con él vamos a crear una ventana de 200x200 pixels que sólo se puede destruir desde el shell.

#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
    GtkWidget *window;
    
    gtk_init (&argc, &argv);
    
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_show (window);
    
    gtk_main ();
    
    return 0;
}
Todo programa que use GTK debe llamar a gtk/gtk.h donde se declaran todas las variables, funciones, estructuras etc. que serán usadas en el programa.

La siguiente línea:

gtk_init (&argc, &argv);

Llama a la función gtk_init (gint *argc, gchar *** argv) responsable de `arrancar' la librería y de establecer algunos parámetros (como son los colores y los visuales por defecto), llama a gdk_init (gint *argc, gchar *** argv) que inicializa la biblioteca para que puede utilizarse, establece los controladores de las señales y comprueba los argumentos pasados a la aplicación desde la línea de comandos, buscando alguno de los siguientes:

En el caso de que encuentre alguno lo quita de la lista, dejando todo aquello que no reconozca para que el programa lo utilice o lo ignore. Así se consigue crear un conjunto de argumentos que son comunes a todas las aplicaciones basadas en GTK.

Las dos líneas de código siguientes crean y muestran una ventana.

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_show (window);

El argumento GTK_WINDOW_TOPLEVEL especifica que queremos que el gestor de ventanas decore y sitúe la ventana. En lugar de crear una ventana de tamaño 0 x 0 toda ventana sin hijos por defecto es de 200 x 200, con lo que se consigue que pueda ser manipulada.

La función gtk_widget_show() le comunica a GTK que hemos acabado de especificar los atributos del widget, y que por tanto puede mostrarlo.

La última línea comienza el proceso del bucle principal de GTK.

gtk_main ();

Otra llamada que siempre está presente en cualquier aplicación es gtk_main(). Cuando el control llega a ella GTK se queda dormida esperando a que suceda algún tipo de evento de las X (como puede ser pulsar un botón), que pase el tiempo necesario para que el usuario haga algo, o que se produzcan notificaciones de IO de archivos. En nuestro caso concreto todos los eventos serán ignorados.

2.1 Programa Hello World en GTK

El siguiente ejemplo es un programa con un widget (un botón). Simplemente es la versión de GTK del clásico hello world.

/* comienzo del ejemplo helloworld */
#include <gtk/gtk.h>

/* Ésta es una llamada de respuesta (callback). Sus argumentos
   son ignorados por en este ejemplo */
void hello (GtkWidget *widget, gpointer data)
{
    g_print ("Hello World\n");
}

gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
    g_print ("delete event occured\n");

    /* si se devuelve FALSE al administrador de llamadas "delete_event" GTK emitirá     
     * la señal de destrucción "destroy". Esto es útil para diálogos pop up del     
     * tipo: ¿Seguro que desea salir?*/
    /* Cambiando TRUE por FALSE la ventana será destruida con "delete_event"*/      

    return (TRUE);
}

/* otra respuesta */
void destroy (GtkWidget *widget, gpointer data)
{
    gtk_main_quit ();
}

int main (int argc, char *argv[])
{

    /* GtkWidget es el tipo de almacenamiento usado para los widgets */
    GtkWidget *window;
    GtkWidget *button;

    /* En cualquier aplicación hay que realizar la siguiente llamada. 
     * Los argumentos son tomados de la línea de comandos y devueltos 
     * a la aplicación. */

    gtk_init (&argc, &argv);
    
    /* creamos una ventana nueva */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    /* Cuando la ventana recibe la señal "delete_event" (emitida por el gestor       
     * de ventanas, normalmente mediante la opción 'close', o en la barra del        
     * título) hacemos que llame a la función delete_event() tal y como ya hemos     
     * visto. Los datos pasados a la función de respuesta son NULL e ignorados. */

    gtk_signal_connect (GTK_OBJECT (window), "delete_event",
                        GTK_SIGNAL_FUNC (delete_event), NULL);

    /* Aquí conectamos el evento "destroy" con el administrador de señales. El      
     * evento se produce cuando llamamos a gtk_widget_destroy() desde la ventana      
     * o si devolvemos 'FALSE' en la respuesta "delete_event". */

    gtk_signal_connect (GTK_OBJECT (window), "destroy",
                        GTK_SIGNAL_FUNC (destroy), NULL);
    
    /* establecemos el ancho del borde de la ventana. */

    gtk_container_border_width (GTK_CONTAINER (window), 10);
    
    /* creamos un botón nuevo con la  etiqueta "Hello World" */

    button = gtk_button_new_with_label ("Hello World");
    
    /* Cuando el botón recibe la señal "clicked" llama a la función hello() pasándole 
     * NULL como argumento. (La función ya ha sido definida arriba). */

    gtk_signal_connect (GTK_OBJECT (button), "clicked",
                        GTK_SIGNAL_FUNC (hello), NULL);
    
    /* Esto hará que la ventana sea destruida llamando a gtk_widget_destroy(window) 
     * cuando se produzca "clicked". Una vez mas la señal de destrucción puede
     * provenir del gestor de ventanas o de aquí. */ 
 
    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                               GTK_SIGNAL_FUNC (gtk_widget_destroy),
                               GTK_OBJECT (window));
    
    /* Ahora empaquetamos el botón en la ventana (usamos un gtk container ). */

    gtk_container_add (GTK_CONTAINER (window), button);
    
    /* El último paso es representar el nuevo widget... */

    gtk_widget_show (button);
    
    /* y la ventana */

    gtk_widget_show (window);
    
    /* Todas las aplicaciones basadas en GTK deben tener una llamada  gtk_main()
     * Ya que el control termina justo aquí y debe esperar a que suceda algún evento */  

    gtk_main ();
    
    return 0;
}
/* final del ejemplo*/

2.2 Compilando Hello World

Para compilar el ejemplo hay que usar:

gcc -Wall -g helloworld.c -o hello_world `gtk-config --cflags` \
    `gtk-config --libs`

Usamos el programa gtk-config, que ya viene (y se instala) con la biblioteca. Es muy útil porque `conoce' que opciones son necesarias para compilar programas que usen gtk. gtk-config --cflags dará una lista con los directorios donde el compilador debe buscar ficheros ``include''. A su vez gtk-config --libs nos permite saber las librerías que el compilador intentará enlazar y dónde buscarlas.

Hay que destacar que las comillas simples en la orden de compilación son absolutamente necesarias.

Las librerías que normalmente son enlazadas son:

2.3 Teoría de señales y respuestas

Antes de profundizar en hello world vamos a discutir las señales y las respuestas. GTK es un toolkit (conjunto de herramientas) gestionadas mediante eventos. Esto quiere decir que GTK ``duerme'' en gtk_main hasta que se recibe un evento, momento en el cual el control es transferido a la función adecuada.

El control se transfiere mediante ``señales''. Cuando sucede un evento, como por ejemplo la pulsación de un botón, se ``emitirá'' la señal apropiada por el widget pulsado. Así es como GTK proporciona la mayor parte de su utilidad. Hay un conjunto de señales que todos los widgets heredan, como por ejemplo ``destroy'' y hay señales que son específicas de cada widget, como por ejemplo la señal ``toggled'' de un botón de selección (botón toggle).

Para que un botón haga algo crearemos un controlador que se encarga de recoger las señales y llamar a la función apropiada. Esto se hace usando una función como:

gint gtk_signal_connect( GtkObject     *object,
                         gchar         *name,
                         GtkSignalFunc  func,
                         gpointer       func_data );

Donde el primer argumento es el widget que emite la señal, el segundo el nombre de la señal que queremos `cazar', el tercero es la función a la que queremos que se llame cuando se `cace' la señal y el cuarto los datos que queremos pasarle a esta función.

La función especificada en el tercer argumento se denomina ``función de respuesta'' y debe tener la forma siguiente:

void callback_func( GtkWidget *widget,
                    gpointer   callback_data );

Donde el primer argumento será un puntero al widget que emitió la señal, y el segundo un puntero a los datos pasados a la función tal y como hemos visto en el último argumento a gtk_signal_connect().

Conviene destacar que la declaración de la función de respuesta debe servir sólo como guía general, ya que algunas señales específicas pueden generar diferentes parámetros de llamada. Por ejemplo, la señal de GtkCList "select_row" proporciona los parámetros fila y columna.

Otra llamada usada en el ejemplo del hello world es:

gint gtk_signal_connect_object( GtkObject     *object,
                                gchar         *name,
                                GtkSignalFunc  func,
                                GtkObject     *slot_object );

gtk_signal_connect_object() es idéntica a gtk_signal_connect() excepto en que la función de llamada sólo usa un argumento, un puntero a un objeto GTK. Por tanto cuando usemos esta función para conectar señales, la función de respuesta debe ser de la forma:

void callback_func( GtkObject *object );

Donde, por regla general, el objeto es un widget. Sin embargo no es normal establecer una respuesta para gtk_signal_connect_object. En lugar de ello llamamos a una función de GTK que acepte un widget o un objeto como un argumento, tal y como se vio en el ejemplo hello world.

¿Para qué sirve tener dos funciones para conectar señales? Simplemente para permitir que las funciones de respuesta puedan tener un número diferente de argumentos. Muchas funciones de GTK sólo aceptan un puntero a un GtkWidget como argumento, por lo que tendrá que usar gtk_signal_connect_object() con estas funciones, mientras que probablemente tenga que suministrarle información adicional a sus funciones.

2.4 Eventos

Además del mecanismo de señales descrito arriba existe otro conjunto de eventos que reflejan como las X manejan los eventos. Se pueden asignar funciones de respuesta a estos eventos. Los eventos son:

Para conectar una función de respuesta a alguno de los eventos anteriores debe usar la función gtk_signal_connect, tal y como se descrivió anteriormente, utilizando en el parámetro name uno de los nombres de los eventos que se acaban de mencionar. La función de respuesta para los eventos tiene un forma ligeramente diferente de la que tiene para las señales:

void callback_func( GtkWidget *widget,
                    GdkEvent  *event,
                    gpointer   callback_data );

GdkEvent es una estructura union cuyo tipo depende de cual de los eventos anteriores haya ocurrido. Para que podamos decir que evento se ha lanzado cada una de las posibles alternativas posee un parámetro type que refleja cual es el evento en cuestión. Los otros componentes de la estructura dependerán del tipo de evento. Algunos valores posibles son:

  GDK_NOTHING
  GDK_DELETE
  GDK_DESTROY
  GDK_EXPOSE
  GDK_MOTION_NOTIFY
  GDK_BUTTON_PRESS
  GDK_2BUTTON_PRESS
  GDK_3BUTTON_PRESS
  GDK_BUTTON_RELEASE
  GDK_KEY_PRESS
  GDK_KEY_RELEASE
  GDK_ENTER_NOTIFY
  GDK_LEAVE_NOTIFY
  GDK_FOCUS_CHANGE
  GDK_CONFIGURE
  GDK_MAP
  GDK_UNMAP
  GDK_PROPERTY_NOTIFY
  GDK_SELECTION_CLEAR
  GDK_SELECTION_REQUEST
  GDK_SELECTION_NOTIFY
  GDK_PROXIMITY_IN
  GDK_PROXIMITY_OUT
  GDK_DRAG_BEGIN
  GDK_DRAG_REQUEST
  GDK_DROP_ENTER
  GDK_DROP_LEAVE
  GDK_DROP_DATA_AVAIL
  GDK_CLIENT_EVENT
  GDK_VISIBILITY_NOTIFY
  GDK_NO_EXPOSE
  GDK_OTHER_EVENT       /* En desuso, usar filtros en lugar de ella */

Por lo tanto para conectar una función de respuesta a uno de estos eventos debemos usar algo como:

gtk_signal_connect( GTK_OBJECT(button), "button_press_event",
                    GTK_SIGNAL_FUNC(button_press_callback), 
                        NULL);

Por supuesto se asume que button es un widget GtkButton. Cada vez que el puntero del ratón se encuentre sobre el botón y éste sea presionado, se llamará a la función button_press_callback. Esta función puede declararse así:

static gint button_press_event (GtkWidget      *widget, 
                                GdkEventButton *event,
                                gpointer        data);

Conviene destacar que se puede declarar el segundo argumento como GdkEventButton porque sabemos que este tipo de evento ocurrirá cuando se llame a la función.

El valor devuelto por esta función es usado para saber si el evento debe ser propagado? a un nivel más profundo dentro del mecanismo de GTK para gestionar los eventos. Si devuelve TRUE el evento ya ha sido gestionado y por tanto no tiene que ser tratado por el mecanismo de gestión. Por contra si devuelve FALSE se continua con la gestión normal del evento. Para más detalles se recomienda leer la sección donde se aclara como se produce el proceso de propagación.

Para más detalles acerca de los tipos de información GdkEvent consultar el apéndice Tipos de eventos GDK.

2.5 Aclaración de Hello World

Ahora que conocemos la teoría vamos a aclarar las ideas estudiando en detalle el programa hello world.

Ésta es la función respuesta a la que se llamará cuando se pulse el botón. En el ejemplo ignoramos tanto el widget como la información, pero no es difícil usarlos. El siguiente ejemplo usará la información que recibe como argumento para decirnos que botón fue presionado.

void hello (GtkWidget *widget, gpointer data)
{
    g_print ("Hello World\n");
}

La siguiente respuesta es un poco especial, el ``delete_event'' ocurre cuando el gestor de ventanas envía este evento a la aplicación. Aquí podemos decidir que hacemos con estos eventos. Los podemos ignorar, dar algún tipo de respuesta, o simplemente terminar la aplicación.

El valor devuelto en esta respuesta le permite a GTK saber que tiene que hacer. Si devolvemos TRUE, estamos diciendo que no queremos que se emita la señal ``destroy'' y por lo tanto queremos que nuestra aplicación siga ejecutándose. Si devolvemos FALSE, decimos que se emita ``destroy'', lo que hará que se ejecute nuestro manejador de señal de ``destroy''.

gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
    g_print ("delete event occured\n");

    return (TRUE); 
}

Con el siguiente ejemplo presentamos otra función de respuesta que hace que el programa salga llamando a gtk_main_quit(). Con esta función le decimos a GTK que salga de la rutina gtk_main() cuando vuelva a estar en ella.

void destroy (GtkWidget *widget, gpointer data)
{
    gtk_main_quit ();
}

Como el lector probablemente ya sabe toda aplicación debe tener una función main(), y una aplicación GTK no va a ser menos. Todas las aplicaciones GTK también tienen una función de este tipo.

int main (int argc, char *argv[])

Las líneas siguientes declaran un puntero a una estructura del tipo GtkWidget, que se utilizarán más adelante para crear una ventana y un botón.

    GtkWidget *window;
    GtkWidget *button;

Aquí tenemos otra vez a gtk_init. Como antes arranca el conjunto de herramientas y filtra las opciones introducidas en la línea de órdenes. Cualquier argumento que sea reconocido será borrado de la lista de argumentos, de modo que la aplicación recibirá el resto.

    gtk_init (&argc, &argv);

Ahora vamos a crear una ventana. Simplemente reservamos memoria para la estructura GtkWindow *window, con lo que ya tenemos una nueva ventana, ventana que no se mostrará hasta que llamemos a gtk_widget_show (window) hacia el final del programa.

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

Aquí tenemos un ejemplo de como conectar un manejador de señal a un objeto, en este caso, la ventana. La señal a cazar será ``destroy''. Esta señal se emite cuando utilizamos el administrador de ventanas para matar la ventana (y devolvemos TRUE en el manejador ``delete_event''), o cuando usamos llamamos a gtk_widget_destroy() pasándole el widget que representa la ventana como argumento. Así conseguimos manejar los dos casos con una simple llamada a la función destroy () (definida arriba) pasándole NULL como argumento y ella acabará con la aplicación por nosotros.

GTK_OBJECT y GTK_SIGNAL_FUNC son macros que realizan la comprobación y transformación de tipos por nosotros. También aumentan la legibilidad del código.

    gtk_signal_connect (GTK_OBJECT (window), "destroy",
                        GTK_SIGNAL_FUNC (destroy), NULL);

La siguiente función establece un atributo a un objeto contenedor (discutidos luego). En este caso le pone a la ventana un área negra de 10 pixels de ancho donde no habrá widgets. Hay funciones similares que serán tratadas con más detalle en la sección: Estableciendo los atributos de los <em/widgets/

De nuevo, GTK_CONTAINER es una macro que se encarga de la conversión entre tipos

    gtk_container_border_width (GTK_CONTAINER (window), 10);

La siguiente llamada crea un nuevo botón. Reserva espacio en la memoria para una nueva estructura del tipo GtkWidget, la inicializa y hace que el puntero del botón apunte a él. Su etiqueta será: "Hello World".

    button = gtk_button_new_with_label ("Hello World");

Ahora hacemos que el botón sea útil, para ello lo enlazamos con el manejador de señales para que se emita la señal ``clicked'', se llame a nuestra función hello(). Los datos adicionales serán ignorados, por lo que simplemente le pasaremos NULL a la función respuesta. Obviamente se emitirá la señal ``clicked'' cuando cliquemos en el botón con el ratón.

    gtk_signal_connect (GTK_OBJECT (button), "clicked",
                        GTK_SIGNAL_FUNC (hello), NULL);

Ahora vamos a usar el botón para terminar nuestro programa. Así aclararemos cómo es posible que la señal "destroy" sea emitida tanto por el gestor de ventanas como por nuestro programa. Cuando el botón es pulsado, al igual que arriba, se llama a la primera función respuesta hello() y después se llamará a esta función. Las funciones respuesta serán ejecutadas en el orden en que sean conectadas. Como la función gtk_widget_destroy() sólo acepta un GtkWidget como argumento, utilizaremos gtk_signal_connect_object() en lugar de gtk_signal_connect().

gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                           GTK_SIGNAL_FUNC (gtk_widget_destroy),
                           GTK_OBJECT (window));

La siguiente llamada sirve para empaquetar (más detalles luego). Se usa para decirle a GTK que el botón debe estar en la ventana dónde será mostrado. Conviene destacar que un contenedor GTK sólo puede contener un widget. Existen otros widgets (descritos después) que sirven para contener y establecer la disposición de varios widgets de diferentes formas.

    gtk_container_add (GTK_CONTAINER (window), button);

Ahora ya tenemos todo bien organizado. Como todos los controladores de las señales ya están en su sitio, y el botón está situado en la ventana donde queremos que esté, sólo nos queda pedirle a GTK que muestre todos los widgets en pantalla. El widget window será el último en mostrarse queremos que aparezca todo de golpe, en vez de ver aparecer la ventana, y después ver aparecer el botón. De todas formas con un ejemplo tan simple nunca se notaría cual es el orden de aparición.

    gtk_widget_show (button);

    gtk_widget_show (window);

Llamamos a gtk_main() que espera hasta que el servidor X le comunique que se ha producido algún evento para emitir las señales apropiadas.

    gtk_main ();

Por último el `return' final que devuelve el control cuando gtk_quit() sea invocada.

    return 0;

Cuando pulsemos el botón del ratón el widget emite la señal correspondiente ``clicked''. Para que podamos usar la información el programa activa el gestor de eventos que al recibir la señal llama a la función que hemos elegido. En nuestro ejemplo cuando pulsamos el botón se llama a la función hello() con NULL como argumento y además se invoca al siguiente manipulador de señal. Así conseguimos que se llame a la función gtk_widget_destroy() con el widget asociado a la ventana como argumento, lo que destruye al widget. Esto hace que la ventana emita la señal ``destroy'', que es cazada, y que llama a nuestra función respuesta destroy(), que simplemente sale de GTK.

Otra posibilidad es usar el gestor de ventanas para acabar con la aplicación. Esto emitirá ``delete_event'' que hará que se llame a nuestra función manejadora correspondiente. Si en la función manejadora ``delete_event'' devolvemos TRUE la ventana se quedará como si nada hubiese ocurrido, pero si devolvemos FALSE GTK emitirá la señal ``destroy'' que, por supuesto, llamará a la función respuesta ``destroy'', que saldrá de GTK.

Conviene destacar que las señales de GTK no son iguales que las de los sistemas UNIX, aunque la terminología es la misma.


Página siguiente Página anterior Índice general