Escribir un GUI (Graphic User Interface: Interfáz gráfico de Usuario) basado en GTK+ es en esencia extremadamente simple, y nosotros usaremos el ejemplo de Hola Mundo del excelente tutorial de GTK+ de Ian Main, el cual es una utilísima guía para escribir aplicaciones para GNOME. Pero primero hablaremos sobre la filosofía oculta de GTK+.
GTK+ es una herramienta basada en contenedores, queriendo esto decir que no especifica dónde está el widget, sino en que contenedor está. Algunos widget, como una ventana, un marco o un botón, son contenedores que sólo pueden almacenar otro único widget. Por ejemplo un botón con una etiqueta es realmente un botón dentro del cual hemos añadido un widget etiqueta. Si necesita poner más widgets dentro de ese contenedor, necesitará añadir otro contenedor dentro de él.
De hecho la mayoría de capas de una ventana normalmente están formadas con contenedores como cajas horizontales, cajas verticales y tablas, estos son los más importantes de aprender. Una caja horizontal es un widget al que puede añadir varios widgets dentro y estos serán añadidos en una fila horizontal. La altura de la caja horizontal es la altura del widget añadido más alto y su longitud es la suma de la longitud de todos los widgets. Las cajas verticales se comportan exactamente igual, excepto que es vertical en lugar de horizontal. Una tabla puede incluir widget en diferentes filas y columnas.
Figura 1. Ejemplo de la Jerarquía de la Ventana
El modelo de objetos de Gtk es un marco de trabajo orientado a objetos para C. Incluye una singular herencia de objetos, metodos virtuales, señales, modificación de objetos en tiempo de ejecución, comprobación de tipos en tiempo de ejecución y otras bondades. Aunque escribir un objeto GTK+ es más enrevesado que escribirlo en algo como Java, tiene muchas ventajas. GTK+ es un modelo de objetos que no requiere herencia para la mayoría de las cosas que se hacen con objetos. Por ejemplo, como los métodos son simplemente funciones que cogen un puntero al objeto como primer argumento, es muy fácil escribir más metodos en su propio código, que no existían en el objeto original.
Los objetos en GTK+ son simplemente estructuras en C y la herencia se logra simplemente incluyendo la estructura padre como primer elemento en la estructura hija. Esto significa que podemos hacer conversiones explícitas entre tipos sin problemas. Además de la estructura del objeto que es una instancia, hay también una estructura de clase para cada clase que contenga cosas como punteros a funciones virtuales y manejadores de señales.
El modelo de objetos en GTK+ usa 3 tipos de métodos, un método puede ser una simple función C que espera un puntero a un objeto como primer argumento. Este tipo de método es el más rápido en ejecutarse, pero el programador no dispone de ningún mecanismo para modificar su funcionamiento o para reemplazarlo por su propio método.
El segundo tipo es el método virtual. Usted puede reemplazar estos métodos por los suyos propios en clases derivadas (N.T: lo que comúnmente se llama "polimorfismo" en los términos de la programación orientada a objetos), pero hasta aquí llegan sus ventajas. Un método virtual se implementa como un puntero a una función en la estructura de la clase, normalmente con una función de recubrimiento que se encarga de llamar al método virtual.
El tercer tipo de método y el más flexible (y también el de llamada más lenta) es el método de señal. El tipo señal es parecido al virtual, el usuario puede modificar el funcionamiento del método pero esto lo consigue conectando manejadores que se pueden ejecutar antes o despues del método por defecto. Algunas veces los objetos tienen una señal con el único propósito de tener manejadores del programador conectados a ella. Estas señales tienen su cuerpo de método vacío (No aportan método por defecto alguno).
Hay una forma de almacenar datos arbitrarios en los objetos para extender el objeto. Esto se hace con el método, gtk_object_set_data (o gtk_object_set_user_data para un puntero simple sin nombre). Para escoger el dato, usamos gtk_object_get_data. Ejemplo:
GtkObject *obj; void *some_pointer; ... /* aquí ponemos el dato "some_data" en obj para que apunte a some_pointer */ gtk_object_set_data(obj,"some_data",some_pointer); ... /* recuperamos el puntero de some_data desde obj y lo guardamos en some_pointer */ some_pointer = gtk_object_get_data(obj,"some_data"); |
GTK+ y GNOME usan la misma norma para los nombres de objetos y funciones. GTK+ usa el prefijo gtk_ para las funciones, y Gtk para los objetos, y GNOME usa gnome_ y Gnome. Cuando una función es un método de un objeto, el nombre (en minúsculas) se añade al prefijo. Por ejemplo, el objeto botón se llama GtkButton (que es el nombre de la estructura en C que sustenta los datos del objeto), y llama al método "new" (El constructor) para GtkButton como gtk_button_new. Las macros asociadas con objetos usan la misma norma que las funciones, pero todas están en mayúsculas. Por ejemplo una macro que convierte explícitamente un objeto a GtkButton se llama GTK_BUTTON. Hay excepciones importantes, la macro que comprueba el tipo para GtkButton, se llama GTK_IS_BUTTON.
Puesto que GTK+ es orientado a objetos, usa herencia para los widgets. Por ejemplo GtkHBox y GtkVBox derivan de GtkBox. Y así se puede usar cualquier método GtkBox sobre GtkVBox o GtkHBox. Sin embargo necesitamos convertir explícitamente el objeto GtkVBox a GtkBox antes de que poder llamar a la función. Esto se podría hacer en C estándar de esta forma:
GtkVBox *vbox; ... gtk_box_pack_start((GtkBox *)vbox, ...); ... |
GtkVBox *vbox; ... gtk_box_pack_start(GTK_BOX(vbox), ...); ... |
GNOME usa exactamente la misma forma así que cualquier concimiento que haya adquirido sobre GTK+ puede ser usado para los widget de GNOME, simplemente cambie el prefijo GTK por GNOME.
Aquí esta el código de ejemplo prometido para el programa 'Hola mundo'. No usa ningún contenedor avanzado, solamente una ventana y un botón dentro del cual se le ha añadido una etiqueta. Ilustra el funcionamiento básico de un GUI escrito en GTK+. No se asuste de su tamaño, casi todo son comentarios.
/* ejemplo holamundo holamundo.c */ #include <gtk/gtk.h> /* esto es una función de retrollamada. los argumentos son ignorados en este * ejemplo... Mas retrollamadas más adelante. */ void hello (GtkWidget *widget, gpointer data) { g_print ("Hola Mundo\n"); } gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { g_print ("Evento delete\n"); /* si devuelve FALSE en el manejador de la * señal "delete_event", GTK emitirá la señal "destroy". * Devolviendo TRUE da a entender que no quiere * que la ventana sea destruida. Esto es útil para * que aparezcan los diálogos típicos de '¿está seguro * de que desea salir?'. */ /* Cambiando TRUE a FALSE la ventana principal * sera destruida con un "delete_event". */ return (TRUE); } /* otra retrollamada */ void destroy (GtkWidget *widget, gpointer data) { gtk_main_quit (); } int main (int argc, char *argv[]) { /* GtkWidget es el tipo para el almacenamiento de los widgets */ GtkWidget *window; GtkWidget *button; /* Esta función se debe llamar en todas la aplicaciones GTK * antes de ninguna otra los argumentos son pasados desde la * línea de órdenes y devueltos a la aplicación. */ gtk_init (&argc, &argv); /* crear una nueva ventana */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* cuando a la ventana se le pasa la señal "delete_event" * (la envía el usuario a través del gestor de ventanas), le * pedimos que llame a la función delete_event () tal y como * está definida arriba. El dato pasado a la función de * retrollamada es NULL y es ignorado en la función de * retrollamada. */ gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL); /* aquí conectamos el evento "destroy" al manejador * de la señal. Este evento sucede cuando llamamos a * gtk_widget_destroy() en la ventana, o si devolvemos * 'FALSE' en la retrollamada "delete_event". */ gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (destroy), NULL); /* configura el ancho del borde de la ventana. */ gtk_container_border_width (GTK_CONTAINER (window), 10); /* crea un nuevo botón con la etiqueta "Hola Mundo". */ button = gtk_button_new_with_label ("Hola Mundo"); /* Cuando el botón recibe la señal "clicked", llamará * a la función hello() pasándole NULL como argumento. * La función hello() esta definida arriba. */ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (hello), NULL); /* Esto causará que la ventana se destruya por la llamada * a gtk_widget_destroy(window) cuando se emita "clicked". * De nuevo, la señal "destroy" podría venir desde aquí o * desde el gestor de ventanas. */ gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window)); /* esto pone el botón dentro de una ventana * (un contenedor gtk). */ gtk_container_add (GTK_CONTAINER (window), button); /* el paso final es ver este recién creado widget... */ gtk_widget_show (button); /* y la ventana */ gtk_widget_show (window); /* Todas las aplicaciones GTK deben tener un gtk_main(). * Aquí termina el control y se espera hasta que suceda un evento * (como una pulsación del teclado o un evento del ratón). */ gtk_main (); return 0; } /* Fin del ejemplo */ |
Para más información mire el archivo de cabecera en <prefijo>/include/gtk/ y <prefijo>/include/gdk/ ('prefijo' será el directorio donde haya instalado los archivos de cabecera de GTK+) y la documentación en el sitio web www.gtk.org.