Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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
9.5 Pixmaps ........................................................................................................................................54
9.6 Reglas ...........................................................................................................................................61
9.7 Barras de estado ............................................................................................................................64
9.8 Entrada de texto ............................................................................................................................66
9.9 Selección de Color ........................................................................................................................69
9.10 Selección de ficheros ..................................................................................................................72
10. Widgets Contenedores...................................................................................................................74
10.1 Libros de notas (Notebooks) ......................................................................................................74
10.2 Ventanas con barras de desplazamiento .....................................................................................79
10.3 El widget ``ventana dividida'' (Paned Window) .........................................................................81
10.4 Barras de herramientas ...............................................................................................................85
10.5 Marcos con proporciones fijas ...................................................................................................91
11. El widget GtkCList........................................................................................................................93
11.1 Creando un widget GtkCList ......................................................................................................93
11.2 Modos de operación ...................................................................................................................93
11.3 Trabajando con los títulos ...........................................................................................................94
11.4 Manipulando la lista en sí. ..........................................................................................................95
11.5 Añadiendo filas a la lista .............................................................................................................96
11.6 Poniendo texto y pixmaps en las celdas .....................................................................................97
11.7 Almacenando punteros a datos ...................................................................................................99
11.8 Trabajando con la selección ........................................................................................................99
11.9 Las señales que lo hacen todo ....................................................................................................99
11.10 Un ejemplo GtkCList .............................................................................................................100
12. El widget lista..............................................................................................................................103
12.1 Señales ......................................................................................................................................104
12.2 Funciones ..................................................................................................................................104
12.3 Ejemplo ....................................................................................................................................106
12.4 El widget GtkListItem ..............................................................................................................111
12.5 Señales ......................................................................................................................................112
12.6 Funciones ..................................................................................................................................112
12.7 Ejemplo .....................................................................................................................................112
13. El widget árbol............................................................................................................................113
13.1 Creando un árbol ......................................................................................................................113
13.2 Añadiendo un Subárbol ............................................................................................................113
13.3 Manejando la lista de selección ................................................................................................114
13.4 Estructura interna del widget árbol ...........................................................................................114
Señales..........................................................................................................................................115
Funciones y macros......................................................................................................................116
13.5 El widget elemento de árbol .....................................................................................................118
Señales..........................................................................................................................................119
Funciones y Macros......................................................................................................................120
13.6 Árbol ejemplo ...........................................................................................................................121
14. El widget menú............................................................................................................................124
14.1 Creación manual de menús .......................................................................................................124
14.2 Ejemplo de la creación manual de un menú .............................................................................127
14.3 Utilizando GtkMenuFactory .....................................................................................................130
14.4 Ejemplo de la fábrica de menús ...............................................................................................130
15. El widget texto.............................................................................................................................133
15.1 Creando y configurando un cuadro de texto ............................................................................133
15.2 Manipulación de texto ..............................................................................................................134
15.3 Atajos por teclado .....................................................................................................................136
Atajos para el movimiento............................................................................................................136
Atajos para la edición...................................................................................................................136
Atajos de selección.......................................................................................................................136
15.4 Un ejemplo de GtkText ............................................................................................................136
16. Widgets no documentados...........................................................................................................140
16.1 Fixed Container ........................................................................................................................140
16.2 Curves .......................................................................................................................................140
16.3 Previews ...................................................................................................................................140
17. El widget EventBox.....................................................................................................................149
18. Estableciendo los atributos de un widget....................................................................................150
19. Tiempos de espera, ES (IO) y funciones ociosas (idle)...............................................................151
19.1 Tiempos de espera ....................................................................................................................151
19.2 Monitorizando la ES .................................................................................................................151
19.3 Funciones ociosas .....................................................................................................................152
20. Manejo avanzado de eventos y señales.......................................................................................152
20.1 Funciones señal ........................................................................................................................152
Conectando y desconectando los manejadores de señal...............................................................152
Bloqueando y desbloqueando los manejadores de señal..............................................................153
Emitiendo y deteniendo señales....................................................................................................154
20.2 Emisión y propagación de señales ............................................................................................154
21. Manejando selecciones................................................................................................................155
21.1 Contenido .................................................................................................................................155
21.2 Recuperando la selección .........................................................................................................156
21.3 Proporcionando la selección .....................................................................................................158
22. glib...............................................................................................................................................161
22.1 Definiciones ..............................................................................................................................161
22.2 Listas doblemente enlazadas ....................................................................................................162
22.3 Listas simplemente enlazadas ..................................................................................................163
22.4 Control de la memoria ..............................................................................................................164
22.5 Timers .......................................................................................................................................164
22.6 Manejo de cadenas de texto ......................................................................................................165
22.7 Funciones de error y funciones varias ......................................................................................165
23. Ficheros rc de GTK.....................................................................................................................166
23.1 Funciones para los ficheros rc ..................................................................................................166
23.2 Formato de los ficheros rc de GTK ..........................................................................................167
23.3 Fichero rc de ejemplo ...............................................................................................................168
24. Escribiendo sus propios widgets.................................................................................................171
24.1 Visión general ...........................................................................................................................171
24.2 La anatomía de un widget ........................................................................................................171
24.3 Creando un widget compuesto .................................................................................................172
Introducción..................................................................................................................................172
Escogiendo una clase padre..........................................................................................................172
El fichero de cabecera...................................................................................................................172
La función _get_type().................................................................................................................174
La función _class_init()................................................................................................................175
La función _init()..........................................................................................................................177
Y el resto.......................................................................................................................................177
24.4 Creando un widget desde cero. .................................................................................................179
Introducción..................................................................................................................................179
Mostrando un widget en la pantalla..............................................................................................180
Los orígenes del widget Dial........................................................................................................180
Los comienzos..............................................................................................................................181
gtk_dial_realize()..........................................................................................................................185
Negociación del tamaño...............................................................................................................186
gtk_dial_expose().........................................................................................................................187
Manejo de eventos........................................................................................................................189
Posibles mejoras...........................................................................................................................194
24.5 Aprendiendo más ......................................................................................................................194
25. Scribble, un sencillo programa de dibujo de ejemplo.................................................................194
25.1 Objetivos ..................................................................................................................................194
25.2 Manejo de eventos ....................................................................................................................195
25.3 El widget DrawingArea, y dibujando .......................................................................................198
25.4 Añadiendo la capacidad de utilizar XInput ..............................................................................201
Activando la información del dispositivo extendido....................................................................202
Utilizando la información de los dispositivos extras....................................................................203
Obteniendo más información de un dispositivo...........................................................................205
Sofisticaciones adicionales ..........................................................................................................206
26. Trucos para escribir aplicaciones GTK.......................................................................................207
27. Contribuyendo.............................................................................................................................207
28. Créditos.......................................................................................................................................207
29. Copyright del Tutorial y notas sobre los permisos......................................................................208
29.1 Acerca de la traducción ............................................................................................................209
Appendix...........................................................................................................................................209
30. Tipos de eventos GDK................................................................................................................209
31. Código ejemplo...........................................................................................................................216
31.1 Tictactoe ...................................................................................................................................216
tictactoe.h......................................................................................................................................216
tictactoe.c......................................................................................................................................217
ttt_test.c.........................................................................................................................................220
31.2 GtkDial .....................................................................................................................................221
gtkdial.h........................................................................................................................................221
gtkdial.c........................................................................................................................................223
31.3 Scribble .....................................................................................................................................234
1. Introducción
En 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:
/*************************************************************
* 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.
2. Comenzando
Por supuesto lo primero que hay que hacer es descargar las fuentes de GTK e instalarlas. La última
versión siempre se puede obtener de ftp.gtk.org (en el directorio /pub/gtk). En http://www.gtk.org/ hay
más información sobre GTK.
Para configurar GTK hay que usar GNU autoconf. Una vez descomprimido se puede obtener las
opciones usando ./configure --help. El código de GTK además contiene las fuentes completas
de todos los ejemplos usados en este manual, así como los makefiles para compilarlos.
Para comenzar nuestra introducción a GTK vamos a empezar con el programa más sencillo posible.
Con él vamos a crear una ventana de 200x200 pixels que sólo se puede destruir desde el shell.
#include <gtk/gtk.h>
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.
Las dos líneas de código siguientes crean y muestran una ventana.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
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.
return (TRUE);
}
/* otra respuesta */
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
gtk_widget_show (button);
/* y la ventana */
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* final del ejemplo*/
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.
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 );
Donde, por regla general, el objeto es un widget. Sin embargo no es normal establecer una respuesta
para gtk_signal_connect_object. En lugar de ello llamamos a una función de GTK que acepte un
widget o un objeto como un argumento, tal y como se vio en el ejemplo hello world.
¿Para qué sirve tener dos funciones para conectar señales? Simplemente para permitir que las funciones
de respuesta puedan tener un número diferente de argumentos. Muchas funciones de GTK sólo aceptan
un puntero a un GtkWidget como argumento, por lo que tendrá que usar gtk_signal_connect_object()
con estas funciones, mientras que probablemente tenga que suministrarle información adicional a sus
funciones.
2.4 Eventos
Además del mecanismo de señales descrito arriba existe otro conjunto de eventos que reflejan como las
X manejan los eventos. Se pueden asignar funciones de respuesta a estos eventos. Los eventos son:
• 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 );
GdkEvent es una estructura union cuyo tipo depende de cual de los eventos anteriores haya ocurrido.
Para que podamos decir que evento se ha lanzado cada una de las posibles alternativas posee un
parámetro type que refleja cual es el evento en cuestión. Los otros componentes de la estructura
dependerán del tipo de evento. Algunos valores posibles son:
GDK_NOTHING
GDK_DELETE
GDK_DESTROY
GDK_EXPOSE
GDK_MOTION_NOTIFY
GDK_BUTTON_PRESS
GDK_2BUTTON_PRESS
GDK_3BUTTON_PRESS
GDK_BUTTON_RELEASE
GDK_KEY_PRESS
GDK_KEY_RELEASE
GDK_ENTER_NOTIFY
GDK_LEAVE_NOTIFY
GDK_FOCUS_CHANGE
GDK_CONFIGURE
GDK_MAP
GDK_UNMAP
GDK_PROPERTY_NOTIFY
GDK_SELECTION_CLEAR
GDK_SELECTION_REQUEST
GDK_SELECTION_NOTIFY
GDK_PROXIMITY_IN
GDK_PROXIMITY_OUT
GDK_DRAG_BEGIN
GDK_DRAG_REQUEST
GDK_DROP_ENTER
GDK_DROP_LEAVE
GDK_DROP_DATA_AVAIL
GDK_CLIENT_EVENT
GDK_VISIBILITY_NOTIFY
GDK_NO_EXPOSE
GDK_OTHER_EVENT /* En desuso, usar filtros en lugar de ella */
Por lo tanto para conectar una función de respuesta a uno de estos eventos debemos usar algo como:
gtk_signal_connect( GTK_OBJECT(button), "button_press_event",
GTK_SIGNAL_FUNC(button_press_callback),
NULL);
Por supuesto se asume que button es un widget GtkButton. Cada vez que el puntero del ratón se
encuentre sobre el botón y éste sea presionado, se llamará a la función button_press_callback.
Esta función puede declararse así:
static gint button_press_event (GtkWidget *widget,
GdkEventButton *event,
gpointer data);
Conviene destacar que se puede declarar el segundo argumento como GdkEventButton porque
sabemos que este tipo de evento ocurrirá cuando se llame a la función.
El valor devuelto por esta función es usado para saber si el evento debe ser propagado? a un nivel más
profundo dentro del mecanismo de GTK para gestionar los eventos. Si devuelve TRUE el evento ya ha
sido gestionado y por tanto no tiene que ser tratado por el mecanismo de gestión. Por contra si devuelve
FALSE se continua con la gestión normal del evento. Para más detalles se recomienda leer la sección
donde se aclara como se produce el proceso de propagación.
Para más detalles acerca de los tipos de información GdkEvent consultar el apéndice Tipos de eventos
GDK.
La siguiente respuesta es un poco especial, el ``delete_event'' ocurre cuando el gestor de ventanas envía
este evento a la aplicación. Aquí podemos decidir que hacemos con estos eventos. Los podemos
ignorar, dar algún tipo de respuesta, o simplemente terminar la aplicación.
El valor devuelto en esta respuesta le permite a GTK saber que tiene que hacer. Si devolvemos TRUE,
estamos diciendo que no queremos que se emita la señal ``destroy'' y por lo tanto queremos que nuestra
aplicación siga ejecutándose. Si devolvemos FALSE, decimos que se emita ``destroy'', lo que hará que
se ejecute nuestro manejador de señal de ``destroy''.
gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("delete event occured\n");
return (TRUE);
}
Con el siguiente ejemplo presentamos otra función de respuesta que hace que el programa salga
llamando a gtk_main_quit(). Con esta función le decimos a GTK que salga de la rutina gtk_main()
cuando vuelva a estar en ella.
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
Como el lector probablemente ya sabe toda aplicación debe tener una función main(), y una aplicación
GTK no va a ser menos. Todas las aplicaciones GTK también tienen una función de este tipo.
int main (int argc, char *argv[])
Las líneas siguientes declaran un puntero a una estructura del tipo GtkWidget, que se utilizarán más
adelante para crear una ventana y un botón.
GtkWidget *window;
GtkWidget *button;
Aquí tenemos otra vez a gtk_init. Como antes arranca el conjunto de herramientas y filtra las opciones
introducidas en la línea de órdenes. Cualquier argumento que sea reconocido será borrado de la lista de
argumentos, de modo que la aplicación recibirá el resto.
gtk_init (&argc, &argv);
Ahora vamos a crear una ventana. Simplemente reservamos memoria para la estructura GtkWindow
*window, con lo que ya tenemos una nueva ventana, ventana que no se mostrará hasta que llamemos a
gtk_widget_show (window) hacia el final del programa.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
Aquí tenemos un ejemplo de como conectar un manejador de señal a un objeto, en este caso, la
ventana. La señal a cazar será ``destroy''. Esta señal se emite cuando utilizamos el administrador de
ventanas para matar la ventana (y devolvemos TRUE en el manejador ``delete_event''), o cuando
usamos llamamos a gtk_widget_destroy() pasándole el widget que representa la ventana como
argumento. Así conseguimos manejar los dos casos con una simple llamada a la función destroy ()
(definida arriba) pasándole NULL como argumento y ella acabará con la aplicación por nosotros.
GTK_OBJECT y GTK_SIGNAL_FUNC son macros que realizan la comprobación y transformación
de tipos por nosotros. También aumentan la legibilidad del código.
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
La siguiente función establece un atributo a un objeto contenedor (discutidos luego). En este caso le
pone a la ventana un área negra de 10 pixels de ancho donde no habrá widgets. Hay funciones similares
que serán tratadas con más detalle en la sección: Estableciendo los atributos de los <em/widgets/
De nuevo, GTK_CONTAINER es una macro que se encarga de la conversión entre tipos
gtk_container_border_width (GTK_CONTAINER (window), 10);
La siguiente llamada crea un nuevo botón. Reserva espacio en la memoria para una nueva estructura
del tipo GtkWidget, la inicializa y hace que el puntero del botón apunte a él. Su etiqueta será: "Hello
World".
button = gtk_button_new_with_label ("Hello World");
Ahora hacemos que el botón sea útil, para ello lo enlazamos con el manejador de señales para que se
emita la señal ``clicked'', se llame a nuestra función hello(). Los datos adicionales serán ignorados, por
lo que simplemente le pasaremos NULL a la función respuesta. Obviamente se emitirá la señal
``clicked'' cuando cliquemos en el botón con el ratón.
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
Ahora vamos a usar el botón para terminar nuestro programa. Así aclararemos cómo es posible que la
señal "destroy" sea emitida tanto por el gestor de ventanas como por nuestro programa. Cuando el
botón es pulsado, al igual que arriba, se llama a la primera función respuesta hello() y después se
llamará a esta función. Las funciones respuesta serán ejecutadas en el orden en que sean conectadas.
Como la función gtk_widget_destroy() sólo acepta un GtkWidget como argumento, utilizaremos
gtk_signal_connect_object() en lugar de gtk_signal_connect().
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
La siguiente llamada sirve para empaquetar (más detalles luego). Se usa para decirle a GTK que el
botón debe estar en la ventana dónde será mostrado. Conviene destacar que un contenedor GTK sólo
puede contener un widget. Existen otros widgets (descritos después) que sirven para contener y
establecer la disposición de varios widgets de diferentes formas.
gtk_container_add (GTK_CONTAINER (window), button);
Ahora ya tenemos todo bien organizado. Como todos los controladores de las señales ya están en su
sitio, y el botón está situado en la ventana donde queremos que esté, sólo nos queda pedirle a GTK que
muestre todos los widgets en pantalla. El widget window será el último en mostrarse queremos que
aparezca todo de golpe, en vez de ver aparecer la ventana, y después ver aparecer el botón. De todas
formas con un ejemplo tan simple nunca se notaría cual es el orden de aparición.
gtk_widget_show (button);
gtk_widget_show (window);
Llamamos a gtk_main() que espera hasta que el servidor X le comunique que se ha producido algún
evento para emitir las señales apropiadas.
gtk_main ();
Por último el `return' final que devuelve el control cuando gtk_quit() sea invocada.
return 0;
Cuando pulsemos el botón del ratón el widget emite la señal correspondiente ``clicked''. Para que
podamos usar la información el programa activa el gestor de eventos que al recibir la señal llama a la
función que hemos elegido. En nuestro ejemplo cuando pulsamos el botón se llama a la función hello()
con NULL como argumento y además se invoca al siguiente manipulador de señal. Así conseguimos
que se llame a la función gtk_widget_destroy() con el widget asociado a la ventana como argumento, lo
que destruye al widget. Esto hace que la ventana emita la señal ``destroy'', que es cazada, y que llama a
nuestra función respuesta destroy(), que simplemente sale de GTK.
Otra posibilidad es usar el gestor de ventanas para acabar con la aplicación. Esto emitirá
``delete_event'' que hará que se llame a nuestra función manejadora correspondiente. Si en la función
manejadora ``delete_event'' devolvemos TRUE la ventana se quedará como si nada hubiese ocurrido,
pero si devolvemos FALSE GTK emitirá la señal ``destroy'' que, por supuesto, llamará a la función
respuesta ``destroy'', que saldrá de GTK.
Conviene destacar que las señales de GTK no son iguales que las de los sistemas UNIX, aunque la
terminología es la misma.
3. Avanzando
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.
#include <gtk/gtk.h>
/* 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 ();
}
/* 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);
gtk_widget_show(button);
gtk_widget_show(box1);
gtk_widget_show (window);
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
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).
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.
Estudiemos el código usado para crear las imágenes anteriores. Con los comentarios no debería de
haber ningún problema para entenderlo.
#include <stdio.h>
#include "gtk/gtk.h"
void
delete_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./
return box;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
GtkWidget *box2;
GtkWidget *separator;
GtkWidget *label;
GtkWidget *quitbox;
int which;
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);
}
/* Creamos la ventana */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* mostramos la etiqueta.*/
gtk_widget_show (label);
/* 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);
case 3:
/* 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.*/
/* El botón de salida. */
button = gtk_button_new_with_label ("Quit");
gtk_widget_show (box1);
/* Si mostramos la ventana lo último todo aparece de golpe. */
gtk_widget_show (window);
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.
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 );
y
void gtk_table_set_col_spacing ( GtkTable *table,
gint column,
gint spacing );
y
void gtk_table_set_col_spacings( GtkTable *table,
gint spacing );
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);
}
gtk_widget_show (button);
gtk_widget_show (button);
gtk_widget_show (table);
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* final del ejemplo */
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.
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
#include <gtk/gtk.h>
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);
}
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
gtk_widget_show(box1);
gtk_widget_show(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
} else {
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);
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 );
#include <gtk/gtk.h>
#include <glib.h>
gtk_init(&argc,&argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_main();
return(0);
}
/* final del ejemplo */
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.
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
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);
...
¿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");
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
guía.
Todas las funcioenes precedentes se encuentran definidas en: <gtk/gtkscale.h>.
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)
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>
/* Funciones varias */
opt = gtk_option_menu_new();
menu = gtk_menu_new();
opt = gtk_option_menu_new();
menu = gtk_menu_new();
gtk_widget_show (window);
}
create_range_controls();
gtk_main();
return(0);
}
/* fin del ejemplo */
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.
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
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 );
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.
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.
/* comienzo del programa-ejemplo progressbar.c */
#include <gtk/gtk.h>
return TRUE;
}
table = gtk_table_new(3,2,TRUE);
gtk_container_add (GTK_CONTAINER (window), table);
label = gtk_label_new ("Progress Bar Example");
gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1);
gtk_widget_show(label);
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 ();
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;
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.
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 );
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
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. ",
" .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 );
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>
/* 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",
"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* ",
" 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 ",
" ",
" "};
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 );
/* 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 );
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 );
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
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>
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 );
/* mostramos todo */
gtk_widget_show( area );
gtk_widget_show( hrule );
gtk_widget_show( vrule );
gtk_widget_show( table );
gtk_widget_show( window );
gtk_main();
return 0;
}
/* final del ejemplo */
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 );
#include <gtk/gtk.h>
#include <glib.h>
GtkWidget *status_bar;
return;
}
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *button;
int context_id;
status_bar = gtk_statusbar_new();
gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0);
gtk_widget_show (status_bar);
gtk_main ();
return 0;
}
/* Fin del ejemplo */
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 );
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 );
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>
GtkWidget *window;
GtkWidget *vbox, *hbox;
GtkWidget *entry;
GtkWidget *button;
GtkWidget *check;
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);
gtk_widget_show(window);
gtk_main();
return(0);
}
/* fin del ejemplo */
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 );
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 );
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
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>
gtk_color_selection_get_color (colorsel,color);
gdk_color.red = (guint16)(color[0]*65535.0);
gdk_color.green = (guint16)(color[1]*65535.0);
gdk_color.blue = (guint16)(color[2]*65535.0);
gdk_window_clear (drawingarea->window);
}
handled = TRUE;
colorsel = GTK_COLOR_SELECTION_DIALOG(colorseldlg)->colorsel;
gtk_signal_connect(GTK_OBJECT(colorsel), "color_changed",
(GtkSignalFunc)color_changed_cb, (gpointer)colorsel);
gtk_widget_show(colorseldlg);
}
return handled;
}
/* Principal */
gtk_init (&argc,&argv);
gtk_widget_show (drawingarea);
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* final del ejemplo */
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 );
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>
gtk_widget_show(filew);
gtk_main ();
return 0;
}
/* fin del ejemplo */
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):
• 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 );
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 );
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);
#include <gtk/gtk.h>
page = gtk_notebook_current_page(notebook);
gtk_notebook_remove_page (notebook, page);
/* Hay que redibujar el widget --
Esto fuerza que el widget se autoredibuje */
gtk_widget_draw(GTK_WIDGET(notebook), NULL);
}
table = gtk_table_new(2,6,TRUE);
gtk_container_add (GTK_CONTAINER (window), table);
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.
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.
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>
/* 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);
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.
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);
#include <gtk/gtk.h>
GtkWidget *scrolled_window;
GtkWidget *list;
GtkWidget *list_item;
int i;
char buffer[16];
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;
}
void
realize_text (GtkWidget *text, gpointer data)
{
gtk_text_freeze (GTK_TEXT (text));
gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
"From: pathfinder@nasa.gov\n"
"To: mom@nasa.gov\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);
return table;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *vpaned;
GtkWidget *list;
GtkWidget *text;
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 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 );
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"
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
programa
GtkWidget* tooltips_button; // para activar/desactivar los tooltips
GtkWidget* text_button,
* icon_button,
* both_button; // botones circulares para el estilo de la barra
GtkWidget* 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)
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);
}
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;
Lo de arriba debería ser parecido en cualquier aplicación GTK. Sólo está la inicialización de GTK, la
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 );
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
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 ) );
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,
"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" );
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 );
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",
"................+...............",
"..............+++++.............",
"............+++++@@++...........",
"..........+++++@@@@@@++.........",
"........++++@@@@@@@@@@++........",
"......++++@@++++++++@@@++.......",
".....+++@@@+++++++++++@@@++.....",
"...+++@@@@+++@@@@@@++++@@@@+....",
"..+++@@@@+++@@@@@@@@+++@@@@@++..",
".++@@@@@@+++@@@@@@@@@@@@@@@@@@++",
".+#+@@@@@@++@@@@+++@@@@@@@@@@@@+",
".+##++@@@@+++@@@+++++@@@@@@@@$@.",
".+###++@@@@+++@@@+++@@@@@++$$$@.",
".+####+++@@@+++++++@@@@@+@$$$$@.",
".+#####+++@@@@+++@@@@++@$$$$$$+.",
".+######++++@@@@@@@++@$$$$$$$$+.",
".+#######+##+@@@@+++$$$$$$@@$$+.",
".+###+++##+##+@@++@$$$$$$++$$$+.",
".+###++++##+##+@@$$$$$$$@+@$$@+.",
".+###++++++#+++@$$@+@$$@++$$$@+.",
".+####+++++++#++$$@+@$$++$$$$+..",
".++####++++++#++$$@+@$++@$$$$+..",
".+#####+++++##++$$++@+++$$$$$+..",
".++####+++##+#++$$+++++@$$$$$+..",
".++####+++####++$$++++++@$$$@+..",
".+#####++#####++$$+++@++++@$@+..",
".+#####++#####++$$++@$$@+++$@@..",
".++####++#####++$$++$$$$$+@$@++.",
".++####++#####++$$++$$$$$$$$+++.",
".+++####+#####++$$++$$$$$$$@+++.",
"..+++#########+@$$+@$$$$$$+++...",
"...+++########+@$$$$$$$$@+++....",
".....+++######+@$$$$$$$+++......",
"......+++#####+@$$$$$@++........",
".......+++####+@$$$$+++.........",
".........++###+$$$@++...........",
"..........++##+$@+++............",
"...........+++++++..............",
".............++++..............."};
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:
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>
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *aspect_frame;
GtkWidget *drawing_area;
gtk_init (&argc, &argv);
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* fin del ejemplo */
11. El widget GtkCList
El 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.
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.
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.
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 );
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 );
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 );
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 );
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 );
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 );
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;
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 );
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 );
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...
#include <gtk/gtk.h>
#include <glib.h>
gtk_init(&argc, &argv);
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize(GTK_WIDGET(window), 300, 150);
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);
return 0;
}
/* 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;
}
return;
}
if (flag == 0)
{
/* Oculta los títulos y pone la bandera a 1 */
gtk_clist_column_titles_hide((GtkCList*) data);
flag++;
}
else
{
/* Muestra los títulos y pone la bandera a 0 */
gtk_clist_column_titles_show((GtkCList*) data);
flag--;
}
return;
}
return;
}
/* final del ejemplo */
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 );
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 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);
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 );
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 */
gtk_init(&argc, &argv);
/* crear un separador
*/
separator=gtk_hseparator_new();
gtk_container_add(GTK_CONTAINER(vbox), separator);
gtk_widget_show(separator);
list_item=dlist->data;
gtk_widget_reparent(list_item, gtklist);
dlist=dlist->next;
}
g_list_free(free_list);
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);
}
}
}
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.6 Funciones
guint gtk_list_item_get_type( 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 );
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.
13. El widget árbol
El 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.
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 );
Observe que debe añadir elementos a un GtkTree de uno en uno - no hay un equivalente a
gtk_list_*_items().
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.
GList *children;
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ñales
void 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);
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 macros
guint gtk_tree_get_type( 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 );
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 );
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 );
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 );
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);
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.
GtkWidget *subtree;
GtkWidget *pixmaps_box;
GtkWidget *plus_pix_widget, *minus_pix_widget;
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);
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 );
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 Macros
guint gtk_tree_item_get_type( 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 );
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 );
#include <gtk/gtk.h>
i = GTK_TREE_SELECTION(tree);
while (i){
gchar *name;
GtkLabel *label;
GtkWidget *item;
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 */
/* Añadirlos al menú */
gtk_menu_append( GTK_MENU(file_menu), open_item);
gtk_menu_append( GTK_MENU(file_menu), save_item);
gtk_menu_append( GTK_MENU(file_menu), quit_item);
/* 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 );
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);
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 );
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 );
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.
#include <gtk/gtk.h>
/* Muestra el widget */
gtk_widget_show(menu_items);
}
gtk_widget_show(root_menu);
gtk_main ();
return 0;
}
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;
}
/* Le dice al que llamó a la rutina que no hemos manejado el
* evento. */
return FALSE;
}
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ú.
#ifndef __MENUFACTORY_H__
#define __MENUFACTORY_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MENUFACTORY_H__ */
#include <gtk/gtk.h>
#include <strings.h>
#include "mfmain.h"
static void print_hello(GtkWidget *widget, gpointer data);
static void
print_hello(GtkWidget *widget, gpointer data)
{
printf("hello!\n");
}
factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
subfactory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
if (menubar)
*menubar = subfactory->widget;
}
Y aquí el mfmain.h
/* principio del ejemplo menu mfmain.h */
#ifndef __MFMAIN_H__
#define __MFMAIN_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MFMAIN_H__ */
Y mfmain.c
/* principio del ejemplo menu mfmain.c */
#include <gtk/gtk.h>
#include "mfmain.h"
#include "menufactory.h"
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);
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);
}
CC = gcc
PROF = -g
C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG
L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib
L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
PROGNAME = menufactory
$(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.out
distclean: clean
rm -f *~
Por ahora, sólo está este ejemplo. Ya llegará una explicación del mismo y más comentarios.
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.
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 );
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 );
Esta es una función de la clase padre del widget texto. Un valor de -1 en end_pos significa el final del
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.
Atajos de selección
• Ctrl-X Cortar al portapapeles
• Ctrl-C Copiar al portapapeles
• Ctrl-V Pegar desde el portapapeles
/* text.c */
#include <stdio.h>
#include <gtk/gtk.h>
void text_toggle_editable (GtkWidget *checkbutton,
GtkWidget *text)
{
gtk_text_set_editable(GTK_TEXT(text),
GTK_TOGGLE_BUTTON(checkbutton)->active);
}
FILE *infile;
while (1)
{
nchars = fread(buffer, 1, 1024, infile);
gtk_text_insert (GTK_TEXT (text), fixed_font, NULL,
NULL, buffer, nchars);
fclose (infile);
}
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);
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* fin del ejemplo */
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
is
this. 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
takes
you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert
after
you make an error) to choose the desired modification, it make take you
literally hours to make the right one - if you don't run out of memory
first. 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 is
helpful to compare the Before and After versions side-by-side or at least
back-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
right
out for most people, while back-to-back is more like back-to-1001, 1002,
..., 1010-back! Previews to the rescue!
Finally, a couple of misc uses. One can use previews even when not
working
with big images. For example, they are useful when rendering complicated
patterns. (Just check out the venerable Diffraction plug-in + many other
ones!) As another example, take a look at the colormap rotation plug-in
(work in progress). You can also use previews for little logos inside you
plug-ins and even for an image of yourself, The Author. Let's go
previews!
Don't use previews for graphs, drawing etc. GDK is much faster for that.
Use
previews only for rendered images!
Let's go previews!
You can stick a preview into just about anything. In a vbox, an hbox, a
table, 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.
(Of
course, if the flat look is what you want...) Tight frames provide the
necessary borders.
[Image][Image]
Previews in many ways are like any other widgets in GTK (whatever that
means) except they possess an additional feature: they need to be filled
with
some sort of an image! First, we will deal exclusively with the GTK
aspect
of previews and then we'll discuss how to fill them.
GtkWidget *preview!
Oh yeah, like I said, previews look good inside frames, so how about:
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
can
place it somewhere else in your interface. Of course, you can pass the
parent frame to this routine as a parameter. In many situations, however,
the contents of the preview are changed continually by your application.
In
this 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.
Sometimes
it is desirable to label you preview. For example, you may label the
preview
containing the original image as "Original" and the one containing the
modified image as "Less Original". It might occur to you to pack the
preview along with the appropriate label into a vbox. The unexpected
caveat
is that if the label is wider than the preview (which may happen for a
variety of reasons unforseeable to you, from the dynamic decision on the
size of the preview to the size of the font) the frame expands and no
longer
fits tightly over the preview. The same problem can probably arise in
other
situations as well.
[Image]
The solution is to place the preview and the label into a 2x1 table and
by
attaching them with the following parameters (this is one possible
variations
of 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);
[Image]
Misc
Filling In a Preview
[Image]
void
my_preview_rendering_function(GtkWidget *preview)
{
#define SIZE 100
#define HALF (SIZE/2)
Image Preview
typedef struct {
gint width;
gint height;
gint bbp;
guchar *rgb;
guchar *mask;
} ReducedImage;
enum {
SELECTION_ONLY,
SELECTION_IN_CONTEXT,
ENTIRE_IMAGE
};
/* How we can determine the width and the height of the area being */
/* reduced. */
width = x2-x1;
height = y2-y1;
/* 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];
void
my_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);;
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
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;
/* La recortamos. */
gtk_widget_set_usize (label, 110, 20);
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 */
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:
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 );
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.
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
átomo. Ambas, selecciones y objetivos, están identificados por átomos.
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;
};
#include <gtk/gtk.h>
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
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* fin del ejemplo */
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:
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 );
#include <gtk/gtk.h>
#include <time.h>
return TRUE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *selection_button;
gtk_widget_show (selection_button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* fin del ejemplo */
22. glib
glib 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_MINFLOAT
G_MAXFLOAT
G_MINDOUBLE
G_MAXDOUBLE
G_MINSHORT
G_MAXSHORT
G_MININT
G_MAXINT
G_MINLONG
G_MAXLONG
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;
float gfloat;
double gdouble;
long double gldouble;
void* gpointer;
gint8
guint8
gint16
guint16
gint32
guint32
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 );
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 );
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
El mismo que el anterior, pero añade "** WARNING **: ", y no sale del programa.
void g_message( 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!
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");
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");
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.
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.
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.
# Here is a list of all the possible states. Note that some do not apply
to
# 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 }
style "button"
{
# This shows all the possible states for a button. The only one that
# doesn't apply is the SELECTED state.
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.
# 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"
24. Escribiendo sus propios widgets
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
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.
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:
#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 __cplusplus
extern "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 __cplusplus
extern "C" {
#endif /* __cplusplus */
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *buttons[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
#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.
guint
tictactoe_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
};
return ttt_type;
}
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 void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
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,
guint nparams,
...);
GTK_TYPE_POINTER,
GTK_TYPE_OBJECT
} GtkFundamentalType;
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 void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint 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''.
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 void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
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;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *ttt;
gtk_widget_show (window);
gtk_main ();
return 0;
}
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 __cplusplus
extern "C" {
#endif /* __cplusplus */
struct _GtkDial
{
GtkWidget widget;
/* política de actualización
* (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* ángulo actual */
gfloat angle;
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
#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>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
/* Declaraciones de funciones */
/* datos locales */
guint
gtk_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,
};
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
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 void
gtk_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;
if (!adjustment)
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0,
0.0, 0.0);
static void
gtk_dial_destroy (GtkObject *object)
{
GtkDial *dial;
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.
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;
}
void
gtk_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;
}
void
gtk_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));
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
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 void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
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);
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 void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
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);
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 gint
gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkDial *dial;
GdkPoint points[3];
gdouble s,c;
gdouble theta;
gint xc, yc;
gint tick_length;
gint i;
if (event->count > 0)
return FALSE;
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/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,
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 gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
dial->button = event->button;
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
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;
}
return FALSE;
}
static gint
gtk_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 void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
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->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);
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 void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
new_value = dial->adjustment->value;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment),
"value_changed");
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
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 void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
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.
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.
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_MASK
GDK_MOD1_MASK
GDK_MOD2_MASK
GDK_MOD3_MASK
GDK_MOD4_MASK
GDK_MOD5_MASK
GDK_BUTTON1_MASK
GDK_BUTTON2_MASK
GDK_BUTTON3_MASK
GDK_BUTTON4_MASK
GDK_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_MASK
GDK_POINTER_MOTION_MASK
GDK_POINTER_MOTION_HINT_MASK
GDK_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
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
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);
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 gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 && pixmap != NULL)
draw_brush (widget, event->x, event->y);
return TRUE;
}
static gint
motion_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;
}
return TRUE;
}
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
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;
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;
}
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
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_gc
widget->style->black_gc
widget->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 void
draw_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);
}
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/
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
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_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_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.)
void
create_input_dialog ()
{
static GtkWidget *inputd = NULL;
if (!inputd)
{
inputd = gtk_input_dialog_new();
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.
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 gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
print_button_press (event->deviceid);
return TRUE;
}
static gint
motion_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;
}
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 void
draw_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;
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);
}
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 void
print_button_press (guint32 deviceid)
{
GList *tmp_list;
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()
27. Contribuyendo
Este 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, gale@gtk.org.
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éditos
Quiero agradecer a las siguientes personas por sus contribuciones a este texto.
• Bawer Dagdeviren, chamele0n@geocities.com por el tutorial sobre los menús.
• Raph Levien, raph@acm.org 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, petm@xcf.berkeley.edu por el más simple de los programas GTK... y por
la posibilidad de hacerlo :)
• Werner Koch werner.koch@guug.de por convertir el texto original a SGML, y por la
jerarquia de clases de widgets.
• Mark Crichton crichton@expert.cc.purdue.edu por el código del menú factory, y el
tutorial sobre el empaquetamiento de las tablas.
• Owen Taylor owt1@cornell.edu 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 mvboom42@calvin.edu por su fantástico trabajo sobre los widgets
Notebook, Progress Bar, Dialog, y selección de ficheros. ¡Muchas gracias Mark! Has sido de
una gran ayuda.
• Tim Janik timj@psynet.net por su gran trabajo en el widget List. Gracias Tim :)
• Rajat Datta rajat@ix.netcom.com por el excelente trabajo con el tutorial Pixmap.
• Michael K. Johnson johnsonm@redhat.com por la información y el código de los menús
("popup").
• David Huggins-Daines bn711@freenet.carleton.ca por las secciones sobre los
widgets Range y Tree.
• Stefan Mars mars@lysator.liu.se por la sección GtkCList
Y a todos los que han comentado y ayudado a refinar este documento.
Gracias.
Appendix
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. */
};
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;
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;
};
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;
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 {
char b[20];
short s[10];
long l[5];
} data;
};
struct _GdkEventOther
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
GdkXEvent *xevent;
};
31.1 Tictactoe
tictactoe.h
/* principio del ejemplo tictactoe tictactoe.h */
#include <gdk/gdk.h>
#include <gtk/gtkvbox.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *buttons[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
tictactoe.c
/* principio del ejemplo tictactoe tictactoe.c */
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
guint
tictactoe_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
};
return ttt_type;
}
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
class->tictactoe = NULL;
}
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint 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);
}
}
static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
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;
}
ttt_test.c
/* principio del ejemplo tictactoe ttt_test.c */
#include <gtk/gtk.h>
#include "tictactoe.h"
void
win (GtkWidget *widget, gpointer data)
{
g_print ("Yay!\n");
tictactoe_clear (TICTACTOE (widget));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *ttt;
gtk_widget_show (window);
gtk_main ();
return 0;
}
31.2 GtkDial
gtkdial.h
/* principio del ejmplo gtkdial gtkdial.h */
#include <gdk/gdk.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkwidget.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
struct _GtkDial
{
GtkWidget widget;
/* política de actualización
* (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* ángulo actual */
gfloat angle;
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
#endif /* __GTK_DIAL_H__ */
/* fin del ejemplo */
gtkdial.c
/* principio del ejemplo gtkdial gtkdial.c */
#include "gtkdial.h"
/* declaraciones de funciones */
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);
/* datos locales */
guint
gtk_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,
};
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
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 void
gtk_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;
if (!adjustment)
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0,
0.0, 0.0);
static void
gtk_dial_destroy (GtkObject *object)
{
GtkDial *dial;
g_return_if_fail (object != NULL);
g_return_if_fail (GTK_IS_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;
}
void
gtk_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;
}
void
gtk_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));
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
gtk_dial_update (dial);
}
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
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);
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
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 gint
gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkDial *dial;
GdkPoint points[3];
gdouble s,c;
gdouble theta;
gint xc, yc;
gint tick_length;
gint i;
if (event->count > 0)
return FALSE;
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 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 gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
if (dial->button != 0)
{
x = event->x;
y = event->y;
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;
}
return FALSE;
}
static gint
gtk_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 void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
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);
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
new_value = dial->adjustment->value;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment),
"value_changed");
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
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 void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
/* fin del ejemplo */
31.3 Scribble
/* principio del ejemplo scribble-simple scribble-simple.c */
#include <gtk/gtk.h>
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;
}
return FALSE;
}
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 gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 && pixmap != NULL)
draw_brush (widget, event->x, event->y);
return TRUE;
}
static gint
motion_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;
}
return TRUE;
}
void
quit ()
{
gtk_exit (0);
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *drawing_area;
GtkWidget *vbox;
GtkWidget *button;
gtk_widget_show (drawing_area);
/* Señales evento */
gtk_widget_show (window);
gtk_main ();
return 0;
}
/* fin del ejemplo */