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 pueden 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.

/* principio del ejemplo base base.c */

#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
    GtkWidget *ventana;
    
    gtk_init (&argc, &argv);
    
    ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_show (ventana);
    
    gtk_main ();
    
    return 0;
}
/* final del ejemplo */

Puede compilar el programa anterior con gcc tecleando:

gcc base.c -o base `gtk-config --cflags --libs`

El significado de la extraña opción de compilación se explica más adelante.

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 biblioteca 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 pueda 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.

  ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_show (ventana);

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 dormido 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 «Hola Mundo» en GTK

El siguiente ejemplo es un programa con un widget (un botón). Simplemente es la versión de GTK del clásico «hola mundo».

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

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

gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
    /* 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 emergentes del
     * tipo: ¿Seguro que desea salir?

    g_print ("Ha ocurrido un evento delete\n");

    /* Cambiando TRUE por FALSE la ventana se destruirá 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 *ventana;
    GtkWidget *boton;

    /* 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 */
    ventana = 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, y serán ignorados. */
    gtk_signal_connect (GTK_OBJECT (ventana), "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 (ventana), "destroy",
                        GTK_SIGNAL_FUNC (destroy), NULL);
    
    /* establecemos el ancho del borde de la ventana. */
    gtk_container_border_width (GTK_CONTAINER (ventana), 10);
    
    /* creamos un botón nuevo con la  etiqueta "Hola mundo" */
    boton = gtk_button_new_with_label ("Hola mundo");
    
    /* 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 (boton), "clicked",
                        GTK_SIGNAL_FUNC (hello), NULL);
    
    /* Esto hará que la ventana sea destruida llamando a
     * gtk_widget_destroy(ventana) 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 (boton), "clicked",
                               GTK_SIGNAL_FUNC (gtk_widget_destroy),
                               GTK_OBJECT (ventana));
    
    /* Ahora empaquetamos el botón en la ventana (usamos un gtk
     * container ). */
    gtk_container_add (GTK_CONTAINER (ventana), boton);
    
    /* El último paso es representar el nuevo widget... */
    gtk_widget_show (boton);
    
    /* y la ventana */
    gtk_widget_show (ventana);
    
    /* 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 bibliotecas 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 bibliotecas que se enlazan normalmente son:

2.3 Teoría de señales y respuestas

Antes de profundizar en holamundo 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 se transfiere el control a la función adecuada.

El control se transfiere mediante «señales». (Conviene destacar que las señales de GTK no son iguales que las de los sistemas UNIX, aunque la terminología es la misma.) 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     *objeto,
                         gchar         *nombre,
                         GtkSignalFunc  func,
                         gpointer       datos_func );

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   datos_respuesta );

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 hola mundo es:

gint gtk_signal_connect_object( GtkObject     *objeto,
                                gchar         *nombre,
                                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 hola mundo.

¿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 Aclaración de Hello World

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

É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 *ventana;
    GtkWidget *boton;

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 *ventana, con lo que ya tenemos una nueva ventana, ventana que no se mostrará hasta que llamemos a gtk_widget_show (ventana) hacia el final del programa.

    ventana = 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 (ventana), "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án 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 (ventana), 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 boton apunte a esta estructura. Su etiqueta será: "Hola mundo".

    boton = gtk_button_new_with_label ("Hola mundo");

Ahora hacemos que el botón sea útil, para ello enlazamos el botón con el manejador de señales para que cuando emita la señal «clicked», se llame a nuestra función hola(). 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 pulsemos en el botón con el ratón.

    gtk_signal_connect (GTK_OBJECT (boton), "clicked",
                        GTK_SIGNAL_FUNC (hola), NULL);
XXX 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 (boton), "clicked",
                           GTK_SIGNAL_FUNC (gtk_widget_destroy),
                           GTK_OBJECT (ventana));

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 (ventana), boton);

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 ventana 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 (boton);

    gtk_widget_show (ventana);

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.


Página siguiente Página anterior Índice general