237
Índice de contenido 1. Introducción.......................................................................................................................................5 2. Comenzando......................................................................................................................................6 2.1 Programa Hello World en GTK ......................................................................................................7 2.2 Compilando Hello World ................................................................................................................9 2.3 Teoría de señales y respuestas ......................................................................................................10 2.4 Eventos .........................................................................................................................................11 2.5 Aclaración de Hello World ...........................................................................................................13 3. Avanzando.......................................................................................................................................16 3.1 Tipos de datos ...............................................................................................................................16 3.2 Más sobre el manejo de señales ...................................................................................................16 3.3 Un Hello World mejorado. ...........................................................................................................16 4. Widgets usados para empaquetar....................................................................................................19 4.1 Empaquetamiento usando cajas ....................................................................................................19 4.2 Detalles de la cajas. ......................................................................................................................19 4.3 Programa demostración de empaquetamiento ..............................................................................21 4.4 Empaquetamiento usando tablas ..................................................................................................27 4.5 Ejemplo de empaquetamiento mediante tablas. ...........................................................................29 5. Estudio general de los widgets........................................................................................................30 5.1 Conversión de tipos ......................................................................................................................30 5.2 Árbol formado por los widgets .....................................................................................................31 5.3 Widgets sin ventanas ....................................................................................................................33 6. El widget Botón...............................................................................................................................33 6.1 Botones normales .........................................................................................................................33 6.2 Botones de selección (Toggle Buttons) ........................................................................................35 6.3 Botones de comprobación ............................................................................................................36 6.4 Botones circulares ........................................................................................................................36 7. Ajustes ............................................................................................................................................39 7.1 Creando un ajuste .........................................................................................................................39 7.2 Forma sencilla de usar los ajustes ................................................................................................39 7.3 Descripción detallada de los ajustes .............................................................................................40 8. Widgets de selección de rango .......................................................................................................41 8.1 Widgets de escala .........................................................................................................................42 Creación de un widget de escala.....................................................................................................42 Funciones y señales .......................................................................................................................42 8.2 Funciones comunes ......................................................................................................................43 Estableciendo cada cúanto se actualizan........................................................................................43 Obteniendo y estableciendo Ajustes...............................................................................................43 8.3 Enlaces con el teclado y el ratón (Key and Mouse bindings) ......................................................44 Widgets de rango vertical...............................................................................................................44 Widgets de rango horizontal...........................................................................................................44 8.4 Ejemplo ........................................................................................................................................44 9. Widgets varios.................................................................................................................................50 9.1 Etiquetas .......................................................................................................................................50 9.2 El widget de información rápida (tooltip) ....................................................................................50 9.3 Barras de progreso ........................................................................................................................51 9.4 Cuadros de diálogo .......................................................................................................................54

GTK Tutorial

Embed Size (px)

Citation preview

Page 1: GTK Tutorial

Índice de contenido1. Introducción.......................................................................................................................................52. Comenzando......................................................................................................................................62.1 Programa Hello World en GTK ......................................................................................................72.2 Compilando Hello World ................................................................................................................92.3 Teoría de señales y respuestas ......................................................................................................102.4 Eventos .........................................................................................................................................112.5 Aclaración de Hello World ...........................................................................................................133. Avanzando.......................................................................................................................................163.1 Tipos de datos ...............................................................................................................................163.2 Más sobre el manejo de señales ...................................................................................................163.3 Un Hello World mejorado. ...........................................................................................................164. Widgets usados para empaquetar....................................................................................................194.1 Empaquetamiento usando cajas ....................................................................................................194.2 Detalles de la cajas. ......................................................................................................................194.3 Programa demostración de empaquetamiento ..............................................................................214.4 Empaquetamiento usando tablas ..................................................................................................274.5 Ejemplo de empaquetamiento mediante tablas. ...........................................................................295. Estudio general de los widgets........................................................................................................305.1 Conversión de tipos ......................................................................................................................305.2 Árbol formado por los widgets .....................................................................................................315.3 Widgets sin ventanas ....................................................................................................................336. El widget Botón...............................................................................................................................336.1 Botones normales .........................................................................................................................336.2 Botones de selección (Toggle Buttons) ........................................................................................356.3 Botones de comprobación ............................................................................................................366.4 Botones circulares ........................................................................................................................367. Ajustes ............................................................................................................................................397.1 Creando un ajuste .........................................................................................................................397.2 Forma sencilla de usar los ajustes ................................................................................................397.3 Descripción detallada de los ajustes .............................................................................................408. Widgets de selección de rango .......................................................................................................418.1 Widgets de escala .........................................................................................................................42

Creación de un widget de escala.....................................................................................................42Funciones y señales .......................................................................................................................42

8.2 Funciones comunes ......................................................................................................................43Estableciendo cada cúanto se actualizan........................................................................................43Obteniendo y estableciendo Ajustes...............................................................................................43

8.3 Enlaces con el teclado y el ratón (Key and Mouse bindings) ......................................................44Widgets de rango vertical...............................................................................................................44Widgets de rango horizontal...........................................................................................................44

8.4 Ejemplo ........................................................................................................................................449. Widgets varios.................................................................................................................................509.1 Etiquetas .......................................................................................................................................509.2 El widget de información rápida (tooltip) ....................................................................................509.3 Barras de progreso ........................................................................................................................519.4 Cuadros de diálogo .......................................................................................................................54

Page 2: GTK Tutorial

9.5 Pixmaps ........................................................................................................................................549.6 Reglas ...........................................................................................................................................619.7 Barras de estado ............................................................................................................................649.8 Entrada de texto ............................................................................................................................669.9 Selección de Color ........................................................................................................................699.10 Selección de ficheros ..................................................................................................................7210. Widgets Contenedores...................................................................................................................7410.1 Libros de notas (Notebooks) ......................................................................................................7410.2 Ventanas con barras de desplazamiento .....................................................................................7910.3 El widget ``ventana dividida'' (Paned Window) .........................................................................8110.4 Barras de herramientas ...............................................................................................................8510.5 Marcos con proporciones fijas ...................................................................................................9111. El widget GtkCList........................................................................................................................9311.1 Creando un widget GtkCList ......................................................................................................9311.2 Modos de operación ...................................................................................................................9311.3 Trabajando con los títulos ...........................................................................................................9411.4 Manipulando la lista en sí. ..........................................................................................................9511.5 Añadiendo filas a la lista .............................................................................................................9611.6 Poniendo texto y pixmaps en las celdas .....................................................................................9711.7 Almacenando punteros a datos ...................................................................................................9911.8 Trabajando con la selección ........................................................................................................9911.9 Las señales que lo hacen todo ....................................................................................................9911.10 Un ejemplo GtkCList .............................................................................................................10012. El widget lista..............................................................................................................................10312.1 Señales ......................................................................................................................................10412.2 Funciones ..................................................................................................................................10412.3 Ejemplo ....................................................................................................................................10612.4 El widget GtkListItem ..............................................................................................................11112.5 Señales ......................................................................................................................................11212.6 Funciones ..................................................................................................................................11212.7 Ejemplo .....................................................................................................................................11213. El widget árbol............................................................................................................................11313.1 Creando un árbol ......................................................................................................................11313.2 Añadiendo un Subárbol ............................................................................................................11313.3 Manejando la lista de selección ................................................................................................11413.4 Estructura interna del widget árbol ...........................................................................................114

Señales..........................................................................................................................................115Funciones y macros......................................................................................................................116

13.5 El widget elemento de árbol .....................................................................................................118Señales..........................................................................................................................................119Funciones y Macros......................................................................................................................120

13.6 Árbol ejemplo ...........................................................................................................................12114. El widget menú............................................................................................................................12414.1 Creación manual de menús .......................................................................................................12414.2 Ejemplo de la creación manual de un menú .............................................................................12714.3 Utilizando GtkMenuFactory .....................................................................................................13014.4 Ejemplo de la fábrica de menús ...............................................................................................13015. El widget texto.............................................................................................................................13315.1 Creando y configurando un cuadro de texto ............................................................................133

Page 3: GTK Tutorial

15.2 Manipulación de texto ..............................................................................................................13415.3 Atajos por teclado .....................................................................................................................136

Atajos para el movimiento............................................................................................................136Atajos para la edición...................................................................................................................136Atajos de selección.......................................................................................................................136

15.4 Un ejemplo de GtkText ............................................................................................................13616. Widgets no documentados...........................................................................................................14016.1 Fixed Container ........................................................................................................................14016.2 Curves .......................................................................................................................................14016.3 Previews ...................................................................................................................................14017. El widget EventBox.....................................................................................................................14918. Estableciendo los atributos de un widget....................................................................................15019. Tiempos de espera, ES (IO) y funciones ociosas (idle)...............................................................15119.1 Tiempos de espera ....................................................................................................................15119.2 Monitorizando la ES .................................................................................................................15119.3 Funciones ociosas .....................................................................................................................15220. Manejo avanzado de eventos y señales.......................................................................................15220.1 Funciones señal ........................................................................................................................152

Conectando y desconectando los manejadores de señal...............................................................152Bloqueando y desbloqueando los manejadores de señal..............................................................153Emitiendo y deteniendo señales....................................................................................................154

20.2 Emisión y propagación de señales ............................................................................................15421. Manejando selecciones................................................................................................................15521.1 Contenido .................................................................................................................................15521.2 Recuperando la selección .........................................................................................................15621.3 Proporcionando la selección .....................................................................................................15822. glib...............................................................................................................................................16122.1 Definiciones ..............................................................................................................................16122.2 Listas doblemente enlazadas ....................................................................................................16222.3 Listas simplemente enlazadas ..................................................................................................16322.4 Control de la memoria ..............................................................................................................16422.5 Timers .......................................................................................................................................16422.6 Manejo de cadenas de texto ......................................................................................................16522.7 Funciones de error y funciones varias ......................................................................................16523. Ficheros rc de GTK.....................................................................................................................16623.1 Funciones para los ficheros rc ..................................................................................................16623.2 Formato de los ficheros rc de GTK ..........................................................................................16723.3 Fichero rc de ejemplo ...............................................................................................................16824. Escribiendo sus propios widgets.................................................................................................17124.1 Visión general ...........................................................................................................................17124.2 La anatomía de un widget ........................................................................................................17124.3 Creando un widget compuesto .................................................................................................172

Introducción..................................................................................................................................172Escogiendo una clase padre..........................................................................................................172El fichero de cabecera...................................................................................................................172La función _get_type().................................................................................................................174La función _class_init()................................................................................................................175La función _init()..........................................................................................................................177Y el resto.......................................................................................................................................177

Page 4: GTK Tutorial

24.4 Creando un widget desde cero. .................................................................................................179Introducción..................................................................................................................................179Mostrando un widget en la pantalla..............................................................................................180Los orígenes del widget Dial........................................................................................................180Los comienzos..............................................................................................................................181gtk_dial_realize()..........................................................................................................................185Negociación del tamaño...............................................................................................................186gtk_dial_expose().........................................................................................................................187Manejo de eventos........................................................................................................................189Posibles mejoras...........................................................................................................................194

24.5 Aprendiendo más ......................................................................................................................19425. Scribble, un sencillo programa de dibujo de ejemplo.................................................................19425.1 Objetivos ..................................................................................................................................19425.2 Manejo de eventos ....................................................................................................................19525.3 El widget DrawingArea, y dibujando .......................................................................................19825.4 Añadiendo la capacidad de utilizar XInput ..............................................................................201

Activando la información del dispositivo extendido....................................................................202Utilizando la información de los dispositivos extras....................................................................203Obteniendo más información de un dispositivo...........................................................................205Sofisticaciones adicionales ..........................................................................................................206

26. Trucos para escribir aplicaciones GTK.......................................................................................20727. Contribuyendo.............................................................................................................................20728. Créditos.......................................................................................................................................20729. Copyright del Tutorial y notas sobre los permisos......................................................................20829.1 Acerca de la traducción ............................................................................................................209Appendix...........................................................................................................................................20930. Tipos de eventos GDK................................................................................................................20931. Código ejemplo...........................................................................................................................21631.1 Tictactoe ...................................................................................................................................216

tictactoe.h......................................................................................................................................216tictactoe.c......................................................................................................................................217ttt_test.c.........................................................................................................................................220

31.2 GtkDial .....................................................................................................................................221gtkdial.h........................................................................................................................................221gtkdial.c........................................................................................................................................223

31.3 Scribble .....................................................................................................................................234

Page 5: GTK Tutorial

1. IntroducciónEn sus comienzos GTK fue desarrollada como un conjunto de herramientas para el Gimp. El GTK es una librería construida sobre GDK (el conjunto de herramientas de dibujo del Gimp), que a su vez no es más que un wrapper de las funciones de Xlib. Conviene aclarar que en estos momentos GTK está siendo utilizado en muchos otros proyectos libres aparte del Gimp. Sus autores son:

• Peter Mattis [email protected] • Spencer Kimball [email protected] • Josh MacDonald [email protected]

GTK es un API orientado a objetos. Aunque está completamente escrita en C, soporta la idea de clases y funciones de respuesta (es decir punteros a funciones).

Existe un tercer componente llamado glib que proporciona un sustituto a algunas llamadas que podríamos denominar estándar. También incorpora funciones adicionales para manejar listas enlazadas, etc... Las funciones sustituto son usadas para aumentar la portabilidad de GTK, ya que algunas de las funciones incluidas no se encuentran disponibles en otros entornos Unix, como es el caso de g_strerror(). Otras simplemente son versiones mejoradas de las que proporciona libc. Por ejemplo g_malloc() proporciona métodos de depuración que no se encuentran la versión de libc.

Este tutorial intenta documentar todo lo que se pueda el uso de GTK. Pero no se puede considerar completo. (N del T: El desarrollo de GTK se produce tan deprisa que no hay forma de mantenerlo al día). En este manual se presupone que el lector entiende bien C y como crear programas usando este lenguaje. Si el lector posee experiencia previa programando bajo X probablemente no le resultará muy difícil entenderlo, pero tampoco es necesario ningún conocimiento previo. En el caso de que GTK sea su primer conjunto de widgets puede expresar libremente su opinión sobre el manual y todo aquello que le resulte complicado. Para acabar esta breve introducción conviene destacar que existen varios wrappers de GTK en diferentes lenguajes, desde C++, Objective C hasta TOM, ADA... Por último el autor destaca que le gustaría saber los problemas que tenga el lector a la hora de aprender a manejar GTK a partir de este manual y cómo mejorarlo.

/*************************************************************

* Este Tutorial lo Pueden mirar en: *

* http://www.linuxlots.com/~barreiro/spanish/gtk/tutorial/gtk_tut.es.html *

*************************************************************/

Yo simplemente lo vi interesante y decidí que tenia que tenerlo en mi HD, asi que lo copie tal como esta en la pagina con créditos y todo, por si un día lo subo y lo posteo en algún sitio, así que mi nombre es absolutamente irrelevante.

Page 6: GTK Tutorial

2. ComenzandoPor 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:

• --display • --debug-level • --no-xshm • --sync • --show-events • --no-show-events • --name • --class

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.

Page 7: GTK Tutorial

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 ();}

Page 8: GTK Tutorial

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í. */

Page 9: GTK Tutorial

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:

• La librería GTK (-lgtk), la librería de widgets que se encuentra encima de GDK. • La librería GDK (-lgdk), el wrapper de Xlib. • La librería glib (-lglib), que contiene diversas funciones. En nuestro ejemplo sólo hemos usado

g_print(). GTK está construida encima de glib por lo que simpre se usará. Vea la sección glib para más detalles.

• La librería Xlib (-lX11) que es usada por GDK. • La librería Xext (-lXext) contiene código para pixmaps de memoria compartida y otras

extensiones. • La librería matemática (-lm). Es usada por GDK para diferentes cosas.

Page 10: GTK Tutorial

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

Page 11: GTK Tutorial

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:

• event • button_press_event • button_release_event • motion_notify_event • delete_event • destroy_event • expose_event • key_press_event • key_release_event • enter_notify_event • leave_notify_event • configure_event • focus_in_event • focus_out_event • map_event • unmap_event • property_notify_event • selection_clear_event • selection_request_event • selection_notify_event • proximity_in_event • proximity_out_event • drag_begin_event • drag_request_event • drag_end_event • drop_enter_event • drop_leave_event • drop_data_available_event • other_event

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

Page 12: GTK Tutorial

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

Page 13: GTK Tutorial

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;

Page 14: GTK Tutorial

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",

Page 15: GTK Tutorial

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.

Page 16: GTK Tutorial

3. Avanzando

3.1 Tipos de datos Existen algunos detalles de los ejemplos anteriores que hay que aclarar. Los tipos gint, gchar, etc. que puede ver por ahí son typedefs a int y a char respectivamente. Sirven para que no haya que tener en cuenta el tamaño de cada uno de ellos a la hora de hacer cálculos.

Un buen ejemplo es "gint32" que es un entero de 32 bits independientemente de la plataforma, bien sea un Alpha de 64 bits o un i386 de 32. Todas las definiciones son muy intuitivas y se encuentran definidas en glib/glib.h (que se incluye desde gtk.h).

Probablemente el lector se haya dado cuenta de que se puede usar GtkWidget cuando la función llama a un GtkObject. Esto es debido a que GTK está orienta a objetos y un widget es un GtkObject.

3.2 Más sobre el manejo de señales Si estudiamos en mayor profundidad la declaración de gtk_signal_connect:

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

Podemos darnos cuenta de que el valor devuelto es del tipo gint. Este valor es una etiqueta que identifica a la función de respuesta. Tal y como ya vimos podemos tener tantas funciones de respuesta por seÑal y objeto como sean necesarias, y cada una de ellas se ejecutará en el mismo orden en el que fueron enlazadas.

Esta etiqueta nos permite eliminar la función respuesta de la lista usando:

void gtk_signal_disconnect( GtkObject *object, gint id );

Por lo tanto podemos desconectar un manejador de señal pasándole a la función anterior el widget del que queremos desconectar y la etiqueta o id devuelta por una de las funciones signal_connect.

Otra función que se usa para quitar desconectar todos los controladores de un objeto es:

void gtk_signal_handlers_destroy( GtkObject *object );

Esta llamada es bastante auto explicativa. Simplemente quitamos todos los controladores de señales del objeto que pasamos como primer argumento.

3.3 Un Hello World mejorado. Vamos a mejorar el ejemplo para obtener una visión más amplia sobre el manejo de señales y respuestas. También introduciremos los widgets usados para empaquetar.

/* Aquí comienza el ejemplo helloworld2 */

#include <gtk/gtk.h>

Page 17: GTK Tutorial

/* Nuestra respuesta mejorada. Los argumentos de la función se imprimen en el stdout.*/void callback (GtkWidget *widget, gpointer data){ g_print ("Hello again - %s was pressed\n", (char *) data);}

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

int main (int argc, char *argv[]){ /* GtkWidget es el tipo de almacenamiento usado para los wigtes*/ GtkWidget *window; GtkWidget *button; GtkWidget *box1; /* Esta llamada está presente en todas las aplicaciones basadas en GTK. * Los argumentos introducidos * a la aplicación*/ gtk_init (&argc, &argv);

/* creamos una nueva ventana*/ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Esta llamada es nueva, establece el título de la ventana a "Hello Buttons!"*/

gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!");

/* Establecemos el controlador para la llamada delete_event que termina la aplicación * inmediatamente. */

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

/* Establecemos el ancho del borde de la ventana.*/ gtk_container_border_width (GTK_CONTAINER (window), 10);

/* Creamos una caja donde empaquetaremos los widgets. El procedimiento de empaquetamiento * se describe en detalle en la sección correspondiente. La caja no se ve realmente, * sólo sirve para introducir los widgets. */

box1 = gtk_hbox_new(FALSE, 0);

/* ponemos la caja en la ventana principal */ gtk_container_add (GTK_CONTAINER (window), box1);

/* Creamos un nuevo botón con la etiqueta "Button 1". */

Page 18: GTK Tutorial

button = gtk_button_new_with_label ("Button 1"); /* Cada vez que el botón sea pulsado llamamos a la función "callback" con un puntero a * "button 1" como argumento.*/ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); /* En lugar de gtk_container_add empaquetamos el botón en la caja invisible, que a su vez * ha sido empaquetado en la ventana.*/

gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);

/* Siempre se debe realizar este paso. Sirve para decirle a GTK que los preparativos del * botón ya se han finalizado y que por tanto puede ser mostrado. */

gtk_widget_show(button);

/* hacemos lo mismo para crear un segundo botón. */ button = gtk_button_new_with_label ("Button 2");

/* Llamamos a la misma función de respuesta pero con diferente argumento: un puntero * a "button 2". */

gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");

gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);

/* El orden en que mostramos los botones no es realmente importante, pero se recomienda * mostrar la ventana la última para que todo aparezca de golpe. */

gtk_widget_show(button);

gtk_widget_show(box1);

gtk_widget_show (window);

/* Esperamos en gtk_main a que comience el espectáculo.*/

gtk_main ();

return 0;}/* final del ejemplo*/

Compile el programa usando los mismos argumentos que en el ejemplo anterior. Probablemente ya se habrá dado cuenta de que no hay una forma sencilla para terminar el programa, se debe usar el gestor de ventanas o la línea de comandos para ello. Un buen ejercicio para el lector es introducir un tercer botón que termine el programa. También puede resultar interesante probar las diferentes opciones de

Page 19: GTK Tutorial

gtk_box_pack_start() mientras lee la siguiente sección. Intente cambiar el tamaño de la ventana y observe el comportamiento.

Como última nota, existe otra definición bastante útil: gtk_widow_new() - GTK_WINDOW_DIALOG. Su comportamiento es un poco diferente y debe ser usado para ventanas intermedias (cuadros de diálogo).

4. Widgets usados para empaquetarAl crear una aplicación normalmente se quiere que haya más de un widget por ventana. Nuestro primer ejemplo sólo usaba un widget por lo que usábamos la función gtk_container_add para ``empaquetar'' el widget en la ventana. Pero cuando cuando se quiere poner más de un widget en una ventana, ¿Cómo podemos controlar donde aparecerá el widget?. Aquí es donde entra el empaquetamiento.

4.1 Empaquetamiento usando cajas Normalmente para empaquetar se usan cajas, tal y como ya hemos visto. Éstas son widgets invisibles que pueden contener nuestros widgets de dos formas diferentes, horizontal o verticalmente. Al hacerlo de la primera forma los objetos son insertados de izquierda a derecha o al revés (dependiendo de que llamada se use). Lo mismo ocurre en los verticales (de arriba a bajo o al revés). Se pueden usar tantas cajas como se quieran para conseguir cualquier tipo de efecto.

Para crear una caja horizontal llamamos a gtk_hbox_new() y para las verticales gtk_vbox_new(). Las funciones usadas para introducir objetos dentro son gtk_box_pack_start() y gtk_box_pack_end(). La primera llenará de arriba a abajo o de izquierda a derecha. La segunda lo hará al revés. Usando estas funciones podemos ir metiendo widgets con una justificación a la izquierda o a la derecha y además podemos mezclarlas de cualquier manera para conseguir el efecto deseado. Nosotros usaremos gtk_box_pack_start() en la mayoria de nuestros ejemplos. Un objeto puede ser otro contenedor o un widget. De hecho, muchos widgets son contenedores, incluyendo el widget botón (button) (aunque normalmente lo único que meteremos dentro será una etiqueta de texto).

Mediante el uso de estas funciones le decimos a GTK dónde queremos situar nuestros widgets, y GTK podrá, por ejemplo, cambiarles el tamaño de forma automática y hacer otras cosas de utilidad. También hay unas cuantas opciones que tienen que ver con la forma en la que los widgets serán empaquetados. Como puede imaginarse, este método nos da una gran flexibilidad a la hora de colocar y crear widgets.

4.2 Detalles de la cajas. Debido a esta flexibilidad el empaquetamiento puede ser confuso al principio. Hay muchas opciones y no es obvio como encajan unas con otras. Pero en la práctica sólo hay cinco estilos diferentes.

Page 20: GTK Tutorial

Cada línea contiene una caja horizontal (hbox) con diferentes botones. La llamada a gtk_box_pack es una manera de conseguir empaquetar cada uno de los botones dentro de la caja. Eso sí, cada uno de ellos se empaqueta de la misma forma que el resto (se llama con los mismos argumentos a gtk_box_pack_start()).

Esta es la declaración de la función gtk_box_pack_start:

void gtk_box_pack_start( GtkBox *box, GtkWidget *child, gint expand, gint fill, gint padding );

El primer argumento es la caja dónde se empaqueta, el segundo el objeto. Por ahora el objeto será un botón, ya que estamos empaquetando botones dentro de las cajas.

El argumento expand de gtk_box_pack_start() y de gtk_box_pack_end() controla si los widgets son expandidos en la caja para rellenar todo el espacio de la misma (TRUE) o si por el contrario no se usa el espacio extra dentro de la caja (FALSE). Poniendo FALSE en expand podremos hacer que nuestros widgets tengan una justaficación a la derecha o a la izquierda. En caso contrario, los widgets se expandirán para llenar toda la caja, y podemos conseguir el mismo efecto utilizando sólo una de las funciones gtk_box_pack_start o pack_end.

El argumento fill de gtk_box controla si el espacio extra se mete dentro de los objetos (TRUE) o como relleno extra (FALSE). Sólo tiene efecto si el argumento de expansión también es TRUE.

Al crear una nueva ventana la función debe ser parecida a esta:

GtkWidget *gtk_hbox_new (gint homogeneous, gint spacing);

El argumento homogeneous (tanto para gtk_hbox_new como para gtk_vbox_new) controla si cada objeto en la caja tiene el mismo tamaño (anchura en una hbox o altura en una vbox). Si se activa, el argumento expand de las rutinas gtk_box_pack siempre estará activado.

Puede que el lector se esté haciendo la siguiente pregunta: ¿Cúal es la diferencia entre espaciar (establecido cuando se crea la caja) y rellenar (determinado cuando se empaquetan los elementos)? El espaciado se añade entre objetos, y el rellenado se hace en cada parte de cada objeto. La siguiente figura debe aclarar la cuestión.

Page 21: GTK Tutorial

Estudiemos el código usado para crear las imágenes anteriores. Con los comentarios no debería de haber ningún problema para entenderlo.

4.3 Programa demostración de empaquetamiento /* comienzo del ejemplo packbox packbox.c */

#include <stdio.h>#include "gtk/gtk.h"

voiddelete_event (GtkWidget *widget, GdkEvent *event, gpointer data){ gtk_main_quit ();}

/* Hacemos una hbox llena de etiquetas de botón. Los argumentos para las variables que estamos * interesados son pasados a esta función. No mostramos la caja, pero hacemos todo lo que * queremos./

GtkWidget *make_box (gint homogeneous, gint spacing, gint expand, gint fill, gint padding) { GtkWidget *box; GtkWidget *button; char padstr[80]; /* creamos una nueva caja con los argumentos homogeneous y spacing */ box = gtk_hbox_new (homogeneous, spacing); /* crear una serie de botones */ button = gtk_button_new_with_label ("gtk_box_pack"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); button = gtk_button_new_with_label ("(box,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); button = gtk_button_new_with_label ("button,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Este botón llevará por etiqueta el valor de expand*/

Page 22: GTK Tutorial

if (expand == TRUE) button = gtk_button_new_with_label ("TRUE,"); else button = gtk_button_new_with_label ("FALSE,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Este es el mismo caso que el de arriba, pero más compacto*/

button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); sprintf (padstr, "%d);", padding); button = gtk_button_new_with_label (padstr); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); return box;}

intmain (int argc, char *argv[]){ GtkWidget *window; GtkWidget *button; GtkWidget *box1; GtkWidget *box2; GtkWidget *separator; GtkWidget *label; GtkWidget *quitbox; int which; /* ¡No olvidar la siguiente llamada! */

gtk_init (&argc, &argv); if (argc != 2) { fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n"); /* this just does cleanup in GTK, and exits with an exit status of 1. */ /* hacemos limpieza en GTK y devolvemos el valor de 1 */ gtk_exit (1); } which = atoi (argv[1]);

/* Creamos la ventana */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

/* Siempre hay que conectar la señal de destrucción con la ventana principal. Esto es muy importante * para que el comportamiento de la ventana sea intuitivo. */ gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL);

Page 23: GTK Tutorial

gtk_container_border_width (GTK_CONTAINER (window), 10); /* Cramos una caja vertical donde empaquetaremos las cajas horizontales. * Así podemos apilar las cajas horizontales llenas con botones una encima de las otras. */ box1 = gtk_vbox_new (FALSE, 0); /* Aclaramos cúal es el ejemplo a mostrar. Se corresponde con las imágenes anteriores. */ switch (which) { case 1: /* creamos una nueva etiqueta. */ label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); /* Alineamos la etiqueta a la izquierda. Está función será discutida en detalle en la * sección de los atributos de los widgets. */ gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

/* Empaquetamos la etiqueta en la caja vertical (vbox box1). Siempre hay que recordar * que los widgets añadidos a una vbox serán empaquetados uno encimo de otro. */

gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);

/* mostramos la etiqueta.*/ gtk_widget_show (label); /* llamada a la función que hace las cajas. Los argumentos son homogenous = FALSE, * expand = FALSE, fill = FALSE, padding = 0 */

box2 = make_box (FALSE, 0, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2);

/* Llamad a la función para hacer cajas - homogeneous = FALSE, spacing = 0, * expand = FALSE, fill = FALSE, padding = 0 */ box2 = make_box (FALSE, 0, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 0, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* creamos un separador. Mas tarde aprenderemos más cosas sobre ellos, pero son * bastante simples. */ separator = gtk_hseparator_new ();

Page 24: GTK Tutorial

/* empaquetamos el separador el la vbox. Los widgets serán apilados verticalmente. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator);

/* creamos una nueva etiqueta y la mostramos */ label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (TRUE, 0, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (TRUE, 0, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* un nuevo separador */ separator = gtk_hseparator_new (); /* Los tres últimos argumentos son: expand, fill, padding. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); break;

case 2:

/* Nueva etiqueta*/ label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 10, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 10, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); separator = gtk_hseparator_new (); /* Los argumentos son: expand, fill, padding. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator);

Page 25: GTK Tutorial

label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 0, TRUE, FALSE, 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Los argumentos son: homogeneous, spacing, expand, fill, padding */ box2 = make_box (FALSE, 0, TRUE, TRUE, 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); separator = gtk_hseparator_new (); /* Los argumentos son: expand, fill, padding. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); break; case 3:

/* Con esto demostramos como hay que usar gtk_box_pack_end () para conseguir que los widgets esten alineados a la izquierda. */ box2 = make_box (FALSE, 0, FALSE, FALSE, 0); /* la última etiqueta*/ label = gtk_label_new ("end"); /* la empaquetamos usando gtk_box_pack_end(), por lo que se sitúa en el lado derecho * de la hbox.*/

gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); /* mostrar la etiqueta */ gtk_widget_show (label);

/* empaquetamos box2 en box1*/ gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* el separador para la parte de abajo. */ separator = gtk_hseparator_new (); /* Así se determina el tamaño del separador a 400 pixels de largo por 5 de alto. * La hbox también tendrá 400 pixels de largo y la etiqueta "end" estará separada de * las demás etiquetas en la hbox. Si no establecemos estos parámetros todos los * widgets en la hbox serán empaquetados tan juntos como se pueda.*/

Page 26: GTK Tutorial

gtk_widget_set_usize (separator, 400, 5); /* Empaquetamos el separador creado al principio de main() en la vbox (box1).*/ /* pack the separator into the vbox (box1) created near the start * of main() */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); } /* Creamos otra hbox... recordar que podemos crear tantas como queramos.*/ quitbox = gtk_hbox_new (FALSE, 0); /* El botón de salida. */ button = gtk_button_new_with_label ("Quit"); /* Establecemos la señal de destrucción de la ventana. Recuerde que emitirá la señal de * "destroy" que a su vez será procesada por el controlador de señales, tal y como ya * hemos visto.*/ gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (gtk_main_quit), GTK_OBJECT (window)); /* Empaquetamos el botón en la caja de salida (quitbox). * los tres últimos argumentos de gtk_box_pack_start son:expand, fill, padding. */

gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); /* empaquetamos la vbox (box1) que ya contiene todos los widgets en la ventana principal.*/ gtk_container_add (GTK_CONTAINER (window), box1); /* mostramos todo aquello que faltaba por mostrar*/ gtk_widget_show (button); gtk_widget_show (quitbox); gtk_widget_show (box1); /* Si mostramos la ventana lo último todo aparece de golpe. */ gtk_widget_show (window); /* por supuesto tenemos una función main. */ gtk_main ();

/* El control se devuelve aquí cuando gtk_main_quit() es llamada, pero no cuando gtk_exit es * usada.*/ return 0;}/* final del ejemplo*/

Page 27: GTK Tutorial

4.4 Empaquetamiento usando tablas Existe otra forma de empaquetar: usando tablas. Estas pueden llegar a ser extremadamente útiles.

Usando tablas creamos una cuadrícula donde podemos poner los widgets. Estos pueden ocupar tanto espacio como queramos.

La primera función que conviene estudiar es gtk_table_new:

GtkWidget *gtk_table_new( gint rows, gint columns, gint homogeneous );

Como es lógico el primer argumento es el número de filas y el segundo el de columnas.

El tercero establece el tamaño de las celdas de la tabla. Si es TRUE se fuerza a que el tamaño de las celdas sea igual al de la celda mayor. Con FALSE se establece el ancho de toda una columna igual al de la celda más ancha de esa columna, y la altura de una fila será la de la celda más alta de esa fila.

El número de filas y columnas varía entre 0 y n, donde n es el número especificado en la llamada a gtk_table_new. Así si se especifica columnas = 2 y filas = 2 la apariencia será parecida a:

0 1 20+----------+----------+ | | |1+----------+----------+ | | |2+----------+----------+

Conviene destacar que el origen de coordenadas se sitúa en la esquina superior izquierda. Para situar un widget en una ventana se usa la siguiente función:

void gtk_table_attach( GtkTable *table, GtkWidget *child, gint left_attach, gint right_attach, gint top_attach, gint bottom_attach, gint xoptions, gint yoptions, gint xpadding, gint ypadding );

El primer argumento (table) es el nombre de la tabla y el segundo (child) el widget que quiere poner en la tabla.

Los argumentos left_attach, right_attach especifican donde se pone el widget y cuantas cajas se usan. Por ejemplo, supongamos que queremos poner un botón que sólo ocupe la esquina inferior izquierda en nuestra tabla 2x2. Los valores serán left_attach = 1, right_attach = 2, top_attach = 2, top_attach = 1, bottom_attach = 2.

Supongamos que queremos ocupar toda la fila de nuestra tabla 2x2, usaríamos left_attach = 0, right_attach = 2, top_attach = 0, bottom_attach = 1.

Page 28: GTK Tutorial

Las opciones xoptions e yoptions son usadas para especificar como queremos el empaquetamiento y podemos utilizar multiples opciones simultaneamente con OR.

Las opciones son:

• GTK_FILL - Si el relleno es más grande que el widget, y se especifica GTK_FILL, el widget se expandirá ocupando todo el espacio disponible.

• GTK_SHRINK - En el caso de que hayamos dejado espacio sin usar cuando el usuario reajuste el tamaño de la ventana los widgets normalmente serán empujados al fondo de la ventana y desaparecerán. Si especifica GTK_SHRINK los widgets se reducirán con la tabla.

• GTK_EXPAND - Mediante esta opción la tabla se expande usando todo el espacio libre de la ventana.

El relleno es igual que con las cajas. Simplemente se crea una zona vacía alrededor del widget (el tamaño se especifica en pixels).

gtk_table_attach() tiene MUCHAS opciones. Asi que hay un atajo:

void gtk_table_attach_defaults( GtkTable *table, GtkWidget *widget, gint left_attach, gint right_attach, gint top_attach, gint bottom_attach );

Las opciones X e Y se ponen por defecto a GTK_FILL | GTK_EXPAND, y el relleno X e Y se pone a 0. El resto de los argumentos son identicos a la función anterior.

Existen otras funciones como gtk_table_set_row_spacing() y gtk_table_set_col_spacing(), que sirven para especificar el espaciado entre las columnas/filas en la columna/fila que queramos.

void gtk_table_set_row_spacing( GtkTable *table, gint row, gint spacing );

y

void gtk_table_set_col_spacing ( GtkTable *table, gint column, gint spacing );

Conviene destacar que el espaciado se sitúa a la derecha de la columna y debajo de la fila.

Tambien se puede forzar que el espaciado sea el mismo para las filas y/o las columnas:

void gtk_table_set_row_spacings( GtkTable *table, gint spacing );

y

void gtk_table_set_col_spacings( GtkTable *table, gint spacing );

Usando estas funciones las últimas fila y columna no estarán espaciadas.

Page 29: GTK Tutorial

4.5 Ejemplo de empaquetamiento mediante tablas. Haremos una ventana con tres botones en una tabla 2x2. Los dos primeros botones ocuparán la fila de arriba, mientras que el tercero (de salida) ocupará toda la fila de abajo. El resultado es el siguiente:

Este es el código:

/* principio del ejemplo table table.c */

#include <gtk/gtk.h>/* La respuesta, que además se imprime en stdout.*/void callback (GtkWidget *widget, gpointer data){ g_print ("Hello again - %s was pressed\n", (char *) data);}

/* con esta otra respuesta terminamos el programa. */void delete_event (GtkWidget *widget, GdkEvent *event, gpointer data){ gtk_main_quit ();}

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *button; GtkWidget *table;

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

gtk_window_set_title (GTK_WINDOW (window), "Table");

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

gtk_container_border_width (GTK_CONTAINER (window), 20);

table = gtk_table_new (2, 2, TRUE);

gtk_container_add (GTK_CONTAINER (window), table);

button = gtk_button_new_with_label ("button 1");

gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");

gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 1, 0, 1);

Page 30: GTK Tutorial

gtk_widget_show (button);

button = gtk_button_new_with_label ("button 2");

gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); gtk_table_attach_defaults (GTK_TABLE(table), button, 1, 2, 0, 1);

gtk_widget_show (button);

button = gtk_button_new_with_label ("Quit");

gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (delete_event), NULL); gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 2, 1, 2);

gtk_widget_show (button);

gtk_widget_show (table); gtk_widget_show (window);

gtk_main ();

return 0;}/* final del ejemplo */

5. Estudio general de los widgetsLos pasos generales a la hora de crear un widget son:

1. Usar gtk_*_new - Una de las diferentes formas de crear un widget. (Todas serán explicadas en esta sección).

2. Connectar todas las señales y los eventos a los controladores apropiados. 3. Establecer los atributos del widget. 4. Empaquetar el widget en un contenedor usando las llamadas apropiadas, como

gtk_container_add() o gtk_box_pack_start(). 5. Mostrar el widget usando gtk_widget_show().

Mediante esta última llamada GTK `sabe' que hemos acabado de establecer los atributos del widget, y que por lo tanto puede ser mostrado. Se puede usar gtk_widget_hide para hacer que desaparezca. El orden en el que se muestran los widgets no es importante, pero se recomienda mostrar lo último la ventana para que todo aparezca de golpe. El hijo de un widget no se muestra hasta que la propia ventana (que en este caso es un widget padre) es mostrada mediante gtk_widget_show().

5.1 Conversión de tipos GTK usa un sistema de conversión de tipos mediante macros que comprueban si se puede realizar la conversión y en caso afirmativo la hacen. Las más comunes son:

• GTK_WIDGET(widget)

Page 31: GTK Tutorial

• GTK_OBJECT(object) • GTK_SIGNAL_FUNC(function) • GTK_CONTAINER(container) • GTK_WINDOW(window) • GTK_BOX(box)

Todas son usados para cambiar de tipo los argumentos de una función. Aparecerán mucho en los ejemplos, para usarlas sólo hay que mirar la declaración de la función.

Tal y como se puede ver en el árbol de clases (situado un poco más adelante) todos los widgets derivan de la clase base GtkObject. Esto significa que siempre se puede usar un widget como argumento de una función (que acepte un objeto, claro) realizando la conversión de tipo GTK_OBJECT().

Por ejemplo:

gtk_signal_connect( GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(callback_function), callback_data);

Hemos hecho que el botón pase a ser un objeto y que se cambie el puntero a la función a una función respuesta.

Muchos widgets son contenedores, por lo que unos pueden derivar de otros (la mayoría lo hace de GtkContainer). Cualquiera puede ser usado junto con la macro GTK_CONTAINER como argumento a funciones en forma de puntero.

Desgraciadamente estas macros no son descritas en detalle en el tutorial, por lo que se recomienda echar un vistazo a los archivos de cabecera de GTK. En la práctica es posible aprender a manejar un widget leyendo las declaraciones de las funciones.

5.2 Árbol formado por los widgets A continuación se detallan todas las ramas del árbol que forman los widgets.

GtkObject +GtkData | +GtkAdjustment | `GtkTooltips `GtkWidget +GtkContainer | +GtkBin | | +GtkAlignment | | +GtkEventBox | | +GtkFrame | | | `GtkAspectFrame | | +GtkHandleBox | | +GtkItem | | | +GtkListItem | | | +GtkMenuItem | | | | `GtkCheckMenuItem | | | | `GtkRadioMenuItem | | | `GtkTreeItem | | +GtkViewport | | `GtkWindow | | +GtkColorSelectionDialog | | +GtkDialog | | | `GtkInputDialog

Page 32: GTK Tutorial

| | `GtkFileSelection | +GtkBox | | +GtkButtonBox | | | +GtkHButtonBox | | | `GtkVButtonBox | | +GtkHBox | | | +GtkCombo | | | `GtkStatusbar | | `GtkVBox | | +GtkColorSelection | | `GtkGammaCurve | +GtkButton | | +GtkOptionMenu | | `GtkToggleButton | | `GtkCheckButton | | `GtkRadioButton | +GtkCList | `GtkCTree | +GtkFixed | +GtkList | +GtkMenuShell | | +GtkMenuBar | | `GtkMenu | +GtkNotebook | +GtkPaned | | +GtkHPaned | | `GtkVPaned | +GtkScrolledWindow | +GtkTable | +GtkToolbar | `GtkTree +GtkDrawingArea | `GtkCurve +GtkEditable | +GtkEntry | | `GtkSpinButton | `GtkText +GtkMisc | +GtkArrow | +GtkImage | +GtkLabel | | `GtkTipsQuery | `GtkPixmap +GtkPreview +GtkProgressBar +GtkRange | +GtkScale | | +GtkHScale | | `GtkVScale | `GtkScrollbar | +GtkHScrollbar | `GtkVScrollbar +GtkRuler | +GtkHRuler | `GtkVRuler `GtkSeparator +GtkHSeparator `GtkVSeparator

Page 33: GTK Tutorial

5.3 Widgets sin ventanas Los siguientes widgets no tienen ventanas asociadas. Si se quieren capturar eventos se tendrá que utilizar GtkEventBox. En la sección El Widget EventBox se pueden encontrar más detalles sobre su uso.

GtkAlignmentGtkArrowGtkBinGtkBoxGtkImageGtkItemGtkLabelGtkPixmapGtkScrolledWindowGtkSeparatorGtkTableGtkAspectFrameGtkFrameGtkVBoxGtkHBoxGtkVSeparatorGtkHSeparator

Vamos a continuar la explicación describiendo cada uno de los widgets mediante ejemplos. También se puede consultar el programa testgtk.c (Se encuentra en gtk/testgtk.c).

6. El widget Botón

6.1 Botones normales Ya hemos visto prácticamente todo lo que hay que saber a cerca de este widget. Existen dos formas diferentes de crear un botón. Se puede usar gtk_button_new_with_label() para conseguir un botón con etiqueta o simplemente gtk_button_new(). Si se quiere se puede añadir una etiqueta a este último empaquetándola, primero se crea una nueva caja y luego se empaquetan los objetos que se quieran mediante gtk_box_pack_start. Una vez finalizado esto se relaciona la caja con el botón mediante gtk_container_add.

Estudiemos un ejemplo de gtk_button_new para crear un botón con una imagen y una etiqueta. El código está dividido en dos para que pueda ser reusado.

/* comienzo del ejemplo buttons buttons.c */

#include <gtk/gtk.h>

/* Creamos la caja con una imagen y una etiqueta empaquetadas. Se devuelve la caja.*/

GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text){ GtkWidget *box1;

Page 34: GTK Tutorial

GtkWidget *label; GtkWidget *pixmapwid; GdkPixmap *pixmap; GdkBitmap *mask; GtkStyle *style;

/* create box for xpm and label */ box1 = gtk_hbox_new (FALSE, 0); gtk_container_border_width (GTK_CONTAINER (box1), 2);

/* obtenemos el estilo del botón (probablemente para el color de fondo, pero no estoyseguro)*/ style = gtk_widget_get_style(parent);

/* cargamos el pixmap. Hay una sección que describe el proceso en detalle */ pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask, &style->bg[GTK_STATE_NORMAL], xpm_filename); pixmapwid = gtk_pixmap_new (pixmap, mask);

label = gtk_label_new (label_text);

gtk_box_pack_start (GTK_BOX (box1), pixmapwid, FALSE, FALSE, 3);

gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3);

gtk_widget_show(pixmapwid); gtk_widget_show(label);

return (box1);}

/* respuesta */void callback (GtkWidget *widget, gpointer data){ g_print ("Hello again - %s was pressed\n", (char *) data);}

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *button; GtkWidget *box1;

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!");

/* It's a good idea to do this for all windows. */ gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL);

Page 35: GTK Tutorial

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

gtk_container_border_width (GTK_CONTAINER (window), 10); gtk_widget_realize(window);

button = gtk_button_new ();

gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (callback), (gpointer) "cool button");

box1 = xpm_label_box(window, "info.xpm", "cool button");

gtk_widget_show(box1);

gtk_container_add (GTK_CONTAINER (button), box1);

gtk_widget_show(button);

gtk_container_add (GTK_CONTAINER (window), button);

gtk_widget_show (window);

gtk_main ();

return 0;}/* final del ejemplo */

La función xpm_label_box puede ser usada para empaquetar xpm y etiquetas en cualquier widget que pueda ser un contenedor.

El botón puede responder a las siguientes señales:

• pressed • released • clicked • enter • leave

6.2 Botones de selección (Toggle Buttons) Estos botones son muy similares a los normales. La única diferencia es que sólo pueden estar en dos posiciones diferentes alternadas mediante pulsaciones del ratón.

Los botones de selección son la base de otros tipos: los de comprobación y los circulares. Por lo tanto muchas de sus llamadas seran heredadas por estos.

Creamos un nuevo botón de selección:

GtkWidget *gtk_toggle_button_new( void );

GtkWidget *gtk_toggle_button_new_with_label( gchar *label );

Page 36: GTK Tutorial

Como se ha podido imaginar estas funciones son iguales a las de un botón normal. La primera crea un botón, mientras que la segunda crea un botón con una etiqueta.

Para saber cual es el estado de un botón de selección, comprobación o circular se usa una de las macros del ejemplo siguiente. En éstas se comprueba el estado del botón mediante una respuesta. La señal que queremos recibir es "toggled". Generalmente para comprobar el estado de una señal se establece un controlador de señales y luego se usa la siguiente macro. La función de respuesta debe ser de la forma:

void toggle_button_callback (GtkWidget *widget, gpointer data){ if (GTK_TOGGLE_BUTTON (widget)->active) { /* Si el control llega aquí el botón se encuentra pulsado */ } else { /* El botón no está pulsado (sobresale) */ }}

void gtk_toggle_button_set_state( GtkToggleButton *toggle_button, gint state );

La llamada de arriba puede ser usada para establecer el estado de un botón de selección (o de cualquiera de sus hijos: el circular o el de comprobación). El primer argumento es el botón, el segundo TRUE cuando queremos que el botón no esté pulsado o FALSE para cuando lo esté. Por defecto se establece FALSE.

Hay que destacar que cuando se usa gtk_toggle_button_set_state() y se cambia el estado del botón este emite la señal ``clicked''.

void gtk_toggle_button_toggled (GtkToggleButton *toggle_button);

Cambia el estado del botón emitiendo la señal ``toggled''.

6.3 Botones de comprobación Los botones de comprobación son un poco diferentes a los anteriores, aunque sus propiedades y funciones son bastante similares. En lugar de ser botones con texto en su interior son pequeños cuadrados con texto a su derecha. Normalmente son usados para (des)seleccionar opciones.

Las dos funciones que los crean son muy similares a las de los botones normales.

GtkWidget *gtk_check_button_new( void );

GtkWidget *gtk_check_button_new_with_label ( gchar *label );

La función new_with_label crea un botón de comprobación con una etiqueta dentro.

El proceso para comprobar el estado de un botón de este tipo es igual al de los de comprobación.

6.4 Botones circulares Estos botones son similares a los de selección con la salvedad de que están agrupados, de modo que sólo uno puede estar seleccionado. Por tanto son usados para permitir al usuario seleccionar algo de

Page 37: GTK Tutorial

una lista de opciones mutuamente excluyentes.

Las llamadas para crear un botón circular son:

GtkWidget *gtk_radio_button_new( GSList *group );

GtkWidget *gtk_radio_button_new_with_label( GSList *group, gchar *label );

El nuevo argumento sirve para especificar el grupo al que pertenecen. La primera llamada debe pasar NULL como primer argumento. A continuación de ésta se puede crear el grupo usando:

GSList *gtk_radio_button_group( GtkRadioButton *radio_button );

Para añadir un nuevo botón a un grupo hay que usar gtk_radio_button_group con el anterior botón como argumento. El resultado se le pasa a gtk_radio_button_new o a gtk_radio_button_new_with_label. Así se consigue enlazar una cadena de botones. (El ejemplo siguiente sirve para aclarar el proceso)

También se puede establecer cúal es el botón pulsado por defecto:

void gtk_toggle_button_set_state( GtkToggleButton *toggle_button, gint state );

El siguiente ejemplo crea un grupo de tres botones:

/* Comienzo del ejemplo radiobuttons.c */

#include <gtk/gtk.h>#include <glib.h>

void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit();}

main(int argc,char *argv[]){ static GtkWidget *window = NULL; GtkWidget *box1; GtkWidget *box2; GtkWidget *button; GtkWidget *separator; GSList *group; gtk_init(&argc,&argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC(close_application), NULL);

gtk_window_set_title (GTK_WINDOW (window), "radio buttons"); gtk_container_border_width (GTK_CONTAINER (window), 0);

box1 = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), box1); gtk_widget_show (box1);

Page 38: GTK Tutorial

box2 = gtk_vbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

button = gtk_radio_button_new_with_label (NULL, "button1"); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button);

group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); button = gtk_radio_button_new_with_label(group, "button2"); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), TRUE); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button);

group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); button = gtk_radio_button_new_with_label(group, "button3"); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button);

separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0); gtk_widget_show (separator);

box2 = gtk_vbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0); gtk_widget_show (box2);

button = gtk_button_new_with_label ("close"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC(close_application), GTK_OBJECT (window)); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show (window); gtk_main(); return(0);}/* final del ejemplo */

El proceso se puede acortar quitando la variable que contiene a la lista de botones:

button2 = gtk_radio_button_new_with_label( gtk_radio_button_group (GTK_RADIO_BUTTON (button1)), "button2");

Page 39: GTK Tutorial

7. Ajustes Existen diferentes widgets en GTK+ que pueden ser ajustados visualmente por el usuario mediante el ratón o el teclado. Un ejemplo son los widgets de selección descritos en la sección Widgets de selección de rango. También hay otros widgets que pueden ser ajustados parcialmente, por ejemplo el widget de texto o el viewport.

Como es lógico el programa tiene que poder reaccionar a los cambios que el usuario realiza. Mediante señales especiales se podría conseguir saber qué y cúanto ha sido ajustado, pero en el caso de que se quiera conectar la señal con diferentes widgets, de manera que todos cambien al mismo tiempo, el proceso puede ser un poco tedioso.

El ejemplo más obvio es conectar una barra de desplazamiento a una región con texto. Si cada widget posee su propia forma de establecer u obtener sus valores de ajuste el programador puede que tenga que escribir sus propios controladores de señales para traducir el resultado de la señal producida por un widget como el argumento de una función usada para determinar valores en otro widget.

Para resolver este problema GTK+ usa objetos del tipo GtkAdjustment. Con ellos se consigue almacenar y traspasar información de una forma abstracta y flexible. El uso más obvio es el de almacenes de párametros para widgets de escala (barras deslizantes y escalas). Como los GtkAdjustment derivan de GtkObject poseen cualidades intrínsecas que les permiten ser algo más que simples estructuras de datos. Lo más importante es que pueden emitir señales que a su vez pueden ser usadas tanto para reaccionar frente al cambio de datos introducidos por el usuario como para transferir los nuevos valores de forma transparente entre widgets ajustables.

7.1 Creando un ajuste Los ajustes se pueden crear usando:

GtkObject *gtk_adjustment_new( gfloat value, gfloat lower, gfloat upper, gfloat step_increment, gfloat page_increment, gfloat page_size );

El argumento value es el valor inicial que le queremos dar al ajuste. Normalmente se corresponde con las posiciones situadas más arriba y a la izquierda de un widget ajustable. El argumento lower especifica los valores más pequeños que el ajuste puede contener. A su vez con step_increment se especifica el valor más pequeño en el que se puede variar la magnitud en cuestión (valor de paso asociado), mientras que page_increment es el mayor. Con page_size se determina el valor visible de un widget.

7.2 Forma sencilla de usar los ajustes Los widgets ajustábles se pueden dividir en dos categorias diferentes, aquellos que necesitan saber las unidades de la cantidad almacenada y los que no. Este último grupo incluye los widgets de tamaño (barras deslizantes, escalas, barras de estado, o botones giratorios). Normalmente estos widgets son ajustados ``directamente'' por el usuario. Los argumentos lower y upper serán los limites dentro de los cuales el usuario puede manipular los ajustes. Por defecto sólo se modificará el value (valor) de un ajuste.

Page 40: GTK Tutorial

El otro grupo incluye los widgets de texto, la lista compuesta o la ventana con barra deslizante. Estos widgets usan valores en pixels para sus ajustes, y normalmente son ajustados ``indirectamente'' mediante barras deslizantes. Aunque todos los widgets pueden crear sus propios ajustes o usar otros creados por el programador con el segundo grupo suele ser conveniente dejarles que creen sus propios ajustes. Normalmente no tendrán en cuenta ninguno de los valores de un ajuste proporcionado por el programador, excepto value, pero los resultados son, en general, indefinidos (entiendase que tendrá que leer el código fuente para saber que pasa con cada widget).

Probablemente ya se habrá dado cuenta de que como los widgets de texto (y todos los widgets del segundo grupo), insisten en establecer todos los valores excepto value, mientras que las barras deslizantes sólo modifican value, si se comparte un objeto de ajuste entre una barra deslizante y un widget de texto al manipular la barra se modificará el widget de texto. Ahora queda completamente demostrada la utilidad de los ajustes. Veamos un ejemplo:

/* creamos un ajuste */ text = gtk_text_new (NULL, NULL); /* lo usamos con la barra deslizante */ vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);

7.3 Descripción detallada de los ajustes Puede que se esté preguntando cómo es posible crear sus propios controladores para responder a las modificaciones producidas por el usuario y cómo obtener el valor del ajuste hecho por este. Para aclarar esto y otras cosas vamos a estudiar la estructura del ajuste

struct _GtkAdjustment{ GtkData data; gfloat lower; gfloat upper; gfloat value; gfloat step_increment; gfloat page_increment; gfloat page_size;};

Lo primero que hay que aclarar es que no hay ninguna macro o función de acceso que permita obtener el value de un GtkAdjustment, por lo que tendrá que hacerlo usted mismo. Tampoco se preocupe mucho porque la macro GTK_ADJUSTMENT (Object) comprueba los tipos durante el proceso de ejecución (como hacen todas las macros de GTK+ que sirven para comprobar los tipos).

Cuando se establece el value de un ajuste normalmente se quiere que cualquier widget se entere del cambio producido. Para ello GTK+ posee una función especial:

void gtk_adjustment_set_value( GtkAdjustment *adjustment, gfloat value );

Tal y como se mencionó antes GtkAdjustment es una subclase de GtkObject y por tanto puede emitir señales. Así se consigue que se actualicen los valores de los ajustes cuando se comparten entre varios widgets. Por tanto todos los widgets ajustables deben conectar controladores de señales a sus señales del tipo value_changed. Esta es la definición de la señal como viene en struct _GtkAdjustmentClass

Page 41: GTK Tutorial

void (* value_changed) (GtkAdjustment *adjustment);

Todos los widgets que usan GtkAdjustment deben emitir esta señal cuando cambie el valor de algún ajuste. Esto sucede cuando el usuario cambia algo o el programa modifica los ajustes mediante. Por ejemplo si queremos que rote una figura cuando modificamos un widget de escala habría que usar una respuesta como esta:

void cb_rotate_picture (GtkAdjustment *adj, GtkWidget *picture){ set_picture_rotation (picture, adj->value);...

y conectarla con el ajuste del widget de escala mediante:

gtk_signal_connect (GTK_OBJECT (adj), "value_changed", GTK_SIGNAL_FUNC (cb_rotate_picture), picture);

¿Qué pasa cuando un widget reconfigura los valores upper o lower (por ejemplo cuando se añade más texto)? Simplemente que se emite la señal changed, que debe ser parecida a:

void (* changed) (GtkAdjustment *adjustment);

Los widgets de tamaño normalmente conectan un controlador a esta señal, que cambia el aspecto de éste para reflejar el cambio. Por ejemplo el tamaño de la guía en una barra deslizante que se alarga o encoge según la inversa de la diferencia de los valores lower y upper.

Probablemente nunca tenga que conectar un controlador a esta señal a no ser que esté escribiendo un nuevo tipo de widget. Pero si cambia directamente alguno de los valores de GtkAdjustment debe hacer que se emita la siguiente señal para reconfigurar todos aquellos widgets que usen ese ajuste:

gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");

8. Widgets de selección de rango Este tipo de widgets incluye a las scroollbar (barras de deslizamiento) y la menos conocida scale (escala). Ambos pueden ser usados para muchas cosas, pero como sus funciones y su implementación son muy parecidas los describimos al mismo tiempo. Principalmente se utilizan para permitirle al usuario escoger un valor dentro de un rango ya prefijado.

Todos los widgets de selección comparten elementos gráficos, cada uno de los cuales tiene su propia ventana X window y recibe eventos. Todos contienen una guía y un rectángulo para determinar la posición dentro de la guía (en una procesador de textos con entorno gráfico se encuentra situado a la derecha del texto y sirve para situarnos en las diferentes partes del texto). Con el ratón podemos subir o bajar el rectángulo, mientras que si hacemos `click' dentro de la guía, pero no sobre el rectángulo, este se mueve hacia donde hemos hecho el click. Dependiendo del botón pulsado el rectángulo se moverá hasta la posición del click o una cantidad prefijada de ante mano.

Tal y como se mencionó en Ajustes todos los widgets usados para seleccionar un rango estan asociados con un objeto de ajuste, a partir del cual calculan la longitud de la barra y su posición. Cuando el usuario manipula la barra de desplazamiento el widget cambiará el valor del ajuste.

Page 42: GTK Tutorial

8.1 Widgets de escala Los widgets de escala se usan para determinar el valor de una cantidad que se puede interpretar visualmente. El usuario probablemente fijará el valor a ojo. Por ejemplo el widget GtkColorSelection contiene widgets de escala que controlan las componentes del color a seleccionar. Normalmente el valor preciso es menos importante que el efecto visual, por lo que el color se selecciona con el ratón y no mediante un número concreto.

Creación de un widget de escala

Existen dos tipos de widgets de escala: GtkHScale (que es horizontal) y GtkVscale (vertical). Como funcionan de la misma manera los vamos a describir a la vez. Las funciones definidas en <gtk/gtkvscale.h> y <gtk/gtkhscale.h>, crean widgets de escala verticales y horizontales respectivamente.

GtkWidget* gtk_vscale_new( GtkAdjustment *adjustment );

GtkWidget* gtk_hscale_new( GtkAdjustment *adjustment );

El ajuste (adjustment) puede ser tanto un ajuste creado mediante gtk_adjustment_new() como NULL. En este último caso se crea un GtkAdjustment anónimo con todos sus valores iguales a 0.0. Si no ha quedado claro el uso de esta función consulte la sección Ajustes para una discusión más detallada.

Funciones y señales

Los widgets de escala pueden indicar su valor actual como un número. Su comportamiento por defecto es mostrar este valor, pero se puede modificar usando:

void gtk_scale_set_draw_value( GtkScale *scale, gint draw_value );

Los valores posibles de draw_value son son TRUE o FALSE. Con el primero se muestra el valor y con el segundo no.

El valor mostrado por un widget de escala por defecto se redondea a un valor decimal (igual que con value en un GtkAdjustment). Se puede cambiar con:

void gtk_scale_set_digits( GtkScale *scale, gint digits );

donde digits es el número de posiciones decimales que se quiera. En la práctica sólo se mostrarán 13 como máximo.

Por último, el valor se puede dibujar en diferentes posiciones con respecto a la posición del rectangulo que hay dentro de la guía:

void gtk_scale_set_value_pos( GtkScale *scale, GtkPositionType pos );

Si ha leido la sección acerca del widget libro de notas entonces ya conoce cuales son los valores posibles de pos. Estan definidos en <gtk/gtkscale.h> como enum GtkPositionType y son auto explicatorios. Si se escoge un lateral de la guía, entonces seguirá al rectángulo a lo largo de la

Page 43: GTK Tutorial

guía.

Todas las funcioenes precedentes se encuentran definidas en: <gtk/gtkscale.h>.

8.2 Funciones comunes La descripción interna de la clase GtkRange es bastante complicada, pero al igual que con el resto de las ``clases base'' sólo es interesante si se quiere ``hackear''. Casi todas las señales y funciones sólo son útiles para desarrollar derivados. Para un usuario normal las funciones interesantes son aquellas definidas en: <gtk/gtkrange.h> y funcionan igual en todos los widgets de rango.

Estableciendo cada cúanto se actualizan

La política de actualización de un widget define en que puntos de la interacción con el usuario debe cambiar el valor value en su GtkAdjustment y emitir la señal ``value_changed''. Las actualizaciones definidas en <gtk/gtkenums.h> como enum GtkUpdateType, son:

• GTK_UPDATE_POLICY_CONTINUOUS - Este es el valor por defecto.La señal ``value_changed'' se emite continuamente, por ejemplo cuando la barra deslizante se mueve incluso aunque sea un poquito.

• GTK_UPDATE_POLICY_DISCONTINUOUS - La señal ``value_changed'' sólo se emite cuando se ha parado de mover la barra y el usuario ha soltado el botón del ratón.

• GTK_UPDATE_POLICY_DELAYED - La señal sólo se emite cuando el usuario suelta el botón del ratón o si la barra no se mueve durante un periodo largo de tiempo.

Para establecer la política de actualización se usa la conversión definida en la macro

void gtk_range_set_update_policy( GtkRange *range, GtkUpdateType policy) ;

Obteniendo y estableciendo Ajustes

Para obtener o establecer el ajuste de un widget de rango se usa:

GtkAdjustment* gtk_range_get_adjustment( GtkRange *range );

void gtk_range_set_adjustment( GtkRange *range, GtkAdjustment *adjustment );

La función gtk_range_get_adjustment() devuelve un puntero al ajuste al que range esté conectado.

La función gtk_range_set_adjustment() no hace nada si se le pasa como argumento el valor range del ajuste que esta siendo usado (aunque se haya modificado algún valor). En el caso de que sea un ajuste nuevo (GtkAdjustment) dejará de usar el antiguo (probablemente lo destruirá) y conectará las señales apropiadas al nuevo. A continuación llamará a la función gtk_range_adjustment_changed() que en teoría recalculará el tamaño y/o la posición de la barra, redibujándola en caso de que sea necesario. Tal y como se mencionó en la sección de los ajustes si se quiere reusar el mismo GtkAdjustment cuando se modifican sus valores se debe emitir la señal ``changed''. Por ejemplo:

gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");

Page 44: GTK Tutorial

8.3 Enlaces con el teclado y el ratón (Key and Mouse bindings) Todos los widgets de rango reaccionan más o menos de la misma manera a las pulsaciones del ratón. Al pulsar el botón 1 sobre el rectángulo de la barra el value del ajuste aumentará o disminuirá según page_increment. Con el botón 2 la barra se desplazará al punto en el que el botón fue pulsado. Con cada pulsación de cualquier botón sobre las flechas el valor del ajuste se modifica una cantidad igual a step_increment.

Acostumbrarse a que tanto las barras deslizantes como los widgets de escala puedan tomar la atención del teclado puede ser un proceso largo. Si que se cree que los usuarios no lo van a entender se puede anular mediante la función GTK_WIDGET_UNSET_FLAGS y con GTK_CAN_FOCUS como argumento:

GTK_WIDGET_UNSET_FLAGS (scrollbar, GTK_CAN_FOCUS);

Los enlaces entre teclas (que sólo estan activos cuando el widget tiene la atención (focus)) se comportan de manera diferente para los widgets de rango horizontales que para los verticales. También son diferentes para los widgets de escala y para las barras deslizantes. (Simplemente para evitar confusiones entre las teclas de las barras deslizantes horizontales y verticales, ya que ambas actúan sobre la misma área)

Widgets de rango vertical

Todos los widgets de rango pueden ser manipulados con las teclas arriba, abajo, Re Pág, Av Pág. Las flechas mueven las barras la cantidad fijada mediante step_increment, mientras que Re Pág y Av Pag lo hacen según page_increment.

El usuario también puede mover la barra de un extremo al otro de la guía mediante el teclado. Con el widget GtkVScale podemos ir a los extremos utilizando las teclas Inicio y Final mientras que con el widget GtkVScrollbar habrá que utilizar Control-Re Pág y Control-Av Pág.

Widgets de rango horizontal

Las teclas izquierda y derecha funcionan tal y como espera que funcionen en estos widgets: mueven la barra una cantidad dada por step_increment. A su vez Inicio y Final sirven para pasar de un extremo al otro de la guía. Para el widget GtkHScale el mover la barra una cantidad dada por page_increment se consigue mediante Control-Izquierda y Control-derecha, mientras que para el widget GtkHScrollbar se consigue con Control-Inicio y Control-Final.

8.4 Ejemplo Este ejemplo es una versión modificada del test ``range controls'' que a su vez forma parte de testgtk.c. Simplemente dibuja una ventana con tres widgets de rango conectados al mismo ajuste, y un conjunto de controles para ajustar algunos de los parámetros ya mencionados. Así se consigue ver como funcionan estos widgtes al ser manipulados por el usuario.

/* principio del ejemplo widgets de selección de rango rangewidgets.c */

#include <gtk/gtk.h>

GtkWidget *hscale, *vscale;

Page 45: GTK Tutorial

void cb_pos_menu_select( GtkWidget *item, GtkPositionType pos ){ /* Establece el valor position en los widgets de escala */ gtk_scale_set_value_pos (GTK_SCALE (hscale), pos); gtk_scale_set_value_pos (GTK_SCALE (vscale), pos);}

void cb_update_menu_select( GtkWidget *item, GtkUpdateType policy ){ /* Establece la política de actualización para los widgets * de escala */ gtk_range_set_update_policy (GTK_RANGE (hscale), policy); gtk_range_set_update_policy (GTK_RANGE (vscale), policy);}

void cb_digits_scale( GtkAdjustment *adj ){ /* Establece el número de cifras decimales a las que se * redondeará adj->value */ gtk_scale_set_digits (GTK_SCALE (hscale), (gint) adj->value); gtk_scale_set_digits (GTK_SCALE (vscale), (gint) adj->value);}

void cb_page_size( GtkAdjustment *get, GtkAdjustment *set ){ /* Establece el tamaño de la página y el incremento del * ajuste al valor especificado en la escala "Page Size" */ set->page_size = get->value; set->page_increment = get->value; /* Ahora emite la señal "changed" para reconfigurar todos los * widgets que están enlazados a este ajuste */ gtk_signal_emit_by_name (GTK_OBJECT (set), "changed");}

void cb_draw_value( GtkToggleButton *button ){ /* Activa o desactiva el valor display en los widgets de escala * dependiendo del estado del botón de comprobación */ gtk_scale_set_draw_value (GTK_SCALE (hscale), button->active); gtk_scale_set_draw_value (GTK_SCALE (vscale), button->active); }

/* Funciones varias */

GtkWidget *make_menu_item( gchar *name, GtkSignalFunc callback, gpointer data ){ GtkWidget *item; item = gtk_menu_item_new_with_label (name); gtk_signal_connect (GTK_OBJECT (item), "activate", callback, data); gtk_widget_show (item);

Page 46: GTK Tutorial

return(item);}

void scale_set_default_values( GtkScale *scale ){ gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_CONTINUOUS); gtk_scale_set_digits (scale, 1); gtk_scale_set_value_pos (scale, GTK_POS_TOP); gtk_scale_set_draw_value (scale, TRUE);}

/* crea la ventana principal */

void create_range_controls( void ){ GtkWidget *window; GtkWidget *box1, *box2, *box3; GtkWidget *button; GtkWidget *scrollbar; GtkWidget *separator; GtkWidget *opt, *menu, *item; GtkWidget *label; GtkWidget *scale; GtkObject *adj1, *adj2;

/* creación estándar de una ventana */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC(gtk_main_quit), NULL); gtk_window_set_title (GTK_WINDOW (window), "range controls");

box1 = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), box1); gtk_widget_show (box1);

box2 = gtk_hbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

/* value, lower, upper, step_increment, page_increment, page_size */ /* Observe que el valor de page_size solo sirve para los widgets * barras de desplazamiento (scrollbar), y que el valor más * alto que obtendrá será (upper - page_size). */ adj1 = gtk_adjustment_new (0.0, 0.0, 101.0, 0.1, 1.0, 1.0); vscale = gtk_vscale_new (GTK_ADJUSTMENT (adj1)); scale_set_default_values (GTK_SCALE (vscale)); gtk_box_pack_start (GTK_BOX (box2), vscale, TRUE, TRUE, 0); gtk_widget_show (vscale);

box3 = gtk_vbox_new (FALSE, 10); gtk_box_pack_start (GTK_BOX (box2), box3, TRUE, TRUE, 0); gtk_widget_show (box3);

/* Reutilizamos el mismo ajuste */

Page 47: GTK Tutorial

hscale = gtk_hscale_new (GTK_ADJUSTMENT (adj1)); gtk_widget_set_usize (GTK_WIDGET (hscale), 200, 30); scale_set_default_values (GTK_SCALE (hscale)); gtk_box_pack_start (GTK_BOX (box3), hscale, TRUE, TRUE, 0); gtk_widget_show (hscale);

/* Reutilizamos de nuevo el mismo ajuste */ scrollbar = gtk_hscrollbar_new (GTK_ADJUSTMENT (adj1)); /* Observe que con esto conseguimos que la escala siempre se * actualice de una forma continua cuando se mueva la barra de * desplazamiento */ gtk_range_set_update_policy (GTK_RANGE (scrollbar), GTK_UPDATE_CONTINUOUS); gtk_box_pack_start (GTK_BOX (box3), scrollbar, TRUE, TRUE, 0); gtk_widget_show (scrollbar);

box2 = gtk_hbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

/* Un botón para comprobar si el valor se muestra o no*/ button = gtk_check_button_new_with_label("Display value on scale widgets"); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), TRUE); gtk_signal_connect (GTK_OBJECT (button), "toggled", GTK_SIGNAL_FUNC(cb_draw_value), NULL); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); gtk_widget_show (button); box2 = gtk_hbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10);

/* Una opción en el menú para cambiar la posición del * valor */ label = gtk_label_new ("Scale Value Position:"); gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0); gtk_widget_show (label); opt = gtk_option_menu_new(); menu = gtk_menu_new();

item = make_menu_item ("Top", GTK_SIGNAL_FUNC(cb_pos_menu_select), GINT_TO_POINTER (GTK_POS_TOP)); gtk_menu_append (GTK_MENU (menu), item); item = make_menu_item ("Bottom", GTK_SIGNAL_FUNC (cb_pos_menu_select), GINT_TO_POINTER (GTK_POS_BOTTOM)); gtk_menu_append (GTK_MENU (menu), item); item = make_menu_item ("Left", GTK_SIGNAL_FUNC (cb_pos_menu_select), GINT_TO_POINTER (GTK_POS_LEFT)); gtk_menu_append (GTK_MENU (menu), item); item = make_menu_item ("Right", GTK_SIGNAL_FUNC (cb_pos_menu_select), GINT_TO_POINTER (GTK_POS_RIGHT));

Page 48: GTK Tutorial

gtk_menu_append (GTK_MENU (menu), item); gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu); gtk_box_pack_start (GTK_BOX (box2), opt, TRUE, TRUE, 0); gtk_widget_show (opt);

gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

box2 = gtk_hbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10);

/* Sí, otra opción de menú, esta vez para la política * de actualización de los widgets */ label = gtk_label_new ("Scale Update Policy:"); gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0); gtk_widget_show (label); opt = gtk_option_menu_new(); menu = gtk_menu_new(); item = make_menu_item ("Continuous", GTK_SIGNAL_FUNC (cb_update_menu_select), GINT_TO_POINTER (GTK_UPDATE_CONTINUOUS)); gtk_menu_append (GTK_MENU (menu), item); item = make_menu_item ("Discontinuous", GTK_SIGNAL_FUNC (cb_update_menu_select), GINT_TO_POINTER (GTK_UPDATE_DISCONTINUOUS)); gtk_menu_append (GTK_MENU (menu), item); item = make_menu_item ("Delayed", GTK_SIGNAL_FUNC (cb_update_menu_select), GINT_TO_POINTER (GTK_UPDATE_DELAYED)); gtk_menu_append (GTK_MENU (menu), item); gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu); gtk_box_pack_start (GTK_BOX (box2), opt, TRUE, TRUE, 0); gtk_widget_show (opt); gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

box2 = gtk_hbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); /* Un widget GtkHScale para ajustar el número de dígitos en * la escala. */ label = gtk_label_new ("Scale Digits:"); gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0); gtk_widget_show (label);

adj2 = gtk_adjustment_new (1.0, 0.0, 5.0, 1.0, 1.0, 0.0); gtk_signal_connect (GTK_OBJECT (adj2), "value_changed", GTK_SIGNAL_FUNC (cb_digits_scale), NULL); scale = gtk_hscale_new (GTK_ADJUSTMENT (adj2)); gtk_scale_set_digits (GTK_SCALE (scale), 0); gtk_box_pack_start (GTK_BOX (box2), scale, TRUE, TRUE, 0);

Page 49: GTK Tutorial

gtk_widget_show (scale);

gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2); box2 = gtk_hbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); /* Y un último widget GtkHScale para ajustar el tamaño de la * página de la barra de desplazamiento. */ label = gtk_label_new ("Scrollbar Page Size:"); gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0); gtk_widget_show (label);

adj2 = gtk_adjustment_new (1.0, 1.0, 101.0, 1.0, 1.0, 0.0); gtk_signal_connect (GTK_OBJECT (adj2), "value_changed", GTK_SIGNAL_FUNC (cb_page_size), adj1); scale = gtk_hscale_new (GTK_ADJUSTMENT (adj2)); gtk_scale_set_digits (GTK_SCALE (scale), 0); gtk_box_pack_start (GTK_BOX (box2), scale, TRUE, TRUE, 0); gtk_widget_show (scale);

gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0); gtk_widget_show (separator);

box2 = gtk_vbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0); gtk_widget_show (box2);

button = gtk_button_new_with_label ("Quit"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC(gtk_main_quit), NULL); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show (button);

gtk_widget_show (window);}

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

create_range_controls();

gtk_main();

return(0);}/* fin del ejemplo */

Page 50: GTK Tutorial

9. Widgets varios

9.1 Etiquetas Las etiquetas se usan mucho en GTK y son bastante simples de manejar. No pueden emitir señales ya que no tienen ventanas X window asociadas. Si se desea capturar señales se debe usar el widget EventBox.

Para crear una nueva etiqueta se usa:

GtkWidget *gtk_label_new( char *str );

El único argumento es la cadena de texto que se quiere mostrar. Para cambiarla después de que haya sido creada se usa:

void gtk_label_set( GtkLabel *label, char *str );

En este caso el primer argumento es la etiqueta ya creada (cambiado su tipo mediante la macro GTK_LABEL()) y el segundo es la nueva cadena. El espacio que necesite la nueva etiqueta se ajustará automáticamente, si es necesario.

Para obtener el estado de la cadena en un momento dado existe la función:

void gtk_label_get( GtkLabel *label, char **str );

El primer argumento es la etiqueta, mientras que el segundo es el valor devuelto para la cadena.

9.2 El widget de información rápida (tooltip) Estos widgets son las pequeñas etiquetas que texto que aparecen cuando se sitúa el puntero del ratón sobre un botón u otro widget durante algunos segundos. Son bastante fáciles de usar, así que no se dára ningún ejemplo. Si quiere ver algún ejemplo se recomienda leer el programa testgtk.c que acompaña a GTK.

Algunos widgets (como la etiqueta) no pueden llevar asociado un tooltip.

Para cada función sólo hay que hacer una llamada para conseguir un tooltip. El objeto GtkTooltip que devuelve la siguiente función puede ser usado para crear múltiples widgets.

GtkTooltips *gtk_tooltips_new( void );

Una vez que el tooltip ha sido creado (y el widget sobre el que se quiere usar) simplemente hay que usar la siguiente llamada para pegarlo:

void gtk_tooltips_set_tip( GtkTooltips *tooltips, GtkWidget *widget, const gchar *tip_text, const gchar *tip_private );

El primer argumento es el tooltip que ya ha creado, seguido del widget al que se desea asociar el tooltip, el tercero es el texto que se quiere que aparezca y el último es una cadena de texto que puede ser usada como un identificador cuando se usa GtkTipsQuery para desarollar ayuda sensible al

Page 51: GTK Tutorial

contexto. Por ahora conviene dejarlo como NULL.

Veamos un ejemplo:

GtkTooltips *tooltips;GtkWidget *button;...tooltips = gtk_tooltips_new ();button = gtk_button_new_with_label ("button 1");...gtk_tooltips_set_tip (tooltips, button, "This is button 1", NULL);

Existen otras funciones que pueden ser usadas con los tooltips. Sólamente vamos a enumerlarlas añadiendo una pequeña descripción de que hace cada una.

void gtk_tooltips_enable( GtkTooltips *tooltips );

Permite que funcionen un conjunto de tooltips

void gtk_tooltips_disable( GtkTooltips *tooltips );

Oculta un conjunto de tooltips para que no pueda ser mostrado.

void gtk_tooltips_set_delay( GtkTooltips *tooltips, gint delay );

Establece cuantos milisegundos tiene que estar el puntero sobre el widget para que aparezca el tooltip. Por defecto se usan 1000 milisegundos (1 segundo).

void gtk_tooltips_set_colors( GtkTooltips *tooltips, GdkColor *background, GdkColor *foreground );

Establece el color del texto y del fondo del tooltip. No se como se especifica el color.

9.3 Barras de progreso Estas barras se usan para mostrar el estado de un proceso. Son bastante fáciles de usar, tal y como se verá en los ejemplos siguientes. La primera llamada sirve para crear la barra de progreso:

GtkWidget *gtk_progress_bar_new( void );

Como la barra ya ha sido creada ya puede ser usada:

void gtk_progress_bar_update( GtkProgressBar *pbar, gfloat percentage );

El primer argumento es la barra que se quiere manejar, el segundo es tanto por ciento que ha sido `completado' (indica cuanto ha sido llenada la barra y oscila entre 0-100%). El valor que se le tiene que pasar oscila entre 0 y 1.

Las barras de progreso se usan con otras funciones como los tiempos de espera (timeouts), sección Tiempos de espera, E/S (I/O) y funciones ociosas (idle)) para crear la ilusión de la multitarea. Todas usan la función gtk_progress_bar_update de la misma manera.

Estudiemos un ejemplo de barras de progreso actualizada usando tiempos de espera. También se muestra como se debe reestablecer una barra.

Page 52: GTK Tutorial

/* comienzo del programa-ejemplo progressbar.c */

#include <gtk/gtk.h>

static int ptimer = 0;int pstat = TRUE;

/* Esta función incrementa y actualiza la barra. En el caso de que * pstat sea FALSE la restablece a cero */ gint progress (gpointer data){ gfloat pvalue; /* valor actual de la barra*/ pvalue = GTK_PROGRESS_BAR (data)->percentage; if ((pvalue >= 1.0) || (pstat == FALSE)) { pvalue = 0.0; pstat = TRUE; } pvalue += 0.01; gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); return TRUE;}

/* Esta función indica que la barra debe ser restablecida */

void progress_r (void){ pstat = FALSE; }

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

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *button; GtkWidget *label; GtkWidget *table; GtkWidget *pbar; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (destroy), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); table = gtk_table_new(3,2,TRUE); gtk_container_add (GTK_CONTAINER (window), table);

Page 53: GTK Tutorial

label = gtk_label_new ("Progress Bar Example"); gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1); gtk_widget_show(label); /* Crear una nueva barra de progreso, empaquetarla en la tabla y * mostrarla */ pbar = gtk_progress_bar_new (); gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2); gtk_widget_show (pbar);

ptimer = gtk_timeout_add (100, progress, pbar); /* Mediante este botón se restablece la barra */ button = gtk_button_new_with_label ("Reset"); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (progress_r), NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3); gtk_widget_show(button); button = gtk_button_new_with_label ("Cancel"); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (destroy), NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3); gtk_widget_show (button); gtk_widget_show(table); gtk_widget_show(window); gtk_main (); return 0;}/* final del ejemplo */

Para manejar las barras de progreso hay que llevar a cabo cuatro pasos diferentes. Ahora vamos a estudiar cada uno a partir del ejemplo.

pbar = gtk_progress_bar_new ();

Creamos una nueva barra llamada pbar.

ptimer = gtk_timeout_add (100, progress, pbar);

Usamos un timeout para que el intervalo de tiempo sea constante. Los timeouts no son necesarios para usar barras de progreso.

pvalue = GTK_PROGRESS_BAR (data)->percentage;

Asignamos el valor actual del porcentaje a pvalue.

gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);

Por último actualizamos la barra con el valor de pvalue. Eso es todo lo que hay que saber a cerca de las barras de progreso.

Page 54: GTK Tutorial

9.4 Cuadros de diálogo El widget de cuadro de diálogo es bastante simple, sólo es una ventana con algunas cosas ya preempaquetadas. Su estructura es la siguiente:

struct GtkDialog{ GtkWindow window; GtkWidget *vbox; GtkWidget *action_area;};

Simplemente se crea una ventana en la cual se empaqueta una vbox, un separador y una hbox llamada ``action_area''.

Este tipo de widgets puede ser usado como mensages pop-up (pequeñas ventanas con texto en su interior que aparecen cuando el usuario hace algo y queremos informarle de alguna cosa) y otras cosas parecidas. Su manejo desde el punto de vista del programador es bastante fácil, sólo hay que usar una función:

GtkWidget *gtk_dialog_new( void );

Para crear un nuevo cuadro de diálogo hay que llamar a:

GtkWidget *window;window = gtk_dialog_new ();

Una vez que el cuadro ha sido creado sólo hay que usarlo. Por ejemplo para empaquetar un botón en la action_area escribiríamos algo así:

button = ...gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0);gtk_widget_show (button);

Otra cosa que nos puede interesar es empaquetar una etiqueta en la vbox:

label = gtk_label_new ("Dialogs are groovy");gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, TRUE, 0);gtk_widget_show (label);

Otros ejemplo posible es poner dos botones en el action_area (uno para cancelar y el otro para permitir algo) junto con una etiqueta en la vbox el usuario puede seleccionar lo que quiera.

Si se precisa algo más complejo siempre se puede empaquetar otro widget en cualquiera de las cajas (p.j. una tabla en una vbox).

9.5 Pixmaps Los pixmaps son estructuras de datos que contienen dibujos. Estos pueden ser usados en diferentes lugares, pero los iconos y los cursores son los más comunes. Un bitmap es un pixmap que sólo tiene dos colores.

Para usar un pixmap en GTK primero tiene que construir una estructura del tipo GdkPixmap usando

Page 55: GTK Tutorial

rutinas de GDK. Los pixmaps se pueden crear usando datos que se encuentren en la memoria o en un archivo. Veremos con detalle cada una de las dos posibilidades.

GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window, gchar *data, gint width, gint height );

Esta rutina se utiliza para crear un bitmap a partir de datos almacenados en la memoria. Cada bit de información indica si el pixel luce o no. Tanto la altura como la anchura estan expresadas en pixels. El puntero del tipo GdkWindow indica la ventana en cuestión, ya que los pixmaps sólo tienen sentido dentro de la pantalla en la que van a ser mostrados.

GdkPixmap *gdk_pixmap_create_from_data( GdkWindow *window, gchar *data, gint width, gint height, gint depth, GdkColor *fg, GdkColor *bg );

Con esto creamos un pixmap con la profundidad (número de colores) especificada en los datos del bitmap. Los valores fg y bg son los colores del frente y del fondo respectivamente.

GdkPixmap *gdk_pixmap_create_from_xpm( GdkWindow *window, GdkBitmap **mask, GdkColor *transparent_color, const gchar *filename );

El formato XPM es una representacion de los pixmaps para el sistema X Window. Es bastante popular y existen muchos programas para crear imágenes en este formato. El archivo especificado mediante filename debe contener una imagen en ese formato para que sea cargada en la estructura. La máscara especifica que bits son opacos. Todos los demás bits se colorean usando el color especificado en transparent_color. Más adelante veremos un ejemplo.

GdkPixmap *gdk_pixmap_create_from_xpm_d( GdkWindow *window, GdkBitmap **mask, GdkColor *transparent_color, gchar **data );

Se puede incorporar Imágenes pequeñas dentro de un programa en formato XPM. Un pixmap se crea usando esta información, en lugar de leerla de un archivo. Un ejemplo sería:

/* XPM */static const char * xpm_data[] = {"16 16 3 1"," c None",". c #000000000000","X c #FFFFFFFFFFFF"," "," ...... "," .XXX.X. "," .XXX.XX. "," .XXX.XXX. "," .XXX..... "," .XXXXXXX. ",

Page 56: GTK Tutorial

" .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. "," ......... "," "," "};

Cuando hayamos acabado de usar un pixmap y no lo vayamos a usar durante un tiempo suele ser conveniente liberar el recurso mediante gdk_pixmap_unref(). (Los pixmaps deben ser considerados recursos preciosos).

Una vez que hemos creado el pixmap lo podemos mostrar como un widget GTK. Primero tenemos que crear un widget pixmap que contenga un pixmap GDK. Esto se hace usando:

GtkWidget *gtk_pixmap_new( GdkPixmap *pixmap, GdkBitmap *mask );

Las otras funciones del widget pixmap son:

guint gtk_pixmap_get_type( void );

void gtk_pixmap_set( GtkPixmap *pixmap, GdkPixmap *val, GdkBitmap *mask );

void gtk_pixmap_get( GtkPixmap *pixmap, GdkPixmap **val, GdkBitmap **mask);

La función gtk_pixmap_set se usa para cambiar los datos del pixmap que el widget está manejando en ese momento. val es el pixmap creado usando GDK.

El ejemplo siguiente usa un pixmap en un botón:

/* comienzo del ejemplo pixmap.c */

#include <gtk/gtk.h>

/* Datos en formato XPM del icono de apertura de archivo */static const char * xpm_data[] = {"16 16 3 1"," c None",". c #000000000000","X c #FFFFFFFFFFFF"," "," ...... "," .XXX.X. "," .XXX.XX. "," .XXX.XXX. "," .XXX..... "," .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. ",

Page 57: GTK Tutorial

" .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. "," .XXXXXXX. "," ......... "," "," "};

/* Cuando se llama a esta función (usando signal delete_event) se * termina la aplicación*/

void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit();}

/* Al presionar el botón aparece el mensaje */void button_clicked( GtkWidget *widget, gpointer data ) { printf( "button clicked\n" );}

int main( int argc, char *argv[] ){ GtkWidget *window, *pixmapwid, *button; GdkPixmap *pixmap; GdkBitmap *mask; GtkStyle *style; /* Creamos la ventana principal y relacionamos la señal * delete_event con acabar el programa.*/ gtk_init( &argc, &argv ); window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (close_application), NULL ); gtk_container_border_width( GTK_CONTAINER (window), 10 ); gtk_widget_show( window );

/* Ahora para el pixmap de gdk */ style = gtk_widget_get_style( window ); pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, &style->bg[GTK_STATE_NORMAL], (gchar **)xpm_data );

/* Un pixmap widget que contendrá al pixmap */ pixmapwid = gtk_pixmap_new( pixmap, mask ); gtk_widget_show( pixmapwid );

/* Un botón para contener al pixmap */ button = gtk_button_new(); gtk_container_add( GTK_CONTAINER(button), pixmapwid ); gtk_container_add( GTK_CONTAINER(window), button ); gtk_widget_show( button );

gtk_signal_connect( GTK_OBJECT(button), "clicked",

Page 58: GTK Tutorial

GTK_SIGNAL_FUNC(button_clicked), NULL );

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

Para cargar un archivo llamado icon0.xpm con la información XPM (que se encuentra en en directorio actual) habríamos usado:

/* cargar un pixmap desde un fichero */ pixmap = gdk_pixmap_create_from_xpm( window->window, &mask, &style->bg[GTK_STATE_NORMAL], "./icon0.xpm" ); pixmapwid = gtk_pixmap_new( pixmap, mask ); gtk_widget_show( pixmapwid ); gtk_container_add( GTK_CONTAINER(window), pixmapwid );

Una desventaja de los pixmaps es que la imagen mostrada siempre es rectangular (independientemente de como sea la imagen en sí). Si queremos usar imágenes con otras formas debemos usar ventanas con forma (shaped windows).

Este tipo de ventanas son pixmaps en los que el fondo es transparente. Así cuando la imagen del fondo tiene muchos colores no los sobreescribimos con el borde de nuestro icono. El ejemplo siguiente muestra la imagen de una carretilla en el escritorio.

/* comienzo del ejemplo carretilla wheelbarrow.c */

#include <gtk/gtk.h>

/* XPM */static char * WheelbarrowFull_xpm[] = {"48 48 64 1"," c None",". c #DF7DCF3CC71B","X c #965875D669A6","o c #71C671C671C6","O c #A699A289A699","+ c #965892489658","@ c #8E38410330C2","# c #D75C7DF769A6","$ c #F7DECF3CC71B","% c #96588A288E38","& c #A69992489E79","* c #8E3886178E38","= c #104008200820","- c #596510401040","; c #C71B30C230C2",": c #C71B9A699658","> c #618561856185",", c #20811C712081","< c #104000000000","1 c #861720812081","2 c #DF7D4D344103","3 c #79E769A671C6",

Page 59: GTK Tutorial

"4 c #861782078617","5 c #41033CF34103","6 c #000000000000","7 c #49241C711040","8 c #492445144924","9 c #082008200820","0 c #69A618611861","q c #B6DA71C65144","w c #410330C238E3","e c #CF3CBAEAB6DA","r c #71C6451430C2","t c #EFBEDB6CD75C","y c #28A208200820","u c #186110401040","i c #596528A21861","p c #71C661855965","a c #A69996589658","s c #30C228A230C2","d c #BEFBA289AEBA","f c #596545145144","g c #30C230C230C2","h c #8E3882078617","j c #208118612081","k c #38E30C300820","l c #30C2208128A2","z c #38E328A238E3","x c #514438E34924","c c #618555555965","v c #30C2208130C2","b c #38E328A230C2","n c #28A228A228A2","m c #41032CB228A2","M c #104010401040","N c #492438E34103","B c #28A2208128A2","V c #A699596538E3","C c #30C21C711040","Z c #30C218611040","A c #965865955965","S c #618534D32081","D c #38E31C711040","F c #082000000820"," "," .XoO "," +@#$%o& "," *=-;#::o+ "," >,<12#:34 "," 45671#:X3 "," +89<02qwo ","e* >,67;ro ","ty> 459@>+&& ","$2u+ ><ipas8* ","%$;=* *3:.Xa.dfg> ","Oh$;ya *3d.a8j,Xe.d3g8+ "," Oh$;ka *3d$a8lz,,xxc:.e3g54 "," Oh$;kO *pd$%svbzz,sxxxxfX..&wn> "," Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 "," Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ",

Page 60: GTK Tutorial

" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& "," Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* "," OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* "," 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM "," :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo"," +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g"," *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en"," p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>"," OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B "," 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* "," @26MvzxNzvlbwfpdettttttttttt.c,n& "," *;16=lsNwwNwgsvslbwwvccc3pcfu<o "," p;<69BvwwsszslllbBlllllllu<5+ "," OS0y6FBlvvvzvzss,u=Blllj=54 "," c1-699Blvlllllu7k96MMMg4 "," *10y8n6FjvllllB<166668 "," S-kg+>666<M<996-y6n<8* "," p71=4 m69996kD8Z-66698&& "," &i0ycm6n4 ogk17,0<6666g "," N-k-<> >=01-kuu666> "," ,6ky& &46-10ul,66, "," Ou0<> o66y<ulw<66& "," *kk5 >66By7=xu664 "," <<M4 466lj<Mxu66o "," *>> +66uv,zN666* "," 566,xxj669 "," 4666FF666> "," >966666M "," oM6668+ "," *4 "," "," "};

void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit();}

int main (int argc, char *argv[]){ GtkWidget *window, *pixmap, *fixed; GdkPixmap *gdk_pixmap; GdkBitmap *mask; GtkStyle *style; GdkGC *gc; /* Creamos la ventana principal y relacionamos la señal * delete_event para terminar la aplicación. Conviene destacar * que la ventana no tendrá título puesto que es popup.*/ gtk_init (&argc, &argv); window = gtk_window_new( GTK_WINDOW_POPUP ); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (close_application), NULL); gtk_widget_show (window);

Page 61: GTK Tutorial

style = gtk_widget_get_default_style(); gc = style->black_gc; gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, &style->bg[GTK_STATE_NORMAL], WheelbarrowFull_xpm ); pixmap = gtk_pixmap_new( gdk_pixmap, mask ); gtk_widget_show( pixmap );

fixed = gtk_fixed_new(); gtk_widget_set_usize( fixed, 200, 200 ); gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); gtk_container_add( GTK_CONTAINER(window), fixed ); gtk_widget_show( fixed );

/* Con esto cubrimos todo menos la imagen */ gtk_widget_shape_combine_mask( window, mask, 0, 0 ); /* mostramos la ventana */ gtk_widget_set_uposition( window, 20, 400 ); gtk_widget_show( window ); gtk_main (); return 0;}/* final del ejemplo */

Para que la carretilla sea más realista podríamos relacionar la pulsación del botón con que haga algo. Con las líneas siguientes la pulsación del botón hace que se acabe el programa.

gtk_widget_set_events( window, gtk_widget_get_events( window ) | GDK_BUTTON_PRESS_MASK );

gtk_signal_connect( GTK_OBJECT(window), "button_press_event", GTK_SIGNAL_FUNC(close_application), NULL );

9.6 Reglas Las reglas son usadas para indicar la posición del puntero del ratón en una ventana dada. Una ventana puede tener una regla vertical a lo largo de su alto y una horizontal a lo largo de su ancho. Un pequeño indicador triangular muestra la relación entre el puntero del ratón y la regla.

Las reglas (horizontales y verticales) se crean usando:

GtkWidget *gtk_hruler_new( void ); /* horizontal */GtkWidget *gtk_vruler_new( void ); /* vertical */

Las unidades de la regla pueden ser pixels, pulgadas o centímetros (GKD_PIXELS, GDK_INCHES, GDK_CENTIMETRES). Esto se hace usando:

void gtk_ruler_set_metric( GtkRuler *ruler, GtkMetricType metric );

El valor por defecto es GTK_PIXELS.

Page 62: GTK Tutorial

gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS );

Otra característica importante de las reglas es cómo mostrar las unidades de escala y la posicion inicial dónde se situa el indicador. Todo esto se consigue mediante:

void gtk_ruler_set_range( GtkRuler *ruler, gfloat lower, gfloat upper, gfloat position, gfloat max_size );

Los argumentos lower (valor más bajo) y upper (más alto) delimitan la extensión de la regla. El argumento max_size es el número más alto que será mostrado. Como es lógico position define la posición inicial del indicador dentro de la regla.

Una regla vertical puede puede llegar a ser de 800 pixels:

gtk_ruler_set_range( GTK_RULER(vruler), 0, 800, 0, 800);

Las marcas dentro de la regla oscilarán entre 0 y 800 con una periodicidad de 100. Si queremos que varíe entre 7 y 16 debemos usar:

gtk_ruler_set_range( GTK_RULER(vruler), 7, 16, 0, 20);

El indicador de la regla es un pequeño triángulo que señala la posición del puntero con relación a la regla. Si la regla debe seguir al puntero del ratón la señal motion_notify_event debe estar conectada con el motion_notify_event de la regla. Para seguir todos los movimientos dentro de una ventana conviene usar:

#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x

gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", (GtkSignalFunc)EVENT_METHOD(ruler, motion_notify_event), GTK_OBJECT(ruler) );

El siguiente ejemplo crea una zona de dibujo con una regla horizontal y otra vertical. El tamaño de la zona de dibujo es de 600 x 400 pixels. La regla horizontal oscila entre 7 y 13 con marcas cada 100 pixels, mientras que la vertical va desde 0 a 400 con separaciones cada 100. La zona de dibujo y las reglas se sitúan usando una tabla.

/* comienzo del ejemplo rulers.c */

#include <gtk/gtk.h>

#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x

#define XSIZE 600#define YSIZE 400

/* Esta rutina toma el control cuando se pulsa el botón close */void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) { gtk_main_quit();}

Page 63: GTK Tutorial

int main( int argc, char *argv[] ) { GtkWidget *window, *table, *area, *hrule, *vrule;

gtk_init( &argc, &argv );

window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC( close_application ), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10);

/* creación de la tabla donde pondremos las reglas y la zona de * dibujo */ table = gtk_table_new( 3, 2, FALSE ); gtk_container_add( GTK_CONTAINER(window), table );

area = gtk_drawing_area_new(); gtk_drawing_area_size( (GtkDrawingArea *)area, XSIZE, YSIZE ); gtk_table_attach( GTK_TABLE(table), area, 1, 2, 1, 2, GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0 ); gtk_widget_set_events( area, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK );

/* La regla horizontal está arriba. Cuando el ratón se mueve * a lo largo de la zona de dibujo el controlador de eventos de la * regla recibe motion_notify_event. */ hrule = gtk_hruler_new(); gtk_ruler_set_metric( GTK_RULER(hrule), GTK_PIXELS ); gtk_ruler_set_range( GTK_RULER(hrule), 7, 13, 0, 20 ); gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", (GtkSignalFunc)EVENT_METHOD(hrule, motion_notify_event), GTK_OBJECT(hrule) ); /* GTK_WIDGET_CLASS(GTK_OBJECT(hrule)->klass)->motion_notify_event, */ gtk_table_attach( GTK_TABLE(table), hrule, 1, 2, 0, 1, GTK_EXPAND|GTK_SHRINK|GTK_FILL, GTK_FILL, 0, 0 );

/* la zona de dibujo el controlador de eventos de la regla recibe * motion_notify_event. */ vrule = gtk_vruler_new(); gtk_ruler_set_metric( GTK_RULER(vrule), GTK_PIXELS ); gtk_ruler_set_range( GTK_RULER(vrule), 0, YSIZE, 10, YSIZE ); gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", (GtkSignalFunc) GTK_WIDGET_CLASS(GTK_OBJECT(vrule)->klass)->motion_notify_event, GTK_OBJECT(vrule) ); gtk_table_attach( GTK_TABLE(table), vrule, 0, 1, 1, 2, GTK_FILL, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0 );

/* mostramos todo */ gtk_widget_show( area ); gtk_widget_show( hrule ); gtk_widget_show( vrule );

Page 64: GTK Tutorial

gtk_widget_show( table ); gtk_widget_show( window ); gtk_main();

return 0;}/* final del ejemplo */

9.7 Barras de estado Las barras de estado son widgets usados para mostrar un mensaje. Todo aquello que haya sido mostrado se guarda en una pila, con lo que es muy fácil repetir el último mensaje.

Para permitir que diferentes partes del programa usen la misma barra de estado éstas usan Identificadores por Contexto (Context Identifiers) para identificar a los `usuarios'. El mensaje que está en lo alto de la pila será el siguiente en mostrarse, sin importar el contexto en el que se esté. Los mensajes se almacenan en el orden el último en entrar es el primero en salir, y el Identificador por Contexto no influye en este orden.

Las barras de estado se crean con una llamada a:

GtkWidget *gtk_statusbar_new( void );

Se pide un nuevo Identificador por Contexto con una pequeña descripción textual del contexto y una llamada a la función:

guint gtk_statusbar_get_context_id( GtkStatusbar *statusbar, const gchar *context_description );

Hay tres funciones que pueden manipular las barras de estado:

guint gtk_statusbar_push( GtkStatusbar *statusbar, guint context_id, gchar *text );

void gtk_statusbar_pop( GtkStatusbar *statusbar) guint context_id );

void gtk_statusbar_remove( GtkStatusbar *statusbar, guint context_id, guint message_id );

La primera, gtk_statusbar_push, se utiliza para añadir un nuevo mensaje a la barra de estado. Devuelve un Identificador de Mensaje, que podemos pasarle más tarde a la función gtk_statusbar_remove para eliminar el mensaje con los Identificadores de Contexto y de Mensaje que hay en la pila de barras de estado.

La función gtk_statusbar_pop elimina el mensaje que se encuentra más alto en pila y que contiene el Identificador por Contexto especificado.

El ejemplo siguiente crea una barra de estado y dos botones, uno para meter un elemento en la barra y el otro para sacar el último elemento introducido.

/* Principio del ejemplo de barras de estado statusbar.c */

#include <gtk/gtk.h>

Page 65: GTK Tutorial

#include <glib.h>

GtkWidget *status_bar;

void push_item (GtkWidget *widget, gpointer data){ static int count = 1; char buff[20];

g_snprintf(buff, 20, "Item %d", count++); gtk_statusbar_push( GTK_STATUSBAR(status_bar), (guint) &data, buff);

return;}

void pop_item (GtkWidget *widget, gpointer data){ gtk_statusbar_pop( GTK_STATUSBAR(status_bar), (guint) &data ); return;}

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

GtkWidget *window; GtkWidget *vbox; GtkWidget *button;

int context_id;

gtk_init (&argc, &argv);

/* crear una nueva ventana */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); gtk_window_set_title(GTK_WINDOW (window), "GTK Statusbar Example"); gtk_signal_connect(GTK_OBJECT (window), "delete_event", (GtkSignalFunc) gtk_exit, NULL); vbox = gtk_vbox_new(FALSE, 1); gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox); status_bar = gtk_statusbar_new(); gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0); gtk_widget_show (status_bar);

context_id = gtk_statusbar_get_context_id( GTK_STATUSBAR(status_bar), "Statusbar example");

button = gtk_button_new_with_label("push item"); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC (push_item), &context_id); gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2); gtk_widget_show(button);

button = gtk_button_new_with_label("pop last item"); gtk_signal_connect(GTK_OBJECT(button), "clicked",

Page 66: GTK Tutorial

GTK_SIGNAL_FUNC (pop_item), &context_id); gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2); gtk_widget_show(button);

/* siempre mostramos la ventana en el último paso para que todo se * dibuje en la pantalla de un golpe. */ gtk_widget_show(window);

gtk_main ();

return 0;}/* Fin del ejemplo */

9.8 Entrada de texto El widget Entry permite mostrar e introducir texto en una línea de un cuadro de diálogo. El texto se puede poner con llamadas a funciones que permiten reemplazar, preañadir o añadir el texto al contenido actual del widget Entry.

Hay dos funciones para crear widget Entry:

GtkWidget *gtk_entry_new( void );

GtkWidget *gtk_entry_new_with_max_length( guint16 max );

La primera sirve para crear un nuevo widget Entry, mientras que la segunda crea el widget y además establece un límite en la longitud del texto que irá en el mismo.

hay varias funciones que sirven para alterar el que texto que se está en el widget Entry.

void gtk_entry_set_text( GtkEntry *entry, const gchar *text );

void gtk_entry_append_text( GtkEntry *entry, const gchar *text );

void gtk_entry_prepend_text( GtkEntry *entry, const gchar *text );

La función gtk_entry_set_text cambia el contenido actual del widget Entry. Las funciones gtk_entry_append_text y gtk_entry_prepend_text permiten añadir o preañadir texto.

Las función siguientes permiten decir donde poner el punto de inserción.

void gtk_entry_set_position( GtkEntry *entry, gint position );

Se pueden obtener los contenidos del widget llamando a la función que se describe a continuación. Obtener los contenidos del widget puede ser útil en las funciones de llamada descritas más adelante.

gchar *gtk_entry_get_text( GtkEntry *entry );

Si quiere impedir que alguien cambie el contenido del widget escribiendo en él, utilice la función

void gtk_entry_set_editable( GtkEntry *entry, gboolean editable );

Page 67: GTK Tutorial

Esta función permite camiar el estado de edición de un widget Entry, siendo el argumento editable TRUE o FALSE.

Si estamos utilizando el widget Entry en un sitio donde no queremos que el texto que se introduce sea visible, como por ejemplo cuando estamos introduciendo una clave, podemos utilizar la función siguiente, que también admite como argumento una bandera booleana.

void gtk_entry_set_visibility( GtkEntry *entry, gboolean visible );

Se puede seleccionar una región del texto utilizando la siguiente función. Esta función se puede utilizar después de poner algún texto por defecto en el widget, haciéndole fácil al usuario eliminar este texto.

void gtk_entry_select_region( GtkEntry *entry, gint start, gint end );

Si queremos saber el momento en el que el usuario ha introducido el texto, podemos conectar con las señales activate o changed. activate se activa cuando el usuario aprieta la tecla enter en el widget. changed se activa cuando cambia algo del texto, p.e. cuando se introduce o se elimina algún carácter.

/* inicio-ejemplo entry entry.c */

#include <gtk/gtk.h>

void enter_callback(GtkWidget *widget, GtkWidget *entry){ gchar *entry_text; entry_text = gtk_entry_get_text(GTK_ENTRY(entry)); printf("Entry contents: %s\n", entry_text);}

void entry_toggle_editable (GtkWidget *checkbutton, GtkWidget *entry){ gtk_entry_set_editable(GTK_ENTRY(entry), GTK_TOGGLE_BUTTON(checkbutton)->active);}

void entry_toggle_visibility (GtkWidget *checkbutton, GtkWidget *entry){ gtk_entry_set_visibility(GTK_ENTRY(entry), GTK_TOGGLE_BUTTON(checkbutton)->active);}

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

GtkWidget *window; GtkWidget *vbox, *hbox; GtkWidget *entry; GtkWidget *button; GtkWidget *check;

gtk_init (&argc, &argv);

Page 68: GTK Tutorial

/* crear una nueva ventana */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); gtk_window_set_title(GTK_WINDOW (window), "GTK Entry"); gtk_signal_connect(GTK_OBJECT (window), "delete_event", (GtkSignalFunc) gtk_exit, NULL);

vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox);

entry = gtk_entry_new_with_max_length (50); gtk_signal_connect(GTK_OBJECT(entry), "activate", GTK_SIGNAL_FUNC(enter_callback), entry); gtk_entry_set_text (GTK_ENTRY (entry), "hello"); gtk_entry_append_text (GTK_ENTRY (entry), " world"); gtk_entry_select_region (GTK_ENTRY (entry), 0, GTK_ENTRY(entry)->text_length); gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0); gtk_widget_show (entry);

hbox = gtk_hbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (vbox), hbox); gtk_widget_show (hbox); check = gtk_check_button_new_with_label("Editable"); gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); gtk_signal_connect (GTK_OBJECT(check), "toggled", GTK_SIGNAL_FUNC(entry_toggle_editable), entry); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); gtk_widget_show (check); check = gtk_check_button_new_with_label("Visible"); gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); gtk_signal_connect (GTK_OBJECT(check), "toggled", GTK_SIGNAL_FUNC(entry_toggle_visibility), entry); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); gtk_widget_show (check); button = gtk_button_new_with_label ("Close"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC(gtk_exit), GTK_OBJECT (window)); gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show(window);

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

Page 69: GTK Tutorial

9.9 Selección de Color El widget selección de color, nos permite (¡sorpresa!) la selección interactiva de colores. Este widget compuesto le permite al usuario seleccionar un color manipulando los tripletes RGB (rojo, verde y azul) y HSV (tono, saturación, valor). Para conseguirlo puede ajustar cada variable mediante las regletas o introduciendo directamente el valor deseado. También puede pinchar en la rueda de colores y seleccionar así el color deseado. También se puede establecer, opcionalmente, la transparencia del color.

El widget de selección de color emite (por ahora) sólo una señal, color_changed, que se emite cuando cambia el color seleccionado, ya sea mediante un cambio que haga el usuario o median el resultado de una llamada a la función gtk_color_selection_set_color().

Echémosle un vistazo a lo que nos ofrece el widget de selección de color. El widget tiene dos ``sabores'' diferentes; gtk_color_selection y gtk_color_selection_dialog:

GtkWidget *gtk_color_selection_new( void );

Probablemente no utilizará este constructor directamente. Crea un widget GtkColorSelection huérfano al que le tendrá que asignarle un padre. El widget GtkColorSelection está heredado del widget GtkVBox.

GtkWidget *gtk_color_selection_dialog_new( const gchar *title );

Éste es el constructor del cuadro de selección de color más común. Crea un GtkColorSelectionDialog, heredado de un GtkDialog. Consiste en un GtkFrame con un GtkColorSelection, un GtkHSeparator y un GtkHBox con tres botones, ``Aceptar'', ``Cancelar'' y ``Ayuda''. Puede utilizar estos botones accediendo a los widgets ok_button, cancel_button y help_button de la estructura GtkColorSelectionDialog, (es decir GTK_COLOR_SELECTION_DIALOG(colorseldialog)->ok_button).

void gtk_color_selection_set_update_policy( GtkColorSelection *colorsel, GtkUpdateType policy );

Esta función se utiliza para indicar la política de actuación. La política por defecto es GTK_UPDATE_CONTINUOUS que significa que el color seleccionado se actualiza continuamente cuando el usuario arrastra la barra o selecciona con el ratón un color de la rueda de colores. Si tiene problemas de rendimiento, puede poner la política GTK_UPDATE_DISCONTINUOUS o GTK_UPDATE_DELAYED.

void gtk_color_selection_set_opacity( GtkColorSelection *colorsel, gint use_opacity );

El widget de selección de color admite el ajuste de la transparencia de un color (también conocido como el canal alfa). Esta opción está desactivada por defecto. Si se llama a esta función con use_opacity como TRUE se activa la transparencia. Si se utiliza use_opacity como FALSE se desactiva la transparencia.

void gtk_color_selection_set_color( GtkColorSelection *colorsel, gdouble *color );

Puede poner el color actual explicitamente haciendo uso de esta función con un puntero a un vector de colores (de tipo gdouble). La longitud del vector depende de si está activada la transparencia. La

Page 70: GTK Tutorial

posición 0 contiene la componente roja del color, la 1 contiene la verde, la 2 la azul y la transparencia está en la posición 3 (solamente si está activada la transparencia, ver gtk_color_selection_set_opacity()). Todos los valores se encuentran entre 0.0 y 1.0.

void gtk_color_selection_get_color( GtkColorSelection *colorsel, gdouble *color );

Cuando necesite preguntar por el color actual, normalmente cuando haya recibido una señal color_changed, utilice esta función. color es un puntero al vector de colores que se rellenará. Ver la descripción de la función gtk_color_selection_set_color() para conocer la estructura de este vector.

Aquí tenemos un pequeño ejemplo que muestra el uso de GtkColorSelectionDialog. El programa muestra una ventana con una zona de dibujo. Pulsando en ella se abre un cuadro de diálogo de selección del color, y cambiando el color en el cuadro de diálogo se cambia el color de fondo de la zona de dibujo.

/* principio del ejemplo colorsel colorsel.c */

#include <glib.h>#include <gdk/gdk.h>#include <gtk/gtk.h>

GtkWidget *colorseldlg = NULL;GtkWidget *drawingarea = NULL;

/* Manejador del cambio de color */

void color_changed_cb (GtkWidget *widget, GtkColorSelection *colorsel){ gdouble color[3]; GdkColor gdk_color; GdkColormap *colormap;

/* Obtener el mapa de colores de la zona de dibujo */

colormap = gdk_window_get_colormap (drawingarea->window);

/* Obtener el color actual */

gtk_color_selection_get_color (colorsel,color);

/* Meterlo en un entero sin signo de 16 bits (0..65535) e insertarlo en la estructura GdkColor */

gdk_color.red = (guint16)(color[0]*65535.0); gdk_color.green = (guint16)(color[1]*65535.0); gdk_color.blue = (guint16)(color[2]*65535.0);

/* Pedir memoria para el color */

gdk_color_alloc (colormap, &gdk_color);

/* Poner el color de fondo de la ventana */

gdk_window_set_background (drawingarea->window, &gdk_color);

Page 71: GTK Tutorial

/* Limpiar la ventana */

gdk_window_clear (drawingarea->window);}

/* Manejador del evento Drawingarea */

gint area_event (GtkWidget *widget, GdkEvent *event, gpointer client_data){ gint handled = FALSE; GtkWidget *colorsel;

/* Comprobar si hemos recibido un evento de pulsación de botón */

if (event->type == GDK_BUTTON_PRESS && colorseldlg == NULL) { /* Sí, ¡tenemos un evento y todavía no está el colorseldlg! */

handled = TRUE;

/* Crear el cuadro de diálogo de selección del color */

colorseldlg = gtk_color_selection_dialog_new("Select background color");

/* Obtener el widget GtkColorSelection */

colorsel = GTK_COLOR_SELECTION_DIALOG(colorseldlg)->colorsel;

/* Conectar con la señal ``color_changed'', darle al dato del cliente el valor del widget colorsel */

gtk_signal_connect(GTK_OBJECT(colorsel), "color_changed", (GtkSignalFunc)color_changed_cb, (gpointer)colorsel);

/* Mostrar el cuadro de diálogo */

gtk_widget_show(colorseldlg); }

return handled;}

/* Manipulador de los eventos cerrar y salir */

void destroy_window (GtkWidget *widget, gpointer client_data){ gtk_main_quit ();}

/* Principal */

gint main (gint argc, gchar *argv[]){ GtkWidget *window;

/* Inicializa el toolkit, y elimina las opciones relacionadas con

Page 72: GTK Tutorial

gtk incluidas en la línea de órdenes */

gtk_init (&argc,&argv);

/* Crea la ventana de más alto nivel, le da título y la política */

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW(window), "Color selection test"); gtk_window_set_policy (GTK_WINDOW(window), TRUE, TRUE, TRUE);

/* Enlaza con los eventos ``delete'' y ``destroy'', para que podamos salir */

gtk_signal_connect (GTK_OBJECT(window), "delete_event", (GtkSignalFunc)destroy_window, (gpointer)window);

gtk_signal_connect (GTK_OBJECT(window), "destroy", (GtkSignalFunc)destroy_window, (gpointer)window); /* Crea la zona de dibujo, pone el tamaño y caza los eventos de los botones */

drawingarea = gtk_drawing_area_new ();

gtk_drawing_area_size (GTK_DRAWING_AREA(drawingarea), 200, 200);

gtk_widget_set_events (drawingarea, GDK_BUTTON_PRESS_MASK);

gtk_signal_connect (GTK_OBJECT(drawingarea), "event", (GtkSignalFunc)area_event, (gpointer)drawingarea); /* Add drawingarea to window, then show them both */

gtk_container_add (GTK_CONTAINER(window), drawingarea);

gtk_widget_show (drawingarea); gtk_widget_show (window); /* Entrar en el bucle principal de gtk (nunca sale de aquí) */

gtk_main ();

/* Para satisfacer a los compiladores pijos */

return 0;}/* final del ejemplo */

9.10 Selección de ficheros El widget de selección de ficheros nos proporciona una forma rápida y sencilla de mostrar un cuadro de diálogo para la selección de un fichero (¡sorpresa!). Ya viene con los botones Aceptar, Cancelar y ayuda. Una magnifica ayuda para acortar el tiempo de programación.

Para crear un nuevo cuadro de diálogo de selección de ficheros utilice:

Page 73: GTK Tutorial

GtkWidget *gtk_file_selection_new( gchar *title );

Para poner el nombre del fichero en el cuadro de diálogo, por ejemplo para poder utilizar un directorio o un fichero por defecto, utilice la función:

void gtk_file_selection_set_filename( GtkFileSelection *filesel, gchar *filename );

Para obtener el texto que el usuario ha introducido, utilice la función:

gchar *gtk_file_selection_get_filename( GtkFileSelection *filesel );

También hay punteros a los widgets que contiene el cuadro de diálogo. Son los siguientes:

• dir_list • file_list • selection_entry • selection_text • main_vbox • ok_button • cancel_button • help_button

Lo más probable es que sólo utilice los punteros ok_button, cancel_button, y help_button para controlar cuando se pulsan.

Aquí incluímos un ejemplo robado de testgtk.c, modificado para que se puede ejecutar independientemente. Como puede ver, no es muy complicado crear un widget para la selección de ficheros. Aunque aparezca el botón de ayuda en la pantalla, no hace nada y no tiene ninguna señal conectada.

/* principio del ejemplo filesel filesel.c */

#include <gtk/gtk.h>

/* Obtener el nombre del fichero e imprimirlo en la consola */void file_ok_sel (GtkWidget *w, GtkFileSelection *fs){ g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));}

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

int main (int argc, char *argv[]){ GtkWidget *filew; gtk_init (&argc, &argv); /* Crear un nuevo widget de selección de ficheros */ filew = gtk_file_selection_new ("File selection");

Page 74: GTK Tutorial

gtk_signal_connect (GTK_OBJECT (filew), "destroy", (GtkSignalFunc) destroy, &filew); /* Conectar el ok_button con la función file_ok_sel */ gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), "clicked", (GtkSignalFunc) file_ok_sel, filew ); /* Conectar el cancel_button con la destrucción del widget */ gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), "clicked", (GtkSignalFunc) gtk_widget_destroy, GTK_OBJECT (filew)); /* Damos el nombre del fichero, como si fuese un cuadro de diálogo para grabar ficheros y estuviesemos dando un nombre por defecto */ gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), "penguin.png"); gtk_widget_show(filew); gtk_main (); return 0;}/* fin del ejemplo */

10. Widgets Contenedores

10.1 Libros de notas (Notebooks) El widget Notebook es una colección de `páginas' que se solapan las unas a las otras, cada unas con un contenido diferente. Este widget se ha vuelto cada vez más común últimamente en la programación de interfaces gráficos de usuario (GUI en inglés), y es una buena forma de mostrar bloques de información similar que necesitan aparecer de forma separada.

La primera función que necesita conocer, como probablemente ya habrá adivinado, se utiliza para crear un nuevo widget notebook.

GtkWidget *gtk_notebook_new( void );

Una vez haya crear el libro de notas, hay 12 funciones que se pueden utilizar para trabajar con él. Echémosles un vistazo una a una.

La primera que estudiaremos será la que nos permita establecer la posición de los indicadores de la página. Estos indicadores se pueden poner en cuatro lugares diferentes: arriba, abajo, a la derecha o a la izquierda.

void gtk_notebook_set_tab_pos( GtkNotebook *notebook, GtkPositionType pos );

GtkPositionType debe tener uno de los valores siguientes (su significado está bastante claro):

Page 75: GTK Tutorial

• GTK_POS_LEFT • GTK_POS_RIGHT • GTK_POS_TOP • GTK_POS_BOTTOM

GTK_POS_TOP es el valor por defecto.

Lo siguiente que estudiaremos es como añadir páginas al libro de notas. Hay tres formas de añadirle páginas al widget. Veamos las dos primeras formas (son muy parecidas).

void gtk_notebook_append_page( GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label );

void gtk_notebook_prepend_page( GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label );

Estas funciones le añaden páginas al libro de notas insertándolas desde el fondo del libro (añadiéndolas), o desde parte superior del libro (preañadiéndolas). child es el widget que se mete en la página del libro de notas, y tab_label es la etiqueta para la página que estamos añadiendo.

La función que queda que sirve para añadir una página contiene todas las propiedades de las anteriores, pero además permite especificar en que posición quiere que esté la página dentro del libro de notas.

void gtk_notebook_insert_page( GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position );

Los parámetros son los mismos que habían en las funciones _append_ y _prepend_ excepto que hay uno más que antes, position. Este parámetro se utiliza para especificar en que lugar debe introducirse la página.

Ahora que sabemos como añadir un página, veamos como podemos eliminar una página del libro de notas.

void gtk_notebook_remove_page( GtkNotebook *notebook, gint page_num );

Esta función coge la página especificada por page_num y la elimina del widget al que apunta notebook.

Para saber cual es la página actual del libro de notas utilice la función:

gint gtk_notebook_current_page( GtkNotebook *notebook );

Las dos funciones siguientes sirven para ir a la página siguiente o a la anterior del libro de notas. Para utilizarlas sólo hay que proporcionar el widget notebook que queremos manipular. Nota: cuando el libro de notas está en la última página y se llama a gtk_notebook_next_page, se pasará a la primera página. Sin embargo, si el libro de notas está en la primera página, y se llama a gtk_notebook_prev_page, no se pasará a la última página.

void gtk_notebook_next_page( GtkNoteBook *notebook );

void gtk_notebook_prev_page( GtkNoteBook *notebook );

Page 76: GTK Tutorial

La siguiente función establece la página `activa'. Si quiere que se abra el libro de notas por la página 5, por ejemplo, debe utilizar esta función. Si no utiliza esta función el libro de notas empezará por defecto en la primera página.

void gtk_notebook_set_page( GtkNotebook *notebook, gint page_num );

Las dos funciones siguientes añaden o eliminan los indicadores de las páginas o el borde del libro, respectivamente.

void gtk_notebook_set_show_tabs( GtkNotebook *notebook, gint show_tabs);

void gtk_notebook_set_show_border( GtkNotebook *notebook, gint show_border );

show_tabs y show_border puede ser TRUE o FALSE.

Ahora echémosle un vistaza a un ejemplo, sacado del código de testgtk.c que viene con la distribución de GTK, y que muestra la utilización de las 13 funciones. Este pequeño programa crea una ventana con un libro de notas y seis botones. El libro de notas contiene 11 páginas, incluidas de tres formas diferentes, añadidas, insertadas, y preañadidas. Los botones le permiten rotar las posiciones de los indicadores, añadir y eliminar los indicadores y el borde, eliminar una página, cambiar páginas hacia delante y hacia detrás, y salir del programa.

/* principio del ejemplo notebook notebook.c */

#include <gtk/gtk.h>

/* Esta función rota la posición de los indicadores */void rotate_book (GtkButton *button, GtkNotebook *notebook){ gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4);}

/* Añade/Elimina los indicadores de la página y los bordes */void tabsborder_book (GtkButton *button, GtkNotebook *notebook){ gint tval = FALSE; gint bval = FALSE; if (notebook->show_tabs == 0) tval = TRUE; if (notebook->show_border == 0) bval = TRUE; gtk_notebook_set_show_tabs (notebook, tval); gtk_notebook_set_show_border (notebook, bval);}

/* Elimina una página del libro de notas */void remove_book (GtkButton *button, GtkNotebook *notebook){ gint page; page = gtk_notebook_current_page(notebook); gtk_notebook_remove_page (notebook, page); /* Hay que redibujar el widget --

Page 77: GTK Tutorial

Esto fuerza que el widget se autoredibuje */ gtk_widget_draw(GTK_WIDGET(notebook), NULL);}

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

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *button; GtkWidget *table; GtkWidget *notebook; GtkWidget *frame; GtkWidget *label; GtkWidget *checkbutton; int i; char bufferf[32]; char bufferl[32]; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); table = gtk_table_new(2,6,TRUE); gtk_container_add (GTK_CONTAINER (window), table); /* Crea un nuevo libro de notas, indicando la posición de los indicadores */ notebook = gtk_notebook_new (); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1); gtk_widget_show(notebook); /* le añadimos un montón de páginas al libro de notas */ for (i=0; i < 5; i++) { sprintf(bufferf, "Append Frame %d", i+1); sprintf(bufferl, "Page %d", i+1); frame = gtk_frame_new (bufferf); gtk_container_border_width (GTK_CONTAINER (frame), 10); gtk_widget_set_usize (frame, 100, 75); gtk_widget_show (frame); label = gtk_label_new (bufferf); gtk_container_add (GTK_CONTAINER (frame), label); gtk_widget_show (label); label = gtk_label_new (bufferl); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); }

Page 78: GTK Tutorial

/* Ahora añadimos una página en punto específico */ checkbutton = gtk_check_button_new_with_label ("Check me please!"); gtk_widget_set_usize(checkbutton, 100, 75); gtk_widget_show (checkbutton); label = gtk_label_new ("Add spot"); gtk_container_add (GTK_CONTAINER (checkbutton), label); gtk_widget_show (label); label = gtk_label_new ("Add page"); gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); /* Y finalmente preañadimos páginas en el libro de notas */ for (i=0; i < 5; i++) { sprintf(bufferf, "Prepend Frame %d", i+1); sprintf(bufferl, "PPage %d", i+1); frame = gtk_frame_new (bufferf); gtk_container_border_width (GTK_CONTAINER (frame), 10); gtk_widget_set_usize (frame, 100, 75); gtk_widget_show (frame); label = gtk_label_new (bufferf); gtk_container_add (GTK_CONTAINER (frame), label); gtk_widget_show (label); label = gtk_label_new (bufferl); gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label); } /* Decimos en que página empezar (página 4) */ gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3); /* creamos un montón de botones */ button = gtk_button_new_with_label ("close"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (delete), NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("next page"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_notebook_next_page, GTK_OBJECT (notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("prev page"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_notebook_prev_page, GTK_OBJECT (notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("tab position");

Page 79: GTK Tutorial

gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) rotate_book, GTK_OBJECT(notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("tabs/border on/off"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) tabsborder_book, GTK_OBJECT (notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("remove page"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) remove_book, GTK_OBJECT(notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2); gtk_widget_show(button); gtk_widget_show(table); gtk_widget_show(window); gtk_main (); return 0;}/* fin del ejemplo */

Espero que la explicación le ayude de alguna manera a crear libros de notas en sus aplicaciones GTK.

10.2 Ventanas con barras de desplazamiento Las ventanas con barras de desplazamiento se utilizan para crear una zona con barras de desplazamiento dentro de una ventana real. Puede insertar cualquier tipo de widget en una ventana con barras de desplazamiento, y podrá utilizarlo sin importar su tamaño gracias a las barras de desplazamiento.

La función siguiente se utiliza para crear una nueva ventana con barras de desplazamiento.

GtkWidget *gtk_scrolled_window_new( GtkAdjustment *hadjustment, GtkAdjustment *vadjustment );

Donde el primer argumento es el ajuste para la dirección horizontal, y el segundo es el ajuste para la dirección vertical. Casi siempre valen NULL.

void gtk_scrolled_window_set_policy( GtkScrolledWindow *scrolled_window, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy );

Esta función establece la política que se utilizará con respecto a las barras de desplazamiento. El primer argumento es la ventana con barras de desplazamiento sobre la que queremos actuar. El segundo establece la política para la barra de desplazamiento horizontal, y el tercero la política para la barra de desplazamiento vertical.

Page 80: GTK Tutorial

La política puede ser GTK_POLICY_AUTOMATIC, o GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC decidirá automáticamente si necesita barras de desplazamiento, mientras que GTK_POLICY_ALWAYS pondrá siempre las barras de desplazamiento.

Aquí tenemos un ejemplo sencillo que empaqueta 100 botones de selección en una ventana con barras de desplazamiento. Solamente he comentado las partes que debería ser nuevas para usted.

/* principio del ejemplo scrolledwin scrolledwin.c */

#include <gtk/gtk.h>

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

int main (int argc, char *argv[]){ static GtkWidget *window; GtkWidget *scrolled_window; GtkWidget *table; GtkWidget *button; char buffer[32]; int i, j; gtk_init (&argc, &argv); /* Crea un nuevo cuadro de diálogo para que la ventana con barras de * desplazamiento se meta dentro. Un cuadro de diálogo es como una * ventana normal excepto que tiene dentro una vbox y un separador * horizontal. Es sólo un atajo para crear cuadros de diálogo */ window = gtk_dialog_new (); gtk_signal_connect (GTK_OBJECT (window), "destroy", (GtkSignalFunc) destroy, NULL); gtk_window_set_title (GTK_WINDOW (window), "dialog"); gtk_container_border_width (GTK_CONTAINER (window), 0); gtk_widget_set_usize(window, 300, 300); /* crea una nueva ventana con barras de desplazamiento. */ scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10);

/* la política es GTK_POLICY_AUTOMATIC, o GTK_POLICY_ALWAYS. * GTK_POLICY_AUTOMATIC decidirá automáticamente si necesita * barras de desplazamiento, mientras que GTK_POLICY_ALWAYS pondrá * siempre las barras de desplazamiento. El primer argumento se * refiere a la barra horizontal, el segundo a la vertical. */ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); /* El cuadro de diálogo se crea con una vbox dentro de él. */ gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, TRUE, TRUE, 0); gtk_widget_show (scrolled_window);

Page 81: GTK Tutorial

/* crea una tabla de 10 por 10 casillas. */ table = gtk_table_new (10, 10, FALSE); /* pone el espacio en x y en y a 10 */ gtk_table_set_row_spacings (GTK_TABLE (table), 10); gtk_table_set_col_spacings (GTK_TABLE (table), 10); /* empaqueta la tabla en la ventana con barras de desplazamiento */ gtk_container_add (GTK_CONTAINER (scrolled_window), table); gtk_widget_show (table); /* crea una rejilla de botones de selección en la tabla para * demostrar la ventana con barras de desplazamiento. */ for (i = 0; i < 10; i++) for (j = 0; j < 10; j++) { sprintf (buffer, "button (%d,%d)\n", i, j); button = gtk_toggle_button_new_with_label (buffer); gtk_table_attach_defaults (GTK_TABLE (table), button, i, i+1, j, j+1); gtk_widget_show (button); } /* Añade un botón "close" en la parte de abajo del cuadro de * diálogo */ button = gtk_button_new_with_label ("close"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_widget_destroy, GTK_OBJECT (window)); /* hace que el botón puede ser elegido por defecto. */ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); /* Hace que el botón sea el elegido por defecto. Con pulsar la * tecla "Enter" se activará este botón. */ gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show (window); gtk_main(); return(0);}/* fin del ejemplo */

Juegue un poco redimensionando la ventana. Vea como actuan las barras de desplazamiento. También puede utilizar la función gtk_widget_set_usize() para poner el tamaño por defecto de la ventana o de cualquier otro widget.

10.3 El widget ``ventana dividida'' (Paned Window) El widget ventana dividida es útil para cuando se quiere dividir una zona en dos partes, con un tamaño relativo controlado por el usuario. Entre las dos porciones de la ventana se dibuja un separador con un

Page 82: GTK Tutorial

botoncito que el usuario puede arrastrar para cambiar el tamaño de cada zona. La división puede ser horizontal (HPaned) o vertical (VPaned).

Para crear una nueva ventana dividida, utilice una de las siguientes funciones:

GtkWidget *gtk_hpaned_new (void);

GtkWidget *gtk_vpaned_new (void);

Después de crear el widget ventana dividida, tiene que añadirle un widget hijo a cada mitad. Para hacerlo, utilice:

void gtk_paned_add1 (GtkPaned *paned, GtkWidget *child);

void gtk_paned_add2 (GtkPaned *paned, GtkWidget *child);

gtk_paned_add1() añade el widget hijo a la mitad que se encuentra en la parte izquierda o superior de la ventana dividida. gtk_paned_add2() añade el widget a la mitad que hay en la parte derecha o inferior de la ventana.

Por ejemplo, si queremos crear una parte del interface de usuario de un programa de correo-e imaginario. Dividiremos verticalmente una ventana en dos partes, teniendo en la parte superior una lista de los mensajes de correo-e y en la parte inferior el texto de uno de estos mensajes. El programa es bastante fácil de entender. Solo un par de cosillas: no se puede añadir texto en un widget de texto (Text) si no se ha hecho antes gtk_widget_realize(), pero como demostración de una técnica alternativa, para añadir el texto conectaremos un manipulador a la señal ``realize''. Y tenemos que añadir la opción GTK_SHRINK a algunos de los elementos que hay en la tabla con la ventana de texto y sus barras de desplazamiento, así cuando la porción de abajo se haga más pequeña, se encogerá correctamente en lugar de desaparecer por la parte de abajo de la ventana.

/* principio del ejemplo paned paned.c */

#include <gtk/gtk.h> /* Crear la lista de "messages" */GtkWidget *create_list (void){

GtkWidget *scrolled_window; GtkWidget *list; GtkWidget *list_item; int i; char buffer[16]; /* Crear una nueva ventana con barras de desplazamiento si hacen falta */ scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); /* Crear una nueva lista y poner en ella la ventana con barras */ list = gtk_list_new (); gtk_container_add (GTK_CONTAINER(scrolled_window), list);

Page 83: GTK Tutorial

gtk_widget_show (list); /* Añadir algunos mensajes a la ventana */ for (i=0; i<10; i++) {

sprintf(buffer,"Message #%d",i); list_item = gtk_list_item_new_with_label (buffer); gtk_container_add (GTK_CONTAINER(list), list_item); gtk_widget_show (list_item);

} return scrolled_window;} /* Añadir algún texto a nuestro widget de texto - esta función se invoca cada vez que se produce una señal realize en la ventana. Podemos forzar esta señal mediante gtk_widget_realize, pero primero tiene que formar parte de una jerarquía */

voidrealize_text (GtkWidget *text, gpointer data){ gtk_text_freeze (GTK_TEXT (text)); gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL, "From: [email protected]\n" "To: [email protected]\n" "Subject: Made it!\n" "\n" "We just got in this morning. The weather has been\n" "great - clear but cold, and there are lots of fun sights.\n" "Sojourner says hi. See you soon.\n" " -Path\n", -1); gtk_text_thaw (GTK_TEXT (text));} /* Creamos una zona con texto que muestra un "message" */GtkWidget *create_text (void){ GtkWidget *table; GtkWidget *text; GtkWidget *hscrollbar; GtkWidget *vscrollbar; /* Crea una tabla para contener el widget de texto y las barras de desplazamiento */ table = gtk_table_new (2, 2, FALSE); /* Pone un widget de texto en la esquina superior izquierda. Observe la utilización de GTK_SHRINK en la dirección y */ text = gtk_text_new (NULL, NULL); gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0); gtk_widget_show (text);

Page 84: GTK Tutorial

/* Pone una HScrollbar en la esquina inferior izquierda */ hscrollbar = gtk_hscrollbar_new (GTK_TEXT (text)->hadj); gtk_table_attach (GTK_TABLE (table), hscrollbar, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (hscrollbar); /* Y una VScrollbar en la esquina superior derecha */ vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj); gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1, GTK_FILL, GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0, 0); gtk_widget_show (vscrollbar); /* Y un manejador para poner un mensaje en el widget de texto cuando reciba realize */ gtk_signal_connect (GTK_OBJECT (text), "realize", GTK_SIGNAL_FUNC (realize_text), NULL); return table;} intmain (int argc, char *argv[]){ GtkWidget *window; GtkWidget *vpaned; GtkWidget *list; GtkWidget *text;

gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Paned Windows"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); /* crea un widget vpaned y lo añade a nuestra ventana superior */ vpaned = gtk_vpaned_new (); gtk_container_add (GTK_CONTAINER(window), vpaned); gtk_widget_show (vpaned); /* Ahora crea los contenidos de las dos mitades de la ventana */ list = create_list (); gtk_paned_add1 (GTK_PANED(vpaned), list); gtk_widget_show (list); text = create_text (); gtk_paned_add2 (GTK_PANED(vpaned), text); gtk_widget_show (text); gtk_widget_show (window); gtk_main (); return 0;}/* fin del ejemplo */

Page 85: GTK Tutorial

10.4 Barras de herramientas Las barras de herramientas acostumbran a agrupar un conjunto de widgets para hacer más sencilla la personalización de su aspecto y composición. Típicamente una barra de herramientas consiste en botones con iconos, etiquetas y tips para los iconos (pequeño texto descriptivo que aparece cuando se mantiene el ratón sobre el icono), pero en realidad en una barra se puede poner cualquier tipo de widget. Finalmente, los elementos se pueden disponer de forma horizontal o vertical, y los botones pueden mostrar iconos, etiquetas o ambos.

La creación de una barra de herramientas se hace (como puede que ya haya sospechado) mediante la función siguiente:

GtkWidget *gtk_toolbar_new( GtkOrientation orientation, GtkToolbarStyle style );

donde orientation puede ser:

GTK_ORIENTATION_HORIZONTAL GTK_ORIENTATION_VERTICAL

y style:

GTK_TOOLBAR_TEXT GTK_TOOLBAR_ICONS GTK_TOOLBAR_BOTH

La variable style se aplica a todos los botones que se crean con las funciones `item' (pero no a los botones insertados en la barra de herramientas como widgets separados).

Después de crear una barra de herramientas, se pueden añadir, preañadir e insertar elementos (o sea, botones) en la misma. Los campos que describen un elemento son el texto de la etiqueta, el texto del tip, un texto para el tip privado, un icono para el botón y una función de llamada para el mismo. Por ejemplo, para añadir un elemento puede utilizar la siguiente función:

GtkWidget *gtk_toolbar_append_item( GtkToolbar *toolbar, const char *text, const char *tooltip_text, const char *tooltip_private_text, GtkWidget *icon, GtkSignalFunc callback, gpointer user_data );

Si quiere utilizar gtk_toolbar_insert_item, el único parámetro adicional que debería especificar es la posición en la que quiere que se introduzca el elemento.

Para añadir un espacio en blanco entre los elementos de la barra de herramientas, puede utilizar la función siguiente:

void gtk_toolbar_append_space( GtkToolbar *toolbar );

void gtk_toolbar_prepend_space( GtkToolbar *toolbar );

void gtk_toolbar_insert_space( GtkToolbar *toolbar, gint position );

Page 86: GTK Tutorial

Y el tamaño del espacio en blanco puede establecerse globalmente para toda una barra de herramientas con la función:

void gtk_toolbar_set_space_size( GtkToolbar *toolbar, gint space_size) ;

Si tiene que establecer la orientación de una barra de herramientas y su estilo, puede hacerlo `al vuelo' con las funciones siguientes:

void gtk_toolbar_set_orientation( GtkToolbar *toolbar, GtkOrientation orientation );

void gtk_toolbar_set_style( GtkToolbar *toolbar, GtkToolbarStyle style );

void gtk_toolbar_set_tooltips( GtkToolbar *toolbar, gint enable );

Para mostrar algunas otras cosas que pueden hacerse con una barra de herramientas, vamos a ver el siguiente programa (interrumpiremos el listado con alguna explicación adicional):

#include <gtk/gtk.h>

#include "gtk.xpm"

/* Esta función está conectada al botón Close o a la acción de cerrar * la ventana desde el WM */void delete_event (GtkWidget *widget, GdkEvent *event, gpointer data){ gtk_main_quit ();}

Este principio ya debería de sonarle familiar, a no ser que éste sea su primer programa GTK. En nuestro programa no habrá ninguna novedad, salvo un bonito dibujo XPM que utilizaremos como icono para todos los botones.

GtkWidget* close_button; // este botón emitirá la señal de cerrar el programaGtkWidget* tooltips_button; // para activar/desactivar los tooltipsGtkWidget* text_button, * icon_button, * both_button; // botones circulares para el estilo de la barraGtkWidget* entry; // un widget para meter texto para mostrar como // empaquetar widgets en la barra de herramientas

En realidad no necesitamos todos los widgets que acabo de poner, pero para aclarar las cosas un poco más los he puesto todos.

/* Esto es fácil... cuando uno de los botones cambia, sólo * tenemos que comprobar quien está activo y hacer que el estilo * de la barra de herramientas esté acorde con la elección * ATENCIÓN: ¡nuestra barra de herramientas es data !void radio_event (GtkWidget *widget, gpointer data){ if (GTK_TOGGLE_BUTTON (text_button)->active) gtk_toolbar_set_style(GTK_TOOLBAR ( data ), GTK_TOOLBAR_TEXT); else if (GTK_TOGGLE_BUTTON (icon_button)->active)

Page 87: GTK Tutorial

gtk_toolbar_set_style(GTK_TOOLBAR ( data ), GTK_TOOLBAR_ICONS); else if (GTK_TOGGLE_BUTTON (both_button)->active) gtk_toolbar_set_style(GTK_TOOLBAR ( data ), GTK_TOOLBAR_BOTH);}

/* todavía más fácil, sólo hay que comprobar el botón de selección * y activar/desactivar los tooltips */void toggle_event (GtkWidget *widget, gpointer data){ gtk_toolbar_set_tooltips (GTK_TOOLBAR ( data ), GTK_TOGGLE_BUTTON (widget)->active );}

Lo de arriba son sólo dos funciones de llamada que se invocarán cuando se presione uno de los botones de la barra de herramientas. Todo esto ya debería resultarle familiar si ha utilizado alguna vez los botones de selección (o los botones circulares)

int main (int argc, char *argv[]){ /* Aquí está nuestra ventana principal (un cuadro de diálogo) y una * caja flotante */ GtkWidget* dialog; GtkWidget* handlebox;

/* De acuerdo, necesitamos una barra de herramientas, un icono con * una máscara (una para todos los botones) y un widget icono donde * meter el icono (crearemos un widget diferente para cada botón) */ GtkWidget * toolbar; GdkPixmap * icon; GdkBitmap * mask; GtkWidget * iconw;

/* a esta función se le llama en todas las aplicación GTK */ gtk_init (&argc, &argv); /* crear una ventana nueva con un título y el tamaño adecuado */ dialog = gtk_dialog_new (); gtk_window_set_title ( GTK_WINDOW ( dialog ) , "GTKToolbar Tutorial"); gtk_widget_set_usize( GTK_WIDGET ( dialog ) , 600 , 300 ); GTK_WINDOW ( dialog ) ->allow_shrink = TRUE;

/* salimos si alguien intenta cerrarnos */ gtk_signal_connect ( GTK_OBJECT ( dialog ), "delete_event", GTK_SIGNAL_FUNC ( delete_event ), NULL);

/* tenemos que mandar la señalo realize porque utilizamos pixmaps * para los elementos que hay en la barra de herramientas */ gtk_widget_realize ( dialog );

/* para hacerlo más bonito ponemos la barra de herramientas en la * caja flotante, para que así se pueda desatar de la ventana * principal */ handlebox = gtk_handle_box_new (); gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG(dialog)->vbox ), handlebox, FALSE, FALSE, 5 );

Lo de arriba debería ser parecido en cualquier aplicación GTK. Sólo está la inicialización de GTK, la

Page 88: GTK Tutorial

creación de la ventana, etc... Solamente hay una cosa que probablemente necesite una explicación: una barra de herramientas flotante. Una barra de herramientas flotante sólo es otra barra donde pueden empaquetarse widgets. La diferencia que tiene con una barra típica es que puede desatarse de la ventana padre (o, de hecho, la barra de herramientas flotante permanece en el padre, pero reducida a un rectángulo muy pequeño, mientras que todos sus contenidos se pasan a una nueva ventana flotante). Es bonito tener una barra de herramientas flotante, por lo que estos dos widgets suelen aparecer juntos.

/* la barra de herramientas será horizontal, con iconos y texto, y * con un espacio de 5pxl entre elementos y finalmente, la ponemos en * nuestra caja flotante */ toolbar = gtk_toolbar_new ( GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH ); gtk_container_border_width ( GTK_CONTAINER ( toolbar ) , 5 ); gtk_toolbar_set_space_size ( GTK_TOOLBAR ( toolbar ), 5 ); gtk_container_add ( GTK_CONTAINER ( handlebox ) , toolbar );

/* ahora creamos el icono con la máscara: utilizaremos el widget * icon con todos los elementos de la barra de herramientas */ icon = gdk_pixmap_create_from_xpm_d ( dialog->window, &mask, &dialog->style->white, gtk_xpm );

Bien, lo que acabamos de escribir es la inicialización del widget de la barra de herramientas y la creación de un pixmap GDK con su máscara. Si quiere saber algo más sobre la utilización de pixmaps, vea la documentación de GDK o la sección Pixmaps en este tutorial.

/* nuestro primer elemento es el botón <close> */ iconw = gtk_pixmap_new ( icon, mask ); // icon widget close_button = gtk_toolbar_append_item ( GTK_TOOLBAR (toolbar), // nuestra barra "Close", // etiqueta del botón "Closes this app", // tooltip para el botón "Private", // cadena privada del tooltip iconw, // widget del icono GTK_SIGNAL_FUNC (delete_event), // una señal NULL ); gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) ); // espacio después del elemento

En el trozo de código de arriba puede ver como se hace la acción más simple: añadir un botón a la barra de herramientas. Justo antes de añadir un nuevo elemento, tenemos que construir un widget pixmap para que sirva como icono para este elemento; este paso tendrá que repetirse para cada nuevo elemento. Después del elemento añadiremos un espacio en blanco en la barra de herramientas, para que los elementos que añadamos a continuación no se toquen los unos a los otros. Como puede ver, gtk_toolbar_append_item devuelve un puntero al widget de nuestro nuevo botón recien creado, por lo que podremos trabajar con él como siempre.

/* ahora, vamos a hacer nuestro grupo de botones circulares... */ iconw = gtk_pixmap_new ( icon, mask ); icon_button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_CHILD_RADIOBUTTON, // un tipo de elemento

Page 89: GTK Tutorial

NULL, // puntero al widget "Icon", // etiqueta "Only icons in toolbar", // tooltip "Private", // cadena privada del tooltip iconw, // icono GTK_SIGNAL_FUNC (radio_event), // señal toolbar); // dato para la señal gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );

Aquí empezamos creando un grupo de botones circulares. Para hacerlo hemos utilizado gtk_toolbar_append_element. De hecho, utilizando esta función se pueden añadir tanto elementos simples como espacios en blanco (tipo = GTK_TOOLBAR_CHILD_SPACE o GTK_TOOLBAR_CHILD_BUTTON). En el caso de arriba, hemos empezado creando un grupo de botones circulares. Para crear más botones circulares para este grupo necesitaremos un puntero al botón anterior del grupo, mediante el que podremos construir fácilmente una lista de botones (ver la sección Botones circulares que se encuentra más adelante en este tutorial).

/* following radio buttons refer to previous ones */ iconw = gtk_pixmap_new ( icon, mask ); text_button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_CHILD_RADIOBUTTON, icon_button, "Text", "Only texts in toolbar", "Private", iconw, GTK_SIGNAL_FUNC (radio_event), toolbar); gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) ); iconw = gtk_pixmap_new ( icon, mask ); both_button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_CHILD_RADIOBUTTON, text_button, "Both", "Icons and text in toolbar", "Private", iconw, GTK_SIGNAL_FUNC (radio_event), toolbar); gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) ); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(both_button),TRUE);

Al final hemos activado manualmente uno de los botones (en caso contrario los botones permanecerían todos en estado activo, impidiéndonos poder cambiar de uno a otro).

/* aquí tenemos un sencillo botón de selección */ iconw = gtk_pixmap_new ( icon, mask ); tooltips_button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_CHILD_TOGGLEBUTTON, NULL,

Page 90: GTK Tutorial

"Tooltips", "Toolbar with or without tips", "Private", iconw, GTK_SIGNAL_FUNC (toggle_event), toolbar); gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) ); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(tooltips_button),TRUE);

Un botón de selección puede crearse de una forma obvia (si ya sabe como crear botones circulares).

/* para empaquetar un widget en la barra de herramientas, sólo * tenemos que crearlo y añadirlo en la barra con el tooltip * apropiado */ entry = gtk_entry_new (); gtk_toolbar_append_widget( GTK_TOOLBAR (toolbar), entry, "This is just an entry", "Private" );

/* bien, no se ha creado con la barra, así que debemos mostrarlo * explicitamente */ gtk_widget_show ( entry );

Como puede ver, añadir cualquier tipo de widget a la barra de herramientas es fácil. Lo único que debe recordar es que este widget debe mostrarse manualmente (al contrario que los demás elementos que se mostrarán junto con la barra de herramientas).

/* ¡ Eso es ! mostremos algo. */ gtk_widget_show ( toolbar ); gtk_widget_show (handlebox); gtk_widget_show ( dialog );

/* quedémonos en gtk_main y ¡esperemos a que empiece la diversión! */ gtk_main (); return 0;}

Y ya estamos en el final del tutorial sobre la barra de herramientas. Por supuesto, para apreciar completamente el ejemplo, necesita además del código este precioso icono XPM que le mostramos a continuación:

/* XPM */static char * gtk_xpm[] = {"32 39 5 1",". c none","+ c black","@ c #3070E0","# c #F05050","$ c #35E035","................+...............","..............+++++.............","............+++++@@++...........","..........+++++@@@@@@++.........","........++++@@@@@@@@@@++........","......++++@@++++++++@@@++.......",

Page 91: GTK Tutorial

".....+++@@@+++++++++++@@@++.....","...+++@@@@+++@@@@@@++++@@@@+....","..+++@@@@+++@@@@@@@@+++@@@@@++..",".++@@@@@@+++@@@@@@@@@@@@@@@@@@++",".+#+@@@@@@++@@@@+++@@@@@@@@@@@@+",".+##++@@@@+++@@@+++++@@@@@@@@$@.",".+###++@@@@+++@@@+++@@@@@++$$$@.",".+####+++@@@+++++++@@@@@+@$$$$@.",".+#####+++@@@@+++@@@@++@$$$$$$+.",".+######++++@@@@@@@++@$$$$$$$$+.",".+#######+##+@@@@+++$$$$$$@@$$+.",".+###+++##+##+@@++@$$$$$$++$$$+.",".+###++++##+##+@@$$$$$$$@+@$$@+.",".+###++++++#+++@$$@+@$$@++$$$@+.",".+####+++++++#++$$@+@$$++$$$$+..",".++####++++++#++$$@+@$++@$$$$+..",".+#####+++++##++$$++@+++$$$$$+..",".++####+++##+#++$$+++++@$$$$$+..",".++####+++####++$$++++++@$$$@+..",".+#####++#####++$$+++@++++@$@+..",".+#####++#####++$$++@$$@+++$@@..",".++####++#####++$$++$$$$$+@$@++.",".++####++#####++$$++$$$$$$$$+++.",".+++####+#####++$$++$$$$$$$@+++.","..+++#########+@$$+@$$$$$$+++...","...+++########+@$$$$$$$$@+++....",".....+++######+@$$$$$$$+++......","......+++#####+@$$$$$@++........",".......+++####+@$$$$+++.........",".........++###+$$$@++...........","..........++##+$@+++............","...........+++++++..............",".............++++..............."};

10.5 Marcos con proporciones fijas El widget aspect frame (marco proporcional) es como el widget frame (marco), excepto que conserva las proporciones (esto es, la relación entre el ancho y el alto) del widget hijo, añadiendo espacio extra en caso de ser necesario. Esto es útil, por ejemplo, si quiere hacer una vista previa de una gran imagen. El tamaño de la vista previa debería variar cuando el usuario redimensione la ventana, pero la proporción tiene que coincidir con la de la imagen original.

Para crear un nuevo marco proporcional utilice:

GtkWidget *gtk_aspect_frame_new( const gchar *label, gfloat xalign, gfloat yalign, gfloat ratio, gint obey_child);

xalign e yalign indican la alineación exactamente igual que con los widgets Alignment. Si obey_child es TRUE, la proporción de un widget hijo será la misma que la proporción del tamaño ideal que éste pida. En caso contrario, vendrá dada por ratio.

Para cambiar las opciones de un marco proporcional ya existente, puede utilizar:

Page 92: GTK Tutorial

void gtk_aspect_frame_set( GtkAspectFrame *aspect_frame, gfloat xalign, gfloat yalign, gfloat ratio, gint obey_child);

Como por ejemplo, el siguiente programa utiliza un marco proporcional para mostrar una zona de dibujo cuyas proporciones siempre será de 2:1, no importa como el usuario redimensione la ventana.

/* principio del ejemplo aspectframe aspectframe.c */

#include <gtk/gtk.h> intmain (int argc, char *argv[]){ GtkWidget *window; GtkWidget *aspect_frame; GtkWidget *drawing_area; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); /* Crear un aspect_frame y añadirlo a nuestra ventana superior */ aspect_frame = gtk_aspect_frame_new ("2x1", /* etiqueta */ 0.5, /* centro x */ 0.5, /* centro y */ 2, /* tamañox/tamañoy = 2 */ FALSE /* ignorar el aspecto del hijo */); gtk_container_add (GTK_CONTAINER(window), aspect_frame); gtk_widget_show (aspect_frame); /* Añadir un widget hijo al marco proporcional */ drawing_area = gtk_drawing_area_new (); /* Pediremos una ventana de 200x200, pero el marco proporcional * sólo no dejará una ventana de 200x100, ya que tenemos una * relación de 2x1 */ gtk_widget_set_usize (drawing_area, 200, 200); gtk_container_add (GTK_CONTAINER(aspect_frame), drawing_area); gtk_widget_show (drawing_area); gtk_widget_show (window); gtk_main (); return 0;}/* fin del ejemplo */

Page 93: GTK Tutorial

11. El widget GtkCListEl widget GtkCList ha reemplazado al widget GtkList (que sigue estando disponible).

El widget GtkCList es un widget de una lista multicolumna que es capaz de manejar, literalmente, miles de filas de información. Cada columna puede tener (opcionalmente) un título, que puede estar activado (opcionalmente), permitiéndonos enlazar una función con la selección.

11.1 Creando un widget GtkCList Crear un GtkCList es algo bastante sencillo, una vez que sabe como crear un widget en general. Se proporcionan al menos dos formas estándar de crearlo, la forma fácil y la forma difícil. Pero antes de crear una GtkCList, hay una cosa que debemos saber: ¿Cuántas columnas va a tener?

No todas las columnas tienen que ser visibles y pueden utilizarse para almacenar datos que estén relacionados con una cierta celda de la lista.

GtkWidget *gtk_clist_new ( gint columns );

GtkWidget *gtk_clist_new_with_titles( gint columns, gchar *titles[] );

Esta primera aproximación al problema es muy sencilla, pero la segunda requerirá alguna explicación adicional. Cada columna puede tener un título asociado. Si utilizamos la segunda forma, deberemos proporcionar punteros al texto del título, y el número de punteros debe ser igual al número de columnas especificadas. Por supuesto, siempre podemos utilizar la primera forma de creación y añadir más tarde los títulos de forma manual.

11.2 Modos de operación Hay varios atributos que pueden utilizarse para alterar el aspecto de un GtkCList. Primero tenemos

void gtk_clist_set_selection_mode( GtkCList *clist, GtkSelectionMode mode );

que, como el propio nombre indica, establece el modo de selección de la lista GtkCList. El primer argumento es el widget GtkCList, y el segundo especifica el modo de selección de la celda (están definidos en gtkenums.h). En el momento de escribir esto, estaban disponibles los siguientes modos:

• GTK_SELECTION_SINGLE - La selección o es NULL o contiene un puntero GList a un elemento seleccionado.

• GTK_SELECTION_BROWSE - La selección es NULL si la lista no contiene widgets o si los que contiene son insensibles, en caso contrario contendrá un puntero GList hacia una estructura GList, y por tanto con exactamente un elemento.

• GTK_SELECTION_MULTIPLE - La selección es NULL si no hay seleccionados una lista de elementos o un puntero GList para el primer elemento seleccionado. Éste apunta de nuevo a una estructura GList para el segundo elemento seleccionado y continua así. Éste es, actualmente, el modo por defecto para el widget GtkCList.

• GTK_SELECTION_EXTENDED - La selección siempre es NULL.

Puede que se añadan otros modos en versiones posteriores de GTK.

Page 94: GTK Tutorial

También tenemos

void gtk_clist_set_policy (GtkCList *clist, GtkPolicyType vscrollbar_policy, GtkPolicyType hscrollbar_policy);

que define que es lo que ocurre con las barras de desplazamiento. Los siguientes valores son los posibles para las barras de desplazamiento horizontal y vertical:

• GTK_POLICY_ALWAYS - La barra de desplazamiento siempre está ahí. • GTK_POLICY_AUTOMATIC - La barra de desplazamiento estará ahí sólo cuando el número

de elementos en la GtkCList supere el número que puede mostrarse en el widget.

También podemos definir como debería ser el aspecto del borde del widget GtkCList. Esto lo podemos hacer mediante

void gtk_clist_set_border( GtkCList *clist, GtkShadowType border );

Y los posibles valores para el segundo argumento son

• GTK_SHADOW_NONE • GTK_SHADOW_IN • GTK_SHADOW_OUT • GTK_SHADOW_ETCHED_IN • GTK_SHADOW_ETCHED_OUT

11.3 Trabajando con los títulos Cuando cree un widget GtkCList, también obtendrá automáticamente un conjunto de botones título. Vivirán en lo alto de una ventana CList, y pueden actuar como botones normales que responden cuando se pulsa sobre ellos, o bien pueden ser pasivos, en cuyo caso no serán nada más que un título. Hay cuatro llamadas diferentes que nos ayudarán a establecer el estado de los botones título.

void gtk_clist_column_title_active( GtkCList *clist, gint column );

void gtk_clist_column_title_passive( GtkCList *clist, gint column );

void gtk_clist_column_titles_active( GtkCList *clist );

void gtk_clist_column_titles_passive( GtkCList *clist );

Un título activo es aquel que actua como un botón normal, y uno pasivo es sólo una etiqueta. Las primeras dos llamadas de arriba activarán/desactivarán el botón título correspondiente a la columna column, mientras que las dos llamadas siguientes activarán/desactivarán todos los botones título que hayan en el widget clist que se le proporcione a la función.

Pero, por supuesto, habrá casos en el que no querremos utilizar los botones título, así que también tenemos la posibilidad de ocultarlos y de volverlos a mostrar utilizando las dos llamadas siguientes:

void gtk_clist_column_titles_show( GtkCList *clist );

void gtk_clist_column_titles_hide( GtkCList *clist );

Page 95: GTK Tutorial

Para que los títulos sean realmente útiles necesitamos un mecanismo que nos permita darles el valor que nosotros queramos y cambiar ese valor, y podremos hacerlo mediante

void gtk_clist_set_column_title( GtkCList *clist, gint column, gchar *title );

Debe llevar cuidado, ya que sólo se puede especificar el título de una columna a la vez, por lo que si conoce todos los títulos desde el principio, le sugiero que utilice gtk_clist_new_with_titles (como se describe arriba) para establecerlos adecuadamente. Le ahorrará tiempo de programación, y hará su programa más pequeño. Hay algunos casos donde es mejor utilizar la forma manual, y uno de ellos es cuando no todos los títulos son texto. GtkCList nos proporciona botones título que pueden, de hecho, incorporar un widget entero, por ejemplo un pixmap. Todo esto se hace mediante

void gtk_clist_set_column_widget( GtkCList *clist, gint column, GtkWidget *widget );

que no debería necesitar de explicaciones adicionales.

11.4 Manipulando la lista en sí. Es posible cambiar la justificación de una columna, y esto se hace mediante

void gtk_clist_set_column_justification( GtkCList *clist, gint column, GtkJustification justification );

El tipo GtkJustification puede tomar los valores siguientes:

• GTK_JUSTIFY_LEFT - El texto en la columna empezará desde el lado izquierdo. • GTK_JUSTIFY_RIGHT - El texto en la columna empezará desde el lado derecho. • GTK_JUSTIFY_CENTER - El texto se colocará en el centro de la columna. • GTK_JUSTIFY_FILL - El texto utilizará todo el espacio disponible en la columna.

Normalmente se hace añadiendo espacios en blanco entre las palabras (o entre letras por separado, si se trata de una sola palabra). Más o menos de la misma forma en la que lo hace un procesador de textos WYSIWYG.

La siguiente función es muy importante, y debería ser un estándar para inicializar todos los widgets GtkCList. Cuando se crea la lista, los anchos de las distintas columnas se eligen para que coincidan con sus títulos, y éste es el ancho adecuado que tenemos que poner, utilizando

void gtk_clist_set_column_width( GtkCList *clist, gint column, gint width );

Observe que el ancho viene dado en pixeles y no en letras. Lo mismo vale para el alto de la celda en las columnas, pero como el valor por defecto es la altura del tipo de letra actual, no es algo tan crítico para la aplicación. De todas formas, la altura se cambia mediante

void gtk_clist_set_row_height( GtkCList *clist, gint height );

Page 96: GTK Tutorial

De nuevo, hay que advertir que el ancho viene dado en pixeles.

También podemos ir hacia un elemento sin la intervención del usuario, sin embargo hace falta que sepamos hacia donde queremos ir. O en otras palabras, necesitamos la fila y la columna del elemento al que queremos pasar.

void gtk_clist_moveto( GtkCList *clist, gint row, gint column, gfloat row_align, gfloat col_align );

Es importante comprender bien el significado de gfloat row_align. Tiene un valor entre 0.0 y 1.0, donde 0.0 significa que debemos hacer que la fila seleccionada aparezca en la alto de la lista, mientras que 1.0 significa que la fila aparecerá en la parte de abajo. El resto de valores entre 0.0 y 1.0 son válidos y harán que la fila aparezca entre la parte superior y la inferior. El último argumento, gfloat col_align funciona igual, siendo 0.0 la izquierda y 1.0 la derecha.

Dependiendo de las necesidades de la aplicación, puede que no tengamos que hacer un desplazamiento hacia un elemento que ya sea visible. Por tanto, ¿cómo podemos saber si ya es visible? Como siempre, hay una función que sirve para averiguarlo

GtkVisibility gtk_clist_row_is_visible( GtkCList *clist, gint row );

El valor devuelto es uno de los siguientes:

• GTK_VISIBILITY_NONE • GTK_VISIBILITY_PARTIAL • GTK_VISIBILITY_FULL

Como puede ver, sólo nos dice si una fila es visible. Actualmente no hay ninguna forma de obtener el mismo dato para una columna. Sin embargo podemos obtener información parcial, porque si el valor devuelto es GTK_VISIBILITY_PARTIAL, entonces es que alguna parte está oculta, pero no sabemos si es la fila que está cortada por la parte de abajo de la lista, o si la fila tiene columnas que están fuera.

También podemos cambiar el color del primer y del segundo plano de una fila en particular. Esto es útil para marcar la fila seleccionada por el usuario, y las dos funciones que hay que utilizar son

void gtk_clist_set_foreground( GtkCList *clist, gint row, GdkColor *color );

void gtk_clist_set_background( GtkCList *clist, gint row, GdkColor *color );

Cuidado, ya que los colores deben estar asignados previamente en la memoria.

11.5 Añadiendo filas a la lista Podemos añadir filas de dos formas. Se pueden añadir al final de la lista utilizando

gint gtk_clist_append( GtkCList *clist, gchar *text[] );

Page 97: GTK Tutorial

o podemos insertar una fila en un lugar determinado utilizando

void gtk_clist_insert( GtkCList *clist, gint row, gchar *text[] );

En ambas llamadas podemos proporcionar un conjunto de punteros que serán los textos que queremos poner en las columnas. El número de punteros debe ser igual al número de columnas en la lista. Si el argumento text[] es NULL, entonces no habrá texto en las columnas de la fila. Esto sería útil, por ejemplo, si queremos añadir pixmaps en lugar de texto (en general para cualquier cosa que haya que hacer manualmente).

De nuevo, cuidado ya que el número de filas y de columnas comienza en 0.

Para eliminar una fila individual podemos utilizar

void gtk_clist_remove( GtkCList *clist, gint row );

Hay también una llamada que elimina todas las filas en la lista. Es mucho más rápido que llamar a gtk_clist_remove una vez por cada fila, que sería la única alternativa.

void gtk_clist_clear( GtkCList *clist );

También hay dos funciones que es conveniente utilizarlas cuando hay que hacerle muchos cambios a una lista. Son para evitar que la lista parpadee mientras es actualizada repetidamente, que puede ser muy molesto para el usuario. Por tanto es una buena idea congelar la lista, hacer los cambios, y descongelarla, que hará que la lista se actualice en la pantalla.

void gtk_clist_freeze( GtkCList * clist );

void gtk_clist_thaw( GtkCList * clist );

11.6 Poniendo texto y pixmaps en las celdas Una celda puede contener un pixmap, texto o ambos. Para ponerlos en las celdas, podemos utilizar las siguientes funciones.

void gtk_clist_set_text( GtkCList *clist, gint row, gint column, gchar *text );

void gtk_clist_set_pixmap( GtkCList *clist, gint row, gint column, GdkPixmap *pixmap, GdkBitmap *mask );

void gtk_clist_set_pixtext( GtkCList *clist, gint row, gint column, gchar *text, guint8 spacing, GdkPixmap *pixmap, GdkBitmap *mask );

Page 98: GTK Tutorial

Son bastante sencillas de entender. Todas las llamadas tienen la GtkCList como primer argumento, seguidas por la fila y la columna de la celda, y seguidas por el dato que debe ponerse en la celda. El argumento gint8 spacing en gtk_clist_set_pixtext es el número de pixels entre el pixmap y el principio del texto.

Para leer los datos que hay en una celda, podemos utilizar

gint gtk_clist_get_text( GtkCList *clist, gint row, gint column, gchar **text );

gint gtk_clist_get_pixmap( GtkCList *clist, gint row, gint column, GdkPixmap **pixmap, GdkBitmap **mask );

gint gtk_clist_get_pixtext( GtkCList *clist, gint row, gint column, gchar **text, guint8 *spacing, GdkPixmap **pixmap, GdkBitmap **mask );

No es necesario leer todos los datos en caso de que no estemos interesados. Cualquiera de los punteros que se supone contendrán los valores a devolver (cualquiera excepto el clist) pueden ser NULL. Por lo que si sólo queremos leer el texto de una celda que es de tipo pixtext, deberíamos hacer lo siguiente, suponiendo que clist, row y column ya existan:

gchar *mytext;

gtk_clist_get_pixtext(clist, row, column, &mytext, NULL, NULL, NULL);

Hay una rutina más que está relacionada con lo que está dentro de una celda de una clist, y es

GtkCellType gtk_clist_get_cell_type( GtkCList *clist, gint row, gint column );

que devuelve el tipo de datos que hay en la celda. El valor devuelto es uno de los siguientes

• GTK_CELL_EMPTY • GTK_CELL_TEXT • GTK_CELL_PIXMAP • GTK_CELL_PIXTEXT • GTK_CELL_WIDGET

También hay una función que nos permite especificar la indentación de un celda (horizontal o vertical). El valor de la indentación es del tipo gint, viene dado en pixeles, y puede ser positivo o negativo.

void gtk_clist_set_shift( GtkCList *clist, gint row, gint column, gint vertical, gint horizontal );

Page 99: GTK Tutorial

11.7 Almacenando punteros a datos Con una GtkCList es posible poner un puntero a datos en una fila. Este puntero no será visible al usuario, pero puede serle útil al programador.

De nuevo, las funciones son lo suficientemente autoexplicativas

void gtk_clist_set_row_data( GtkCList *clist, gint row, gpointer data );

void gtk_clist_set_row_data_full( GtkCList *clist, gint row, gpointer data, GtkDestroyNotify destroy );

gpointer gtk_clist_get_row_data( GtkCList *clist, gint row );

gint gtk_clist_find_row_from_data( GtkCList *clist, gpointer data );

11.8 Trabajando con la selección También hay funciones que nos permiten forzar la (de)selección de una fila. Son

void gtk_clist_select_row( GtkCList *clist, gint row, gint column );

void gtk_clist_unselect_row( GtkCList *clist, gint row, gint column );

Y también una función que tomará las coordenadas x e y (por ejemplo, recibidas del ratón), mirará en la lista y devolverá la fila y la columna que les corresponden.

gint gtk_clist_get_selection_info( GtkCList *clist, gint x, gint y, gint *row, gint *column );

Cuando detectemos algo interesante, como por ejemplo el movimiento del ratón, o una pulsación en cualquier lugar de la lista, podemos leer las coordenadas del ratón y encontrar en que elemento de la lista se encuentra. ¿Engorroso? Afortunadamente, hay una forma más sencilla de hacer las cosas...

11.9 Las señales que lo hacen todo Como con el resto de widgets, hay unas cuantas señales que podemos utilizar. El widget GtkCList está derivado del widget GtkContainer, y por tanto tiene las mismas señales que éste, pero además añade las siguientes:

• select_row - Esta señal enviará la siguiente información, en este orden: GtkCList *clist, gint

Page 100: GTK Tutorial

row, gint column, GtkEventButton *event • unselect_row - Cuando el usuario deselecciona una fila, se activará esta señal. Envia la

misma información que select_row • click_column - Envia GtkCList *clist, gint column

Por tanto si queremos conectar una llamada a select_row, la llamada se deberá declarar como

void select_row_callback(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data);

La llamada se conectará, como siempre, con

gtk_signal_connect(GTK_OBJECT( clist), "select_row" GTK_SIGNAL_FUNC(select_row_callback), NULL);

11.10 Un ejemplo GtkCList /* principio del ejemplo clist clist.c */

#include <gtk/gtk.h>#include <glib.h>

/* Aquí tenemos algunos prototipos de las funciones de llamada */void button_add_clicked( GtkWidget *button, gpointer data);void button_clear_clicked( GtkWidget *button, gpointer data);void button_hide_show_clicked( GtkWidget *button, gpointer data);void selection_made( GtkWidget *clist, gint row, gint column, GdkEventButton *event, gpointer data);

gint main (int argc, gchar *argv[]){ GtkWidget *window; GtkWidget *vbox, *hbox; GtkWidget *clist; GtkWidget *button_add, *button_clear, *button_hide_show; gchar *titles[2] = {"Ingredients","Amount"};

gtk_init(&argc, &argv); window=gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_usize(GTK_WIDGET(window), 300, 150);

gtk_window_set_title(GTK_WINDOW(window), "GtkCList Example"); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(gtk_main_quit), NULL); vbox=gtk_vbox_new(FALSE, 5); gtk_container_border_width(GTK_CONTAINER(vbox), 5);

Page 101: GTK Tutorial

gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox); /* Crear el GtkCList. Para este ejemplo utilizaremos 2 columnas */ clist = gtk_clist_new_with_titles( 2, titles);

/* Cuando se hace una selección, queremos saber algo acerca de * ella. La función de llamada utilizada es selection_made, y su * código lo podemos encontrar más abajo */ gtk_signal_connect(GTK_OBJECT(clist), "select_row", GTK_SIGNAL_FUNC(selection_made), NULL);

/* No es necesario ponerle sombra al borde, pero es bonito :) */ gtk_clist_set_border(GTK_CLIST(clist), GTK_SHADOW_OUT);

/* Lo que sí que es importante, es poner el ancho de las columnas * ya no tendrán el valor correcto en caso contrario. Recuerde que * las columnas se numeran desde el 0 en adelante (hasta el 1 en * este caso). */ gtk_clist_set_column_width (GTK_CLIST(clist), 0, 150);

/* Scollbars _only when needed_ */ gtk_clist_set_policy(GTK_CLIST(clist), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

/* Añade el widget GtkCList a la caja vertical y lo muestra. */ gtk_box_pack_start(GTK_BOX(vbox), clist, TRUE, TRUE, 0); gtk_widget_show(clist);

/* Crea los botones y los añade a la ventana. Ver la parte del * tutorial sobre botones para ver más ejemplos y comentarios * acerca de todo esto. */ hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); gtk_widget_show(hbox);

button_add = gtk_button_new_with_label("Add List"); button_clear = gtk_button_new_with_label("Clear List"); button_hide_show = gtk_button_new_with_label("Hide/Show titles");

gtk_box_pack_start(GTK_BOX(hbox), button_add, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), button_clear, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), button_hide_show, TRUE, TRUE, 0);

/* Conectar nuestras funciones de llamada a los tres botones */ gtk_signal_connect_object(GTK_OBJECT(button_add), "clicked", GTK_SIGNAL_FUNC(button_add_clicked), (gpointer) clist); gtk_signal_connect_object(GTK_OBJECT(button_clear), "clicked", GTK_SIGNAL_FUNC(button_clear_clicked), (gpointer) clist); gtk_signal_connect_object(GTK_OBJECT(button_hide_show), "clicked", GTK_SIGNAL_FUNC(button_hide_show_clicked), (gpointer) clist);

Page 102: GTK Tutorial

gtk_widget_show(button_add); gtk_widget_show(button_clear); gtk_widget_show(button_hide_show);

/* Ahora hemos terminado el interface y sólo nos queda mostrar la * ventana y entrar en el bucle gtk_main. */ gtk_widget_show(window); gtk_main(); return 0;}

/* El usuario pulsó el botón "Add List". */void button_add_clicked( GtkWidget *button, gpointer data){ int indx;

/* Algo tonto que añadir a la lista. 4 filas con 2 columnas cada * una */ gchar *drink[4][2] = {{"Milk", "3 Oz"}, {"Water", "6 l"}, {"Carrots", "2"}, {"Snakes", "55"}};

/* Aquí hacemos la adición del texto. Se hace una vez por cada * fila. */ for( indx=0; indx < 4; indx++) gtk_clist_append( (GtkCList*) data, drink[indx]);

return;}

/* El usuario pulsó el botón "Clear List" */void button_clear_clicked( GtkWidget *button, gpointer data){ /* Borrar la lista utilizando gtk_clist_clear. Esto es mucho más * rápido que llamar a gtk_clist_remove una vez por cada fila. */ gtk_clist_clear((GtkCList*) data);

return;}

/* El usuario pulsó el botón "Hide/Show titles". */void button_hide_show_clicked( GtkWidget *button, gpointer data){ /* Una bandera para recordar el estado. 0 = actualmente visible */ static short int flag = 0;

if (flag == 0) { /* Oculta los títulos y pone la bandera a 1 */ gtk_clist_column_titles_hide((GtkCList*) data); flag++; }

Page 103: GTK Tutorial

else { /* Muestra los títulos y pone la bandera a 0 */ gtk_clist_column_titles_show((GtkCList*) data); flag--; }

return;}

/* Se llegamos aquí, entonces el usuario ha seleccionado una fila de * la lista. */void selection_made( GtkWidget *clist, gint row, gint column, GdkEventButton *event, gpointer data){ gchar *text;

/* Obtiene el texto que se ha almacenado en la fila y columna * sobre las que se ha pulsado. Lo recibiremos como un puntero en * el argumento text. */ gtk_clist_get_text(GTK_CLIST(clist), row, column, &text);

/* Imprime alguna información sobre la fila seleccionada */ g_print("You selected row %d. More specifically you clicked in column %d, and the text in this cell is %s\n\n", row, column, text);

return;}/* final del ejemplo */

12. El widget listaATENCIÓN: El widget GtkList ha sido reemplazado por el widget GtkCList.

El widget GtkList está diseñado para actuar como un contenedor vertical de widgets que deben ser del tipo GtkListItem.

Un widget GtkList tiene su propia ventana para recibir eventos y su propio color de fondo, que normalmente es blanco. Como es un objeto derivado directamente de GtkContainer puede tratarse utilizando la macro GTK_CONTAINER(List), ver el widget GtkContainer para obtener más información.

Debería familiarizarse con la utilización de un GList y con sus funciones relacionadas g_list_*() para ser capaz de explotar el widget GtkList hasta su límite.

Sólo hay un campo dentro de la definición de la estructura del widget GtkList que nos es de interés, y es:

struct _GtkList{ ... GList *selection; guint selection_mode;

Page 104: GTK Tutorial

...};

El campo selection de un GtkList apunta a una lista enlazada de todos los elementos que están actualmente seleccionados, o NULL si la selección está vacia. Por lo tanto para saber quien es la actual selección debemos leer el campo GTK_LIST()->selection, pero no modificarlo ya que los campos de los que está constituido GtkList están controlados por las funciones gtk_list_*().

El selection_mode de la GtkList determina las posibilidades de selección de una GtkList y por tanto los contenidos del campo GTK_LIST()->selection. El selection_mode puede tener uno de los valores siguientes:

• GTK_SELECTION_SINGLE - La selección es o NULL o contiene un puntero a un GList con un solo elemento seleccionado.

• GTK_SELECTION_BROWSE - La selección es NULL si la lista no contiene widgets o si los que contiene no son sensibles, en cualquier otro caso contiene un puntero GList a una estructura GList, y contendrá por tanto un solo elemento.

• GTK_SELECTION_MULTIPLE - La selección es NULL si no hay elementos seleccionados o un puntero GList hacia el primer elemento seleccionado. ("That in turn") apunta a una estructura GList para el segundo elemento seleccionado y así.

• GTK_SELECTION_EXTENDED - La selección siempre es NULL.

El valor por defecto es GTK_SELECTION_MULTIPLE.

12.1 Señales void selection_changed( GtkList *list );

Se invocará esta señal cuando cambie el campo selection de un GtkList. Es decir, cuando un hijo de una GtkList se selecciona o deselecciona.

void select_child( GtkList *list, GtkWidget *child);

Se invoca esta señal cuando un hijo de la GtkList está siendo seleccionado. Esto ocurre principalmente en llamadas a gtk_list_select_item(), a gtk_list_select_child(), cuando se pulsa algún botón y a veces se lanza indirectamente cuando se añade o se elimina un hijo del GtkList.

void unselect_child( GtkList *list, GtkWidget *child );

Se invoca esta señal cuando un hijo del GtkList está siendo deseleccionado. Esto ocurre principalmente cuando ocurre una llamada a gtk_list_unselect_item(), gtk_list_unselect_item(), pulsaciones de botón y a veces se lanza indirectamente cuando se añade o se elimina algún hijo de la GtkList.

12.2 Funciones guint gtk_list_get_type( void );

Devuelve el identificador de tipo `GtkList'.

Page 105: GTK Tutorial

GtkWidget *gtk_list_new( void );

Crea un nuevo objeto GtkList. Se devuelve el nuevo widget como un puntero a un objeto GtkWidget. Se devuelve NULL en caso de producirse algún fallo.

void gtk_list_insert_items( GtkList *list, GList *items, gint position );

Introduce elementos en la lista, comenzando en la posición position. items es una lista doblemente enlazada donde cada puntero de datos de cada nodo se supone que apunta a una nueva GtkListItem (recien creada). Los nodos GList de items son controlados por la lista.

void gtk_list_append_items( GtkList *list, GList *items);

Introduce elementos tal y como lo hace gtk_list_insert_items(), pero los mete en el final de la lista. Los nodos GList de items son controlados por la lista.

void gtk_list_prepend_items( GtkList *list, GList *items);

Introduce elementos tal y como lo hace gtk_list_insert_items(), pero los mete al principio de la lista. Los nodos GList de items son controlados por la lista.

void gtk_list_remove_items( GtkList *list, GList *items);

Elimina elementos de la lista. items es una lista doblemente enlazada donde cada puntero de datos de cada nodo se supone que apunta a un hijo directo de la lista. El ejecutar o no g_list_free(items) cuando la función termine de ejecutarse es responsabilidad del que llama a la misma. Está bajo su responsabilidad la destrucción de los elementos de la lista.

void gtk_list_clear_items( GtkList *list, gint start, gint end );

Elimina y destruye los elementos de la lista. Esta operación afectará a todos los widgets que se encuentren en la lista y en el rango especificado por start y end.

void gtk_list_select_item( GtkList *list, gint item );

Invoca la señal select_child para el elemento especificado mediante su posición actual en la lista.

void gtk_list_unselect_item( GtkList *list, gint item);

Invoca la señal unselect_child para un elemento especificado mediante su posición actual en la lista.

void gtk_list_select_child( GtkList *list, GtkWidget *child);

Invoca la señal select_child para el hijo especificado.

Page 106: GTK Tutorial

void gtk_list_unselect_child( GtkList *list, GtkWidget *child);

Invoca la señal unselect_child para el hijo especificado.

gint gtk_list_child_position( GtkList *list, GtkWidget *child);

Devuelve la posición de child en la lista. Se devuelve ``-1'' en caso de producirse algún error.

void gtk_list_set_selection_mode( GtkList *list, GtkSelectionMode mode );

Pone el modo de selección, que puede ser GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE o GTK_SELECTION_EXTENDED.

GtkList *GTK_LIST( gpointer obj );

Convierte un puntero general en `GtkList *'. Para más información *Note Standard Macros::.

GtkListClass *GTK_LIST_CLASS( gpointer class);

Convierte un puntero general en `GtkListClass *'. Para más información *Note Standard Macros::.

gint GTK_IS_LIST( gpointer obj);

Determina si un puntero general se refiere a un objeto `GtkList'. Para más información, *Note Standard Macros::.

12.3 Ejemplo A continuación tenemos un programa ejemplo que muestra los cambios de la selección de un GtkList, y le deja ``arrestar'' elementos de la lista en una prisión, seleccionándolos con el botón derecho del ratón.

/* principio del ejemplo list list.c */

/* incluye los ficheros de cabecera de gtk+ * incluye stdio.h, que necesitamos para la función printf() */#include <gtk/gtk.h>#include <stdio.h>

/* ésta es nuestra cadena de identificación para almacenar datos en la * lista de elementos */const gchar *list_item_data_key="list_item_data";

/* prototipos para los manejadores de señal que vamos a conectar con * el widget GtkList */static void sigh_print_selection (GtkWidget *gtklist, gpointer func_data);static void sigh_button_event (GtkWidget *gtklist, GdkEventButton *event, GtkWidget *frame);

Page 107: GTK Tutorial

/* función principal donde se establece el interface con el usuario */

gint main (int argc, gchar *argv[]){ GtkWidget *separator; GtkWidget *window; GtkWidget *vbox; GtkWidget *scrolled_window; GtkWidget *frame; GtkWidget *gtklist; GtkWidget *button; GtkWidget *list_item; GList *dlist; guint i; gchar buffer[64]; /* inicializar gtk+ (y consecuentemente gdk) */

gtk_init(&argc, &argv); /* crear una ventana donde meter todos los widgets y conectar * gtk_main_quit() con el evento "destroy" de la ventana para * poder controlar los eventos de cerrado de ventana del * administrador de ventanas */ window=gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "GtkList Example"); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(gtk_main_quit), NULL); /* dentro de la ventana necesitamos una caja para alinear los * widgets verticalmente */ vbox=gtk_vbox_new(FALSE, 5); gtk_container_border_width(GTK_CONTAINER(vbox), 5); gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox); /* Ésta es la ventana con barras de desplazamiento donde meteremos * el widget GtkList */ scrolled_window=gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_usize(scrolled_window, 250, 150); gtk_container_add(GTK_CONTAINER(vbox), scrolled_window); gtk_widget_show(scrolled_window); /* crear el widget GtkList * conectar la función manipuladora de señal * sigh_print_selection() a la señal "selection_changed" del * GtkList para imprimir los elementos seleccionados cada vez que * cambie la selección */ gtklist=gtk_list_new(); gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist); gtk_widget_show(gtklist);

Page 108: GTK Tutorial

gtk_signal_connect(GTK_OBJECT(gtklist), "selection_changed", GTK_SIGNAL_FUNC(sigh_print_selection), NULL); /* creamos una "Prisión" donde meteremos una lista de elementos ;) */ frame=gtk_frame_new("Prison"); gtk_widget_set_usize(frame, 200, 50); gtk_container_border_width(GTK_CONTAINER(frame), 5); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); gtk_container_add(GTK_CONTAINER(vbox), frame); gtk_widget_show(frame); /* conectamos el manipulador de señal sigh_button_event() al * GtkList que manejará la lista de elementos "arrestados" */ gtk_signal_connect(GTK_OBJECT(gtklist), "button_release_event", GTK_SIGNAL_FUNC(sigh_button_event), frame); /* crear un separador */ separator=gtk_hseparator_new(); gtk_container_add(GTK_CONTAINER(vbox), separator); gtk_widget_show(separator); /* crear finalmente un botón y conectar su señal "clicked" con la * destrucción de la ventana */ button=gtk_button_new_with_label("Close"); gtk_container_add(GTK_CONTAINER(vbox), button); gtk_widget_show(button); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window)); /* ahora creamos 5 elementos de lista, teniendo cada uno su propia * etiqueta y añadiéndolos a la GtkList mediante * gtk_container_add() también consultaremos la cadena de texto de * la etiqueta y la asociaremos con la list_item_data_key para * cada elemento de la lista */ for (i=0; i<5; i++) { GtkWidget *label; gchar *string; sprintf(buffer, "ListItemContainer with Label #%d", i); label=gtk_label_new(buffer); list_item=gtk_list_item_new(); gtk_container_add(GTK_CONTAINER(list_item), label); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(gtklist), list_item); gtk_widget_show(list_item); gtk_label_get(GTK_LABEL(label), &string);

Page 109: GTK Tutorial

gtk_object_set_data(GTK_OBJECT(list_item), list_item_data_key, string); } /* aquí, estamos creando otras 5 etiquetas, esta vez utilizaremos * gtk_list_item_new_with_label() para la creación * no podemos consultar la cadena de texto de la etiqueta ya que * no tenemos el puntero de etiquetas y por tanto lo único que * haremos será asociar el list_item_data_key de cada elemento de * la lista con la misma cadena de texto. Para añadirlo a la lista * de elementos los pondremos en lista doblemente enlazada * (GList), y entonces los añadimos mediante una simple llamada a * gtk_list_append_items() * como utilizamos g_list_prepend() para poner los elementos en la * lista doblemente enlazada, su orden será descendente (en vez de * ascendente como cuando utilizamos g_list_append()) */ dlist=NULL; for (; i<10; i++) { sprintf(buffer, "List Item with Label %d", i); list_item=gtk_list_item_new_with_label(buffer); dlist=g_list_prepend(dlist, list_item); gtk_widget_show(list_item); gtk_object_set_data(GTK_OBJECT(list_item), list_item_data_key, "ListItem with integrated Label"); } gtk_list_append_items(GTK_LIST(gtklist), dlist); /* finalmente queremos ver la ventana, ¿verdad? ;) */ gtk_widget_show(window); /* y nos metemos en el bucle de eventos de gtk */ gtk_main(); /* llegaremos aquí después de que se llame a gtk_main_quit(), lo * que ocurre si se destruye la ventana */ return 0;}

/* éste es el manejador de señal que se conectó a los eventos de * pulsar/soltar de los botones de la GtkList */voidsigh_button_event (GtkWidget *gtklist, GdkEventButton *event, GtkWidget *frame){ /* sólo hacemos algo si el tercer botón (el botón derecho) se * levanta */ if (event->type==GDK_BUTTON_RELEASE && event->button==3) { GList *dlist, *free_list; GtkWidget *new_prisoner;

Page 110: GTK Tutorial

/* sacar la lista de elementos que están actualmente * seleccionados y que serán nuestro próximos prisioneros ;) */ dlist=GTK_LIST(gtklist)->selection; if (dlist) new_prisoner=GTK_WIDGET(dlist->data); else new_prisoner=NULL; /* buscar por elementos de la lista ya encarcelados, los * volveremos a poner en la lista, recordar que hay que * eliminar la lista doblemente enlazada que devuelve * gtk_container_children() */ dlist=gtk_container_children(GTK_CONTAINER(frame)); free_list=dlist; while (dlist) { GtkWidget *list_item; list_item=dlist->data; gtk_widget_reparent(list_item, gtklist); dlist=dlist->next; } g_list_free(free_list); /* si tenemos un nuevo prisionero, lo eliminamos de la GtkList * y lo ponemos en el marco "Prisión". Primero tenemos que * deseleccionarlo */ if (new_prisoner) { GList static_dlist; static_dlist.data=new_prisoner; static_dlist.next=NULL; static_dlist.prev=NULL; gtk_list_unselect_child(GTK_LIST(gtklist), new_prisoner); gtk_widget_reparent(new_prisoner, frame); } }}

/* éste es el manipulador de señal que se llama si GtkList emite la * señal "selection_changed" */voidsigh_print_selection (GtkWidget *gtklist, gpointer func_data){ GList *dlist; /* sacar la lista doblemente enlazada de los elementos * seleccionados en GtkList, ¡recuerde que hay que tratarla como * de solo lectura!

Page 111: GTK Tutorial

*/ dlist=GTK_LIST(gtklist)->selection; /* si no hay elementos seleccionados no queda nada por hacer * excepto informar al usuario */ if (!dlist) { g_print("Selection cleared\n"); return; } /* Bien, conseguimos una selección y la imprimimos */ g_print("The selection is a "); /* obtenemos la lista de elementos de la lista doblemente enlazada * y entonces consultamos los datos asociados con la * list_item_data_key que acabamos de imprimir */ while (dlist) { GtkObject *list_item; gchar *item_data_string; list_item=GTK_OBJECT(dlist->data); item_data_string=gtk_object_get_data(list_item, list_item_data_key); g_print("%s ", item_data_string); dlist=dlist->next; } g_print("\n");}/* fin del ejemplo */

12.4 El widget GtkListItem El widget GtkListItem está diseñado para comportarse como un contenedor que tiene un hijo, proporcionando funciones para la selección/deselección justo como las necesitan los hijos del widget GtkList.

Un GtkListItem tiene su propia ventana para recibir eventos y tiene su propio color de fondo, que normalmente es blanco.

Como está derivado directamente de un GtkItem, puede tratarse como tal utilizando la macro GTK_ITEM(ListItem), ver el widget GtkItem para más detalles. Normalmente un GtkListItem sólo tiene una etiqueta para identificar, por ejemplo, el nombre de un fichero dentro de una GtkList -- por lo tanto se proporciona la función gtk_list_item_new_with_label(). Se puede conseguir el mismo efecto creando un GtkLabel, poniendo su alineación a xalign=0 e yalign=0.5 y seguido de una adición al contenedor GtkListItem.

Nadie le obliga a meter un GtkLabel en un GtkListItem, puede meter un GtkVBox o un GtkArrow, etc...

Page 112: GTK Tutorial

12.5 Señales Un GtkListItem no crea por sí misma nuevas señales, pero hereda las señales de un GtkItem. Para más información *Note GtkItem::.

12.6 Funciones guint gtk_list_item_get_type( void );

Devuelve el identificador de tipo `GtkListItem'.

GtkWidget *gtk_list_item_new( void );

Crea un nuevo objeto GtkListItem. Se devuelve el nuevo widget como un puntero a un objeto GtkWidget. Se devuelve NULL en caso de producirse algún error.

GtkWidget *gtk_list_item_new_with_label( gchar *label );

Crea un nuevo objeto GtkListItem, con una sola GtkLabel como único hijo. Se devuelve el nuevo widget como un puntero a un objeto GtkWidget. Se devuelve NULL en caso de producirse algún error.

void gtk_list_item_select( GtkListItem *list_item );

Esta función es, básicamente, un recubrimiento de una llamada a gtk_item_select (GTK_ITEM (list_item)), y emitirá la señal select. Para más información *Note GtkItem::.

void gtk_list_item_deselect( GtkListItem *list_item );

Esta función es, básicamente, un recubrimiento de una llamada a gtk_item_deselect (GTK_ITEM (list_item)), y emitirá la señal deselect. Para más información *Note GtkItem::.

GtkListItem *GTK_LIST_ITEM( gpointer obj );

Convierte un puntero general a `GtkListItem *'. Para más información *Note Standard Macros::.

GtkListItemClass *GTK_LIST_ITEM_CLASS( gpointer class );

Convierte un puntero general a `GtkListItemClass *'. Para más información *Note Standard Macros::.

gint GTK_IS_LIST_ITEM( gpointer obj );

Determina si un puntero general se refiere a un puntero `GtkListItem'. Para más información *Note Standard Macros::.

12.7 Ejemplo Para ver un ejemplo de todo esto, mire el que de GtkList, que también cubre la utilización un GtkListItem.

Page 113: GTK Tutorial

13. El widget árbolEl propósito del widget GtkTree es mostrar datos organizados de forma jerárquica. El widget GtkTree en sí es un contenedor vertical para los widgets del tipo GtkTreeItem. GtkTree en sí mismo no es muy diferente de GtkList - ambos están derivados directamente de GtkContainer, y los métodos GtkContainer funcionan igual en los widgets GtkTree que en los GtkList. La diferencia es que los widgets GtkTree pueden anidarse dentro de otros widgets GtkTree. Vamos a verlo de forma resumida.

El widget GtkTree tiene su propia ventana, y tiene por defecto un fondo de color blanco, como GtkList. La mayoría de los métodos de GtkTree funcionan igual que sus correspondientes de GtkList. Sin embargo, GtkTree no está derivado de GtkList, por lo que no puede intercambiarlos.

13.1 Creando un árbol Puede crear un GtkTree de la forma usual, utilizando:

GtkWidget* gtk_tree_new( void );

Como el widget GtkList, un GtkTree crecerá cuando le añadan elementos o cuando crezca alguno de sus subárboles. Por esta razón, suelen venir dentro de una GtkScrolledWindow. Puede que quiera utilizar gtk_widget_set_usize() con la ventana para asegurarse de que es lo suficientemente grande como para poder ver todos los elementos del árbol, ya que el valor por defecto de GtkScrolledWindow es bastante pequeño.

Ahora que ya sabemos como crear un árbol, probablemente quiera añadirle algunos elementos. El <em/widget/ elemento de árbol más adelante explica todos los detalles de GtkTreeItem. Por ahora, es suficiente con saber como crear uno, utilizando:

GtkWidget* gtk_tree_item_new_with_label( gchar *label );

Puede añadirlo al árbol utilizando una de las siguientes funciones (ver Funciones y macros más adelante para leer más opciones):

void gtk_tree_append( GtkTree *tree, GtkWidget *tree_item );

void gtk_tree_prepend( GtkTree *tree, GtkWidget *tree_item );

Observe que debe añadir elementos a un GtkTree de uno en uno - no hay un equivalente a gtk_list_*_items().

13.2 Añadiendo un Subárbol Un subárbol se crea como cualquier otro widget GtkTree. Un subárbol se añade a otro árbol bajo un elemento del mismo, utilizando:

void gtk_tree_item_set_subtree( GtkTreeItem *tree_item, GtkWidget *subtree );

No necesita llamar a gtk_widget_show() en un subárbol ni antes ni después de añadirlo a GtkTreeItem. Sin embargo, deberá haber añadido el GtkTreeItem en cuestión a un árbol padre antes de llamar a gtk_tree_item_set_subtree(). Esto se debe a que, técnicamente, el padre del subárbol no es el

Page 114: GTK Tutorial

GtkTreeItem ``propietario'', sino el GtkTree que contiene al GtkTreeItem.

Cuando le añade un subárbol a un GtkTreeItem, aparece el signo de un más o de un menos a su lado, donde puede pinchar el usuario para ``expandirlo'' u ``contraerlo'', o sea, para mostrar u ocultar su subárbol. Los GtkTreeItems están contraídos por defecto. Observe que cuando contrae un GtkTreeItem, cualquier elemento seleccionado en el subárbol permanece seleccionado, que puede no coincidir con lo que el usuario espera.

13.3 Manejando la lista de selección Como con GtkList, GtkTree tiene un campo selection, y es posible controlar el comportamiento del árbol (de alguna manera) estableciendo el tipo de selección, utilizando:

void gtk_tree_set_selection_mode( GtkTree *tree, GtkSelectionMode mode );

La semántica asociada con los distintos modos de selección está descrita en la sección del widget GtkList. Como ocurría con el widget GtkList, se enviarán las señales select_child, unselect_child (realmente no - ver Señales más adelante para una explicación), y selection_changed cuando los elementos de la lista sean seleccionados o deseleccionados. Sin embargo, para aprovechar estas señales, necesita conocer por medio de que widget GtkTree serán emitidas, y donde encontrar una lista con los elementos seleccionados.

Todo esto es una potencial fuente de confusión. La mejor manera de entenderlo es imaginarse que aunque todos los widgets GtkTree son creados iguales, algunos son más iguales que otros. Todos los widgets GtkTree tienen su propia ventana X, y por tanto pueden recibir eventos como pulsaciones de ratón (¡si sus hijos o GtkTreeItems no las capturan primero!). Sin embargo, para hacer que GTK_SELECTION_SINGLE y GTK_SELECTION_BROWSE funcionen bien, la lista de elementos seleccionados debe ser específica al widget GtkTree superior de la jerarquia, conocido como el ``árbol raíz''.

Por tanto no es una buena idea acceder al campo selection directamente en un widget GtkTree arbitrario, a menos que sepa que es el árbol raíz. En vez de eso, utilice la macro GTK_TREE_SELECTION (Tree), que da la lista selección del árbol raíz como un puntero GList. Por supuesto, esta lista puede incluir elementos que no estén en el subárbol en cuestión si el tipo de selección es GTK_SELECTION_MULTIPLE.

Para terminar, las señales select_child (y tt/unselect_child/, en teoría) son emitidas por todos los árboles, pero la señal selection_changed es emitida sólo por el árbol raíz. En consecuencia, si quiere manipular la señal select_child de un árbol y todos sus subárboles, tendrá que llamar a gtk_signal_connect() una vez por cada subárbol.

13.4 Estructura interna del widget árbol La definición de la estructura GtkTree es ls siguiente:

struct _GtkTree{ GtkContainer container;

GList *children; GtkTree* root_tree; /* propietario de la lista de selección */

Page 115: GTK Tutorial

GtkWidget* tree_owner; GList *selection; guint level; guint indent_value; guint current_indent; guint selection_mode : 2; guint view_mode : 1; guint view_line : 1;};

Ya se han mencionado los peligros asociados con el acceso directo al campo selection. Se puede acceder a los otros campos importantes de la estructura mediante macros manipuladoras o funciones de clase. GTK_TREE_IS_ROOT_TREE (Tree) devuelve un valor booleano que indica si un árbol es árbol raíz de una jerarquia GtkTree, mientras que GTK_TREE_ROOT_TREE (Tree) devuelve el árbol raíz, un objeto de tipo GtkTree (recuerde transformarlo utilizando GTK_WIDGET (Tree) si quiere utilizar con él alguna de la funciones gtk_widget_*()).

En lugar de acceder directamente al campo hijo de un widget GtkTree, probablemente sea mejor transformarlo utilizando GTK_CONTAINER (Tree), y pasárselo a la función gtk_container_children(). Con esto crearemos un duplicado de la lista original, por lo que deberá eliminarlo de la memoria utilizando g_list_free() después haber hecho con él lo que tenga que hacer, o bien crear un bucle que lo vaya destruyendo de elemento en elemento, como por ejemplo así:

children = gtk_container_children (GTK_CONTAINER (tree));while (children) { do_something_nice (GTK_TREE_ITEM (children->data)); children = g_list_remove_link (children, children);}

El campo tree_owner sólo está definido en subárboles, donde apunta al widget GtkTreeItem que contiene al árbol en cuestión. El campo level indica el nivel de profundidad de un árbol en particular; los árboles raíz tienen un nivel 0, y cada nivel sucesivo de subárboles tiene un nivel superior al del padre. Sólo se puede asegurar que este campo contiene un valor correcto después de que el widget GtkTree se dibuje en la pantalla.

Señalesvoid selection_changed( GtkTree *tree );

Esta señal se emitirá cuando cambie el campo selection de un GtkTree. Esto ocurre cuando se selecciona o deselecciona un hijo del GtkTree.

void select_child( GtkTree *tree, GtkWidget *child );

Esta señal se emite cuando se está seleccionando un hijo del GtkTree. Esto ocurre en las llamadas a gtk_tree_select_item(), gtk_tree_select_child(), en todas las pulsaciones de botón y llamadas a gtk_tree_item_toggle() y gtk_item_toggle(). Puede que a veces se invoque indirectamente en otras ocasiones, cuando el hijo se añada o elimine del GtkTree.

void unselect_child (GtkTree *tree, GtkWidget *child);

Page 116: GTK Tutorial

Esta señal se emite cuando se deselecciona un hijo del GtkTree. Con GTK+ 1.0.4, esto sólo parece ocurrir en las llamadas a gtk_tree_unselect_item() o a gtk_tree_unselect_child(), y quizás en otras ocasiones, pero no cuando la pulsación de un botón deselecciona un hijo, y tampoco por la emisión de la señal ``toggle'' por gtk_item_toggle().

Funciones y macrosguint gtk_tree_get_type( void );

Devuelve el identificador de tipo de `GtkTree'.

GtkWidget* gtk_tree_new( void );

Crea un nuevo objeto GtkTree. El nuevo widget se devuelve como un puntero a un objeto GtkWidget. Se devolverá NULL si se produce algún error.

void gtk_tree_append( GtkTree *tree, GtkWidget *tree_item );

Añade un árbol a un GtkTree.

void gtk_tree_prepend( GtkTree *tree, GtkWidget *tree_item );

Preañade un árbol a un GtkTree.

void gtk_tree_insert( GtkTree *tree, GtkWidget *tree_item, gint position );

Inserta un árbol en un GtkTree en la posición de la lista especificada por position.

void gtk_tree_remove_items( GtkTree *tree, GList *items );

Elimina una lista de elementos (en forma de una GList *) de un GtkTree. Eliminar un elemento de un árbol lo dereferencia (y por tanto normalmente) lo destruye (""), a él y a su subárbol, de haberlo, y a todos los subárboles que contenga ese subárbol. Si quiere eliminar sólo un elemento, deberá utilizar gtk_container_remove().

void gtk_tree_clear_items( GtkTree *tree, gint start, gint end );

Elimina los elementos de un GtkTree desde la posición start hasta la posición end. De nuevo hay que llevarse cuidado con donde se aplica la dereferencia, ya que gtk_tree_clear_items() simplemente construye una lista y se la pasa a gtk_tree_remove_items().

void gtk_tree_select_item( GtkTree *tree, gint item );

Emite la señal select_item para el hijo que se encuentra en la posición item, y por tanto selecciona a ese hijo (a menos que lo deseleccione en un manejador de señal...)

void gtk_tree_unselect_item( GtkTree *tree, gint item );

Page 117: GTK Tutorial

Emite la señal unselect_item para el hijo en la posición item, y por tanto deselecciona al hijo.

void gtk_tree_select_child( GtkTree *tree, GtkWidget *tree_item );

Emite la señal select_item para el hijo tree_item, y por tanto lo selecciona.

void gtk_tree_unselect_child( GtkTree *tree, GtkWidget *tree_item );

Emite la señal unselect_item para el hijo tree_item, y por tanto lo deselecciona.

gint gtk_tree_child_position( GtkTree *tree, GtkWidget *child );

Devuelve la posición en el árbol de child, a menos que child no esté en el árbol, en cuya caso devuelve -1.

void gtk_tree_set_selection_mode( GtkTree *tree, GtkSelectionMode mode );

Establece el modo de selección, que puede ser uno de los siguientes GTK_SELECTION_SINGLE (por defecto), GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE, o GTK_SELECTION_EXTENDED. Esto sólo está definido para los árboles raíz, que es donde tiene sentido, ya que el árbol raíz es el ``propietario'' de la selección. Establecer este valor en un subárbol no tiene ningún efecto en absoluto; el valor simplemente será ignorado.

void gtk_tree_set_view_mode( GtkTree *tree, GtkTreeViewMode mode );

Establece el ``modo de visión'', que puede ser o GTK_TREE_VIEW_LINE (por defecto) o GTK_TREE_VIEW_ITEM. El modo de visión se propaga de un árbol a sus subárboles, y no puede establecerse en exclusiva para un subárbol (esto no es exacto del todo - vea los comentarios en el código de ejemplo).

El termino ``modo de visión'' es algo ambiguo - básicamente, controla la forma en que se resalta a uno de los hijos del árbol cuando es seleccionado. Si es GTK_TREE_VIEW_LINE, se resaltará el widget GtkTreeItem completo, mientras que si es GTK_TREE_VIEW_ITEM, sólo se resaltará el widget hijo (es decir, lo que normalmente es la etiqueta).

void gtk_tree_set_view_lines( GtkTree *tree, guint flag );

Controla si se dibujarán las líneas de conexión entre los elementos del árbol. flag es o TRUE, en cuyo caso se dibujarán, o FALSE, en cuyo caso no se dibujarán.

GtkTree *GTK_TREE (gpointer obj);

Convierte un puntero genérico a `GtkTree *'.

GtkTreeClass *GTK_TREE_CLASS (gpointer class);

Convierte un puntero genérico a `GtkTreeClass *'.

gint GTK_IS_TREE (gpointer obj);

Page 118: GTK Tutorial

Determina si un puntero genérico se refiere a un objeto `GtkTree'.

gint GTK_IS_ROOT_TREE (gpointer obj)

Determina si un puntero genérico se refiere a un objeto `GtkTree' y es un árbol raíz. Aunque la función acepta cualquier puntero, los resultados de pasarle un puntero que no se refiera a un GtkTree no están definidos y probablemente no tengan ningún sentido.

GtkTree *GTK_TREE_ROOT_TREE (gpointer obj)

Devuelve el árbol raíz de un puntero a un objeto `GtkTree'. Seguimos con el mismo problema que en el caso anterior.

GList *GTK_TREE_SELECTION(gpointer obj)

Devuelve la lista de selección del árbol raíz de un objeto `GtkTree'. Seguimos con el mismo problema que antes.

13.5 El widget elemento de árbol El widget GtkTreeItem, cómo el GtkListItem, está derivado de GtkItem, que de nuevo, está derivado de GtkBin. Sin embargo, el elemento en sí mismo es un contenedor genérico que contiene un widget hijo, que puede ser de cualquier tipo. El widget GtkTreeItem tiene ciertos campos extra, pero el único que nos interesa ahora es el campo subárbol.

La definición de la estructura GtkTreeItem es así:

struct _GtkTreeItem{ GtkItem item;

GtkWidget *subtree; GtkWidget *pixmaps_box; GtkWidget *plus_pix_widget, *minus_pix_widget;

GList *pixmaps /* nodo pixmap para esta profundidad de color */

guint expanded : 1;};

El campo pixmaps_box es un GtkEventBox que caza las pulsaciones en el símbolo más/menos que controla la expansión y contracción. El campo pixmaps apunta a una estructura de datos interna. Ya que siempre puede obtener el subárbol de un GtkTreeItem de una forma (relativamente) segura mediante la macro GTK_TREE_ITEM_SUBTREE (Item), es aconsejable no tocar las tripas de un GtkTreeItem a menos que realmente sepa que es lo que está haciendo.

Ya que está derivado directamente de un GtkItem, puede tratarse como tal utilizando la macro GTK_ITEM (TreeItem). Un GtkTreeItem normalmente tiene una etiqueta, por lo que tenemos a nuestra disposición la función gtk_list_item_new_with_label(). Podemos conseguir el mismo efecto utilizando código como el siguiente, que por ahora es sólo una copia de la función gtk_tree_item_new_with_label():

tree_item = gtk_tree_item_new ();label_widget = gtk_label_new (label);gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.5);

Page 119: GTK Tutorial

gtk_container_add (GTK_CONTAINER (tree_item), label_widget);gtk_widget_show (label_widget);

Cómo no es obligatorio añadir una GtkLabel a un GtkTreeItem, puede también añadirle un GtkHBox o una GtkArrow, o hasta un GtkNotebook (aunque en esos casos su aplicación no será muy popular).

Si elimina todos los elementos de un subárbol, será destruido y se eliminará la información sobre su padre, a menos que lo referencie de antemano, además el GtkTreeItem que sea su propietario se colapsará. Por lo tanto, si quiere que se mantenga el subárbol tendrá que hacer algo así:

gtk_widget_ref (tree);owner = GTK_TREE(tree)->tree_owner;gtk_container_remove (GTK_CONTAINER(tree), item);if (tree->parent == NULL){ gtk_tree_item_expand (GTK_TREE_ITEM(owner)); gtk_tree_item_set_subtree (GTK_TREE_ITEM(owner), tree);}else gtk_widget_unref (tree);

Finalmente, hay que mencionar que la opción de drag-n-drop (arrastar y soltar) funciona con los GtkTreeItems. Sólo tiene que asegurarse de que el GtkTreeItem que quiere convertir en un elemento de arrastre o en un lugar en el que, además de haber sido añadido a GtkTree, sino que además cada su widget padre tiene a su vez un padre, y así hasta llegar al nivel más alto o ventana de diálogo, cuando llamamos a gtk_widget_dnd_drag_set() o gtk_widget_dnd_drop_set(). En caso contrario, podrían ocurrir cosas extrañas.

Señales

GtkTreeItem hereda las señales select, deselect, y toggle de GtkItem. Además, añade dos señales propias, expand y collapse.

void select( GtkItem *tree_item );

Esta señal se emite cuando un elemento está siendo seleccionado, o bien después de que el usuario pinche en él, o bien cuando el programa llame a gtk_tree_item_select(), gtk_item_select(), o a gtk_tree_select_child().

void deselect( GtkItem *tree_item );

Esta señal se emite cuando un elemento está siendo deseleccionado, o bien después de que el usuario pinche en él, o bien cuando el programa llame a gtk_tree_item_deselect() o a gtk_item_deselect(). En el caso de GtkTreeItems, también se emitirá por gtk_tree_unselect_child(), y a veces por gtk_tree_select_child().

void toggle( GtkItem *tree_item );

Esta señal se emite cuando el programa llama a gtk_item_toggle(). El efecto que tiene cuando se emite en un GtkTreeItem es llamar a gtk_tree_select_child() (y nunca a gtk_tree_unselect_child()) en el árbol padre del elemento, si el elemento tiene un árbol padre. Si no lo tiene, entonces se cambiará el resaltado del elemento.

void expand( GtkTreeItem *tree_item );

Page 120: GTK Tutorial

Esta señal se emite cuando se está expandiendo el subárbol del elemento, esto es, cuando el usuario pincha en el signo más que hay al lado del elemento, o cuando el programa llama a gtk_tree_item_expand().

void collapse( GtkTreeItem *tree_item );

Esta señal se emite cuando se está contrayendo el subárbol del elemento, esto es, cuando el usuario pincha en el signo menos que hay al lado del elemento, o cuando el programa llama a gtk_tree_item_collapse().

Funciones y Macrosguint gtk_tree_item_get_type( void );

Devuelve el identificador de tipo de `GtkTreeItem'.

GtkWidget* gtk_tree_item_new( void );

Crea un nuevo objeto GtkTreeItem. El nuevo widget se devuelve como un puntero a un objeto GtkWidget. Se devolverá NULL si hay algún fallo.

GtkWidget* gtk_tree_item_new_with_label (gchar *label);

Crea un nuevo objeto GtkTreeItem, teniendo una simple GtkLabel como único hijo. El nuevo widget se devolverá como un puntero a un objeto GtkWidget. Se devolverá NULL en caso de haber algún fallo.

void gtk_tree_item_select( GtkTreeItem *tree_item );

Esta función es básicamente un recubrimiento de una llamada a gtk_item_select (GTK_ITEM (tree_item)) que emitirá la señal select.

void gtk_tree_item_deselect( GtkTreeItem *tree_item );

Esta función es básicamente un recubrimiento de una llamada a gtk_item_deselect (GTK_ITEM (tree_item)) que emitirá la señal deselect.

void gtk_tree_item_set_subtree( GtkTreeItem *tree_item, GtkWidget *subtree );

Esta función añade subtree a tree_item, mostrándolo si tree_item está expandido, u ocultándolo si tree_item está contraído. De nuevo, recuerde que el tree_item ya debe de haber sido añadido a un árbol para que esto funcione.

void gtk_tree_item_remove_subtree( GtkTreeItem *tree_item );

Esto elimina todos los hijos de los subárboles del tree_item (esto es, dereferencia y destruye a los subárboles hijos, y a los hijos de los hijos y...), entonces elimina el subárbol en si mismo, y oculta el signo más/menos.

void gtk_tree_item_expand( GtkTreeItem *tree_item );

Esto emite la señal ``expand'' para el tree_item, que lo expande.

void gtk_tree_item_collapse( GtkTreeItem *tree_item );

Page 121: GTK Tutorial

Esto emite la señal ``collapse'' en el tree_item, que lo contrae.

GtkTreeItem *GTK_TREE_ITEM (gpointer obj)

Convierte un puntero genérico en un `GtkTreeItem *'.

GtkTreeItemClass *GTK_TREE_ITEM_CLASS (gpointer obj)

Convierte un puntero genérico en un `GtkTreeItemClass'.

gint GTK_IS_TREE_ITEM (gpointer obj)

Determina si un puntero genérico se refiere a un objeto `GtkTreeItem'.

GtkWidget GTK_TREE_ITEM_SUBTREE (gpointer obj)

Devuelve un subárbol del elemento (obj debería apuntar a un objeto `GtkTreeItem').

13.6 Árbol ejemplo Este ejemplo es muy parecido al árbol ejemplo que hay en testgtk.c, pero mucho menos completo (aunque mucho mejor comentado). Pone una ventana con un árbol, y conecta todas las señales de los objetos relevantes, con lo que podrá ver cuando se emiten.

/* principio del ejemplo tree tree.c */

#include <gtk/gtk.h>

/* para todas las señales GtkItem:: y GtkTreeItem:: */static void cb_itemsignal (GtkWidget *item, gchar *signame){ gchar *name; GtkLabel *label;

/* Es un GtkBin, por lo que tiene un hijo, que sabemos que es una * etiqueta, por lo que la cogemos */ label = GTK_LABEL (GTK_BIN (item)->child); /* Conseguimos el texto de la etiqueta */ gtk_label_get (label, &name); /* Conseguimos el nivel del árbol en el que se encuentra el elemento */ g_print ("%s called for item %s->%p, level %d\n", signame, name, item, GTK_TREE (item->parent)->level);}

/* nunca se llamará a esta función */static void cb_unselect_child (GtkWidget *root_tree, GtkWidget *child, GtkWidget *subtree){ g_print ("unselect_child called for root tree %p, subtree %p, child %p\n", root_tree, subtree, child);}

/* Se llamará a esta función cada vez que el usuario pulse en un * elemento, esté o no seleccionado. */ whether it is already selected or not. */static void cb_select_child (GtkWidget *root_tree, GtkWidget *child,

Page 122: GTK Tutorial

GtkWidget *subtree){ g_print ("select_child called for root tree %p, subtree %p, child %p\n", root_tree, subtree, child);}

static void cb_selection_changed (GtkWidget *tree){ GList *i; g_print ("selection_change called for tree %p\n", tree); g_print ("selected objects are:\n");

i = GTK_TREE_SELECTION(tree); while (i){ gchar *name; GtkLabel *label; GtkWidget *item;

/* Get a GtkWidget pointer from the list node */ item = GTK_WIDGET (i->data); label = GTK_LABEL (GTK_BIN (item)->child); gtk_label_get (label, &name); g_print ("\t%s on level %d\n", name, GTK_TREE (item->parent)->level); i = i->next; }}

int main (int argc, char *argv[]){ GtkWidget *window, *scrolled_win, *tree; static gchar *itemnames[] = {"Foo", "Bar", "Baz", "Quux", "Maurice"}; gint i;

gtk_init (&argc, &argv);

/* una ventana general */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); gtk_container_border_width (GTK_CONTAINER(window), 5);

/* una ventana con barras de desplazamiento */ scrolled_win = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_usize (scrolled_win, 150, 200); gtk_container_add (GTK_CONTAINER(window), scrolled_win); gtk_widget_show (scrolled_win); /* Crear el árbol raíz */ tree = gtk_tree_new(); g_print ("root tree is %p\n", tree); /* connect all GtkTree:: signals */

Page 123: GTK Tutorial

gtk_signal_connect (GTK_OBJECT(tree), "select_child", GTK_SIGNAL_FUNC(cb_select_child), tree); gtk_signal_connect (GTK_OBJECT(tree), "unselect_child", GTK_SIGNAL_FUNC(cb_unselect_child), tree); gtk_signal_connect (GTK_OBJECT(tree), "selection_changed", GTK_SIGNAL_FUNC(cb_selection_changed), tree); /* Añadirlo a la ventana con barras de desplazamiento */ gtk_container_add (GTK_CONTAINER(scrolled_win), tree); /* Poner el modo de selección */ gtk_tree_set_selection_mode (GTK_TREE(tree), GTK_SELECTION_MULTIPLE); /* mostrar el árbol */ gtk_widget_show (tree);

for (i = 0; i < 5; i++){ GtkWidget *subtree, *item; gint j;

/* Crear un elemento del árbol */ item = gtk_tree_item_new_with_label (itemnames[i]); /* Conectar todas las señales GtkItem:: y GtkTreeItem:: */ gtk_signal_connect (GTK_OBJECT(item), "select", GTK_SIGNAL_FUNC(cb_itemsignal), "select"); gtk_signal_connect (GTK_OBJECT(item), "deselect", GTK_SIGNAL_FUNC(cb_itemsignal), "deselect"); gtk_signal_connect (GTK_OBJECT(item), "toggle", GTK_SIGNAL_FUNC(cb_itemsignal), "toggle"); gtk_signal_connect (GTK_OBJECT(item), "expand", GTK_SIGNAL_FUNC(cb_itemsignal), "expand"); gtk_signal_connect (GTK_OBJECT(item), "collapse", GTK_SIGNAL_FUNC(cb_itemsignal), "collapse"); /* Añadirlo al árbol padre */ gtk_tree_append (GTK_TREE(tree), item); /* Mostrarlo - esto se puede hacer en cualquier momento */ gtk_widget_show (item); /* Crear el subárbol de este elemento */ subtree = gtk_tree_new(); g_print ("-> item %s->%p, subtree %p\n", itemnames[i], item, subtree);

/* Esto todavía es necesario si quiere que se llamen a están * señales en el subárbol hijo. Note that selection_change will * be signalled for the root tree regardless. */ gtk_signal_connect (GTK_OBJECT(subtree), "select_child", GTK_SIGNAL_FUNC(cb_select_child), subtree); gtk_signal_connect (GTK_OBJECT(subtree), "unselect_child", GTK_SIGNAL_FUNC(cb_unselect_child), subtree); /* Esto no tiene absolutamente ningún efecto, ya que se ignora * completamente en los subárboles */ gtk_tree_set_selection_mode (GTK_TREE(subtree), GTK_SELECTION_SINGLE); /* Esto tampoco hace nada, pero por una razón diferente - los * valores view_mode y view_line de un árbol se propagan a los * subárboles cuando son mapeados. Por tanto, establecer los * valores después actualmente tendría (algún impredecible) efecto */ gtk_tree_set_view_mode (GTK_TREE(subtree), GTK_TREE_VIEW_ITEM); /* Establecer este subárbol del elemento - ¡Recuerde que no puede

Page 124: GTK Tutorial

* hacerlo hasta que se haya añadido a su árbol padre! */ gtk_tree_item_set_subtree (GTK_TREE_ITEM(item), subtree);

for (j = 0; j < 5; j++){ GtkWidget *subitem;

/* Crea un elemento subárbol, más o menos lo mismo de antes */ subitem = gtk_tree_item_new_with_label (itemnames[j]); /* Conectar todas las señales GtkItem:: y GtkTreeItem:: */ gtk_signal_connect (GTK_OBJECT(subitem), "select", GTK_SIGNAL_FUNC(cb_itemsignal), "select"); gtk_signal_connect (GTK_OBJECT(subitem), "deselect", GTK_SIGNAL_FUNC(cb_itemsignal), "deselect"); gtk_signal_connect (GTK_OBJECT(subitem), "toggle", GTK_SIGNAL_FUNC(cb_itemsignal), "toggle"); gtk_signal_connect (GTK_OBJECT(subitem), "expand", GTK_SIGNAL_FUNC(cb_itemsignal), "expand"); gtk_signal_connect (GTK_OBJECT(subitem), "collapse", GTK_SIGNAL_FUNC(cb_itemsignal), "collapse"); g_print ("-> -> item %s->%p\n", itemnames[j], subitem); /* Añadirlo a su árbol padre */ gtk_tree_append (GTK_TREE(subtree), subitem); /* Mostrarlo */ gtk_widget_show (subitem); } }

/* Mostrar la ventana y entrar en el bucle final */ gtk_widget_show (window); gtk_main(); return 0;}/* fin del ejemplo */

14. El widget menúHay dos formas de crear menús, la fácil, y la difícil. Ambas tienen su utilidad, aunque lo más probable es que normalmente utilice la menufactory (la forma fácil). La forma ``difícil'' consiste en crear todos los menús utilizando las llamadas directamente. La forma fácil consiste en utilizar las llamadas de gtk_menu_factory. Es mucho más fácil, pero aun así cada aproximación tiene sus ventajas y sus inconvenientes.

La menufactory es mucho más fácil de utilizar, y tambíen es más fácil añadir nuevos menús, aunque a larga, escribiendo unas cuántas funciones de recubrimiento para crear menús utilizando el método manual puede acabar siendo más útil. Con la menufactory, no es posible añadir imágenes o el carácter `/' a los menús.

14.1 Creación manual de menús Siguiendo la auténtica tradición de la enseñanza, vamos a enseñarle primero la forma difícil. :)

Se utilizan tres widgets para hacer una barra de menús y submenús:

Page 125: GTK Tutorial

• un elemento del menú, que es lo que el usuario quiere seleccionar, p.e. 'Guardar' • un menú, que actua como un contenedor para los elementos del menú, y • una barra de menú, que es un contenedor para cada uno de los menús,

Todo esto se complica ligeramente por el hecho de que los widgets de los elementos del menú se utilizan para dos cosas diferentes. Están los widgets que se empaquetan en el menú, y los que se empaquetan en una barra de menús, que cuando se selecciona, activa el menú.

Vamos a ver las funciones que se utilizan para crear menús y barras de menús. ésta primera función se utiliza para crear una barra de menús.

GtkWidget *gtk_menu_bar_new( void );

Como el propio nombre indica, esta función crea una nueva barra de menús. Utilice gtk_container_add para empaquetarla en una ventana, o las funciones box_pack para empaquetarla en una caja - exactamente igual que si fuesen botones.

GtkWidget *gtk_menu_new( void );

Esta función devuelve un puntero a un nuevo menú, que no se debe mostrar nunca (no hace falta utilizar gtk_widget_show), es sólo un contenedor para los elementos del menú. Espero que todo esto se aclare un poco cuando vea en el ejemplo que hay más abajo.

Las siguientes dos llamadas se utilizan para crear elementos de menú que se empaquetarán en el menú (y en la barra de menú).

GtkWidget *gtk_menu_item_new( void );

y

GtkWidget *gtk_menu_item_new_with_label( const char *label );

Estas llamadas se utilizan para crear los elementos del menú que van a mostrarse. Recuerde que hay que distinguir entre un ``menú'' creado con gtk_menu_new y un ``elemento del menú'' creado con las funciones gtk_menu_item_new. El elemento de menú será un botón con una acción asociada, y un menú será un contenedor con los elementos del menú.

Las funciones gtk_menu_new_with_label y gtk_menu_new son sólo lo que espera que sean después de leer lo de los botones. Una crea un nuevo elemento del menú con una etiqueta ya dentro, y la otra crea un elemento del menú en blanco.

Una vez ha creado un elemento del menú tiene que ponerlo en un menú. Esto se hace utilizando la función gtk_menu_append. Para capturar el momento en el que el elemento se selecciona por el usuario, necesitamos conectar con la señal activate de la forma usual. Por tanto, si quiere crear un menú estándar File, con las opciones Open, Save y Quit el código debería ser algo como

file_menu = gtk_menu_new(); /* No hay que mostrar menús */

/* Crear los elementos del menú */open_item = gtk_menu_item_new_with_label("Open");save_item = gtk_menu_item_new_with_label("Save");quit_item = gtk_menu_item_new_with_label("Quit");

/* Añadirlos al menú */gtk_menu_append( GTK_MENU(file_menu), open_item);gtk_menu_append( GTK_MENU(file_menu), save_item);

Page 126: GTK Tutorial

gtk_menu_append( GTK_MENU(file_menu), quit_item);

/* Enlazar las función de llamada a la señal "activate" */gtk_signal_connect_object( GTK_OBJECT(open_items), "activate", GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");gtk_signal_connect_object( GTK_OBJECT(save_items), "activate", GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");

/* Podemos enlazar el elemento de menú Quit con nuestra función de * salida */gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate", GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");

/* Tenemos que mostrar los elementos del menú */We do need to show menu items */gtk_widget_show( open_item );gtk_widget_show( save_item );gtk_widget_show( quit_item );

En este momento tendremos nuestro menú. Ahora necesitamos crear una barra de menús y un elemento de menú para el elemento File, que vamos a añadir a nuestro menú. El código es el siguiente

menu_bar = gtk_menu_bar_new();gtk_container_add( GTK_CONTAINER(window), menu_bar);gtk_widget_show( menu_bar );

file_item = gtk_menu_item_new_with_label("File");gtk_widget_show(file_item);

Ahora necesitamos asociar el menú con file_item. Esto se hace con la función

void gtk_menu_item_set_submenu( GtkMenuItem *menu_item, GtkWidget *submenu );

Por lo que nuestro ejemplo continua con

gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );

Todo lo que queda por hacer es añadir el menú a la barra de menús, que se hace mediante la función

void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);

que en nuestro caso habrá que utilizar así:

gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );

Si queremos que el menú esté alineado a la derecha en la barra de menús, como suele estar la opción de ayuda, podemos utilizar la función siguiente (otra vez en file_item en el ejemplo actual) antes de enlazarla en la barra de menú.

void gtk_menu_item_right_justify( GtkMenuItem *menu_item );

Page 127: GTK Tutorial

Aquí hay un resumen de los pasos que son necesarios para crear una barra de menús con los menús correspondientes ya enlazados:

• Crear un nuevo menú utilizando gtk_menu_new() • Utilizar multiples llamadas a gtk_menu_item_new() para cada elemento que desee tener

en su menú. Y utilizar gtk_menu_append() para poner cada uno de esos nuevos elementos en el menú.

• Crear un elemento de menú utilizando gtk_menu_item_new(). Ésta será la raíz del menú, el texto que aparezca aquí estará en la barra de menús.

• Utilizar gtk_menu_item_set_submenu() para enlazar el menú al elemento del menú raíz (el creado en el paso anterior).

• Crear una nueva barra de menús utilizando gtk_menu_bar_new. Este paso solo necesita hacerse una vez cuando se crea una serie de menús en una barra de menús.

• Utilizar gtk_menu_bar_append para poner el menú raíz en la barra de menús.

Para hacer un menú desplegable hay que seguir prácticamente los mismos pasos. La única diferencia es que el menú no estará conectado `automáticamente' a una barra de menú, sino que para que aparezca deberá llamarse explícitamente a la función gtk_menu_popup() utilizando, por ejemplo, un evento de pulsación de botón. Siga los pasos siguientes:

• Cree una función manejadora de eventos. Tiene que tener el siguiente prototipo

static gint handler( GtkWidget *widget, GdkEvent *event );

y utilice el evento para encontrar donde debe aparecer el menú. • En el manejador de eventos, si el evento es una pulsación de un botón del ratón, tratar event

como un evento de botón (que lo es) y utilizarlo como se indica en el código ejemplo para pasarle información a gtk_menu_popup().

• Enlazar este manejador de eventos con el widget con

gtk_signal_connect_object(GTK_OBJECT(widget), "event", GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));

donde widget es el widget con el que esta conectando, handler es la función manejadora, y menu es un menú creado con gtk_menu_new(). Éste puede ser un menú que esté contenido en una barra de menús, como se puede ver en el código de ejemplo.

14.2 Ejemplo de la creación manual de un menú Esto debería funcionar. Échele un vistazo al ejemplo para aclarar los conceptos.

/* principio del ejemplo menu menu.c */

#include <gtk/gtk.h>

static gint button_press (GtkWidget *, GdkEvent *);static void menuitem_response (gchar *);

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

Page 128: GTK Tutorial

GtkWidget *window; GtkWidget *menu; GtkWidget *menu_bar; GtkWidget *root_menu; GtkWidget *menu_items; GtkWidget *vbox; GtkWidget *button; char buf[128]; int i;

gtk_init (&argc, &argv);

/* crear una nueva ventana */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test"); gtk_signal_connect(GTK_OBJECT (window), "delete_event", (GtkSignalFunc) gtk_main_quit, NULL);

/* Inicializar el widget-menu, y recuerde -- ¡¡Nunca haga * gtk_show_widget() con el widget menu!! * Éste es el menú que contiene todos los elementos del menú, el * que se desplegará cuando pulse en el "Root Menu" en la * aplicación */ menu = gtk_menu_new();

/* Ahora hacemos un pequeño bucle que crea tres elementos de menú * para "test-menu". Recuerde llamar a gtk_menu_append. Aquí * estamos añadiendo una lista de elementos de menú a nuestro * menú. Normalmente tendríamos que cazar aquí la señal "clicked" * de cada uno de los elementos del menú y le deberíamos dar una * función de llamada a cada uno, pero lo vamos a omitimos para * ahorrar espacio. */

for(i = 0; i < 3; i++) { /* Copia los nombres al búfer. */ sprintf(buf, "Test-undermenu - %d", i);

/* Crea un nuevo elemento de menú con un nombre... */ menu_items = gtk_menu_item_new_with_label(buf);

/* ...y lo añade al menú. */ gtk_menu_append(GTK_MENU (menu), menu_items);

/* Hace algo interesante cuando se selecciona el menuitem */ gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate", GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));

/* Muestra el widget */ gtk_widget_show(menu_items); }

/* Ésta es el menú raíz, y será la etiqueta mostrada en la * barra de menús. No habrá ningún manejador de señal conectado, ya que

Page 129: GTK Tutorial

* lo único que hace es desplegar el resto del menú. */ root_menu = gtk_menu_item_new_with_label("Root Menu");

gtk_widget_show(root_menu);

/* Ahora especificamos que queremos que el recien creado "menu" * sea el menú para el "root menu" */ gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);

/* Un vbox para poner dentro un menú y un botón */ vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox);

/* Crear una barra de menú para que contenga al menú y la añadamos * a nuestra ventana principal */ menu_bar = gtk_menu_bar_new(); gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2); gtk_widget_show(menu_bar);

/* Crear un botón al que atar los menús como un popup */ button = gtk_button_new_with_label("press me"); gtk_signal_connect_object(GTK_OBJECT(button), "event", GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu)); gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2); gtk_widget_show(button);

/* Y finalmente añadimos el elemento de menú y la barra de menú -- * éste es el elemento de menú "raíz" sobre el que he estado * delirando =) */ gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);

/* siempre mostramos la ventana como último paso para que todo se * pongo en pantalla a la vez. */ gtk_widget_show(window);

gtk_main ();

return 0;}

/* Responde a una pulsación del botón enviando un menú como un widget * Recuerde que el argumento "widget" es el menú que se está enviando, * NO el botón que se ha pulsado. */

static gint button_press (GtkWidget *widget, GdkEvent *event){

if (event->type == GDK_BUTTON_PRESS) { GdkEventButton *bevent = (GdkEventButton *) event; gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL, bevent->button, bevent->time); /* Le dice al que llamó a la rutina que hemos manejado el * evento; la historia termina aquí. */ return TRUE; }

Page 130: GTK Tutorial

/* Le dice al que llamó a la rutina que no hemos manejado el * evento. */ return FALSE;}

/* Imprime una cadena cuando se selecciona un elemento del menú */

static void menuitem_response (gchar *string){ printf("%s\n", string);}/* final del ejemplo */

También puede hacer que un elemento del menú sea insensible y, utilizando una tabla de teclas aceleradoras, conectar las teclas con las funciones del menú.

14.3 Utilizando GtkMenuFactory Ahora que le hemos enseñado la forma difícil, le mostraremos como utilizar las llamadas gtk_menu_factory.

14.4 Ejemplo de la fábrica de menús Aquí hay un ejemplo de cómo utilizar la fábrica de menús GTK. Éste es el primer fichero, menufactory.h. Mantendremos un menufactory.c y mfmain.c separados debido a las variables globales utilizadas en el fichero menufactory.c.

/* principio del ejemplo menu menufactory.h */

#ifndef __MENUFACTORY_H__#define __MENUFACTORY_H__

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */

void get_main_menu (GtkWidget *, GtkWidget **menubar);

#ifdef __cplusplus}#endif /* __cplusplus */

#endif /* __MENUFACTORY_H__ */

/* fin del ejemplo */

Y aquí está el fichero menufactory.c.

/* principio del ejemplo menu menufactory.c */

#include <gtk/gtk.h>#include <strings.h>

#include "mfmain.h"

Page 131: GTK Tutorial

static void print_hello(GtkWidget *widget, gpointer data);

/* ésta es la estructura GtkMenuEntry utilizada para crear nuevo * menús. El primer miembro es la cadena de definición de menús. El * segundo, la tecla utilizada para acceder a este menú por el * teclado. El tercero es la función de llamada utilizada cuando se * seleccione este elemento de menú (mediante el teclado o mediante el * ratón.) El último miembro es el dato que se debe pasar a nuestra * función de llamada. */

static GtkMenuEntry menu_items[] ={ {"<Main>/File/New", "<control>N", print_hello, NULL}, {"<Main>/File/Open", "<control>O", print_hello, NULL}, {"<Main>/File/Save", "<control>S", print_hello, NULL}, {"<Main>/File/Save as", NULL, NULL, NULL}, {"<Main>/File/<separator>", NULL, NULL, NULL}, {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"}, {"<Main>/Options/Test", NULL, NULL, NULL}};

static voidprint_hello(GtkWidget *widget, gpointer data){ printf("hello!\n");}

void get_main_menu(GtkWidget *window, GtkWidget ** menubar){ int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); GtkMenuFactory *factory; GtkMenuFactory *subfactory;

factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); subfactory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);

gtk_menu_factory_add_subfactory(factory, subfactory, "<Main>"); gtk_menu_factory_add_entries(factory, menu_items, nmenu_items); gtk_window_add_accelerator_table(GTK_WINDOW(window), subfactory->table); if (menubar) *menubar = subfactory->widget;}

/* fin del ejemplo */

Y aquí el mfmain.h

/* principio del ejemplo menu mfmain.h */

#ifndef __MFMAIN_H__#define __MFMAIN_H__

Page 132: GTK Tutorial

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */

void file_quit_cmd_callback(GtkWidget *widget, gpointer data);

#ifdef __cplusplus}#endif /* __cplusplus */

#endif /* __MFMAIN_H__ */

/* fin del ejemplo */

Y mfmain.c

/* principio del ejemplo menu mfmain.c */

#include <gtk/gtk.h>

#include "mfmain.h"#include "menufactory.h"

int main(int argc, char *argv[]){ GtkWidget *window; GtkWidget *main_vbox; GtkWidget *menubar; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(file_quit_cmd_callback), "WM destroy"); gtk_window_set_title(GTK_WINDOW(window), "Menu Factory"); gtk_widget_set_usize(GTK_WIDGET(window), 300, 200); main_vbox = gtk_vbox_new(FALSE, 1); gtk_container_border_width(GTK_CONTAINER(main_vbox), 1); gtk_container_add(GTK_CONTAINER(window), main_vbox); gtk_widget_show(main_vbox); get_main_menu(window, &menubar); gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0); gtk_widget_show(menubar); gtk_widget_show(window); gtk_main(); return(0);}

/* Esta función es sólo para demostrar como funcionan las funciones de * llamada cuando se utiliza la menufactory. Normalmente, la gente pone * todas las funciones de llamada de los menús en un fichero diferente, y

Page 133: GTK Tutorial

* hace referencia a estas funciones desde aquí. Ayuda a mantenerlo todo * más organizado. */void file_quit_cmd_callback (GtkWidget *widget, gpointer data){ g_print ("%s\n", (char *) data); gtk_exit(0);}

/* fin del ejemplo */

Y un makefile para que sea fácil de compilar.

# Makefile.mf

CC = gccPROF = -gC_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUGL_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lmPROGNAME = menufactory

O_FILES = menufactory.o mfmain.o

$(PROGNAME): $(O_FILES) rm -f $(PROGNAME) $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)

.c.o: $(CC) -c $(C_FLAGS) $<

clean: rm -f core *.o $(PROGNAME) nohup.outdistclean: clean rm -f *~

Por ahora, sólo está este ejemplo. Ya llegará una explicación del mismo y más comentarios.

15. El widget textoEl widget texto permite mostrar y editar multiples líneas de texto. Admite texto en varios colores y con varios tipos de letra, permitiendo mezclarlos de cualquier forma que desee. También hay un gran número de teclas para la edición de textos, que son compatibles con Emacs.

El widget texto admite copiar-y-pegar, incluyendo la utilización de doble y triple-click para seleccionar una palabra y una línea completa, respectivamente.

15.1 Creando y configurando un cuadro de texto Sólo hay una función para crear un nuevo widget texto.

GtkWidget *gtk_text_new( GtkAdjustment *hadj, GtkAdjustment *vadj );

Page 134: GTK Tutorial

Los argumentos nos permitirán dar al widget texto punteros a GtkAdjustement que pueden ser utilizados para controlar la visión de la posición del widget. Si le ponemos un valor NULL en cualquiera de los dos argumentos (o en los dos), la función gtk_text_new creará su propio ajuste.

void gtk_text_set_adjustments( GtkText *text, GtkAdjustment *hadj, GtkAdjustment *vadj );

La función de arriba permite cambiar en cualquier momento el ajuste horizontal y vertical de un widget texto.

El widget texto no crea automáticamente sus propiar barras de desplazamiento cuando el texto a mostrar es demasiado largo para la ventana en la que se encuentra. Tenemos que crearlas y añadirlas a la capa del display nosotros mismos.

vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj); gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0); gtk_widget_show (vscrollbar);

El trozo de código de arriba crea una nueva barra de desplazamiento vertical, y la conecta con el ajuste vertical del widget de texto, text. Entonces la empaqueta en un cuadro de la forma usual.

Observe que, actualmente el widget GtkText no admite barras de desplazamiento horizontal.

Principalmente hay dos maneras de utilizar un widget de texto: permitiendo al usuario editar el texto, o permitiéndonos mostrar varias líneas de texto al usuario. Para cambiar entre estos dos modos de operación, el widget de texto tiene las siguientes funciones:

void gtk_text_set_editable( GtkText *text, gint editable );

El argumento editable es un valor TRUE o FALSE que especifica si se permite al usuario editar los contenidos del widget texto. Cuando el widget texto se pueda editar, mostrará un cursor en la posición actual de inserción.

Sin embargo la utilización del widget en estos dos modos no es algo permanente, ya que puede cambiar el estado editable del widget texto e insertar texto en cualquier momento.

El widget texto corta las líneas de texto que son demasiado largas para que quepan en una sólo línea en la ventana. Su comportamiento por defecto es cortar las palabras donde se terminan las líneas. Esto puede cambiarse utilizando la siguiente función:

void gtk_text_set_word_wrap( GtkText *text, gint word_wrap );

Utilizando esta función podremos especificar que el widget texto debería cortar las líneas largas en los límites de las palabras. El argumento word_wrap es un valor TRUE o FALSE.

15.2 Manipulación de texto El punto actual de inserción en un widget texto puede cambiarse utilizando

void gtk_text_set_point( GtkText *text, guint index );

donde index es la posición en la que poner el punto de inserción.

Page 135: GTK Tutorial

Análogamente tenemos la función para obtener la posición del punto de inserción:

guint gtk_text_get_point( GtkText *text );

Una función que es útil en combinación con las dos anteriores es

guint gtk_text_get_length( GtkText *text );

que devuelve la longitud actual del widget texto. La longitud es el número de caracteres que hay en el bloque de texto, incluyendo caracteres como el retorno de carro, que marca el final de las líneas.

Para insertar texto en la posición actual del cursor, tendrá que utilizar la función gtk_text_insert, que nos permitirá especificar los colores de fondo y de la letra y un tipo de letra para el texto.

void gtk_text_insert( GtkText *text, GdkFont *font, GdkColor *fore, GdkColor *back, const char *chars, gint length );

Pasar un valor NULL como el color de la letra (fore), el color de fondo (back) o el tipo de letra (font) hará que se utilicen los valores que indiquen el estilo del widget. Utilizar un valor de -1 para el parámetro length hará que se inserte todo el texto.

El widget texto es uno de los pocos de GTK que se redibuja a sí mismo dinámicamente, fuera de la función gtk_main. Esto significa que todos los cambios en el contenido del widget texto se manifestarán inmediatamente. Para permitirnos realizar varias actualizaciones del widget de texto sin que se redibuje continuamente, podemos congelar el widget, lo que hará que pare momentaneamente de redibujarse a sí mismo cada vez que haya algún cambio. Podemos descongelarlo cuando hayamos acabado con nuestras actualizaciones.

Las siguientes dos funciones realizarán la acción de congelar y descongelar el widget:

void gtk_text_freeze( GtkText *text );

void gtk_text_thaw( GtkText *text );

Se puede borrar el texto que se encuentra en el punto actual de inserción del widget de texto mediante dos funciones. El valor devuelto es TRUE o FALSE en función del éxito de la operación.

gint gtk_text_backward_delete( GtkText *text, guint nchars );

gint gtk_text_forward_delete ( GtkText *text, guint nchars );

Si quiere recuperar el contenido del widget de texto, entonces la macro GTK_TEXT_INDEX(t, index) le permitirá obtener el carácter que se encuentra en la posición index del widget de texto t.

Para obtener mayores bloques de texto, podemos utilizar la función

gchar *gtk_editable_get_chars( GtkEditable *editable, gint start_pos, gint end_pos );

Esta es una función de la clase padre del widget texto. Un valor de -1 en end_pos significa el final del

Page 136: GTK Tutorial

texto. El índice del texto empieza en 0.

La función reserva un espacio de memoria para el bloque de texto, por lo que no debe olvidarse de liberarlo con una llamada a g_free cuando haya acabado el bloque.

15.3 Atajos por teclado El widget texto tiene ciertos atajos de teclado preinstalados para las funciones de edición estándar, movimiento y selección. Pueden utilizarse mediante combinaciones de las teclas Control y Alt.

Además, si se mantiene apretada la tecla de Control y se utilizan las teclas de movimiento, el cursor se moverá por palabras en lugar de por caracteres. Manteniendo apretada la tecla Shift, las teclas de movimiento harán que se extienda la selección.

Atajos para el movimiento

• Ctrl-A Principio de línea • Ctrl-E Final de línea • Ctrl-N Línea siguiente • Ctrl-P Línea anterior • Ctrl-B Retrasarse un carácter • Ctrl-F Adelantarse un carácter • Alt-B Retrasarse una palabra • Alt-F Adelantarse una palabra

Atajos para la edición

• Ctrl-H Borrar el carácter anterior (Backspace) • Ctrl-D Borrar el carácter siguiente (Suprimir) • Ctrl-W Borrar la palabra anterior • Alt-D Borrar la palabra siguiente • Ctrl-K Borrar hasta el fin de la línea • Ctrl-U Borrar la línea

Atajos de selección

• Ctrl-X Cortar al portapapeles • Ctrl-C Copiar al portapapeles • Ctrl-V Pegar desde el portapapeles

15.4 Un ejemplo de GtkText /* principio del ejemplo text text.c */

/* text.c */

#include <stdio.h>#include <gtk/gtk.h>

Page 137: GTK Tutorial

void text_toggle_editable (GtkWidget *checkbutton, GtkWidget *text){ gtk_text_set_editable(GTK_TEXT(text), GTK_TOGGLE_BUTTON(checkbutton)->active);}

void text_toggle_word_wrap (GtkWidget *checkbutton, GtkWidget *text){ gtk_text_set_word_wrap(GTK_TEXT(text), GTK_TOGGLE_BUTTON(checkbutton)->active);}

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

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *box1; GtkWidget *box2; GtkWidget *hbox; GtkWidget *button; GtkWidget *check; GtkWidget *separator; GtkWidget *table; GtkWidget *vscrollbar; GtkWidget *text; GdkColormap *cmap; GdkColor colour; GdkFont *fixed_font;

FILE *infile;

gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_usize (window, 600, 500); gtk_window_set_policy (GTK_WINDOW(window), TRUE, TRUE, FALSE); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC(close_application), NULL); gtk_window_set_title (GTK_WINDOW (window), "Text Widget Example"); gtk_container_border_width (GTK_CONTAINER (window), 0); box1 = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), box1); gtk_widget_show (box1); box2 = gtk_vbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); gtk_widget_show (box2);

Page 138: GTK Tutorial

table = gtk_table_new (2, 2, FALSE); gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2); gtk_table_set_col_spacing (GTK_TABLE (table), 0, 2); gtk_box_pack_start (GTK_BOX (box2), table, TRUE, TRUE, 0); gtk_widget_show (table); /* Crear el widget GtkText */ text = gtk_text_new (NULL, NULL); gtk_text_set_editable (GTK_TEXT (text), TRUE); gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1, GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (text);

/* Añadir una barra de desplazamiento vertical al widget GtkText */ vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj); gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1, GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (vscrollbar);

/* Obtener el mapa de colores del sistema y conseguir el color rojo */ cmap = gdk_colormap_get_system(); colour.red = 0xffff; colour.green = 0; colour.blue = 0; if (!gdk_color_alloc(cmap, &colour)) { g_error("couldn't allocate colour"); }

/* Cargar un fuente de tamaño fijo */ fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*");

/* Al enviar la señal relize a un widget se crea una ventana para el * mismo, y nos permite insertar texto */ gtk_widget_realize (text);

/* Congela el widget text, lo que nos permite hacer varias * actualizaciones */ gtk_text_freeze (GTK_TEXT (text)); /* Insertamos algún texto coloreado */ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL, "Supports ", -1); gtk_text_insert (GTK_TEXT (text), NULL, &colour, NULL, "colored ", -1); gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL, "text and different ", -1); gtk_text_insert (GTK_TEXT (text), fixed_font, &text->style->black, NULL, "fonts\n\n", -1); /* Cargamos el fichero text.c en la ventana de texto */

infile = fopen("text.c", "r");

Page 139: GTK Tutorial

if (infile) { char buffer[1024]; int nchars; while (1) { nchars = fread(buffer, 1, 1024, infile); gtk_text_insert (GTK_TEXT (text), fixed_font, NULL, NULL, buffer, nchars); if (nchars < 1024) break; } fclose (infile); }

/* Descongelamos el widget text, permitiéndonos ver todos los * cambios */ gtk_text_thaw (GTK_TEXT (text)); hbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (box2), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox);

check = gtk_check_button_new_with_label("Editable"); gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, FALSE, 0); gtk_signal_connect (GTK_OBJECT(check), "toggled", GTK_SIGNAL_FUNC(text_toggle_editable), text); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); gtk_widget_show (check); check = gtk_check_button_new_with_label("Wrap Words"); gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, TRUE, 0); gtk_signal_connect (GTK_OBJECT(check), "toggled", GTK_SIGNAL_FUNC(text_toggle_word_wrap), text); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), FALSE); gtk_widget_show (check);

separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0); gtk_widget_show (separator);

box2 = gtk_vbox_new (FALSE, 10); gtk_container_border_width (GTK_CONTAINER (box2), 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0); gtk_widget_show (box2); button = gtk_button_new_with_label ("close"); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC(close_application), NULL); gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_grab_default (button); gtk_widget_show (button);

gtk_widget_show (window);

Page 140: GTK Tutorial

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

16. Widgets no documentados¡Todos ellos necesitan de gente que los documente! :) Por favor, considere el contribuir a nuestro tutorial.

Si debe utilizar uno de estos widgets, que permanecen no documentados, le sugiero que le eche un vistazo a su fichero de cabecera respectivo en la distribución GTK. Los nombre de las funciones GTK son muy descriptivos. Una vez haya comprendido como funcionan las cosas, no le será difícil ver como hay que utilizar un widget simplemente mirando su declaración de funciones. Con esto, y unos cuántos ejemplos del código de otros, no debería tener problemas.

Cuando haya comprendido todas las funciones de un nuevo widget no documentado, por favor considere el hecho de escribir un tutorial para él, para que así otros se puedan beneficiar del tiempo que usted gastó.

16.1 Fixed Container

16.2 Curves

16.3 Previews (This may need to be rewritten to follow the style of the rest of the tutorial)

Previews serve a number of purposes in GIMP/GTK. The most important one isthis. High quality images may take up to tens of megabytes of memory - easy!Any operation on an image that big is bound to take a long time. If it takesyou 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert afteryou make an error) to choose the desired modification, it make take youliterally hours to make the right one - if you don't run out of memoryfirst. People who have spent hours in color darkrooms know the feeling.Previews to the rescue!

But the annoyance of the delay is not the only issue. Oftentimes it ishelpful to compare the Before and After versions side-by-side or at leastback-to-back. If you're working with big images and 10 second delays,obtaining the Before and After impressions is, to say the least, difficult.For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is rightout for most people, while back-to-back is more like back-to-1001, 1002,

Page 141: GTK Tutorial

..., 1010-back! Previews to the rescue!

But there's more. Previews allow for side-by-side pre-previews. In otherwords, you write a plug-in (e.g. the filterpack simulation) which would havea number of here's-what-it-would-look-like-if-you-were-to-do-this previews.An approach like this acts as a sort of a preview palette and is veryeffective for subtle changes. Let's go previews!

There's more. For certain plug-ins real-time image-specific humanintervention maybe necessary. In the SuperNova plug-in, for example, theuser is asked to enter the coordinates of the center of the futuresupernova. The easiest way to do this, really, is to present the user with apreview and ask him to interactively select the spot. Let's go previews!

Finally, a couple of misc uses. One can use previews even when not workingwith big images. For example, they are useful when rendering complicatedpatterns. (Just check out the venerable Diffraction plug-in + many otherones!) As another example, take a look at the colormap rotation plug-in(work in progress). You can also use previews for little logos inside youplug-ins and even for an image of yourself, The Author. Let's go previews!

When Not to Use Previews

Don't use previews for graphs, drawing etc. GDK is much faster for that. Usepreviews only for rendered images!

Let's go previews!

You can stick a preview into just about anything. In a vbox, an hbox, atable, a button, etc. But they look their best in tight frames around them.Previews by themselves do not have borders and look flat without them. (Ofcourse, if the flat look is what you want...) Tight frames provide thenecessary borders.

[Image][Image]

Previews in many ways are like any other widgets in GTK (whatever thatmeans) except they possess an additional feature: they need to be filled withsome sort of an image! First, we will deal exclusively with the GTK aspectof previews and then we'll discuss how to fill them.

GtkWidget *preview!

Without any ado:

/* Create a preview widget, set its size, an show it */GtkWidget *preview;

Page 142: GTK Tutorial

preview=gtk_preview_new(GTK_PREVIEW_COLOR) /*Other option: GTK_PREVIEW_GRAYSCALE);*/gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);gtk_widget_show(preview);my_preview_rendering_function(preview);

Oh yeah, like I said, previews look good inside frames, so how about:

GtkWidget *create_a_preview(int Width, int Height, int Colorfulness){ GtkWidget *preview; GtkWidget *frame; frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_border_width (GTK_CONTAINER(frame),0); gtk_widget_show(frame);

preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR :GTK_PREVIEW_GRAYSCALE); gtk_preview_size (GTK_PREVIEW (preview), Width, Height); gtk_container_add(GTK_CONTAINER(frame),preview); gtk_widget_show(preview);

my_preview_rendering_function(preview); return frame;}

That's my basic preview. This routine returns the "parent" frame so you canplace it somewhere else in your interface. Of course, you can pass theparent frame to this routine as a parameter. In many situations, however,the contents of the preview are changed continually by your application. Inthis case you may want to pass a pointer to the preview to a"create_a_preview()" and thus have control of it later.

One more important note that may one day save you a lot of time. Sometimesit is desirable to label you preview. For example, you may label the previewcontaining the original image as "Original" and the one containing themodified image as "Less Original". It might occur to you to pack thepreview along with the appropriate label into a vbox. The unexpected caveatis that if the label is wider than the preview (which may happen for avariety of reasons unforseeable to you, from the dynamic decision on thesize of the preview to the size of the font) the frame expands and no longerfits tightly over the preview. The same problem can probably arise in othersituations as well.

[Image]

Page 143: GTK Tutorial

The solution is to place the preview and the label into a 2x1 table and byattaching them with the following parameters (this is one possible variationsof course. The key is no GTK_FILL in the second attachment):

gtk_table_attach(GTK_TABLE(table),label,0,1,0,1, 0, GTK_EXPAND|GTK_FILL, 0,0);gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2, GTK_EXPAND, GTK_EXPAND, 0,0);

And here's the result:

[Image]

Misc

Making a preview clickable is achieved most easily by placing it in abutton. It also adds a nice border around the preview and you may not evenneed to place it in a frame. See the Filter Pack Simulation plug-in for anexample.

This is pretty much it as far as GTK is concerned.

Filling In a Preview

In order to familiarize ourselves with the basics of filling in previews,let's create the following pattern (contrived by trial and error):

[Image]

voidmy_preview_rendering_function(GtkWidget *preview){#define SIZE 100#define HALF (SIZE/2)

guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */ gint i, j; /* Coordinates */ double r, alpha, x, y;

if (preview==NULL) return; /* I usually add this when I want */ /* to avoid silly crashes. You */ /* should probably make sure that */ /* everything has been nicely */ /* initialized! */ for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */ /* glib.h contains ABS(x). */ row[i*3+0] = sqrt(1-r)*255; /* Define Red */ row[i*3+1] = 128; /* Define Green */ row[i*3+2] = 224; /* Define Blue */

Page 144: GTK Tutorial

} /* "+0" is for alignment! */ else { row[i*3+0] = r*255; row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255; row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255; } } gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE); /* Insert "row" into "preview" starting at the point with */ /* coordinates (0,j) first column, j_th row extending SIZE */ /* pixels to the right */ }

free(row); /* save some space */ gtk_widget_draw(preview,NULL); /* what does this do? */ gdk_flush(); /* or this? */}

Non-GIMP users can have probably seen enough to do a lot of things already.For the GIMP users I have a few pointers to add.

Image Preview

It is probably wise to keep a reduced version of the image around with justenough pixels to fill the preview. This is done by selecting every n'thpixel where n is the ratio of the size of the image to the size of thepreview. All further operations (including filling in the previews) are thenperformed on the reduced number of pixels only. The following is myimplementation of reducing the image. (Keep in mind that I've had only basicC!)

(UNTESTED CODE ALERT!!!)

typedef struct { gint width; gint height; gint bbp; guchar *rgb; guchar *mask;} ReducedImage;

enum { SELECTION_ONLY, SELECTION_IN_CONTEXT, ENTIRE_IMAGE};

ReducedImage *Reduce_The_Image(GDrawable *drawable, GDrawable *mask, gint LongerSize, gint Selection){ /* This function reduced the image down to the the selected preview size */

Page 145: GTK Tutorial

/* The preview size is determine by LongerSize, i.e. the greater of the */ /* two dimensions. Works for RGB images only! */ gint RH, RW; /* Reduced height and reduced width */ gint width, height; /* Width and Height of the area being reduced */ gint bytes=drawable->bpp; ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));

guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B; gint i, j, whichcol, whichrow, x1, x2, y1, y2; GPixelRgn srcPR, srcMask; gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */ /* image. */

gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); width = x2-x1; height = y2-y1; /* If there's a SELECTION, we got its bounds!)

if (width != drawable->width && height != drawable->height) NoSelectionMade=FALSE; /* Become aware of whether the user has made an active selection */ /* This will become important later, when creating a reduced mask. */

/* If we want to preview the entire image, overrule the above! */ /* Of course, if no selection has been made, this does nothing! */ if (Selection==ENTIRE_IMAGE) { x1=0; x2=drawable->width; y1=0; y2=drawable->height; }

/* If we want to preview a selection with some surrounding area we */ /* have to expand it a little bit. Consider it a bit of a riddle. */ if (Selection==SELECTION_IN_CONTEXT) { x1=MAX(0, x1-width/2.0); x2=MIN(drawable->width, x2+width/2.0); y1=MAX(0, y1-height/2.0); y2=MIN(drawable->height, y2+height/2.0); }

/* How we can determine the width and the height of the area being */ /* reduced. */ width = x2-x1; height = y2-y1;

/* The lines below determine which dimension is to be the longer */ /* side. The idea borrowed from the supernova plug-in. I suspect I */ /* could've thought of it myself, but the truth must be told. */ /* Plagiarism stinks! */ if (width>height) { RW=LongerSize;

Page 146: GTK Tutorial

RH=(float) height * (float) LongerSize/ (float) width; } else { RH=LongerSize; RW=(float)width * (float) LongerSize/ (float) height; }

/* The entire image is stretched into a string! */ tempRGB = (guchar *) malloc(RW*RH*bytes); tempmask = (guchar *) malloc(RW*RH);

gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);

/* Grab enough to save a row of image and a row of mask. */ src_row = (guchar *) malloc (width*bytes); src_mask_row = (guchar *) malloc (width);

for (i=0; i < RH; i++) { whichrow=(float)i*(float)height/(float)RH; gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);

for (j=0; j < RW; j++) { whichcol=(float)j*(float)width/(float)RW;

/* No selection made = each point is completely selected! */ if (NoSelectionMade) tempmask[i*RW+j]=255; else tempmask[i*RW+j]=src_mask_row[whichcol];

/* Add the row to the one long string which now contains the image! */ tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];

/* Hold on to the alpha as well */ if (bytes==4) tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; } } temp->bpp=bytes; temp->width=RW; temp->height=RH; temp->rgb=tempRGB; temp->mask=tempmask; return temp;}

The following is a preview function which used the same ReducedImage type!Note that it uses fakes transparency (if one is present by means offake_transparency which is defined as follows:

Page 147: GTK Tutorial

gint fake_transparency(gint i, gint j){ if ( ((i%20)- 10) * ((j%20)- 10)>0 ) return 64; else return 196;}

Now here's the preview function:

voidmy_preview_render_function(GtkWidget *preview, gint changewhat, gint changewhich){ gint Inten, bytes=drawable->bpp; gint i, j, k; float partial; gint RW=reduced->width; gint RH=reduced->height; guchar *row=malloc(bytes*RW);;

for (i=0; i < RH; i++) { for (j=0; j < RW; j++) {

row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0]; row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1]; row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];

if (bytes==4) for (k=0; k<3; k++) { float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0; row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j); } } gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW); }

free(a); gtk_widget_draw(preview,NULL); gdk_flush();}

Applicable Routines

guint gtk_preview_get_type (void);/* No idea */void gtk_preview_uninit (void);/* No idea */GtkWidget* gtk_preview_new (GtkPreviewType type);/* Described above */void gtk_preview_size (GtkPreview *preview, gint width, gint height);/* Allows you to resize an existing preview. *//* Apparently there's a bug in GTK which makes */

Page 148: GTK Tutorial

/* this process messy. A way to clean up a mess *//* is to manually resize the window containing *//* the preview after resizing the preview. */

void gtk_preview_put (GtkPreview *preview, GdkWindow *window, GdkGC *gc, gint srcx, gint srcy, gint destx, gint desty, gint width, gint height);/* No idea */

void gtk_preview_put_row (GtkPreview *preview, guchar *src, guchar *dest, gint x, gint y, gint w);/* No idea */

void gtk_preview_draw_row (GtkPreview *preview, guchar *data, gint x, gint y, gint w);/* Described in the text */

void gtk_preview_set_expand (GtkPreview *preview, gint expand);/* No idea */

/* No clue for any of the below but *//* should be standard for most widgets */void gtk_preview_set_gamma (double gamma);void gtk_preview_set_color_cube (guint nred_shades, guint ngreen_shades, guint nblue_shades, guint ngray_shades);void gtk_preview_set_install_cmap (gint install_cmap);void gtk_preview_set_reserved (gint nreserved);GdkVisual* gtk_preview_get_visual (void);GdkColormap* gtk_preview_get_cmap (void);GtkPreviewInfo* gtk_preview_get_info (void);

That's all, folks!

Page 149: GTK Tutorial

17. El widget EventBoxAlgunos widget gtk no tienen asociada una ventana X, por lo que sólo pueden dibujar en la de su padre. Debido a esto, no pueden recibir ningún evento y si tienen un tamaño incorrecto, no se recortarán correctamente por lo que puede que se sobreescriban ciertas zonas, etc... Si necesita este tipo de widgets, el EventBox es para usted.

Cuando se ve por primera vez, el widget EventBox puede parecer completamente inútil. No dibuja nada en la pantalla y no responde a ningún evento. Sin embargo, tiene una utilidad - proporciona una ventana X para su widget hijo. Esto es importante ya que muchos widgets GTK no tienen una ventana X asociada. No tener una ventana X ahorra memoria y mejora el rendimiento, pero tiene sus desventajas. Un widget sin una ventana X no puede recibir eventos, y no realizará ningún recorte en sus contenidos. Aunque el nombre EventBox enfatiza su función de manejador de eventos, el widget también puede utilizarse para hacer los recortes. (Y más... ver el ejemplo más abajo.)

Para crear un nuevo widget EventBox, utilice:

GtkWidget *gtk_event_box_new( void );

Un widget hijo puede añadirse a su EventBox así:

gtk_container_add( GTK_CONTAINER(event_box), widget );

El siguiente ejemplo demuestra los dos usos de EventBox - se crea una etiqueta que se recorta dentro de una pequeña caja, y hace que una pulsación del ratón en la misma finalice el programa.

/* principio del ejemplo eventbox eventbox.c */

#include <gtk/gtk.h>

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *event_box; GtkWidget *label; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Box"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); /* Crea un EventBox y lo añade a nuestra ventana superior */

event_box = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER(window), event_box); gtk_widget_show (event_box); /* Crea una larga etiqueta */ label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");

Page 150: GTK Tutorial

gtk_container_add (GTK_CONTAINER (event_box), label); gtk_widget_show (label); /* La recortamos. */ gtk_widget_set_usize (label, 110, 20); /* Y enlazamos una acción con la etiqueta */ gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event", GTK_SIGNAL_FUNC (gtk_exit), NULL); /* Otra cosa más que necesita una ventana X ... */ gtk_widget_realize (event_box); gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); gtk_widget_show (window); gtk_main (); return 0;}/* fin del ejemplo */

18. Estableciendo los atributos de un widgetEn este capítulo se describen las funciones utilizadas para manejar los widgets. Pueden utilizarse para establecer el estilo, relleno, tamaño, etc...

(Puede que deba hacer una sección completa para los aceleradores.)

void gtk_widget_install_accelerator( GtkWidget *widget, GtkAcceleratorTable *table, gchar *signal_name, gchar key, guint8 modifiers );

void gtk_widget_remove_accelerator ( GtkWidget *widget, GtkAcceleratorTable *table, gchar *signal_name);

void gtk_widget_activate( GtkWidget *widget );

void gtk_widget_set_name( GtkWidget *widget, gchar *name );

gchar *gtk_widget_get_name( GtkWidget *widget );

void gtk_widget_set_sensitive( GtkWidget *widget, gint sensitive );

void gtk_widget_set_style( GtkWidget *widget, GtkStyle *style );

Page 151: GTK Tutorial

GtkStyle *gtk_widget_get_style( GtkWidget *widget );

GtkStyle *gtk_widget_get_default_style( void );

void gtk_widget_set_uposition( GtkWidget *widget, gint x, gint y );

void gtk_widget_set_usize( GtkWidget *widget, gint width, gint height );

void gtk_widget_grab_focus( GtkWidget *widget );

void gtk_widget_show( GtkWidget *widget );

void gtk_widget_hide( GtkWidget *widget );

19. Tiempos de espera, ES (IO) y funciones ociosas (idle)

19.1 Tiempos de espera Puede que se esté preguntando como hacer que GTK haga algo útil cuando se encuentra en gtk_main. Bien, tiene varias opciones. Utilizando las rutinas siguientes puede crear una función a la que se llamará cada interval milisegundos.

gint gtk_timeout_add( guint32 interval, GtkFunction function, gpointer data );

El primer argumento es el número de milisegundos que habrá entre dos llamadas a su función. El segundo argumento es la función a la que desea llamar, y el tercero, los datos que le pasará a ésta función. El valor devuelto es un ``identificador'' (un valor entero) que puede utilizar para detener las llamadas haciendo:

void gtk_timeout_remove( gint tag );

También puede hacer que cesen las llamadas a la función haciendo que la misma devuelva cero o FALSE. Obviamente esto significa que si quiere que se continue llamando a su función, deberá devolver un valor distinto de cero, es decir TRUE.

La declaración de su función debería ser algo como:

gint timeout_callback( gpointer data );

19.2 Monitorizando la ES Otra característica divertida de GTK, es la habilidad que tiene de comprobar datos por usted en un descriptor de fichero (tal y como se devuelven por open(2) o socket(2)). Esto es especialmente útil para las aplicaciones de red. La función:

Page 152: GTK Tutorial

gint gdk_input_add( gint source, GdkInputCondition condition, GdkInputFunction function, gpointer data );

Donde el primer argumento es el descriptor de fichero que desea vigilar, y el segundo especifica que es lo que quiere que GDK busque. Puede ser uno de los siguientes:

• GDK_INPUT_READ - Llama a su función cuando hay datos listos para leerse del fichero. • GDK_INPUT_WRITE - Llama a su función cuando el descriptor del fichero está listo para la

escritura.

Tal y como se habrá imaginado, el tercer argumento es la función a la que desea que se llame cuando se den las condiciones anteriores, y el cuarto son los datos que se le pasarán a ésta función.

El valor devuelto es un identificador que puede utilizarse para que GDK pare de vigilar ese fichero, utilizando la función

void gdk_input_remove( gint tag );

La función a la que quiere que se llame deberá declararse así:

void input_callback( gpointer data, gint source, GdkInputCondition condition );

Donde source y condition están especificados más arriba.

19.3 Funciones ociosas ¿Qué le parece si tuviese una función a la que se llamase cuando no ocurriese nada?

gint gtk_idle_add( GtkFunction function, gpointer data );

Esto hace que GTK llame a la función especificada cuando no ocurra nada más.

void gtk_idle_remove( gint tag );

No voy a explicar el significado de los argumentos ya que se parece mucho a los que he explicado más arriba. La función a la que se apunta mediante el primer argumento de gtk_idle_add será a la que se llame cuando llegue el momento. Como antes, si devuelve FALSE hará que cese de llamarse a la función.

20. Manejo avanzado de eventos y señales

20.1 Funciones señal

Conectando y desconectando los manejadores de señalguint gtk_signal_connect( GtkObject *object,

Page 153: GTK Tutorial

const gchar *name, GtkSignalFunc func, gpointer func_data );

guint gtk_signal_connect_after( GtkObject *object, const gchar *name, GtkSignalFunc func, gpointer func_data );

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

guint gtk_signal_connect_object_after( GtkObject *object, const gchar *name, GtkSignalFunc func, GtkObject *slot_object );

guint gtk_signal_connect_full( GtkObject *object, const gchar *name, GtkSignalFunc func, GtkCallbackMarshal marshal, gpointer data, GtkDestroyNotify destroy_func, gint object_signal, gint after );

guint gtk_signal_connect_interp( GtkObject *object, const gchar *name, GtkCallbackMarshal func, gpointer data, GtkDestroyNotify destroy_func, gint after );

void gtk_signal_connect_object_while_alive( GtkObject *object, const gchar *signal, GtkSignalFunc func, GtkObject *alive_object );

void gtk_signal_connect_while_alive( GtkObject *object, const gchar *signal, GtkSignalFunc func, gpointer func_data, GtkObject *alive_object );

void gtk_signal_disconnect( GtkObject *object, guint handler_id );

void gtk_signal_disconnect_by_func( GtkObject *object, GtkSignalFunc func, gpointer data );

Bloqueando y desbloqueando los manejadores de señalvoid gtk_signal_handler_block( GtkObject *object,

Page 154: GTK Tutorial

guint handler_id);

void gtk_signal_handler_block_by_func( GtkObject *object, GtkSignalFunc func, gpointer data );

void gtk_signal_handler_block_by_data( GtkObject *object, gpointer data );

void gtk_signal_handler_unblock( GtkObject *object, guint handler_id );

void gtk_signal_handler_unblock_by_func( GtkObject *object, GtkSignalFunc func, gpointer data );

void gtk_signal_handler_unblock_by_data( GtkObject *object, gpointer data );

Emitiendo y deteniendo señalesvoid gtk_signal_emit( GtkObject *object, guint signal_id, ... );

void gtk_signal_emit_by_name( GtkObject *object, const gchar *name, ... );

void gtk_signal_emitv( GtkObject *object, guint signal_id, GtkArg *params );

void gtk_signal_emitv_by_name( GtkObject *object, const gchar *name, GtkArg *params );

guint gtk_signal_n_emissions( GtkObject *object, guint signal_id );

guint gtk_signal_n_emissions_by_name( GtkObject *object, const gchar *name );

void gtk_signal_emit_stop( GtkObject *object, guint signal_id );

void gtk_signal_emit_stop_by_name( GtkObject *object, const gchar *name );

20.2 Emisión y propagación de señales La emisión de señales es el proceso mediante el cual GTK+ ejecuta todos los manejadores de un objeto y una señal en especial.

Primero, observe que el valor devuelto por la emisión de una señal es el mismo que el valor devuelto

Page 155: GTK Tutorial

por el último manipulador ejecutado. Ya que las señales de los eventos son todas del tipo GTK_RUN_LAST, el manejador por defecto (proporcionado por GTK+) será de este tipo, a menos que lo conecte con gtk_signal_connect_after().

La forma en que se maneja un evento (digamos GTK_BUTTON_PRESS), es la siguiente:

• Empieza con el widget donde ocurrió el evento. • Emite la señal genérica event. Si esta señal devuelve un valor TRUE, detiene todo el proceso. • En caso contrario, emite una señal especifica, ``button_press_event'' en nuestro caso. Si ésta

devuelve TRUE, detiene todo el proceso. • En caso contrario, va al widget padre y repite los pasos anteriores. • Continua hasta que algún manejador de señal devuelva TRUE, o hasta que se llegue al widget

de más alto nivel.

Algunas consecuencias son:

• El valor que devuelva su manejador no tendrá ningún efecto si hay un manejador por defecto, a menos que lo conecte mediante gtk_signal_connect_after().

• Para evitar que el manejador por defecto se ejecute, necesita conectar mediante gtk_signal_connect() y utilizar gtk_signal_emit_stop_by_name() - el valor devuelto sólo se ve afectado si la señal se propaga, y no sólo por el hecho de emitirse.

21. Manejando selecciones

21.1 Contenido Un tipo de comunicación entre procesos que se puede utilizar con GTK son las selecciones. Una selección identifica un conjunto de datos, por ejemplo, un trozo de texto, seleccionado por el usuario de alguna manera, por ejemplo, cogiéndolo con el ratón. Sólo una aplicación en un display (la propietaria) puede tener una selección en particular en un momento dado, por lo que cuando una aplicación pide una selección, el propietario previo debe indicar al usuario que la selección ya no es válida. Otras aplicaciones pueden pedir el contenido de la selección de diferentes formas, llamadas objetivos. Puede haber cualquier número de selecciones, pero la mayoría de las aplicacion X sólo pueden manejar una, la selección primaria.

En muchos casos, no es necesario para una aplicación GTK tratar por sí misma con las selecciones. Los widgets estándar, como el widget Entry, ya tienen la posibilidad de crear la selección cuando sea necesario (p.e., cuando el usuario pase el ratón sobre el texto manteniendo el botón derecho del ratón pulsado), y de recoger los contenidos de la selección propiedad de otro widget, o de otra aplicación (p.e., cuando el usuario pulsa el segundo botón del ratón). Sin embargo, pueden haber casos en los que quiera darle a otros widgets la posibilidad de proporcionar la selección, o puede que quiera recuperar objetivos que no estén admitidos por defecto.

Un concepto fundamental que es necesario para comprender el manejo de la selección es el de átomo. Un átomo es un entero que identifica de una manera unívoca una cadena (en un cierto display). Ciertos átomos están predefinidos por el servidor X, y en algunos casos hay constantes en gtk.h que corresponden a estos átomos. Por ejemplo la constante GDK_PRIMARY_SELECTION corresponde a la cadena ``PRIMARY''. En otros casos, debería utilizar las funciones gdk_atom_intern(), para obtener el átomo correspondiente a una cadena, y gdk_atom_name(), para obtener el nombre de un

Page 156: GTK Tutorial

átomo. Ambas, selecciones y objetivos, están identificados por átomos.

21.2 Recuperando la selección Recuperar la selección es un proceso asíncrono. Para comenzar el proceso, deberá llamar a:

gint gtk_selection_convert( GtkWidget *widget, GdkAtom selection, GdkAtom target, guint32 time );

Este proceso convierte la selección en la forma especificada por target. Si es posible, el campo time debe ser el tiempo desde que el evento lanzó la selección. Esto ayuda a asegurarse de que los eventos ocurran en el orden en que el usuario los ha pedido. Sin embargo, si no está disponible (por ejemplo, si se empezó la conversión por una señal de ``pulsación''), entonces puede utilizar la constante GDK_CURRENT_TIME.

Cuando el propietario de la selección responda a la petición, se enviará una señal ``selection_received'' a su aplicación. El manejador de esta señal recibe un puntero a una estructura GtkSelectionData, que se define como:

struct _GtkSelectionData{ GdkAtom selection; GdkAtom target; GdkAtom type; gint format; guchar *data; gint length;};

selection y target son los valores que dió en su llamada a gtk_selection_convert(). type es un átomo que identifica el tipo de datos devueltos por el propietario de la selección. Algunos valores posibles son ``STRING'', un cadena de caracteres latin-1, ``ATOM'', una serie de átomos, ``INTEGER'', un entero, etc. Muchos objetivos sólo pueden devolver un tipo. format da la longitud de las unidades (por ejemplo caracteres) en bits. Normalmente, no tiene porque preocuparse de todo esto cuando recibe datos. data es un puntero a los datos devueltos, y length da la longitud de los datos devueltos, en bytes. Si length es negativo, es que a ocurrido un error y no se puede obtener la selección. Esto podría ocurrir si no hay ninguna aplicación que sea la propietaria de la selección, o si pide un objetivo que la aplicación no admite. Actualmente se garantiza que el búfer tendrá un byte más que length; el byte extra siempre será cero, por lo que no es necesario hacer una copia de las cadenas sólo para añadirles un carácter nulo al final.

En el siguiente ejemplo, recuperamos el objetivo especial ``TARGETS'', que es una lista de todos los objetivos en los que se puede convertir la selección.

/* principio del ejemplo selection gettargets.c */

#include <gtk/gtk.h>

void selection_received (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data);

Page 157: GTK Tutorial

/* Manejador de señal invocado cuando el usuario pulsa en el botón"Get Targets" */voidget_targets (GtkWidget *widget, gpointer data){ static GdkAtom targets_atom = GDK_NONE;

/* Obtener el atom correpondiente a la cadena "TARGETS" */ if (targets_atom == GDK_NONE) targets_atom = gdk_atom_intern ("TARGETS", FALSE);

/* Y pide el objetivo "TARGETS" de la selección primaria */ gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom, GDK_CURRENT_TIME);}

/* Manipulador de señal llamado cuando el propietario de la señal * devuelve los datos */voidselection_received (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data){ GdkAtom *atoms; GList *item_list; int i;

/* **** IMPORTANTE **** Comprobar si se da la recuperación de los * datos */ if (selection_data->length < 0) { g_print ("Selection retrieval failed\n"); return; } /* Asegurarse de que obtenemos los datos de la forma esperada */ if (selection_data->type != GDK_SELECTION_TYPE_ATOM) { g_print ("Selection \"TARGETS\" was not returned as atoms!\n"); return; } /* Imprimir los atoms que hemos recibido */ atoms = (GdkAtom *)selection_data->data;

item_list = NULL; for (i=0; i<selection_data->length/sizeof(GdkAtom); i++) { char *name; name = gdk_atom_name (atoms[i]); if (name != NULL) g_print ("%s\n",name); else g_print ("(bad atom)\n"); }

return;}

int

Page 158: GTK Tutorial

main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *button; gtk_init (&argc, &argv);

/* Crear la ventana superior */

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Box"); gtk_container_border_width (GTK_CONTAINER (window), 10);

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

/* Crear un botón que el usuario puede pulsar para obtener los * objetivos */

button = gtk_button_new_with_label ("Get Targets"); gtk_container_add (GTK_CONTAINER (window), button);

gtk_signal_connect (GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC (get_targets), NULL); gtk_signal_connect (GTK_OBJECT(button), "selection_received", GTK_SIGNAL_FUNC (selection_received), NULL);

gtk_widget_show (button); gtk_widget_show (window); gtk_main (); return 0;}/* fin del ejemplo */

21.3 Proporcionando la selección Proporcionar la selección es un poco más complicado. Debe registrar los manejadores a los que se llamarán cuando se le pida la selección. Por cada par selección/objetivo que quiera manejar, deberá hacer una llamada a:

void gtk_selection_add_handler( GtkWidget *widget, GdkAtom selection, GdkAtom target, GtkSelectionFunction function, GtkRemoveFunction remove_func, gpointer data );

widget, selection, y target identifican las peticiones que este manejador puede manipular. Si remove_func no es NULL, se le llamará cuando se elimine el manejador de la señal. Esto es útil, por ejemplo, para los lenguajes interpretados que necesitan mantener una memoria de las referencias a data.

La función de llamada tiene el prototipo:

Page 159: GTK Tutorial

typedef void (*GtkSelectionFunction)( GtkWidget *widget, GtkSelectionData *selection_data, gpointer data );

El GtkSelectionData es el mismo que hay más arriba, pero esta vez, seremos nosotros los responsables de rellenar los campos type, format, data, y length. (El campo format es importante - el servidor X lo utiliza para saber si tienen que intercambiarse los bytes que forman los datos o no. Normalmente será 8 - es decir un carácter - o 32 - es decir un entero.) Esto se hace llamando a la función:

void gtk_selection_data_set( GtkSelectionData *selection_data, GdkAtom type, gint format, guchar *data, gint length );

Esta función tiene la responsabilidad de hacer una copia de los datos para que no tenga que preocuparse de ir guardándolos. (No debería rellenar los campos de la estructura GtkSelectionData a mano.)

Cuando haga falta, puede pedir el propietario de la selección llamando a:

gint gtk_selection_owner_set( GtkWidget *widget, GdkAtom selection, guint32 time );

Si otra aplicación pide el propietario de la selección, recibira un ``selection_clear_event''.

Como ejemplo de proporciar la selección, el programa siguiente le añade la posibilidad de selección a un botón de comprobación. Cuando se presione el botón de comprobación, el programa pedirá la selección primaria. El único objetivo que admite es un objetivo ``STRING'' (aparte de ciertos objetivos como "TARGETS", proporcionados por GTK). Cuando se pida este objetivo, se devolverá una representación del tiempo.

/* principio del ejemplo selection setselection.c */

#include <gtk/gtk.h>#include <time.h>

/* Función de llamada para cuando el usuario cambia la selección */voidselection_toggled (GtkWidget *widget, gint *have_selection){ if (GTK_TOGGLE_BUTTON(widget)->active) { *have_selection = gtk_selection_owner_set (widget, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME); /* Si la demanda de la selección ha fallado, ponemos el botón en * estado apagado */ if (!*have_selection) gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); } else { if (*have_selection) { /* Antes de eliminar la seleción poniendo el propietario a

Page 160: GTK Tutorial

* NULL, comprobamos si somos el propietario actual */ if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window) gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME); *have_selection = FALSE; } }}

/* Llamado cuando otra aplicación pide la selección */gintselection_clear (GtkWidget *widget, GdkEventSelection *event, gint *have_selection){ *have_selection = FALSE; gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);

return TRUE;}

/* Proporciona el tiempo actual como selección. */voidselection_handle (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data){ gchar *timestr; time_t current_time;

current_time = time (NULL); timestr = asctime (localtime(&current_time)); /* Cuando devolvemos una cadena, no debe terminar en NULL. La * función lo hará por nosotros */

gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, 8, timestr, strlen(timestr));}

intmain (int argc, char *argv[]){ GtkWidget *window;

GtkWidget *selection_button;

static int have_selection = FALSE; gtk_init (&argc, &argv);

/* Crear la ventana superior */

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Box"); gtk_container_border_width (GTK_CONTAINER (window), 10);

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

Page 161: GTK Tutorial

/* Crear un botón de selección para que actue como la selección */

selection_button = gtk_toggle_button_new_with_label ("Claim Selection"); gtk_container_add (GTK_CONTAINER (window), selection_button); gtk_widget_show (selection_button);

gtk_signal_connect (GTK_OBJECT(selection_button), "toggled", GTK_SIGNAL_FUNC (selection_toggled), &have_selection); gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", GTK_SIGNAL_FUNC (selection_clear), &have_selection);

gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, selection_handle, NULL);

gtk_widget_show (selection_button); gtk_widget_show (window); gtk_main (); return 0;}/* fin del ejemplo */

22. glibglib proporciona muchas definiciones y funciones útiles disponibles para su utilización cuando se crean aplicaciones GDK y GTK. Haré una lista con todas ellas incluyendo una pequeña explicación. Muchas no son más que duplicados de funciones estándar de libc por lo que no entraré en detalle en la explicación de las mismas. Esta sección está pensada principalmente para que se utilice como referencia, para que sepa que es lo que puede utilizar.

22.1 Definiciones Las definiciones para los límites de muchos de los tipos estándar son:

G_MINFLOATG_MAXFLOATG_MINDOUBLEG_MAXDOUBLEG_MINSHORTG_MAXSHORTG_MININTG_MAXINTG_MINLONGG_MAXLONG

Page 162: GTK Tutorial

Y también, los siguientes typedefs. Cuando no se especifica el tipo que debería aparecer a la izquierda significa que el mismo se establecerá dinámicamente en función de la arquitectura. ¡Recuerde evitar los calculos relativos al tamaño de un puntero si quiere que su aplicación sea portable! P.e., un puntero en un Alpha tiene 8 bytes, pero 4 en Intel.

char gchar;short gshort;long glong;int gint;char gboolean;

unsigned char guchar;unsigned short gushort;unsigned long gulong;unsigned int guint;

float gfloat;double gdouble;long double gldouble;

void* gpointer;

gint8guint8gint16guint16gint32guint32

22.2 Listas doblemente enlazadas Las funciones siguientes se utilizan para crear, manipular, y destruir listas doblemente enlazadas. Supondré que sabe lo que son las listas enlazadas, ya que explicarlas va más allá del objetivo de este documento. Por supuesto, no es necesario que conozca como manejar todo esto para utilizar GTK, pero siempre es bonito aprender cosas.

GList *g_list_alloc( void );

void g_list_free( GList *list );

void g_list_free_1( GList *list );

GList *g_list_append( GList *list, gpointer data ); GList *g_list_prepend( GList *list, gpointer data ); GList *g_list_insert( GList *list, gpointer data, gint position );

GList *g_list_remove( GList *list, gpointer data ); GList *g_list_remove_link( GList *list,

Page 163: GTK Tutorial

GList *link );

GList *g_list_reverse( GList *list );

GList *g_list_nth( GList *list, gint n ); GList *g_list_find( GList *list, gpointer data );

GList *g_list_last( GList *list );

GList *g_list_first( GList *list );

gint g_list_length( GList *list );

void g_list_foreach( GList *list, GFunc func, gpointer user_data );

22.3 Listas simplemente enlazadas Muchas de las funciones para las listas enlazadas son idénticas a las de más arriba. Aquí hay una lista completa:

GSList *g_slist_alloc( void );

void g_slist_free( GSList *list );

void g_slist_free_1( GSList *list );

GSList *g_slist_append( GSList *list, gpointer data ); GSList *g_slist_prepend( GSList *list, gpointer data ); GSList *g_slist_insert( GSList *list, gpointer data, gint position ); GSList *g_slist_remove( GSList *list, gpointer data ); GSList *g_slist_remove_link( GSList *list, GSList *link ); GSList *g_slist_reverse( GSList *list );

GSList *g_slist_nth( GSList *list, gint n ); GSList *g_slist_find( GSList *list, gpointer data ); GSList *g_slist_last( GSList *list );

Page 164: GTK Tutorial

gint g_slist_length( GSList *list );

void g_slist_foreach( GSList *list, GFunc func, gpointer user_data );

22.4 Control de la memoria gpointer g_malloc( gulong size );

Reemplaza a malloc(). No necesita comprobar el valor devuelto, ya que ya lo hace por usted esta función.

gpointer g_malloc0( gulong size );

Lo mismo que antes, pero rellena con ceros la memoria antes de devolver un puntero a ella.

gpointer g_realloc( gpointer mem, gulong size );

Vuelve a reservar size bites de memoria empezando en mem. Obviamente, la memoria debe haber sido previamente reservada.

void g_free( gpointer mem );

Libera la memoria. Fácil.

void g_mem_profile( void );

Crea un fichero donde vuelca la memoria que se está utilizando, pero tiene que añadir #define MEM_PROFILE en lo alto de glib/gmem.c y tendrá que hacer un make y un make install.

void g_mem_check( gpointer mem );

Comprueba que una dirección de memoria es válida. Tiene que añadir #define MEM_CHECK en lo alto de gmem.c y tiene que hacer un make y un make install.

22.5 Timers Temporizadores...

GTimer *g_timer_new( void );

void g_timer_destroy( GTimer *timer );

void g_timer_start( GTimer *timer );

void g_timer_stop( GTimer *timer );

void g_timer_reset( GTimer *timer );

gdouble g_timer_elapsed( GTimer *timer, gulong *microseconds );

Page 165: GTK Tutorial

22.6 Manejo de cadenas de texto Un puñado de funciones para manejar cadenas de texto. Parecen muy interesantes, y probablemente sean mejores en muchos aspectos que las funciones estándar de C, pero necesitan documentación.

GString *g_string_new( gchar *init );

void g_string_free( GString *string, gint free_segment ); GString *g_string_assign( GString *lval, gchar *rval ); GString *g_string_truncate( GString *string, gint len ); GString *g_string_append( GString *string, gchar *val ); GString *g_string_append_c( GString *string, gchar c ); GString *g_string_prepend( GString *string, gchar *val ); GString *g_string_prepend_c( GString *string, gchar c ); void g_string_sprintf( GString *string, gchar *fmt, ...); void g_string_sprintfa ( GString *string, gchar *fmt, ... );

22.7 Funciones de error y funciones varias gchar *g_strdup( const gchar *str );

Reemplaza a la función strdup. Copia el contenido de la cadena original en un nuevo lugar en memoria, y devuelve un puntero al nuevo lugar.

gchar *g_strerror( gint errnum );

Recomiendo utilizar esta función para todos los mensages de error. Es mucho más bonita, y más portable que perror() y demás funciones clásicas. La salida es normalmente de la forma:

nombre del programa:función que falló:fichero o descripción adicional:strerror

Aquí hay un ejemplo de una llamada utilizada en nuestro programa hello_world:

g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));

void g_error( gchar *format, ... );

Page 166: GTK Tutorial

Imprime un mensaje de error. El formato es como el de printf, pero le añade ** ERROR **: a su mensaje, y sale del programa. Sólo para errores fatales.

void g_warning( gchar *format, ... );

El mismo que el anterior, pero añade "** WARNING **: ", y no sale del programa.

void g_message( gchar *format, ... );

Imprime message: antes de la cadena que le pase.

void g_print( gchar *format, ... );

Reemplazo de printf().

Y nuestra última función:

gchar *g_strsignal( gint signum );

Imprime el nombre de la señal del sistema Unix que corresponde con el número signum. Útil para las funciones genéricas de manejo de señal.

Todo lo anterior está más o menos robado de glib.h. Si alguien quiere documentar una función, ¡sólo tiene que enviarme un correo-e!

23. Ficheros rc de GTKGTK tiene su propia forma de conseguir los valores por defecto de una aplicación, y es utilizando los ficheros rc. Pueden ser utilizados para poner los colores de cualquier widget, y también pueden utilizarse para poner imágenes como fondos de algunos widgets.

23.1 Funciones para los ficheros rc Cuando empiece su aplicación, debería incluir una llamada a:

void gtk_rc_parse( char *filename );

Poniendo el nombre del fichero de su rc. Esto hará que GTK analice este fichero, y utilice el estilo para los widgets que se definan ahí.

Si desea tener un conjunto especial de widgets con un estilo diferente de los otros, o realizar cualquier otra división lógica de los widgets, haga una llamada a:

void gtk_widget_set_name( GtkWidget *widget, gchar *name );

Pasándole su nuevo widget como primer argumento, y el nombre que desea darle como el segundo. Mediante este nombre podrá cambiar los atributos de ese widget.

Si hacemos algo así:

button = gtk_button_new_with_label ("Special Button");gtk_widget_set_name (button, "special button");

Page 167: GTK Tutorial

El botón tendrá el nombre ``special button'' y podría referenciársele en el fichero rc como ``special button.GtkButton''. [<--- ¡Verificadme! ]

El fichero de ejemplo rc que mostramos a continuación, establece las propiedades de la ventana principal, y deja que todos los hijos de la ventana principal hereden el estilo descrito por ``main button''. El código utilizado en la aplicación es:

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);gtk_widget_set_name (window, "main window");

Y el estilo se define en el fichero rc utilizando:

widget "main window.*GtkButton*" style "main_button"

Qué hace que todos los widgets GtkButton de la ``main window'' (ventana principal) tengan el estilo "main_buttons" tal y como se define en el fichero rc.

Como puede ver, es un sistema muy poderoso y flexible. Utilice su imaginación para aprovecharse al máximo de este sistema.

23.2 Formato de los ficheros rc de GTK El formato de los ficheros GTK se muestra en el ejemplo de más abajo. Éste es el fichero testgtkrc de la distribución GTK, pero he añadido unos cuantos comentarios y alguna cosilla. Puede que quiera incluir esta explicación en su aplicación para permitir al usuario personalizar su aplicación.

Hay varias directivas para cambiar los atributos de un widget.

• fg - Establece el color de primer plano de un widget. • bg - Establece el color de fondo de un widget. • bg_pixmap - Establece la imagen que servirá de fondo al widget (como mosaico). • font - Establece el tipo de letra que se utilizará con el widget.

Además de esto, hay varios estados en el que puede estar un widget, y puede especificar diferentes colores, imágenes y tipos de letra para cada estado. Estos estados son:

• NORMAL - El estado normal de un widget, sin el ratón sobre él, y no siendo presionado, etc... • PRELIGHT - Cuando el ratón esté sobre este widget se utilizarán los colores definidos para este

estado. • ACTIVE - Cuando se presiona o se pulsa sobre el widget, estará activo, y los atributos

asignados por está etiqueta serán utilizados. • INSENSITIVE - Cuando un widget es insensible, y no se puede activar, tomará estos atributos. • SELECTED - Cuando se seleccione un objeto, tomará estos atributos.

Cuando se utilizan las directivas ``fg'' y ``bg'' para poner los colores de los widgets, se utilizará el formato siguiente:

fg[<STATE>] = { Red, Green, Blue }

Donde STATE es uno de los estados anteriores (PRELIGHT, ACTIVE, etc...), y el Red, Green y Blue (Rojo, Verde y Azul) son valores en el rango 0 - 1.0, { 1.0, 1.0, 1.0 } es blanco. Deben estar en formato flotante, o serán un 0, por lo que "1" no funcionará, debe ser "1.0". Un "0" está bien ya que es lo mismo si no se reconoce. Los valores no reconocidos se pondrán a 0.

Page 168: GTK Tutorial

bg_pixmap es muy similar al de arriba, salvo que los colores se reemplazan por un nombre de fichero.

pixmap_path es una lista de los caminos (paths) separados por ``:''. Estos caminos se utilizarán para buscar cualquier imagen que indique.

La directiva sobre el tipo de letra es simplemente:

font = "<nombre del tipo de letra>"

Donde lo único difícil es saber la cadena del tipo de letra a elegir. Utilizar xfontsel o un programa similar debería ayudar.

El widget_class establece el estilo de una clase de widgets. Estas clases se muestran en el resumen de widgets dentro de la jerarquía de clases.

La directiva widget hace que un conjunto específico de widgets tenga un estido determinado, sobreescribiendo cualquier estilo anterior que tuviese esa clase de widgets. Estos widgets se registran dentro de la aplicación utilizando una llamada a gtk_widget_set_name(). Esto le permitirá especificar los atributos de un widget uno a uno, en vez de establecer los atributos de toda una clase widget. Deberá documentar cualquiera de estos widgets especiales para que los usuarios puedan personalizarlos.

Cuando la palabra clave parent se utiliza como un atributo, el widget tomará los atributos de su padre en la aplicación.

Puede asignar los atributos de un estilo previamente definido a uno nuevo.

style "main_button" = "button"{ font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" bg[PRELIGHT] = { 0.75, 0, 0 }}

Este ejemplo toma el estilo ``button'', y crea un nuevo estilo ``main_button'' cambiando simplemente el tipo de letra y cambiando el color de fondo cuando el widget esté en estado PRELIGHT.

Por supuesto, muchos de estos atributos no se aplican a todos los widgets. Realmente es una cuestión de sentido común. Se utilizará cualquier atributo que se pueda aplicar.

23.3 Fichero rc de ejemplo # pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."#pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"## style <name> [= <name>]# {# <option># }## widget <widget_set> style <style_name># widget_class <widget_class_set> style <style_name>

# Here is a list of all the possible states. Note that some do not apply to

Page 169: GTK Tutorial

# certain widgets.## NORMAL - The normal state of a widget, without the mouse over top of# it, and not being pressed etc.## PRELIGHT - When the mouse is over top of the widget, colors defined# using this state will be in effect.## ACTIVE - When the widget is pressed or clicked it will be active, and# the attributes assigned by this tag will be in effect.## INSENSITIVE - When a widget is set insensitive, and cannot be# activated, it will take these attributes.## SELECTED - When an object is selected, it takes these attributes.## Given these states, we can set the attributes of the widgets in each of# these states using the following directives.## fg - Sets the foreground color of a widget.# fg - Sets the background color of a widget.# bg_pixmap - Sets the background of a widget to a tiled pixmap.# font - Sets the font to be used with the given widget.#

# This sets a style called "button". The name is not really important, as# it is assigned to the actual widgets at the bottom of the file.

style "window"{ #This sets the padding around the window to the pixmap specified. #bg_pixmap[<STATE>] = "<pixmap filename>" bg_pixmap[NORMAL] = "warning.xpm"}

style "scale"{ #Sets the foreground color (font color) to red when in the "NORMAL" #state. fg[NORMAL] = { 1.0, 0, 0 } #Sets the background pixmap of this widget to that of its parent. bg_pixmap[NORMAL] = "<parent>"}

style "button"{ # This shows all the possible states for a button. The only one that # doesn't apply is the SELECTED state. fg[PRELIGHT] = { 0, 1.0, 1.0 } bg[PRELIGHT] = { 0, 0, 1.0 } bg[ACTIVE] = { 1.0, 0, 0 } fg[ACTIVE] = { 0, 1.0, 0 } bg[NORMAL] = { 1.0, 1.0, 0 } fg[NORMAL] = { .99, 0, .99 }

Page 170: GTK Tutorial

bg[INSENSITIVE] = { 1.0, 1.0, 1.0 } fg[INSENSITIVE] = { 1.0, 0, 1.0 }}

# In this example, we inherit the attributes of the "button" style and then# override the font and background color when prelit to create a new# "main_button" style.

style "main_button" = "button"{ font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" bg[PRELIGHT] = { 0.75, 0, 0 }}

style "toggle_button" = "button"{ fg[NORMAL] = { 1.0, 0, 0 } fg[ACTIVE] = { 1.0, 0, 0 } # This sets the background pixmap of the toggle_button to that of its # parent widget (as defined in the application). bg_pixmap[NORMAL] = "<parent>"}

style "text"{ bg_pixmap[NORMAL] = "marble.xpm" fg[NORMAL] = { 1.0, 1.0, 1.0 }}

style "ruler"{ font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"}

# pixmap_path "~/.pixmaps"

# These set the widget types to use the styles defined above.# The widget types are listed in the class hierarchy, but could probably be# just listed in this document for the users reference.

widget_class "GtkWindow" style "window"widget_class "GtkDialog" style "window"widget_class "GtkFileSelection" style "window"widget_class "*Gtk*Scale" style "scale"widget_class "*GtkCheckButton*" style "toggle_button"widget_class "*GtkRadioButton*" style "toggle_button"widget_class "*GtkButton*" style "button"widget_class "*Ruler" style "ruler"widget_class "*GtkText" style "text"

# This sets all the buttons that are children of the "main window" to# the main_button style. These must be documented to be taken advantage of.widget "main window.*GtkButton*" style "main_button"

Page 171: GTK Tutorial

24. Escribiendo sus propios widgets

24.1 Visión general Aunque la distribución de GTK viene con muchos tipos de widgets que debería cubrir todas la mayoría de las necesidades básicas, puede que haya llegado el momento en que necesite crear su propio widget. Debido a que GTK utiliza mucho la herencia de widgets, y si ya hay un widget que se acerque lo suficiente a lo que quiere, tal vez pueda hacer un nuevo widget con tan solo unas cuantas líneas de código. Pero antes de empezar a trabajar en un nuevo widget, asegúrese primero de que no hay nadie que ya haya hecho otro parecido. Así evitará la duplicación de esfuerzo y mantendrá el número de widgets GTK en su valor mínimo, lo que ayudará a que el código y la interfaz de las diferentes aplicaciones sea consistente. Por otra parte, cuando haya acabado su widget, anúncielo al mundo entreo para que todo el mundo se pueda beneficiar. Probablemente el mejor lugar para hacerlo sea la gtk-list.

Las fuentes completas de los widgets de ejemplo están disponibles en el mismo lugar en el que consiguió este tutorial, o en:

http://www.gtk.org/~otaylor/gtk/tutorial/

24.2 La anatomía de un widget Para crear un nuevo widget, es importante conocer como funcionan los objetos de GTK. Esta sección es sólo un breve resumen. Ver la documentación a la que se hace referencia para obtener más detalles.

Los widgets GTK están implementados siguiendo una orientación a objetos. Sin embargo, están implementados en C estándar. De esta forma se mejora enormemente la portabilidad y la estabilidad con respecto a la actual generación de compiladores C++; sin embargo, con todo esto no queremos decir que el creador de widgets tenga que prestar atención a ninguno de los detalles de implementación. La información que es común a todos los widgets de una clase de widgets (p.e., a todos los widgets botón) se almacena en la estructura de clase. Sólo hay una copia de ésta en la que se almacena información sobre las señales de la clase (que actuan como funciones virtuales en C). Para permitir la herencia, el primer campo en la estructura de la clase debe ser una copia de la estructura de la clase del padre. La declaración de la estructura de la clase de GtkButton debe ser algo así:

struct _GtkButtonClass{ GtkContainerClass parent_class;

void (* pressed) (GtkButton *button); void (* released) (GtkButton *button); void (* clicked) (GtkButton *button); void (* enter) (GtkButton *button); void (* leave) (GtkButton *button);};

Cuando un botón se trata como un contenedor (por ejemplo, cuando se le cambia el tamaño), su estructura de clase puede convertirse a GtkContainerClass, y los campos relevantes se utilizarán para manejar las señales.

También hay una estructura que se crea para cada widget. Esta estructura tiene campos para almacenar la información que es diferente para cada copia del widget. Nosotros llamaremos a esta estructura la

Page 172: GTK Tutorial

estructura objeto. Para la clase botón, es así:

struct _GtkButton{ GtkContainer container;

GtkWidget *child;

guint in_button : 1; guint button_down : 1;};

Observe que, como en la estructura de clase, el primer campo es la estructura objeto de la clase padre, por lo que esta estructura puede convertirse en la estructura de la clase del objeto padre cuando haga falta.

24.3 Creando un widget compuesto

Introducción

Un tipo de widget que puede interesarnos es uno que sea un mero agregado de otros widgets GTK. Este tipo de widget no hace nada que no pueda hacerse sin la necesidad de crear un nuevo widget, pero proporciona una forma conveniente de empaquetar los elementos del interfaz de usuario para su reutilización. Los widgets FileSelection y ColorSelection incluidos en la distribución estándar son ejemplos de este tipo de widgets.

El widget ejemplo que hemos creado en esta sección es el widget Tictactoe, una matriz de 3x3 de botones de selección que lanza una señal cuando están deseleccionados tres botones en una misma fila, columna, o diagonal.

Escogiendo una clase padre

Normalmente la clase padre para un widget compuesto es la clase contenedor que tenga todos los elementos del widget compuesto. Por ejemplo, la clase padre del widget FileSelection es la clase Dialog. Ya que nuestros botones se ordenarán en una tabla, parece natural hacer que nuestra clase padre sea la clase GtkTable. Desafortunadamente, esto no funcionaría. La creación de un widget se divide en dos funciones - una función NOMBREWIDGET_new() que utilizará el usuario, y una función NOMBREWIDGET_init() que hará el trabajo básico de inicializar el widget que es independiente de los argumentos que se le pasen a la función _new(). Los widgets derivados sólo llaman a la función _init de su widget padre. Pero esta división del trabajo no funciona bien con las tablas, que necesitan saber en el momento de su creación el número de filas y de columnas que deben tener. A menos que queramos duplicar la mayor parte de lo hecho en gtk_table_new() en nuestro widget Tictactoe, haremos mejor si evitamos derivar de GtkTable. Por esta razón, derivaremos de GtkVBox, y meteremos nuestra tabla dentro de la caja vertical.

El fichero de cabecera

Cada clase widget tiene un fichero de cabecera que declara el objeto y las estructuras de clase para ese widget, así como las funciones públicas. Un par de características que merecen dejarse aparte. Para evitar la duplicación de definiciones, meteremos el fichero de cabecera al completo entre:

Page 173: GTK Tutorial

#ifndef __TICTACTOE_H__#define __TICTACTOE_H__...#endif /* __TICTACTOE_H__ */

Y para que los programas en C++ incluyan sin problemas el fichero de cabecera, pondremos:

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */...#ifdef __cplusplus}#endif /* __cplusplus */

Con las funciones y las estructuras, declararemos tres macros estándar en nuestro fichero de cabecera, TICTACTOE(obj), TICTACTOE_CLASS(class), y IS_TICTACTOE(obj), que, convierten, respectivamente, un puntero en un puntero al objeto o a la estructura de la clase, y comprueba si un objeto es un widget Tictactoe.

Aquí está el fichero de cabecera al completo:

/* tictactoe.h */

#ifndef __TICTACTOE_H__#define __TICTACTOE_H__

#include <gdk/gdk.h>#include <gtk/gtkvbox.h>

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */

#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())

typedef struct _Tictactoe Tictactoe;typedef struct _TictactoeClass TictactoeClass;

struct _Tictactoe{ GtkVBox vbox; GtkWidget *buttons[3][3];};

struct _TictactoeClass{

Page 174: GTK Tutorial

GtkVBoxClass parent_class;

void (* tictactoe) (Tictactoe *ttt);};

guint tictactoe_get_type (void);GtkWidget* tictactoe_new (void);void tictactoe_clear (Tictactoe *ttt);

#ifdef __cplusplus}#endif /* __cplusplus */

#endif /* __TICTACTOE_H__ */

La función _get_type().

Ahora continuaremos con la implementación de nuestro widget. Una función del núcleo de todo widget es NOMBREWIDGET_get_type(). Cuando se llame a esta función por vez primera, le informará a GTK sobre la clase del widget, y devolverá un ID que identificará unívocamente la clase widget. En las llamadas siguientes, lo único que hará será devolver el ID.

guinttictactoe_get_type (){ static guint ttt_type = 0;

if (!ttt_type) { GtkTypeInfo ttt_info = { "Tictactoe", sizeof (Tictactoe), sizeof (TictactoeClass), (GtkClassInitFunc) tictactoe_class_init, (GtkObjectInitFunc) tictactoe_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL };

ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); }

return ttt_type;}

La estructura GtkTypeInfo tiene la definición siguiente:

struct _GtkTypeInfo{ gchar *type_name; guint object_size; guint class_size; GtkClassInitFunc class_init_func; GtkObjectInitFunc object_init_func; GtkArgSetFunc arg_set_func;

Page 175: GTK Tutorial

GtkArgGetFunc arg_get_func;};

Los utilidad de cada campo de esta estructura se explica por su propio nombre. Ignoraremos por ahora los campos arg_set_func y arg_get_func: son importantes, pero todavía es raro utilizarlos, su papel es permitir que las opciones de los wdigets puedan establecerse correctamente mediante lenguajes interpretados. Una vez que GTK tiene una copia de esta estructura correctamente rellenada, sabrá como crear objetos de un tipo particular de widget.

La función _class_init()

La función NOMBREWIDGET_class_init() inicializa los campos de la estructura clase del widget, y establece las señales de la clase. Para nuestro widget Tictactoe será una cosa así:

enum { TICTACTOE_SIGNAL, LAST_SIGNAL};

static gint tictactoe_signals[LAST_SIGNAL] = { 0 };

static voidtictactoe_class_init (TictactoeClass *class){ GtkObjectClass *object_class;

object_class = (GtkObjectClass*) class; tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);

gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);

class->tictactoe = NULL;}

Nuestro widget sólo tiene una señal, la señal tictactoe que se invoca cuando una fila, columna, o diagonal se rellena completamente. No todos los widgets compuestos necesitan señales, por lo que si está leyendo esto por primera vez, puede que sea mejor que pase a la sección siguiente, ya que las cosas van a complicarse un poco.

La función:

gint gtk_signal_new( const gchar *name, GtkSignalRunType run_type, GtkType object_type, gint function_offset, GtkSignalMarshaller marshaller, GtkType return_val,

Page 176: GTK Tutorial

guint nparams, ...);

crea una nueva señal. Los parámetros son:

• name: El nombre de la señal. • run_type: Si el manejador por defecto se ejecuta antes o despues del manejador de usuario.

Normalmente debe ser GTK_RUN_FIRST, o GTK_RUN_LAST, aunque hay otras posibilidades.

• object_type: El ID del objeto al que se le aplica esta señal. (También se aplicará a los descendientes de los objetos)

• function_offset: El desplazamiento en la estructura de la clase de un puntero al manejador por defecto.

• marshaller: Una función que se utiliza para invocar al manejador de señal. Para los manejadores de señal que no tengan más argumentos que el objeto que emitió la señal podemos utilizar la función marshaller por defecto gtk_signal_default_marshaller.

• return_val: El tipo del valor devuelto. • nparams: El número de parámetros del manejador de señal (distintos de los dos por defecto

que hemos mencionado arriba). • ...: Los tipos de los parámetros.

Cuando se especifican los tipos, se utilizará la enumeración GtkType:

typedef enum{ GTK_TYPE_INVALID, GTK_TYPE_NONE, GTK_TYPE_CHAR, GTK_TYPE_BOOL, GTK_TYPE_INT, GTK_TYPE_UINT, GTK_TYPE_LONG, GTK_TYPE_ULONG, GTK_TYPE_FLOAT, GTK_TYPE_DOUBLE, GTK_TYPE_STRING, GTK_TYPE_ENUM, GTK_TYPE_FLAGS, GTK_TYPE_BOXED, GTK_TYPE_FOREIGN, GTK_TYPE_CALLBACK, GTK_TYPE_ARGS,

GTK_TYPE_POINTER,

/* it'd be great if the next two could be removed eventually */ GTK_TYPE_SIGNAL, GTK_TYPE_C_CALLBACK,

GTK_TYPE_OBJECT

} GtkFundamentalType;

gtk_signal_new() devuelve un identificador entero único para la señal, que almacenamos en el

Page 177: GTK Tutorial

vector tictactoe_signals, que indexaremos utilizando una enumeración. (Convencionalmente, los elementos de la enumeración son el nombre de la señal, en mayúsculas, pero aquí tendríamos un conflicto con la macro TICTACTOE(), por lo que lo llamaremos TICTACTOE_SIGNAL.

Después de crear nuestras señales, necesitamos llamar a GTK para asociarlas con la clase Tictactoe. Hacemos esto llamando a gtk_object_class_add_signals(). Entonces haremos que el puntero que apunta al manejador por defecto para la señal `tictactoe' sea NULL, indicando que no hay ninguna acción por defecto.

La función _init().

Cada clase widget también necesita una función para inicializar la estructura del objeto. Normalmente, esta función tiene el limitado rol de poner los distintos campos de la estructura a su valor por defecto. Sin embargo para los widgets de composición, esta función también crea los distintos widgets componentes.

static voidtictactoe_init (Tictactoe *ttt){ GtkWidget *table; gint i,j; table = gtk_table_new (3, 3, TRUE); gtk_container_add (GTK_CONTAINER(ttt), table); gtk_widget_show (table);

for (i=0;i<3; i++) for (j=0;j<3; j++) { ttt->buttons[i][j] = gtk_toggle_button_new (); gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], i, i+1, j, j+1); gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); gtk_widget_show (ttt->buttons[i][j]); }}

Y el resto...

Hay una función más que cada widget (excepto los widget muy básicos como GtkBin que no pueden crear objetos) tiene que tener - la función que el usuario llama para crear un objeto de ese tipo. Normalmente se llama NOMBREWIDGET_new(). En algunos widgets, que no es el caso del widget Tictactoe, esta función toma argumentos, y hace alguna inicialización en función de estos. Las otras dos funciones son específicas al widget Tictactoe.

tictactoe_clear() es una función pública que reinicia todos los botones en el widget a la posición alta. Observe la utilización de gtk_signal_handler_block_by_data() para hacer que no se ejecute nuestro manejador de señal innecesariamente por cambios en los botones.

tictactoe_toggle() es el manejador de señal que se invoca cuando el usuario pulsa un botón. Hace una comprobación para ver si hay alguna combinación ganadora, y si la hay, emite la señal ``tictactoe''.

Page 178: GTK Tutorial

GtkWidget*tictactoe_new (){ return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));}

void tictactoe_clear (Tictactoe *ttt){ int i,j;

for (i=0;i<3;i++) for (j=0;j<3;j++) { gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), FALSE); gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); }}

static voidtictactoe_toggle (GtkWidget *widget, Tictactoe *ttt){ int i,k;

static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 } }; static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 2, 1, 0 } };

int success, found;

for (k=0; k<8; k++) { success = TRUE; found = FALSE;

for (i=0;i<3;i++) { success = success && GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; found = found || ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; } if (success && found) { gtk_signal_emit (GTK_OBJECT (ttt), tictactoe_signals[TICTACTOE_SIGNAL]); break;

Page 179: GTK Tutorial

} }}

Y finalmente, un programa ejemplo que utiliza nuestro widget Tictactoe:

#include <gtk/gtk.h>#include "tictactoe.h"

/* Invocado cuando se completa una fila, columna o diagonal */voidwin (GtkWidget *widget, gpointer data){ g_print ("Yay!\n"); tictactoe_clear (TICTACTOE (widget));}

int main (int argc, char *argv[]){ GtkWidget *window; GtkWidget *ttt; gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10);

/* Create a new Tictactoe widget */ ttt = tictactoe_new (); gtk_container_add (GTK_CONTAINER (window), ttt); gtk_widget_show (ttt);

/* And attach to its "tictactoe" signal */ gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", GTK_SIGNAL_FUNC (win), NULL);

gtk_widget_show (window); gtk_main (); return 0;}

24.4 Creando un widget desde cero.

Introducción

En esta sección, averiguaremos como se dibujan los widgets a sí mismos en pantalla y como

Page 180: GTK Tutorial

interactuan con los eventos. Como ejemplo, crearemos un marcador analógico con un puntero que el usuario podrá arrastrar para hacer que el marcador tenga un valor dado.

Mostrando un widget en la pantalla

Hay varios pasos que están involucrados en el dibujado en pantalla. Después de que el widget se cree con una llamada a NOMBREWIDGET_new(), se necesitarán muchas más funciones:

• NOMBREWIDGET_realize() es la responsable de crear una ventana X para el widget, si tiene alguna.

• NOMBREWIDGET_map() se invoca después de las llamadas del usuario gtk_widget_show(). Es la responsable de asegurarse de que el widget está dibujado (mapeado) en la pantalla. Para una clase contenedor, también deberá ocuparse de llamar a las funciones map() de cada widget hijo.

• NOMBREWIDGET_draw() se invoca cuando se llama a gtk_widget_draw() desde el widget de uno de sus antepasados. Hace las llamadas necesarias a las funciones de dibujo para dibujar el widget en la pantalla. Para los widgets contenedores, esta función debe llamar a las gtk_widget_draw de sus widgets hijos.

• NOMBREWIDGET_expose() es un manejador de los eventos expose del widget. Hace las llamadas necesarias a las funciones de dibujo para dibujar la parte expuesta en la pantalla. Para los widgets contenedores, esta función debe generar los eventos expose de sus widgets hijos que no tengan su propia ventana. (Si tuviesen su propia ventana, X generaría los eventos expose necesarios)

Las últimas dos funciones son bastante similares - ambas son responsables de dibujar el widget en pantalla. De hecho en muchos widgets realmente no importa la diferencia que hay entre ambas funciones. La función draw() que hay por defecto en la clase widget simplemente genera un evento expose artificial de la zona a redibujar. Sin embargo, algunos tipos de widgets puede ahorrarse trabajo distinguiendo entre las dos funciones. Por ejemplo, si un widget tiene varias ventanas X, entonces, como los eventos expose identifican a la ventana expuesta, podrán redibujar sólo la ventana afectada, lo que no es posible con llamadas a draw().

Los widgets contenedores, aunque no utilicen la diferecia existente entre las dos funciones por sí mismos, no pueden utilizar simplemente las funciones draw() que hay por defecto ya que sus widgets hijos puede que tengan que utilizar la diferencia. Sin embargo, sería un derroche duplicar el código de dibujado entre las dos funciones. Lo normal es que cada widget tenga una función llamada NOMBREWIDGET_paint() que haga el trabajo de dibujar el widget, ésta función será a la que se llame por las funciones draw() y expose().

En nuestro ejemplo, como el widget Dial no es un widget contenedor, y sólo tiene una ventana, podemos tomar el camino más corto, utilizar la función draw() por defecto y sólo implementar la función expose().

Los orígenes del widget Dial

Así como todos los animales terrestes son variaciones del primer anfíbio que salió del barro, los widgets Gtk tienden a nacer como variaciones de algún otro widget escrito previamente. Por tanto, aunque esta sección se titule `Creando un widget de la nada', el widget Dial empieza realmente con el código fuente del widget Range. He tomado éste como punto de arranque porque sería bonito que nuestro dial tuviese la misma interfaz que los widgets Scale, que son sólo una especialización del

Page 181: GTK Tutorial

widget Range. Por tanto, aunque el código fuente se presente más adelante en su forma final, no implica que fuese escrito de esta forma deus ex machina. Si todavía no está familiarizado, desde el punto de vista del escritor de aplicaciones, con la forma de funcionar de los widgets Scale, sería una buena idea echarles un vistazo antes de continuar.

Los comienzos

Nuestro widget tiene un aspecto algo parecido al del widget Tictactoe. Primero, tenemos un fichero de cabecera:

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#ifndef __GTK_DIAL_H__#define __GTK_DIAL_H__

#include <gdk/gdk.h>#include <gtk/gtkadjustment.h>#include <gtk/gtkwidget.h>

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */

#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())

typedef struct _GtkDial GtkDial;typedef struct _GtkDialClass GtkDialClass;

struct _GtkDial{ GtkWidget widget;

/* política de actualización

Page 182: GTK Tutorial

* (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ guint policy : 2;

/* Botón actualmente presionado o 0 si no hay ninguno */ guint8 button;

/* Dimensión de los componendes del dial */ gint radius; gint pointer_width;

/* ID del temporizador de actualización, o 0 si no hay ninguno */ guint32 timer;

/* ángulo actual */ gfloat angle;

/* Viejos valores almacenados del adjustment, para que así no * tengamos que saber cuando cambia algo */ gfloat old_value; gfloat old_lower; gfloat old_upper;

/* El objeto adjustment que almacena los datos para este dial */ GtkAdjustment *adjustment;};

struct _GtkDialClass{ GtkWidgetClass parent_class;};

GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);guint gtk_dial_get_type (void);GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);void gtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy);

void gtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment);#ifdef __cplusplus}#endif /* __cplusplus */

#endif /* __GTK_DIAL_H__ */

Como vamos a ir con este widget un poco más lejos que con el último que creamos, ahora tenemos unos cuantos campos más en la estructura de datos, pero el resto de las cosas son muy parecidas.

Ahora, después de incluir los ficheros de cabecera, y declarar unas cuantas constantes, tenemos algunas funciones que proporcionan información sobre el widget y lo inicializan:

#include <math.h>#include <stdio.h>#include <gtk/gtkmain.h>

Page 183: GTK Tutorial

#include <gtk/gtksignal.h>

#include "gtkdial.h"

#define SCROLL_DELAY_LENGTH 300#define DIAL_DEFAULT_SIZE 100

/* Declaraciones de funciones */

[ omitido para salvar espacio ]

/* datos locales */

static GtkWidgetClass *parent_class = NULL;

guintgtk_dial_get_type (){ static guint dial_type = 0;

if (!dial_type) { GtkTypeInfo dial_info = { "GtkDial", sizeof (GtkDial), sizeof (GtkDialClass), (GtkClassInitFunc) gtk_dial_class_init, (GtkObjectInitFunc) gtk_dial_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, };

dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info); }

return dial_type;}

static voidgtk_dial_class_init (GtkDialClass *class){ GtkObjectClass *object_class; GtkWidgetClass *widget_class;

object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class;

parent_class = gtk_type_class (gtk_widget_get_type ());

object_class->destroy = gtk_dial_destroy;

widget_class->realize = gtk_dial_realize; widget_class->expose_event = gtk_dial_expose; widget_class->size_request = gtk_dial_size_request; widget_class->size_allocate = gtk_dial_size_allocate; widget_class->button_press_event = gtk_dial_button_press; widget_class->button_release_event = gtk_dial_button_release;

Page 184: GTK Tutorial

widget_class->motion_notify_event = gtk_dial_motion_notify;}

static voidgtk_dial_init (GtkDial *dial){ dial->button = 0; dial->policy = GTK_UPDATE_CONTINUOUS; dial->timer = 0; dial->radius = 0; dial->pointer_width = 0; dial->angle = 0.0; dial->old_value = 0.0; dial->old_lower = 0.0; dial->old_upper = 0.0; dial->adjustment = NULL;}

GtkWidget*gtk_dial_new (GtkAdjustment *adjustment){ GtkDial *dial;

dial = gtk_type_new (gtk_dial_get_type ());

if (!adjustment) adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

gtk_dial_set_adjustment (dial, adjustment);

return GTK_WIDGET (dial);}

static voidgtk_dial_destroy (GtkObject *object){ GtkDial *dial;

g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_DIAL (object));

dial = GTK_DIAL (object);

if (dial->adjustment) gtk_object_unref (GTK_OBJECT (dial->adjustment));

if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);}

Observe que ésta función init() hace menos cosas de las que hacía la función init() que utilizamos con el widget Tictactoe, ya que éste no es un widget compuesto, y la función new() hace más cosas, ya que ahora admite un argumento. Observe también que cuando almacenamos un puntero en un objeto Adjustment, incrementamos su contador interno, (y lo decrementamos cuando ya no lo utilizamos) por lo que GTK puede saber cuando se puede destruir sin que se produzcan problemas.

Page 185: GTK Tutorial

Aquí tenemos unas cuantas funciones para manipular las opciones del widget:

GtkAdjustment*gtk_dial_get_adjustment (GtkDial *dial){ g_return_val_if_fail (dial != NULL, NULL); g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);

return dial->adjustment;}

voidgtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy){ g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

dial->policy = policy;}

voidgtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment){ g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

if (dial->adjustment) { gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); gtk_object_unref (GTK_OBJECT (dial->adjustment)); }

dial->adjustment = adjustment; gtk_object_ref (GTK_OBJECT (dial->adjustment));

gtk_signal_connect (GTK_OBJECT (adjustment), "changed", (GtkSignalFunc) gtk_dial_adjustment_changed, (gpointer) dial); gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", (GtkSignalFunc) gtk_dial_adjustment_value_changed, (gpointer) dial);

dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper;

gtk_dial_update (dial);}

gtk_dial_realize()

Ahora vienen algunas funciones nuevas. Primero, tenemos una función que hace el trabajo de crear la ventana X. A la función se le pasará una máscara gdk_window_new() que especifica que campos

Page 186: GTK Tutorial

de la estructura GdkWindowAttr tienen datos (los campos restantes tendrán los valores por defecto). También es bueno fijarse en la forma en que se crea la máscara de eventos. Llamamos a gtk_widget_get_events() para recuperar la máscara de eventos que el usuario ha especificado para su widget (con gtk_widget_set_events()), y añadir nosotros mismos los eventos en los que estemos interesados.

Después de crear la ventana, decidiremos su estilo y su fondo, y pondremos un puntero al widget en el campo de datos del usuario de la GdkWindow. Este último paso le permite a GTK despachar los eventos que hayan para esta ventana hacia el widget correcto.

static voidgtk_dial_realize (GtkWidget *widget){ GtkDial *dial; GdkWindowAttr attributes; gint attributes_mask;

g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget));

GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); dial = GTK_DIAL (widget);

attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget);

attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);

widget->style = gtk_style_attach (widget->style, widget->window);

gdk_window_set_user_data (widget->window, widget);

gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);}

Negociación del tamaño

Antes de que se muestre por primera vez la ventana conteniendo un widget, y cuando quiera que la capa de la ventana cambie, GTK le preguntara a cada widget hijo por su tamaño deseado. Esta petición se controla mediante la función gtk_dial_size_request(). Como nuestro widget no es un widget contenedor, y no tiene ninguna limitación en su tamaño, nos contentaremos con devolver un

Page 187: GTK Tutorial

valor por defecto.

static void gtk_dial_size_request (GtkWidget *widget, GtkRequisition *requisition){ requisition->width = DIAL_DEFAULT_SIZE; requisition->height = DIAL_DEFAULT_SIZE;}

Después de que todos los widgets hayan pedido su tamaño ideal, se calculará la ventana y cada widget hijo será informado de su tamaño actual. Normalmente, éste será al menos tan grande como el pedido, pero si por ejemplo, el usuario ha redimensionado la ventana, entonces puede que el tamaño que se le de al widget sea menor que el que pidió. La notificación del tamaño se maneja mediante la función gtk_dial_size_allocate(). Fíjese que esta función calcula los tamaños de los diferentes elementos que componen la ventana para su uso futuro, así como todo el trabajo sucio que poner los widgets de la ventana X en la nueva posición y con el nuevo tamaño.

static voidgtk_dial_size_allocate (GtkWidget *widget, GtkAllocation *allocation){ GtkDial *dial;

g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget)); g_return_if_fail (allocation != NULL);

widget->allocation = *allocation; if (GTK_WIDGET_REALIZED (widget)) { dial = GTK_DIAL (widget);

gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height);

dial->radius = MAX(allocation->width,allocation->height) * 0.45; dial->pointer_width = dial->radius / 5; }}

.

gtk_dial_expose()

Como se mencionó arriba, todo el dibujado de este widget se hace en el manejador de los eventos expose. No hay mucho destacable aquí, excepto la utilización de la función gtk_draw_polygon para dibujar el puntero con un degradado tridimensional de acuerdo con los colores almacenados en el estilo del widget.

static gintgtk_dial_expose (GtkWidget *widget, GdkEventExpose *event){ GtkDial *dial;

Page 188: GTK Tutorial

GdkPoint points[3]; gdouble s,c; gdouble theta; gint xc, yc; gint tick_length; gint i;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

if (event->count > 0) return FALSE; dial = GTK_DIAL (widget);

gdk_window_clear_area (widget->window, 0, 0, widget->allocation.width, widget->allocation.height);

xc = widget->allocation.width/2; yc = widget->allocation.height/2;

/* Dibujar las rayitas */

for (i=0; i<25; i++) { theta = (i*M_PI/18. - M_PI/6.); s = sin(theta); c = cos(theta);

tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2; gdk_draw_line (widget->window, widget->style->fg_gc[widget->state], xc + c*(dial->radius - tick_length), yc - s*(dial->radius - tick_length), xc + c*dial->radius, yc - s*dial->radius); }

/* Dibujar el puntero */

s = sin(dial->angle); c = cos(dial->angle);

points[0].x = xc + s*dial->pointer_width/2; points[0].y = yc + c*dial->pointer_width/2; points[1].x = xc + c*dial->radius; points[1].y = yc - s*dial->radius; points[2].x = xc - s*dial->pointer_width/2; points[2].y = yc - c*dial->pointer_width/2;

gtk_draw_polygon (widget->style, widget->window,

Page 189: GTK Tutorial

GTK_STATE_NORMAL, GTK_SHADOW_OUT, points, 3, TRUE); return FALSE;}

Manejo de eventos

El resto del código del widget controla varios tipos de eventos, y no es muy diferente del que podemos encontrar en muchas aplicaciones GTK. Pueden ocurrir dos tipos de eventos - el usuario puede pulsar en el widget con el ratón y arrastrar para mover el puntero, o el valor del objeto Adjustement puede cambiar debido a alguna circunstancia externa.

Cuando el usuario pulsa en el widget, haremos una comprobación para ver si la pulsación se hizo lo suficientemente cerca del puntero, y si así fue, almacenamos el botón que pulsó el usuario en en el campo button de la estructura del widget, y grabamos todos los eventos del ratón con una llamada a gtk_grab_add(). El movimiento del ratón hará que se recalcule el valor del control (mediante la función gtk_dial_update_mouse). Dependiendo de la política que sigamos, o bien se generarán instantáneamente los eventos value_changed (GTK_UPDATE_CONTINUOUS), o bien después de una espera del temporizador establecido mediante gtk_timeout_add() (GTK_UPDATE_DELAYED), o bien sólo cuando se levante el botón (GTK_UPDATE_DISCONTINUOUS).

static gintgtk_dial_button_press (GtkWidget *widget, GdkEventButton *event){ GtkDial *dial; gint dx, dy; double s, c; double d_parallel; double d_perpendicular;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

dial = GTK_DIAL (widget);

/* Determinar si la pulsación del botón fue dentro de la región del puntero - esto lo hacemos calculando la distancia x e y del punto donde se pulsó el botón ratón de la línea que se ha pasado mediante el puntero */ dx = event->x - widget->allocation.width / 2; dy = widget->allocation.height / 2 - event->y; s = sin(dial->angle); c = cos(dial->angle); d_parallel = s*dy + c*dx; d_perpendicular = fabs(s*dx - c*dy);

Page 190: GTK Tutorial

if (!dial->button && (d_perpendicular < dial->pointer_width/2) && (d_parallel > - dial->pointer_width)) { gtk_grab_add (widget);

dial->button = event->button;

gtk_dial_update_mouse (dial, event->x, event->y); }

return FALSE;}

static gintgtk_dial_button_release (GtkWidget *widget, GdkEventButton *event){ GtkDial *dial;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

dial = GTK_DIAL (widget);

if (dial->button == event->button) { gtk_grab_remove (widget);

dial->button = 0;

if (dial->policy == GTK_UPDATE_DELAYED) gtk_timeout_remove (dial->timer); if ((dial->policy != GTK_UPDATE_CONTINUOUS) && (dial->old_value != dial->adjustment->value)) gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); }

return FALSE;}

static gintgtk_dial_motion_notify (GtkWidget *widget, GdkEventMotion *event){ GtkDial *dial; GdkModifierType mods; gint x, y, mask;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

dial = GTK_DIAL (widget);

Page 191: GTK Tutorial

if (dial->button != 0) { x = event->x; y = event->y;

if (event->is_hint || (event->window != widget->window)) gdk_window_get_pointer (widget->window, &x, &y, &mods);

switch (dial->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; }

if (mods & mask) gtk_dial_update_mouse (dial, x,y); }

return FALSE;}

static gintgtk_dial_timer (GtkDial *dial){ g_return_val_if_fail (dial != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);

if (dial->policy == GTK_UPDATE_DELAYED) gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");

return FALSE;}

static voidgtk_dial_update_mouse (GtkDial *dial, gint x, gint y){ gint xc, yc; gfloat old_value;

g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

xc = GTK_WIDGET(dial)->allocation.width / 2; yc = GTK_WIDGET(dial)->allocation.height / 2;

old_value = dial->adjustment->value;

Page 192: GTK Tutorial

dial->angle = atan2(yc-y, x-xc);

if (dial->angle < -M_PI/2.) dial->angle += 2*M_PI;

if (dial->angle < -M_PI/6) dial->angle = -M_PI/6;

if (dial->angle > 7.*M_PI/6.) dial->angle = 7.*M_PI/6.;

dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);

if (dial->adjustment->value != old_value) { if (dial->policy == GTK_UPDATE_CONTINUOUS) { gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } else { gtk_widget_draw (GTK_WIDGET(dial), NULL);

if (dial->policy == GTK_UPDATE_DELAYED) { if (dial->timer) gtk_timeout_remove (dial->timer);

dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, (GtkFunction) gtk_dial_timer, (gpointer) dial); } } }}

Cambios en el Adjustment por motivos externos significa que se le comunicarán a nuestro widget mediante las señales changed y value_changed. Los manejadores de estas funciones llaman a gtk_dial_update() para comprobar los argumentos, calcular el nuevo ángulo del puntero, y redibujar el widget (llamando a gtk_widget_draw()).

static voidgtk_dial_update (GtkDial *dial){ gfloat new_value; g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

new_value = dial->adjustment->value; if (new_value < dial->adjustment->lower) new_value = dial->adjustment->lower;

Page 193: GTK Tutorial

if (new_value > dial->adjustment->upper) new_value = dial->adjustment->upper;

if (new_value != dial->adjustment->value) { dial->adjustment->value = new_value; gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); }

dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / (dial->adjustment->upper - dial->adjustment->lower);

gtk_widget_draw (GTK_WIDGET(dial), NULL);}

static voidgtk_dial_adjustment_changed (GtkAdjustment *adjustment, gpointer data){ GtkDial *dial;

g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL);

dial = GTK_DIAL (data);

if ((dial->old_value != adjustment->value) || (dial->old_lower != adjustment->lower) || (dial->old_upper != adjustment->upper)) { gtk_dial_update (dial);

dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper; }}

static voidgtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data){ GtkDial *dial;

g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL);

dial = GTK_DIAL (data);

if (dial->old_value != adjustment->value) { gtk_dial_update (dial);

dial->old_value = adjustment->value; }

Page 194: GTK Tutorial

}

Posibles mejoras

El widget Dial tal y como lo hemos descrito tiene unas 670 líneas de código. Aunque pueda parecer un poco exagerado, todavía no hemos escrito demasiado código, ya que la mayoría de las líneas son de ficheros de cabecera y de adornos. Todavía se le pueden hacer algunas mejoras a este widget:

• Si prueba el widget, verá que el puntero cambia a pantallazos cuando se le arrastra. Esto es debido a que todo el widget se borra cada vez que se mueve el puntero, antes de redibujarse. Normalmente, la mejor forma de tratar este problema es dibujar en un pixmap que no represente lo que se ve directamente en pantalla, y copiar el resultado final en la pantalla en sólo un paso. (El widget ProgressBar funciona de esta forma.)

• El usuario debería ser capaz de utilizar las flechas de arriba y abajo para aumentar y decrementar el valor.

• Sería bonito si el widget tuviese botones para incrementar y decrementar el valor a saltos más o menos grandes. Es posible utilizar widgets Button, aunque también queremos que los botones pudiesen realizar la operación de incrementar o decrementar varias veces, mientras se mantenga el botón pulsado, tal y como lo hacen las flechas en una barra de desplazamiento. La mayoría del código para implementar todo esto lo podemos encontrar en el código del widget GtkRange.

• El widget Dial puede utilizarse en un widget contenedor con un simple widget hijo colocado en la parte inferior entre los botones antes mencionados. El usuario puede añadir (según prefiera) una etiqueta o un widget entry para mostrar el valor actual del marcador.

24.5 Aprendiendo más Sólo se han descrito una pequeña parte de los muchos detalles involucrados en la creación de widgets, la mejor fuente de ejemplos es el código mismo de GTK. Hágase algunas preguntas hacerca del widget que desea crear: ¿es un widget contenedor? ¿Debe tener su propia ventana? ¿Es una modificación de un widget existente? En ese momento busque un widget similar, y comience a hacer los cambios. ¡Buena suerte!

25. Scribble, un sencillo programa de dibujo de ejemplo

25.1 Objetivos En esta sección, vamos a crear un sencillo programa de dibujo. En el proceso, vamos a examinar como se manejan los eventos de ratón, como dibujar en una ventana, y como mejorar el dibujado utilizando un pixmap intermedio. Después de crear el programa de dibujo, lo ampliaremos añadiendole la posibilidad de utilizar dispositivos XInput, como tabletas digitalizadoras. GTK proporciona las rutinas que nos darán la posibilidad de obtener información extra, como la presión y la inclinación, de todo tipo de dispositivos de una forma sencilla.

Page 195: GTK Tutorial

25.2 Manejo de eventos Las señales GTK sobre las que ya hemos discutido son para las acciones de alto nivel, como cuando se selecciona un elemento de un menú. Sin embargo a veces es útil tratar con los acontecimientos a bajo nivel, como cuando se mueve el ratón, o cuando se está presionando una tecla. También hay señales GTK relacionadas con estos eventos de bajo nivel. Los manejadores de estas señales tienen un parámetro extra que es un puntero a una estructura conteniendo información sobre el evento. Por ejemplo, a los manejadores de los eventos de movimiento se les pasa una estructura GdkEventMotion que es (en parte) así:

struct _GdkEventMotion{ GdkEventType type; GdkWindow *window; guint32 time; gdouble x; gdouble y; ... guint state; ...};

type adquirirá su valor adecuado dependiendo del tipo de evento, en nuestro caso GDK_MOTION_NOTIFY, window es la ventana en la que ocurre el evento. x e y dan las coordenadas del evento, y state especifica cual es la modificación que ha habido cuando ocurrió el evento (esto es, especifica que teclas han cambiado su estado y que botones del ratón se han presionado.) Es la operación OR (O) de algunos de los siguientes valores:

GDK_SHIFT_MASK GDK_LOCK_MASK GDK_CONTROL_MASKGDK_MOD1_MASK GDK_MOD2_MASK GDK_MOD3_MASK GDK_MOD4_MASK GDK_MOD5_MASK GDK_BUTTON1_MASKGDK_BUTTON2_MASKGDK_BUTTON3_MASKGDK_BUTTON4_MASKGDK_BUTTON5_MASK

Como con las otras señales, para especificar que es lo que pasa cuando ocurre un evento, llamaremos a gtk_signal_connect(). Pero también necesitamos decirle a GTK sobre que eventos queremos ser informados. Para ello, llamaremos a la función:

void gtk_widget_set_events (GtkWidget *widget, gint events);

El segundo campo especifica los eventos en los que estamos interesados. Es el OR (O) de las constantes que especifican los diferentes tipos de eventos. Por las referencias futuras que podamos hacer, presentamos aquí los tipos de eventos que hay disponibles:

GDK_EXPOSURE_MASKGDK_POINTER_MOTION_MASK

Page 196: GTK Tutorial

GDK_POINTER_MOTION_HINT_MASKGDK_BUTTON_MOTION_MASK GDK_BUTTON1_MOTION_MASK GDK_BUTTON2_MOTION_MASK GDK_BUTTON3_MOTION_MASK GDK_BUTTON_PRESS_MASK GDK_BUTTON_RELEASE_MASK GDK_KEY_PRESS_MASK GDK_KEY_RELEASE_MASK GDK_ENTER_NOTIFY_MASK GDK_LEAVE_NOTIFY_MASK GDK_FOCUS_CHANGE_MASK GDK_STRUCTURE_MASK GDK_PROPERTY_CHANGE_MASK GDK_PROXIMITY_IN_MASK GDK_PROXIMITY_OUT_MASK

Hay unos cuantas sutilezas que debemos respetar cuando llamamos a gtk_widget_set_events(). Primero, debemos llamar a esta función antes de que se cree la ventana X para el widget GTK. En términos prácticos, significa que debemos llamarla inmediatamente después de crear el widget. Segundo, el widget debe tener una ventana X asociado. Por motivos de eficiencia, hay muchos widgets que no tienen su propia ventana, sino que dibujan en la de su padre. Estos widgets son:

GtkAlignmentGtkArrowGtkBinGtkBoxGtkImageGtkItemGtkLabelGtkPixmapGtkScrolledWindowGtkSeparatorGtkTableGtkAspectFrameGtkFrameGtkVBoxGtkHBoxGtkVSeparatorGtkHSeparator

Para capturar eventos para estos widgets, necesita utilizar un widget EventBox. Vea la sección El widget EventBox para más detalles.

Para nuestro programa de dibujo, queremos saber cuando se presiona el botón del ratón y cuando se mueve, por lo que debemos especificar los eventos GDK_POINTER_MOTION_MASK y GDK_BUTTON_PRESS_MASK. También queremos saber cuando necesitamos redibujar nuestra ventana, por lo que especificaremos el evento GDK_EXPOSURE_MASK. Aunque queremos estar informados mediante un evento Configure cuando cambie el tamaño de nuestra ventana, no tenemos que especificar la correspondiente GDK_STRUCTURE_MASK, porque ya está activada por defecto para todas las ventanas.

Tenemos un problema con lo que acabamos de hacer, y tiene que ver con la utilización de GDK_POINTER_MOTION_MASK. Si especificamos este evento, el servidor añadirá un evento de

Page 197: GTK Tutorial

movimiento a la cola de eventos cada vez que el usuario mueva el ratón. Imagine que nos cuesta 0'1 segundo tratar el evento de movimiento, pero que el servidor X añade a la cola un nuevo evento de moviento cada 0'05 segundos. Pronto nos iremos quedando retrasados con respecto al resto de los eventos. Si el usuario dibuja durante 5 segundos, ¡nos llevará otros 5 segundos el cazarle después de que hay levantado el botón del ratón! Lo que queremos es sólo un evento de movimiento por cada evento que procesemos. La manera de hacerlo es especificando GDK_POINTER_MOTION_HINT_MASK.

Cuando especificamos GDK_POINTER_MOTION_HINT_MASK, el servidor nos envia un evento de movimiento la primera ver que el puntero se mueve depués de entrar en nuestra ventana, o después de que se apriete o se suelte un botón (y se reciba el evento correspondiente). Los eventos de movimiento restantes se eliminarán a no ser que preguntemos especificamente por la posición del puntero utilizando la función:

GdkWindow* gdk_window_get_pointer (GdkWindow *window, gint *x, gint *y, GdkModifierType *mask);

(Hay otra función, gtk_widget_get_pointer() que tiene una interfaz más sencillo, pero esta simplificación le resta utilidad, ya que sólo devuelve la posición del ratón, y no si alguno de sus botones está presionado.)

El código para establecer los eventos para nuestra ventana es el siguiente:

gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", (GtkSignalFunc) expose_event, NULL); gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", (GtkSignalFunc) configure_event, NULL); gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", (GtkSignalFunc) motion_notify_event, NULL); gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", (GtkSignalFunc) button_press_event, NULL);

gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);

Vamos a dejar los manejadores de los eventos expose_event y configure_event para después. Los manejadores de motion_notify_event y de button_press_event son bastante simples:

static gintbutton_press_event (GtkWidget *widget, GdkEventButton *event){ if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->x, event->y);

return TRUE;}

static gintmotion_notify_event (GtkWidget *widget, GdkEventMotion *event){ int x, y;

Page 198: GTK Tutorial

GdkModifierType state;

if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, x, y); return TRUE;}

25.3 El widget DrawingArea, y dibujando Vamos a pasar al proceso de dibujar en la pantalla. El widget que utilizaremos será el DrawingArea. Un widget DrawingArea es esencialmente una ventana X y nada más. Es un lienzo en blanco en el que podemos dibujar lo que queramos. Crearemos un área de dibujo utilizando la llamada:

GtkWidget* gtk_drawing_area_new (void);

Se puede especificar un tamaño por defecto para el widget llamando a:

void gtk_drawing_area_size (GtkDrawingArea *darea, gint width, gint height);

Se puede cambiar el tamaño por defecto, como para todos los widgets, llamando a gtk_widget_set_usize(), y esto, además, puede cambiarse si el usuario cambia manualmente el tamaño de la ventana que contiene el área de dibujo.

Debemos hacer notar que cuando creamos un widget DrawingArea, seremos completamente responsables de dibujar su contenido. Si nuestra ventana se tapa y se vuelve a poner al descubierto, obtendremos un evento de exposición y deberemos redibujar lo que se había tapado.

Tener que recordar todo lo que se dibujó en la pantalla para que podamos redibujarla convenientemente es, por decirlo de alguna manera suave, una locura. Además puede quedar mal si hay que borrar partes de la pantalla y hay que redibujarlas paso a paso. La solución a este problema es utilizar un pixmap intermedio. En lugar de dibujar directamente en la pantalla, dibujaremos en una imagen que estará almacenada en la memoria del servidor, pero que no se mostrará, y cuando cambie la imagen o se muestren nuevas partes de la misma, copiaremos las porciones relevantes en la pantalla.

Para crear un pixmap intermedio, llamaremos a la función:

GdkPixmap* gdk_pixmap_new (GdkWindow *window, gint width, gint height, gint depth);

El parámetro widget especifica una ventana GDK de las que este pixmap tomará algunas propiedades. width y height especifican el tamaño del pixmap. depth especifica la profundidad

Page 199: GTK Tutorial

del color, que es el número de bits por pixel de la nueva ventana. Si la profundidad que se especifica es -1, se utilizará la misma profundidad de color que tenga la ventana.

Creamos nuestro pixmap en nuestro manejador del evento configure_event. Este evento se genera cada vez que cambia el tamaño de la ventana, incluyendo cuando ésta se crea.

/* Backing pixmap for drawing area */static GdkPixmap *pixmap = NULL;

/* Create a new backing pixmap of the appropriate size */static gintconfigure_event (GtkWidget *widget, GdkEventConfigure *event){ if (pixmap) gdk_pixmap_unref(pixmap);

pixmap = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); gdk_draw_rectangle (pixmap, widget->style->white_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height);

return TRUE;}

La llamada a gdk_draw_rectangle() rellena todo el pixmap de blanco. Hablaremos más de todo esto en un momento.

Nuestro manejador del evento de exposición simplemente copia la porción relevante del pixmap en la pantalla (determinaremos la zona a redibujar utilizando el campo event->area del evento de exposición):

/* Redraw the screen from the backing pixmap */static gintexpose_event (GtkWidget *widget, GdkEventExpose *event){ gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height);

return FALSE;}

Ahora ya sabemos como mantener la pantalla actualizada con el contenido de nuestro pixmap, pero ¿cómo podemos dibujar algo interesante en nuestro pixmap? Hay un gran número de llamadas en la biblioteca GDK para dibujar en los dibujables. Un dibujable es simplemente algo sobre lo que se puede dibujar. Puede ser una ventana, un pixmap, un bitmap (una imagen en blanco y negro), etc. Ya hemos visto arriba dos de estas llamadas, gdk_draw_rectangle() y gdk_draw_pixmap(). La lista

Page 200: GTK Tutorial

completa de funciones para dibujar es:

gdk_draw_line ()gdk_draw_rectangle ()gdk_draw_arc ()gdk_draw_polygon ()gdk_draw_string ()gdk_draw_text ()gdk_draw_pixmap ()gdk_draw_bitmap ()gdk_draw_image ()gdk_draw_points ()gdk_draw_segments ()

Ver la documentación de estas funciones o el fichero de cabecera <gdk/gdk.h> para obtener más detalles sobre estas funciones. Todas comparten los dos primeros argumentos. El primero es el dibujable en el que se dibujará, y el segundo argumento es un contexto gráfico (GC).

Un contexto gráfico reúne la información sobre cosas como el color de fondo y del color de lo que se dibuja, el ancho de la línea, etc... GDK tiene un conjunto completo de funciones para crear y modificar los contextos gráficos. Cada widget tiene un GC asociado. (Que puede modificarse en un fichero gtkrc, ver la sección ``Ficheros rc de GTK''.) Estos, junto con otras cosas, almacenan GC's. Algunos ejemplos de como acceder a estos GC's son:

widget->style->white_gcwidget->style->black_gcwidget->style->fg_gc[GTK_STATE_NORMAL]widget->style->bg_gc[GTK_WIDGET_STATE(widget)]

Los campos fg_gc, bg_gc, dark_gc, y light_gc se indexan con un parámetro del tipo GtkStateType que puede tomar uno de los valores:

GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED,GTK_STATE_INSENSITIVE

Por ejemplo, para el GTK_STATE_SELECTED, el color que se utiliza para pintar por defecto es el blanco y el color del fondo por defecto, es el azul oscuro.

Nuestra función draw_brush(), que es la que dibuja en la pantalla, será la siguiente:

/* Draw a rectangle on the screen */static voiddraw_brush (GtkWidget *widget, gdouble x, gdouble y){ GdkRectangle update_rect;

update_rect.x = x - 5; update_rect.y = y - 5; update_rect.width = 10; update_rect.height = 10; gdk_draw_rectangle (pixmap, widget->style->black_gc, TRUE, update_rect.x, update_rect.y,

Page 201: GTK Tutorial

update_rect.width, update_rect.height); gtk_widget_draw (widget, &update_rect);}

Después de que dibujemos el rectángulo representando la brocha en el pixmap llamaremos a la función:

void gtk_widget_draw (GtkWidget *widget, GdkRectangle *area);

que le informa a X de que la zona dada por el parámetro area necesita actualizarse. X generará un evento de exposición (combinando posiblemente distintas zonas pasadas mediante distintas llamadas a gtk_widget_draw()) que hará que nuestro manejador de eventos de exposición copie las porciones relevantes en la pantalla.

Ya hemos cubierto el programa de dibujo completo, excepto unos cuantos detalles mundanos como crear la ventana principal. El código completo está disponible en el mismo lugar en el que consiguió este tutorial, o en:

http://www.gtk.org/~otaylor/gtk/tutorial/

25.4 Añadiendo la capacidad de utilizar XInput Ahora es posible comprar dispositos de entrada bastante baratos, como tabletas digitalizadoras, que permiten dibujar de forma artística mucho más fácilmente de cómo lo haríamos con un ratón. La forma más sencilla de utilizar estos dispositivos es simplemente reemplazando a los ratones, pero así perdemos muchas de las ventajas de este tipo de dispositivos, como por ejemplo:

• Sensibilidad a la presión • Información sobre la inclinación • Colocación subpixel • Multiples entradas (por ejemplo, un lápiz con una punta y una goma)

Para información sobre la extensión XInput, ver el XInput-HOWTO.

Si examinamos la definición completa de, por ejemplo, la estructura GdkEventMotion, veremos que tiene campos para almacenar la información de los dispositivos extendidos.

struct _GdkEventMotion{ GdkEventType type; GdkWindow *window; guint32 time; gdouble x; gdouble y; gdouble pressure; gdouble xtilt; gdouble ytilt; guint state; gint16 is_hint; GdkInputSource source; guint32 deviceid;};

pressure da la presión como un número de coma flotante entre 0 y 1. xtilt e ytilt pueden tomar valores entre -1 y 1, correspondiendo al grado de inclinación en cada dirección. source y

Page 202: GTK Tutorial

deviceid especifican el dispositivo para el que ocurre el evento de dos maneras diferentes. source da alguna información simple sobre el tipo de dispositivo. Puede tomar los valores de la enumeración siguiente:

GDK_SOURCE_MOUSEGDK_SOURCE_PENGDK_SOURCE_ERASERGDK_SOURCE_CURSOR

deviceid especifica un número único ID para el dispositivo. Puede utilizarse para obtener más información sobre el dispositivo utilizando la función gdk_input_list_devices() (ver abajo). El valor especial GDK_CORE_POINTER se utiliza para el núcleo del dispositivo apuntador. (Normalmente el ratón.)

Activando la información del dispositivo extendido

Para informar a GTK de nuestro interés en la información sobre los dispositivos extendidos, sólo tenemos que añadirle una línea a nuestro programa:

gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);

Dando el valor GDK_EXTENSION_EVENTS_CURSOR decimos que estamos interesados en los eventos de extensión, pero sólo si no tenemos que dibujar nuestro propio cursor. Ver la sección Sofisticaciones adicionales más abajo para obtener más información sobre el dibujado del cursor. También podríamos dar los valores GDK_EXTENSION_EVENTS_ALL si queremos dibujar nuestro propio cursor, o GDK_EXTENSION_EVENTS_NONE para volver al estado inicial.

Todavía no hemos llegado al final de la historia. Por defecto, no hay ningún dispositivo extra activado. Necesitamos un mecanismo que permita a los usuarios activar y configurar sus dispositivos extra. GTK proporciona el widget InputDialog para automatizar el proceso. El siguiente procedimiento utiliza el widget InputDialog. Crea el cuadro de diálogo si no ha sido ya creado, y lo pone en primer plano en caso contrario.

voidinput_dialog_destroy (GtkWidget *w, gpointer data){ *((GtkWidget **)data) = NULL;}

voidcreate_input_dialog (){ static GtkWidget *inputd = NULL;

if (!inputd) { inputd = gtk_input_dialog_new();

gtk_signal_connect (GTK_OBJECT(inputd), "destroy", (GtkSignalFunc)input_dialog_destroy, &inputd); gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button), "clicked", (GtkSignalFunc)gtk_widget_hide,

Page 203: GTK Tutorial

GTK_OBJECT(inputd)); gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);

gtk_widget_show (inputd); } else { if (!GTK_WIDGET_MAPPED(inputd)) gtk_widget_show(inputd); else gdk_window_raise(inputd->window); }}

(Tome nota de la manera en que hemos manejado el cuadro de diálogo. Conectando la señal destroy, nos aseguramos de que no tendremos un puntero al cuadro de diálogo después de que haya sido destruido, lo que nos podría llevar a un segfault.)

El InputDialog tiene dos botones ``Cerrar'' y ``Guardar'', que por defecto no tienen ninguna acción asignada. En la función anterior hemos hecho que ``Cerrar'' oculte el cuadro de diálogo, ocultando el botón ``Guardar'', ya que no implementaremos en este programa la acción de guardar las opciones de XInput.

Utilizando la información de los dispositivos extras

Una vez hemos activado el dispositivo, podemos utilizar la información que hay respecto a los dispositivos extendidos en los campos extras de las estructuras de los eventos. De hecho, es bueno utilizar esa información ya que esos campos tienen unos valores por defecto razonables aún cuando no se activen los eventos extendidos.

Un cambio que tenemos que hacer es llamar a gdk_input_window_get_pointer() en vez de a gdk_window_get_pointer. Esto es necesario porque gdk_window_get_pointer no devuelve la información de los dispositivos extra.

void gdk_input_window_get_pointer (GdkWindow *window, guint32 deviceid, gdouble *x, gdouble *y, gdouble *pressure, gdouble *xtilt, gdouble *ytilt, GdkModifierType *mask);

Cuando llamamos a esta función, necesitamos especificar tanto el ID del dispositivo como la ventana. Normalmente, obtendremos el ID del dispositivo del campo deviceid de una estructura de evento. De nuevo, esta función devolverá valores razonables cuando no estén activados los eventos extendidos. (En ese caso, event->deviceid tendrá el valor GDK_CORE_POINTER).

Por tanto la estructura básica de nuestros manejadores de los eventos de movimiento y de pulsación del botón del ratón no cambiarán mucho - sólo tenemos que añadir código para manejar la información extra.

static gintbutton_press_event (GtkWidget *widget, GdkEventButton *event){

Page 204: GTK Tutorial

print_button_press (event->deviceid); if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->source, event->x, event->y, event->pressure);

return TRUE;}

static gintmotion_notify_event (GtkWidget *widget, GdkEventMotion *event){ gdouble x, y; gdouble pressure; GdkModifierType state;

if (event->is_hint) gdk_input_window_get_pointer (event->window, event->deviceid, &x, &y, &pressure, NULL, NULL, &state); else { x = event->x; y = event->y; pressure = event->pressure; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, event->source, x, y, pressure); return TRUE;}

También tenemos que hacer algo con la nueva información. Nuestra nueva función draw_brush() dibuja con un color diferente dependiendo de event->source y cambia el tamaño de la brocha dependiendo de la presión.

/* Draw a rectangle on the screen, size depending on pressure, and color on the type of device */static voiddraw_brush (GtkWidget *widget, GdkInputSource source, gdouble x, gdouble y, gdouble pressure){ GdkGC *gc; GdkRectangle update_rect;

switch (source) { case GDK_SOURCE_MOUSE: gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; break; case GDK_SOURCE_PEN: gc = widget->style->black_gc; break; case GDK_SOURCE_ERASER: gc = widget->style->white_gc; break;

Page 205: GTK Tutorial

default: gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; }

update_rect.x = x - 10 * pressure; update_rect.y = y - 10 * pressure; update_rect.width = 20 * pressure; update_rect.height = 20 * pressure; gdk_draw_rectangle (pixmap, gc, TRUE, update_rect.x, update_rect.y, update_rect.width, update_rect.height); gtk_widget_draw (widget, &update_rect);}

Obteniendo más información de un dispositivo

Como ejemplo de como podemos obtener más información de un dispositivo, nuestro programa imprimirá el nombre del dispositivo que genera cada pulsación de botón. Para encontrar el nombre de un dispositivo, llamaremos a la función:

GList *gdk_input_list_devices (void);

que devuelve una GList (una lista enlazada de la biblioteca glib) de estructuras GdkDeviceInfo. La estructura GdkDeviceInfo se define como:

struct _GdkDeviceInfo{ guint32 deviceid; gchar *name; GdkInputSource source; GdkInputMode mode; gint has_cursor; gint num_axes; GdkAxisUse *axes; gint num_keys; GdkDeviceKey *keys;};

Muchos de estos campos son información de configuración que puede ignorar, a menos que quiera permitir la opción de grabar la configuración de XInput. El campo que nos interesa ahora es name que es simplemente el nombre que X le asigna al dispositivo. El otro campo que no tiene información sobre la configuración es has_cursor. Si has_cursor es falso, tendremos que dibujar nuestro propio cursor. Pero como hemos especificado GDK_EXTENSION_EVENTS_CURSOR, no tendremos que preocuparnos por esto.

Nuestra función print_button_press() simplemente recorre la lista devuelta hasta que encuentra una coincidencia, y entonces imprime el nombre del dispositivo.

static voidprint_button_press (guint32 deviceid){ GList *tmp_list;

/* gdk_input_list_devices returns an internal list, so we shouldn't free it afterwards */ tmp_list = gdk_input_list_devices();

Page 206: GTK Tutorial

while (tmp_list) { GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;

if (info->deviceid == deviceid) { printf("Button press on device '%s'\n", info->name); return; }

tmp_list = tmp_list->next; }}

Con esto hemos completado los cambios para `XInputizar' nuestro programa. Como ocurria con la primera versión, el código completo se encuentra disponible en el mismo sitio donde obtuvo éste tutorial, o desde:

http://www.gtk.org/~otaylor/gtk/tutorial/

Sofisticaciones adicionales

Aunque ahora nuestro programa admite XInput bastante bien, todavía falla en algunas características que deberían estar disponibles en una aplicación bien hecha. Primero, el usuario no debería tener que configurar su dispositivo cada vez que ejecute el programa, por lo que debería estar disponible la opción de guardar la configuración del dispositivo. Esto se hace recorriendo el resultado de gdk_input_list_devices() y escribiendo la configuración en un fichero.

Para cargar la configuración del dispositivo cuando se vuelva a ejecutar el programa, puede utilizar las funciones que proporciona GDK para cambiar la configuración de los dispositivos:

gdk_input_set_extension_events()gdk_input_set_source()gdk_input_set_mode()gdk_input_set_axes()gdk_input_set_key()

(La lista devuelta por gdk_input_list_devices() no debería modificarse directamente.) Podemos encontrar un ejemplo de como debe utilizarse en el programa de dibujo gsumi. (Disponible en http://www.msc.cornell.edu/~otaylor/gsumi/) Estaría bien tener un procedimiento estándar para poder hacer todo esto en cualquier aplicaciones. Probablemente se llegue a esto en una capa superior a GTK, quizás en la biblioteca GNOME.

El programa tiene otra carencia importante que ya hemos mencionado más arriba, y es la falta del cursor. Ninguna plataforma distinta de XFree86 permite utilizar simultaneamente un dispositivo como puntero núcleo y como dispositivo directamente utilizable por una aplicación. Ver el XInput-HOWTO para más información sobre esto. Con esto queremos decir que si quiere tener la máxima audiencia necesita dibujar su propio cursor.

Una aplicación que dibuja su propio cursor necesita hacer dos cosas: determinar si el dispositivo actual necesita que se dibuje un cursor o no, y determinar si el dispositivo está ``próximo''. (Si el dispositivo es una tableta digitalizadora, queda muy bonito que el cursor desaparezca cuando el lápiz se separa de la tableta. Cuando el lápiz está tocando la tableta, se dice que el dispositivo está ``próximo.'') Lo

Page 207: GTK Tutorial

primero se hace buscando la lista de dispositivos, tal y como hicimos para encontrar el nombre del dispositivo. Lo segundo se consigue seleccionando los eventos proximity_out. Podemos encontrar un ejemplo de como dibujar nuestro propio cursor en el programa `testinput' que viene con la distribución de GTK.

26. Trucos para escribir aplicaciones GTKEsta sección es sólo un compendio de sabiduria, de guías generales de estilo y de consejos para crear buenas aplicaciones GTK. Y es totalmente inútil por ahora ya que esta frase es sólo un tópico :)

¡Utilice GNU autoconf y automake! Son sus amigos :) Pretendo poner aquí una rápida introducción a ambos.

27. ContribuyendoEste documento, como muchos otros grandes paquetes de programas que hay por ahí, fue creado de forma libre por voluntarios. Si comprende algo de GTK que todavía no se ha documentado, por favor piense en contribuir a este documento.

Si decide contribuir, por favor mande un correo-e con su texto a Tony Gale, [email protected]. Recuerde que todas las partes que componen este documento son libre, y cualquier añadido que haga debe ser libre. Esto es, la gente debe de poder utilizar cualquier trozo de sus ejemplos en sus programas, podrán distribuir copias de su documento como deseen, etc...

Gracias.

28. CréditosQuiero agradecer a las siguientes personas por sus contribuciones a este texto.

• Bawer Dagdeviren, [email protected] por el tutorial sobre los menús. • Raph Levien, [email protected] por el ``hola mundo'' ala GTK, el empaquetado de widgets, y su

sabiduría general. Ha donado generosamente un hogar para este tutorial. • Peter Mattis, [email protected] por el más simple de los programas GTK... y por

la posibilidad de hacerlo :) • Werner Koch [email protected] por convertir el texto original a SGML, y por la

jerarquia de clases de widgets. • Mark Crichton [email protected] por el código del menú factory, y el

tutorial sobre el empaquetamiento de las tablas. • Owen Taylor [email protected] por la sección sobre el widget EventBox (y el parche para

el distro). También es el responsable del código de las selecciones y el tutorial, así como de la sección de escribiendo su propio widget GTK, y la aplicación de ejemplo. ¡Muchas gracias por toda tu ayuda, Owen!

• Mark VanderBoom [email protected] por su fantástico trabajo sobre los widgets Notebook, Progress Bar, Dialog, y selección de ficheros. ¡Muchas gracias Mark! Has sido de

Page 208: GTK Tutorial

una gran ayuda. • Tim Janik [email protected] por su gran trabajo en el widget List. Gracias Tim :) • Rajat Datta [email protected] por el excelente trabajo con el tutorial Pixmap. • Michael K. Johnson [email protected] por la información y el código de los menús

("popup"). • David Huggins-Daines [email protected] por las secciones sobre los

widgets Range y Tree. • Stefan Mars [email protected] por la sección GtkCList

Y a todos los que han comentado y ayudado a refinar este documento.

Gracias.

29. Copyright del Tutorial y notas sobre los permisosEsta traducción está bajo la misma licencia bajo la que está el documento original. A continuación se presenta la traducción de la licencia y la licencia en versión original. En caso de haber alguna discrepancia entre la traducción y la licencia original, se aplicará esta última.

El Tutorial GTK tiene Copyright (C) 1997 Ian Main.

Copyright (C) 1998 Tony Gale.

Se da permiso para hacer y distribuir copias idénticas de este manual siempre que se incluya el copyright en todas las copias.

Se da permiso para copiar y distribuir versiones modificadas de este documento bajo las mismas condiciones que para las copias idénticas, siempre que el copyright se incluya exactamente tal y como se encuentra en el original, y que el trabajo completo derivado de este documento se distribuya bajo los términos de un permiso idéntico a éste.

Se da permiso para copiar y distribuir traducciones de este documento en otro lenguaje, bajo las condiciones arriba mencionadas para las versiones modificadas.

Si se propone incluir este documento en un trabajo que vaya a ser impreso, por favor contacte con el encargado del mantenimiento, y haremos un esfuerzo para asegurarnos de que dispone de la información lo más actualizada posible.

No hay ninguna garantia de que este documento se mantenga activo lo suficiente como para conseguir cumplir con su propósito. Se proporciona como un documento libre. Como tal, los autores y encargados del mantenimiento de la información que se da en el documento no pueden dar ninguna garantia de que la misma esté al día.

-----------------------------

The GTK Tutorial is Copyright (C) 1997 Ian Main.

Copyright (C) 1998 Tony Gale.

Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.

Permission is granted to copy and distribute modified versions of this document under the conditions for verbatim copying, provided that this copyright notice is included exactly as in the original, and that

Page 209: GTK Tutorial

the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

Permission is granted to copy and distribute translations of this document into another language, under the above conditions for modified versions.

If you are intending to incorporate this document into a published work, please contact the maintainer, and we will make an effort to ensure that you have the most up to date information available.

There is no guarantee that this document lives up to its intended purpose. This is simply provided as a free resource. As such, the authors and maintainers of the information provided within can not make any guarantee that the information is even accurate.

29.1 Acerca de la traducción Esta traduccion tiene copyright (C) 1999 de Eduardo Anglada Varela <[email protected]> y Joaquín Cuenca Abela <[email protected]>. Si tiene cualquier duda, sugerencia o corrección no dude en consultarnos.

Gracias a Manuel de Vega Barreiro <[email protected]> por haber hospedado las versiones beta y la versión actual de este tutorial en su página web Linux Landia www.linuxlots.com/~barreiro/spain/gnome/.

Gracias a dreamhosted www.dreamhosted.com/ por proporcionar los servicios http y ftp necesarios para hospedar este tutorial en todas sus versiones.

Appendix

30. Tipos de eventos GDKLos siguientes tipos de datos se pasan en los manejadores de los eventos por GTK+. Para cada tipo de dato que se muestra, se muestran las señales que utilizan ese tipo de dato.

• GdkEvent • drag_end_event

• GdkEventType • GdkEventAny

• delete_event • destroy_event • map_event • unmap_event • no_expose_event

• GdkEventExpose • expose_event

• GdkEventNoExpose • GdkEventVisibility

Page 210: GTK Tutorial

• GdkEventMotion • motion_notify_event

• GdkEventButton • button_press_event • button_release_event

• GdkEventKey • key_press_event • key_release_event

• GdkEventCrossing • enter_notify_event • leave_notify_event

• GdkEventFocus • focus_in_event • focus_out_event

• GdkEventConfigure • configure_event

• GdkEventProperty • property_notify_event

• GdkEventSelection • selection_clear_event • selection_request_event • selection_notify_event

• GdkEventProximity • proximity_in_event • proximity_out_event

• GdkEventDragBegin • drag_begin_event

• GdkEventDragRequest • drag_request_event

• GdkEventDropEnter • drop_enter_event

• GdkEventDropLeave • drop_leave_event

• GdkEventDropDataAvailable • drop_data_available_event

• GdkEventClient • client_event

• GdkEventOther • other_event

El tipo de dato GdkEventType es un tipo de dato especial que se utiliza por todos los otros tipos de datos como un indicador del tipo de dato que se le está pasando al manejador de señal. Como verá más adelante, cada una de estructuras de los datos de los eventos tienen un miembro de este tipo. Se define como la siguiente enumeración:

typedef enum{

Page 211: GTK Tutorial

GDK_NOTHING = -1, GDK_DELETE = 0, GDK_DESTROY = 1, GDK_EXPOSE = 2, GDK_MOTION_NOTIFY = 3, GDK_BUTTON_PRESS = 4, GDK_2BUTTON_PRESS = 5, GDK_3BUTTON_PRESS = 6, GDK_BUTTON_RELEASE = 7, GDK_KEY_PRESS = 8, GDK_KEY_RELEASE = 9, GDK_ENTER_NOTIFY = 10, GDK_LEAVE_NOTIFY = 11, GDK_FOCUS_CHANGE = 12, GDK_CONFIGURE = 13, GDK_MAP = 14, GDK_UNMAP = 15, GDK_PROPERTY_NOTIFY = 16, GDK_SELECTION_CLEAR = 17, GDK_SELECTION_REQUEST = 18, GDK_SELECTION_NOTIFY = 19, GDK_PROXIMITY_IN = 20, GDK_PROXIMITY_OUT = 21, GDK_DRAG_BEGIN = 22, GDK_DRAG_REQUEST = 23, GDK_DROP_ENTER = 24, GDK_DROP_LEAVE = 25, GDK_DROP_DATA_AVAIL = 26, GDK_CLIENT_EVENT = 27, GDK_VISIBILITY_NOTIFY = 28, GDK_NO_EXPOSE = 29, GDK_OTHER_EVENT = 9999 /* Anacrónico, utilice en su lugar los filtros */} GdkEventType;

El otro tipo de evento que es diferente del resto es el mismo GdkEvent. Ésta es una unión de todos los otros tipos de datos, que permite que se convierta en un tipo de dato de evento específico con un manejador de señal.

Por tanto, los tipos de los datos de los eventos se definen como sigue:

struct _GdkEventAny{ GdkEventType type; GdkWindow *window; gint8 send_event;};

struct _GdkEventExpose{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkRectangle area; gint count; /* Si no cero es cero, es el número de eventos que * siguen. */};

Page 212: GTK Tutorial

struct _GdkEventNoExpose{ GdkEventType type; GdkWindow *window; gint8 send_event; /* XXX: ¿Hay alguien que necesite los campos major_code y minor_code de X ? */};

struct _GdkEventVisibility{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkVisibilityState state;};

struct _GdkEventMotion{ GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; gdouble x; gdouble y; gdouble pressure; gdouble xtilt; gdouble ytilt; guint state; gint16 is_hint; GdkInputSource source; guint32 deviceid; gdouble x_root, y_root;};

struct _GdkEventButton{ GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; gdouble x; gdouble y; gdouble pressure; gdouble xtilt; gdouble ytilt; guint state; guint button; GdkInputSource source; guint32 deviceid; gdouble x_root, y_root;};

struct _GdkEventKey{ GdkEventType type; GdkWindow *window; gint8 send_event;

Page 213: GTK Tutorial

guint32 time; guint state; guint keyval; gint length; gchar *string;};

struct _GdkEventCrossing{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkWindow *subwindow; GdkNotifyType detail;};

struct _GdkEventFocus{ GdkEventType type; GdkWindow *window; gint8 send_event; gint16 in;};

struct _GdkEventConfigure{ GdkEventType type; GdkWindow *window; gint8 send_event; gint16 x, y; gint16 width; gint16 height;};

struct _GdkEventProperty{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkAtom atom; guint32 time; guint state;};

struct _GdkEventSelection{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkAtom selection; GdkAtom target; GdkAtom property; guint32 requestor; guint32 time;};

/* Este tipo de evento se utiliza muy raramente. Solamente es * importante para los programas que utilizan XInput y que dibujar su

Page 214: GTK Tutorial

* propio cursor */

struct _GdkEventProximity{ GdkEventType type; GdkWindow *window; gint8 send_event; guint32 time; GdkInputSource source; guint32 deviceid;};

struct _GdkEventDragRequest{ GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint sendreply:1; guint willaccept:1; guint delete_data:1; /* No borrar si se ha mandado un enlace, sólo si se ha mandado el dato */ guint senddata:1; guint reserved:22; } flags; glong allflags; } u; guint8 isdrop; /* Este evento gdk puede ser generado por un par de eventos X - esto le permite a las aplicaciones saber si ha ocurrido realmente el soltado (drop), o si sólo hemos cambiado el valor de algunos datos */

GdkPoint drop_coords; gchar *data_type; guint32 timestamp;};

struct _GdkEventDragBegin{ GdkEventType type; GdkWindow *window; gint8 send_event; union { struct { guint protocol_version:4; guint reserved:28; } flags; glong allflags; } u;};

struct _GdkEventDropEnter{ GdkEventType type;

Page 215: GTK Tutorial

GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint sendreply:1; guint extended_typelist:1; guint reserved:26; } flags; glong allflags; } u;};

struct _GdkEventDropLeave{ GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint reserved:28; } flags; glong allflags; } u;};

struct _GdkEventDropDataAvailable{ GdkEventType type; GdkWindow *window; gint8 send_event; guint32 requestor; union { struct { guint protocol_version:4; guint isdrop:1; guint reserved:25; } flags; glong allflags; } u; gchar *data_type; /* tipo MIME */ gulong data_numbytes; gpointer data; guint32 timestamp; GdkPoint coords;};

struct _GdkEventClient{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkAtom message_type; gushort data_format; union {

Page 216: GTK Tutorial

char b[20]; short s[10]; long l[5]; } data;};

struct _GdkEventOther{ GdkEventType type; GdkWindow *window; gint8 send_event; GdkXEvent *xevent;};

31. Código ejemploA continuación tenemos el código ejemplo que se ha utilizado en el texto anterior y que no se ha incluido al completo en otro lugar.

31.1 Tictactoe

tictactoe.h/* principio del ejemplo tictactoe tictactoe.h */

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */#ifndef __TICTACTOE_H__#define __TICTACTOE_H__

#include <gdk/gdk.h>#include <gtk/gtkvbox.h>

Page 217: GTK Tutorial

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */

#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())

typedef struct _Tictactoe Tictactoe;typedef struct _TictactoeClass TictactoeClass;

struct _Tictactoe{ GtkVBox vbox; GtkWidget *buttons[3][3];};

struct _TictactoeClass{ GtkVBoxClass parent_class;

void (* tictactoe) (Tictactoe *ttt);};

guint tictactoe_get_type (void);GtkWidget* tictactoe_new (void);void tictactoe_clear (Tictactoe *ttt);

#ifdef __cplusplus}#endif /* __cplusplus */

#endif /* __TICTACTOE_H__ */

/* fin del ejemplo */

tictactoe.c/* principio del ejemplo tictactoe tictactoe.c */

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

Page 218: GTK Tutorial

* Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */#include "gtk/gtksignal.h"#include "gtk/gtktable.h"#include "gtk/gtktogglebutton.h"#include "tictactoe.h"

enum { TICTACTOE_SIGNAL, LAST_SIGNAL};

static void tictactoe_class_init (TictactoeClass *klass);static void tictactoe_init (Tictactoe *ttt);static void tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt);

static gint tictactoe_signals[LAST_SIGNAL] = { 0 };

guinttictactoe_get_type (){ static guint ttt_type = 0;

if (!ttt_type) { GtkTypeInfo ttt_info = { "Tictactoe", sizeof (Tictactoe), sizeof (TictactoeClass), (GtkClassInitFunc) tictactoe_class_init, (GtkObjectInitFunc) tictactoe_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL };

ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); }

return ttt_type;}

static voidtictactoe_class_init (TictactoeClass *class){ GtkObjectClass *object_class;

object_class = (GtkObjectClass*) class; tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", GTK_RUN_FIRST, object_class->type,

Page 219: GTK Tutorial

GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);

gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);

class->tictactoe = NULL;}

static voidtictactoe_init (Tictactoe *ttt){ GtkWidget *table; gint i,j; table = gtk_table_new (3, 3, TRUE); gtk_container_add (GTK_CONTAINER(ttt), table); gtk_widget_show (table);

for (i=0;i<3; i++) for (j=0;j<3; j++) { ttt->buttons[i][j] = gtk_toggle_button_new (); gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], i, i+1, j, j+1); gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); gtk_widget_show (ttt->buttons[i][j]); }}

GtkWidget*tictactoe_new (){ return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));}

void tictactoe_clear (Tictactoe *ttt){ int i,j;

for (i=0;i<3;i++) for (j=0;j<3;j++) { gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), FALSE); gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); }}

Page 220: GTK Tutorial

static voidtictactoe_toggle (GtkWidget *widget, Tictactoe *ttt){ int i,k;

static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 } }; static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 2, 1, 0 } };

int success, found;

for (k=0; k<8; k++) { success = TRUE; found = FALSE;

for (i=0;i<3;i++) { success = success && GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; found = found || ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; } if (success && found) { gtk_signal_emit (GTK_OBJECT (ttt), tictactoe_signals[TICTACTOE_SIGNAL]); break; } }}

/* fin del ejemplo */

ttt_test.c/* principio del ejemplo tictactoe ttt_test.c */

#include <gtk/gtk.h>#include "tictactoe.h"

voidwin (GtkWidget *widget, gpointer data){ g_print ("Yay!\n"); tictactoe_clear (TICTACTOE (widget));}

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

Page 221: GTK Tutorial

GtkWidget *window; GtkWidget *ttt; gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10);

ttt = tictactoe_new (); gtk_container_add (GTK_CONTAINER (window), ttt); gtk_widget_show (ttt);

gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", GTK_SIGNAL_FUNC (win), NULL);

gtk_widget_show (window); gtk_main (); return 0;}

/* fin del ejemplo */

31.2 GtkDial

gtkdial.h/* principio del ejmplo gtkdial gtkdial.h */

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA.

Page 222: GTK Tutorial

*/#ifndef __GTK_DIAL_H__#define __GTK_DIAL_H__

#include <gdk/gdk.h>#include <gtk/gtkadjustment.h>#include <gtk/gtkwidget.h>

#ifdef __cplusplusextern "C" {#endif /* __cplusplus */

#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())

typedef struct _GtkDial GtkDial;typedef struct _GtkDialClass GtkDialClass;

struct _GtkDial{ GtkWidget widget;

/* política de actualización * (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ guint policy : 2;

/* Botón actualmente presionado o 0 si no hay ninguno */ guint8 button;

/* Dimensión de los componendes del dial */ gint radius; gint pointer_width;

/* ID del temporizador de actualización, o 0 si no hay ninguno */ guint32 timer;

/* ángulo actual */ gfloat angle;

/* Viejos valores almacenados del adjustment, para que así no * tengamos que saber cuando cambia algo */ gfloat old_value; gfloat old_lower; gfloat old_upper;

/* El objeto adjustment que almacena los datos para este dial */ GtkAdjustment *adjustment;};

struct _GtkDialClass{

Page 223: GTK Tutorial

GtkWidgetClass parent_class;};

GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);guint gtk_dial_get_type (void);GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);void gtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy);

void gtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment);#ifdef __cplusplus}#endif /* __cplusplus */

#endif /* __GTK_DIAL_H__ *//* fin del ejemplo */

gtkdial.c/* principio del ejemplo gtkdial gtkdial.c */

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */#include <math.h>#include <stdio.h>#include <gtk/gtkmain.h>#include <gtk/gtksignal.h>

#include "gtkdial.h"

#define SCROLL_DELAY_LENGTH 300#define DIAL_DEFAULT_SIZE 100

/* declaraciones de funciones */

Page 224: GTK Tutorial

static void gtk_dial_class_init (GtkDialClass *klass);static void gtk_dial_init (GtkDial *dial);static void gtk_dial_destroy (GtkObject *object);static void gtk_dial_realize (GtkWidget *widget);static void gtk_dial_size_request (GtkWidget *widget, GtkRequisition *requisition);static void gtk_dial_size_allocate (GtkWidget *widget, GtkAllocation *allocation);static gint gtk_dial_expose (GtkWidget *widget, GdkEventExpose *event);static gint gtk_dial_button_press (GtkWidget *widget, GdkEventButton *event);static gint gtk_dial_button_release (GtkWidget *widget, GdkEventButton *event);static gint gtk_dial_motion_notify (GtkWidget *widget, GdkEventMotion *event);static gint gtk_dial_timer (GtkDial *dial);

static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y);static void gtk_dial_update (GtkDial *dial);static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment, gpointer data);static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data);

/* datos locales */

static GtkWidgetClass *parent_class = NULL;

guintgtk_dial_get_type (){ static guint dial_type = 0;

if (!dial_type) { GtkTypeInfo dial_info = { "GtkDial", sizeof (GtkDial), sizeof (GtkDialClass), (GtkClassInitFunc) gtk_dial_class_init, (GtkObjectInitFunc) gtk_dial_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, };

dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info); }

return dial_type;}

Page 225: GTK Tutorial

static voidgtk_dial_class_init (GtkDialClass *class){ GtkObjectClass *object_class; GtkWidgetClass *widget_class;

object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class;

parent_class = gtk_type_class (gtk_widget_get_type ());

object_class->destroy = gtk_dial_destroy;

widget_class->realize = gtk_dial_realize; widget_class->expose_event = gtk_dial_expose; widget_class->size_request = gtk_dial_size_request; widget_class->size_allocate = gtk_dial_size_allocate; widget_class->button_press_event = gtk_dial_button_press; widget_class->button_release_event = gtk_dial_button_release; widget_class->motion_notify_event = gtk_dial_motion_notify;}

static voidgtk_dial_init (GtkDial *dial){ dial->button = 0; dial->policy = GTK_UPDATE_CONTINUOUS; dial->timer = 0; dial->radius = 0; dial->pointer_width = 0; dial->angle = 0.0; dial->old_value = 0.0; dial->old_lower = 0.0; dial->old_upper = 0.0; dial->adjustment = NULL;}

GtkWidget*gtk_dial_new (GtkAdjustment *adjustment){ GtkDial *dial;

dial = gtk_type_new (gtk_dial_get_type ());

if (!adjustment) adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

gtk_dial_set_adjustment (dial, adjustment);

return GTK_WIDGET (dial);}

static voidgtk_dial_destroy (GtkObject *object){ GtkDial *dial;

Page 226: GTK Tutorial

g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_DIAL (object));

dial = GTK_DIAL (object);

if (dial->adjustment) gtk_object_unref (GTK_OBJECT (dial->adjustment));

if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);}

GtkAdjustment*gtk_dial_get_adjustment (GtkDial *dial){ g_return_val_if_fail (dial != NULL, NULL); g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);

return dial->adjustment;}

voidgtk_dial_set_update_policy (GtkDial *dial, GtkUpdateType policy){ g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

dial->policy = policy;}

voidgtk_dial_set_adjustment (GtkDial *dial, GtkAdjustment *adjustment){ g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

if (dial->adjustment) { gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); gtk_object_unref (GTK_OBJECT (dial->adjustment)); }

dial->adjustment = adjustment; gtk_object_ref (GTK_OBJECT (dial->adjustment));

gtk_signal_connect (GTK_OBJECT (adjustment), "changed", (GtkSignalFunc) gtk_dial_adjustment_changed, (gpointer) dial); gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", (GtkSignalFunc) gtk_dial_adjustment_value_changed, (gpointer) dial);

dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper;

Page 227: GTK Tutorial

gtk_dial_update (dial);}

static voidgtk_dial_realize (GtkWidget *widget){ GtkDial *dial; GdkWindowAttr attributes; gint attributes_mask;

g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DIAL (widget));

GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); dial = GTK_DIAL (widget);

attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget);

attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);

widget->style = gtk_style_attach (widget->style, widget->window);

gdk_window_set_user_data (widget->window, widget);

gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);}

static void gtk_dial_size_request (GtkWidget *widget, GtkRequisition *requisition){ requisition->width = DIAL_DEFAULT_SIZE; requisition->height = DIAL_DEFAULT_SIZE;}

static voidgtk_dial_size_allocate (GtkWidget *widget, GtkAllocation *allocation){ GtkDial *dial;

g_return_if_fail (widget != NULL);

Page 228: GTK Tutorial

g_return_if_fail (GTK_IS_DIAL (widget)); g_return_if_fail (allocation != NULL);

widget->allocation = *allocation; dial = GTK_DIAL (widget);

if (GTK_WIDGET_REALIZED (widget)) {

gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height);

} dial->radius = MIN(allocation->width,allocation->height) * 0.45; dial->pointer_width = dial->radius / 5;}

static gintgtk_dial_expose (GtkWidget *widget, GdkEventExpose *event){ GtkDial *dial; GdkPoint points[3]; gdouble s,c; gdouble theta; gint xc, yc; gint tick_length; gint i;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

if (event->count > 0) return FALSE; dial = GTK_DIAL (widget);

gdk_window_clear_area (widget->window, 0, 0, widget->allocation.width, widget->allocation.height);

xc = widget->allocation.width/2; yc = widget->allocation.height/2;

/* Dibuja las rayitas */

for (i=0; i<25; i++) { theta = (i*M_PI/18. - M_PI/6.); s = sin(theta); c = cos(theta);

tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;

Page 229: GTK Tutorial

gdk_draw_line (widget->window, widget->style->fg_gc[widget->state], xc + c*(dial->radius - tick_length), yc - s*(dial->radius - tick_length), xc + c*dial->radius, yc - s*dial->radius); }

/* Dibuja el puntero */

s = sin(dial->angle); c = cos(dial->angle);

points[0].x = xc + s*dial->pointer_width/2; points[0].y = yc + c*dial->pointer_width/2; points[1].x = xc + c*dial->radius; points[1].y = yc - s*dial->radius; points[2].x = xc - s*dial->pointer_width/2; points[2].y = yc - c*dial->pointer_width/2;

gtk_draw_polygon (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, points, 3, TRUE); return FALSE;}

static gintgtk_dial_button_press (GtkWidget *widget, GdkEventButton *event){ GtkDial *dial; gint dx, dy; double s, c; double d_parallel; double d_perpendicular;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

dial = GTK_DIAL (widget);

/* Determinar si la pulsación del botón fue dentro de la región del puntero - esto lo hacemos calculando la distancia x e y del punto donde se pulsó el botón ratón de la línea que se ha pasado mediante el puntero */ dx = event->x - widget->allocation.width / 2; dy = widget->allocation.height / 2 - event->y; s = sin(dial->angle);

Page 230: GTK Tutorial

c = cos(dial->angle); d_parallel = s*dy + c*dx; d_perpendicular = fabs(s*dx - c*dy); if (!dial->button && (d_perpendicular < dial->pointer_width/2) && (d_parallel > - dial->pointer_width)) { gtk_grab_add (widget);

dial->button = event->button;

gtk_dial_update_mouse (dial, event->x, event->y); }

return FALSE;}

static gintgtk_dial_button_release (GtkWidget *widget, GdkEventButton *event){ GtkDial *dial;

g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

dial = GTK_DIAL (widget);

if (dial->button == event->button) { gtk_grab_remove (widget);

dial->button = 0;

if (dial->policy == GTK_UPDATE_DELAYED) gtk_timeout_remove (dial->timer); if ((dial->policy != GTK_UPDATE_CONTINUOUS) && (dial->old_value != dial->adjustment->value)) gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); }

return FALSE;}

static gintgtk_dial_motion_notify (GtkWidget *widget, GdkEventMotion *event){ GtkDial *dial; GdkModifierType mods; gint x, y, mask;

g_return_val_if_fail (widget != NULL, FALSE);

Page 231: GTK Tutorial

g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE);

dial = GTK_DIAL (widget);

if (dial->button != 0) { x = event->x; y = event->y;

if (event->is_hint || (event->window != widget->window)) gdk_window_get_pointer (widget->window, &x, &y, &mods);

switch (dial->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; }

if (mods & mask) gtk_dial_update_mouse (dial, x,y); }

return FALSE;}

static gintgtk_dial_timer (GtkDial *dial){ g_return_val_if_fail (dial != NULL, FALSE); g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);

if (dial->policy == GTK_UPDATE_DELAYED) gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");

return FALSE;}

static voidgtk_dial_update_mouse (GtkDial *dial, gint x, gint y){ gint xc, yc; gfloat old_value;

g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

Page 232: GTK Tutorial

xc = GTK_WIDGET(dial)->allocation.width / 2; yc = GTK_WIDGET(dial)->allocation.height / 2;

old_value = dial->adjustment->value; dial->angle = atan2(yc-y, x-xc);

if (dial->angle < -M_PI/2.) dial->angle += 2*M_PI;

if (dial->angle < -M_PI/6) dial->angle = -M_PI/6;

if (dial->angle > 7.*M_PI/6.) dial->angle = 7.*M_PI/6.;

dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);

if (dial->adjustment->value != old_value) { if (dial->policy == GTK_UPDATE_CONTINUOUS) { gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); } else { gtk_widget_draw (GTK_WIDGET(dial), NULL);

if (dial->policy == GTK_UPDATE_DELAYED) { if (dial->timer) gtk_timeout_remove (dial->timer);

dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, (GtkFunction) gtk_dial_timer, (gpointer) dial); } } }}

static voidgtk_dial_update (GtkDial *dial){ gfloat new_value; g_return_if_fail (dial != NULL); g_return_if_fail (GTK_IS_DIAL (dial));

new_value = dial->adjustment->value; if (new_value < dial->adjustment->lower) new_value = dial->adjustment->lower;

if (new_value > dial->adjustment->upper)

Page 233: GTK Tutorial

new_value = dial->adjustment->upper;

if (new_value != dial->adjustment->value) { dial->adjustment->value = new_value; gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); }

dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / (dial->adjustment->upper - dial->adjustment->lower);

gtk_widget_draw (GTK_WIDGET(dial), NULL);}

static voidgtk_dial_adjustment_changed (GtkAdjustment *adjustment, gpointer data){ GtkDial *dial;

g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL);

dial = GTK_DIAL (data);

if ((dial->old_value != adjustment->value) || (dial->old_lower != adjustment->lower) || (dial->old_upper != adjustment->upper)) { gtk_dial_update (dial);

dial->old_value = adjustment->value; dial->old_lower = adjustment->lower; dial->old_upper = adjustment->upper; }}

static voidgtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data){ GtkDial *dial;

g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL);

dial = GTK_DIAL (data);

if (dial->old_value != adjustment->value) { gtk_dial_update (dial);

dial->old_value = adjustment->value; }}/* fin del ejemplo */

Page 234: GTK Tutorial

31.3 Scribble /* principio del ejemplo scribble-simple scribble-simple.c */

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */

#include <gtk/gtk.h>

/* Creamos un backing pixmap para la zona donde dibujamos */static GdkPixmap *pixmap = NULL;

/* Creamos un nuevo backing pixmap del tamaño apropiado */static gintconfigure_event (GtkWidget *widget, GdkEventConfigure *event){ if (pixmap) gdk_pixmap_unref(pixmap);

pixmap = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); gdk_draw_rectangle (pixmap, widget->style->white_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height);

return TRUE;}

/* Redibujamos la pantalla con el backing pixmap */static gintexpose_event (GtkWidget *widget, GdkEventExpose *event){ gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], pixmap,

Page 235: GTK Tutorial

event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height);

return FALSE;}

/* Dibujamos un rectángulo en la pantalla */static voiddraw_brush (GtkWidget *widget, gdouble x, gdouble y){ GdkRectangle update_rect;

update_rect.x = x - 5; update_rect.y = y - 5; update_rect.width = 10; update_rect.height = 10; gdk_draw_rectangle (pixmap, widget->style->black_gc, TRUE, update_rect.x, update_rect.y, update_rect.width, update_rect.height); gtk_widget_draw (widget, &update_rect);}

static gintbutton_press_event (GtkWidget *widget, GdkEventButton *event){ if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->x, event->y);

return TRUE;}

static gintmotion_notify_event (GtkWidget *widget, GdkEventMotion *event){ int x, y; GdkModifierType state;

if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, x, y); return TRUE;}

voidquit (){

Page 236: GTK Tutorial

gtk_exit (0);}

intmain (int argc, char *argv[]){ GtkWidget *window; GtkWidget *drawing_area; GtkWidget *vbox;

GtkWidget *button;

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window, "Test Input");

vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox);

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

/* Crear la zona de dibujado */

drawing_area = gtk_drawing_area_new (); gtk_drawing_area_size (GTK_DRAWING_AREA (drawing_area), 200, 200); gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);

gtk_widget_show (drawing_area);

/* Las señales utilizadas para manejar el backing pixmap */

gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", (GtkSignalFunc) expose_event, NULL); gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", (GtkSignalFunc) configure_event, NULL);

/* Señales evento */

gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", (GtkSignalFunc) motion_notify_event, NULL); gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", (GtkSignalFunc) button_press_event, NULL);

gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);

/* .. Y un botón para salir */ button = gtk_button_new_with_label ("Quit"); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);

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

Page 237: GTK Tutorial

GTK_OBJECT (window)); gtk_widget_show (button);

gtk_widget_show (window);

gtk_main ();

return 0;}/* fin del ejemplo */