gtk2/docs/gtk_tut_12.es.sgml
2000-04-23 09:17:36 +00:00

17639 lines
590 KiB
Plaintext
Executable File

<!doctype linuxdoc system>
<!--
Traducción realizada por:
Joaquín Cuenca Abela (e98cuenc@criens.u-psud.fr)
Eduardo Anglada Varela (eduardo.anglada@adi.uam.es)
Versión beta 2 de la traducción del GTK+
Tutorial 1.2. Cualquier sugerencia será bienvenida.
Los cambios más significativos de esta versión son la traducción
de las variables de los programas y XXX.
Si quiere obtener este documento puede encontrarlo en Linux Landia:
http://www.croftj.net/~barreiro/spanish/gnome-es/
-->
<article>
<title>GTK Tutorial v1.2
<author>Tony Gale <tt><htmlurl url="mailto:gale@gtk.org"
name="&lt;gale@gtk.org&gt;"></tt>,
Ian Main <tt><htmlurl url="mailto:imain@gtk.org"
name="&lt;imain@gtk.org&gt;"></tt>
<date>21 de Febrero de 1999
<abstract>
Este documento es un tutorial sobre como utilizar GTK (el GIMP
Toolkit) en C
</abstract>
<toc>
<!-- ***************************************************************** -->
<sect>Introducción
<!-- ***************************************************************** -->
<p>
GTK (GIMP Toolkit) es una biblioteca para crear interfaces gráficas
de usuario. Su licencia es la LGPL, así que mediante GTK podrá
desarrollar programas con licencias abiertas, gratuitas, libres, y
hasta licencias comerciales no libres sin mayores problemas.
Se llama el GIMP toolkit porque fue escrito para el desarrollo del
General Image Manipulation Program (GIMP), pero ahora GTK se utiliza
en un gran número de proyectos de programación, incluyendo el
proyecto GNU Network Object Model Environment (GNOME). GTK está
construido encima de GDK (GIMP Drawing Kit) que básicamente es un
recubrimiento de las funciones de bajo nivel que deben haber para
acceder al sistema de ventanas sobre el que se programe (Xlib en el
caso de X windows). Los principales autores de GTK son:
<itemize>
<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
name="petm@xcf.berkeley.edu"></tt>
<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu"
name="spencer@xcf.berkeley.edu"></tt>
<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu"
name="jmacd@xcf.berkeley.edu"></tt>
</itemize>
GTK es esencialmente una interfaz para la programación de
aplicaciones orientadas al objeto (API). Aunque está completamente
escrito en C, esta implementado haciendo uso de la idea de clases y de
funciones respuesta o de <em/callback/ (punteros o funciones).
Tenemos un tercer componente llamado glib, que contiene unas cuantas
funciones para reemplazar algunas llamadas estándar, así como
funciones adicionales para manejar listas enlazadas, etc... Se
reemplazan algunas funciones para aumentar la portabilidad de GTK, ya
que algunas de las funciones implementadas no están disponibles o
no son estándar en otros unixs, como por ejemplo
g_strerror(). Algunas otras contienen mejoras a la versión de libc,
como g_malloc que mejora las posibilidades de encontrar errores.
Este tutorial describe la interfaz C de GTK. Hay recubrimientos GTK
para muchos otros lenguajes, incluyendo C++, Guile, Perl, Python, TOM,
Ada95, Objective C, Free Pascal, y Eiffel. Si va a utilizar el
recubrimiento para alguno de estos lenguajes, mire primero su
documentación. En algunos casos la documentación puede describir
algún convenio importante (que debería conocer de antemano) y
después puede volver a este tutorial. También hay algún API
multiplataforma (como wxWindows y V) que utilizan GTK como una de sus
plataformas destino; de nuevo, consulte primero la documentación
que viene con estos paquetes.
Si está desarrollando su aplicación GTK en C++, hay algunas
cosas que debería saber. Hay un recubriento a GTK para C++ llamado
GTK--, que proporciona una interfaz C++ a GTK; probablemente
debería empezar mirando ahí. Si no le gusta esa aproximación
al problema, por los motivos que sean, tiene dos
alternativas. Primero, puede ceñirse al subconjunto C de C++ cuando
realice alguna llamada a GTK a través de su interfaz en C. Segundo,
puede utilizar GTK y C++ al mismo tiempo declarando todas las
funciones respuesta como funciones estáticas en clases C++, y de
nuevo, llamar a GTK utilizando su interfaz C. Si elige esta última
forma de actuar, puede incluir como dato de la función respuesta un
puntero al objeto a manipular (el también llamado valor
«this»). La elección de una u otra opción es cuestión de
gustos personales, ya que de las tres maneras conseguirá utilizar
GTK en C++. Ninguna de estas aproximaciones requiere el uso de un
preprocesador especializado, por lo que sin importar la opción que
escoja podrá utilizar C++ estándar en C++.
Este tutorial es un intento de documentar GTK tanto como sea posible,
pero no está completo. Este tutorial asume un buen conocimiento de
C y de como crear programas bajo este lenguaje. Se verá beneficiado
si tiene un conocimiento previo de la programación en X, pero no
debería ser necesario. Si está aprendiendo GTK y es el primer
conjunto de <em/widgets/ que utiliza, por favor envíenos sus
comentarios sobre este tutorial y los problemas que ha
encontrado.
Este documento es un `trabajo pendiente de finalizar'. Para encontrar
actualizaciones mire en http://www.gtk.org/ <htmlurl
url="http://www.gtk.org/" name="http://www.gtk.org/">.
Me gustaría escuchar cualquier problema que le surja mientras
aprende GTK siguiendo este documento, y apreciaré cualquier
información sobre como mejorarlo. Por favor, vea la sección <ref
id="sec_Contributing" name="Contribuyendo"> para encontrar más
información.
<!-- ***************************************************************** -->
<sect>Comenzando
<!-- ***************************************************************** -->
<p>
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 <htmlurl
url="http://www.gtk.org/" name="http://www.gtk.org/"> hay más
información sobre GTK. Para configurar GTK hay que usar GNU
autoconf. Una vez descomprimido se pueden obtener las opciones usando
<tt>./configure --help</tt>.
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 <em/pixels/ que sólo se puede destruir desde el shell.
<tscreen><verb>
/* principio del ejemplo base base.c */
#include <gtk/gtk.h>
int main (int argc, char *argv[])
{
GtkWidget *ventana;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* final del ejemplo */
</verb></tscreen>
Puede compilar el programa anterior con gcc tecleando:
<tscreen><verb>
gcc base.c -o base `gtk-config --cflags --libs`
</verb></tscreen>
El significado de la extraña opción de compilación se explica
más adelante.
Todo programa que use GTK debe llamar a <tt>gtk/gtk.h</tt> donde se
declaran todas las variables, funciones, estructuras, etc. que serán
usadas en el programa.
La siguiente línea:
<tscreen><verb>
gtk_init (&amp;argc, &amp;argv);
</verb></tscreen>
Llama a la función gtk_init (gint *argc, gchar *** argv) responsable
de `arrancar' la biblioteca y de establecer algunos parámetros (como son
los colores y los visuales por defecto), llama a gdk_init (gint *argc,
gchar *** argv) que inicializa la biblioteca para que pueda
utilizarse, establece los controladores de las señales y comprueba los
argumentos pasados a la aplicación desde la línea de comandos,
buscando alguno de los siguientes:
<itemize>
<item> <tt/--gtk-module/
<item> <tt/--g-fatal-warnings/
<item> <tt/--gtk-debug/
<item> <tt/--gtk-no-debug/
<item> <tt/--gdk-debug/
<item> <tt/--gdk-no-debug/
<item> <tt/--display/
<item> <tt/--sync/
<item> <tt/--no-xshm/
<item> <tt/--name/
<item> <tt/--class/
</itemize>
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.
<tscreen><verb>
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (ventana);
</verb></tscreen>
El argumento GTK_WINDOW_TOPLEVEL especifica que queremos que el gestor
de ventanas decore y sitúe la ventana. En lugar de crear una ventana
de tamaño 0 x 0 toda ventana sin hijos por defecto es de 200 x 200, con
lo que se consigue que pueda ser manipulada.
La función gtk_widget_show() le comunica a GTK que hemos acabado de
especificar los atributos del <em/widget/, y que por tanto puede
mostrarlo.
La última línea comienza el proceso del bucle principal de GTK.
<tscreen><verb>
gtk_main ();
</verb></tscreen>
Otra llamada que siempre está presente en cualquier aplicación es
gtk_main(). Cuando el control llega a ella, GTK se queda dormido
esperando a que suceda algún tipo de evento de las X (como puede ser
pulsar un botón), que pase el tiempo necesario para que el usuario
haga algo, o que se produzcan notificaciones de IO de archivos. En
nuestro caso concreto todos los eventos serán ignorados.
<!-- ----------------------------------------------------------------- -->
<sect1>Programa «Hola Mundo» en GTK
<p>
El siguiente ejemplo es un programa con un <em/widget/ (un
botón). Simplemente es la versión de GTK del clásico «hola mundo».
<tscreen><verb>
/* comienzo del ejemplo holamundo */
#include <gtk/gtk.h>
/* Ésta es una función respuesta (callback). Sus argumentos
son ignorados por en este ejemplo */
void hello (GtkWidget *widget, gpointer data)
{
g_print ("Hola mundo\n");
}
gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
/* si se devuelve FALSE al administrador de llamadas
* "delete_event", GTK emitirá la señal de destrucción
* "destroy". Esto es útil para diálogos emergentes del
* tipo: ¿Seguro que desea salir?
g_print ("Ha ocurrido un evento delete\n");
/* Cambiando TRUE por FALSE la ventana se destruirá con
* "delete_event"*/
return (TRUE);
}
/* otra respuesta */
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
/* GtkWidget es el tipo de almacenamiento usado para los
* widgets */
GtkWidget *ventana;
GtkWidget *boton;
/* En cualquier aplicación hay que realizar la siguiente
* llamada. Los argumentos son tomados de la línea de comandos
* y devueltos a la aplicación. */
gtk_init (&amp;argc, &amp;argv);
/* creamos una ventana nueva */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Cuando la ventana recibe la señal "delete_event" (emitida
* por el gestor de ventanas, normalmente mediante la opción
* 'close', o en la barra del título) hacemos que llame a la
* función delete_event() tal y como ya hemos visto. Los datos
* pasados a la función de respuesta son NULL, y serán ignorados. */
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* Aquí conectamos el evento "destroy" con el administrador de
* señales. El evento se produce cuando llamamos a
* gtk_widget_destroy() desde la ventana o si devolvemos 'FALSE'
* en la respuesta "delete_event". */
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* establecemos el ancho del borde de la ventana. */
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* creamos un botón nuevo con la etiqueta "Hola mundo" */
boton = gtk_button_new_with_label ("Hola mundo");
/* Cuando el botón recibe la señal "clicked" llama a la
* función hello() pasándole NULL como argumento. (La
* función ya ha sido definida arriba). */
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* Esto hará que la ventana sea destruida llamando a
* gtk_widget_destroy(ventana) cuando se produzca "clicked". Una
* vez mas la señal de destrucción puede provenir del gestor
* de ventanas o de aquí. */
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (ventana));
/* Ahora empaquetamos el botón en la ventana (usamos un gtk
* container ). */
gtk_container_add (GTK_CONTAINER (ventana), boton);
/* El último paso es representar el nuevo widget... */
gtk_widget_show (boton);
/* y la ventana */
gtk_widget_show (ventana);
/* Todas las aplicaciones basadas en GTK deben tener una llamada
* gtk_main() ya que el control termina justo aquí y debe
* esperar a que suceda algún evento */
gtk_main ();
return 0;
}
/* final del ejemplo*/
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Compilando Hello World
<p>
Para compilar el ejemplo hay que usar:
<tscreen><verb>
gcc -Wall -g helloworld.c -o hello_world `gtk-config --cflags` \
`gtk-config --libs`
</verb></tscreen>
Usamos el programa <tt>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. <tt>gtk-config
--cflags</tt> dará una lista con los directorios donde el
compilador debe buscar ficheros «include». A su vez <tt>gtk-config
--libs</tt> nos permite saber las bibliotecas que el compilador
intentará enlazar y dónde buscarlas.
Hay que destacar que las comillas simples en la orden de
compilación son absolutamente necesarias.
Las bibliotecas que se enlazan normalmente son:
<itemize>
<item>La biblioteca GTK (-lgtk), la biblioteca de <em/widgets/ que se
encuentra encima de GDK.
<item>La biblioteca GDK (-lgdk), el wrapper de Xlib.
<item>La biblioteca 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 <ref
id="sec_glib" name="glib"> para más detalles.
<item>La biblioteca Xlib (-lX11) que es usada por GDK.
<item> La biblioteca Xext (-lXext) contiene código para
<em/pixmaps/ de memoria compartida y otras extensiones.
<item>La biblioteca matemática (-lm). Es usada por GTK para
diferentes cosas.
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1>Teoría de señales y respuestas
<p>
Antes de profundizar en <tt/holamundo/ vamos a discutir las
señales y las respuestas. GTK es un toolkit (conjunto de
herramientas) gestionadas mediante eventos. Esto quiere decir que GTK
«duerme» en gtk_main hasta que se recibe un evento, momento en el
cual se transfiere el control a la función adecuada.
El control se transfiere mediante «señales». (Conviene destacar
que las señales de GTK no son iguales que las de los sistemas
UNIX, aunque la terminología es la misma.) Cuando sucede un evento,
como por ejemplo la pulsación de un botón, se «emitirá» la
señal apropiada por el <em/widget/ pulsado. Así es como GTK
proporciona la mayor parte de su utilidad. Hay un conjunto de
señales que todos los <em/widgets/ heredan, como por ejemplo
«destroy» y hay señales que son específicas de cada
<em/widget/, como por ejemplo la señal «toggled» de un botón
de selección (botón <em/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:
<tscreen><verb>
gint gtk_signal_connect( GtkObject *objeto,
gchar *nombre,
GtkSignalFunc func,
gpointer datos_func );
</verb></tscreen>
Donde el primer argumento es el <em/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:
<tscreen><verb>
void callback_func( GtkWidget *widget,
gpointer datos_respuesta );
</verb></tscreen>
Donde el primer argumento será un puntero al <em/widget/ que emitió la
señal, y el segundo un puntero a los datos pasados a la función tal y
como hemos visto en el último argumento a gtk_signal_connect().
Conviene destacar que la declaración de la función de respuesta debe
servir sólo como guía general, ya que algunas señales específicas
pueden generar diferentes parámetros de llamada. Por ejemplo, la señal
de GtkCList «select_row» proporciona los parámetros fila y columna.
Otra llamada usada en el ejemplo del hola mundo es:
<tscreen><verb>
gint gtk_signal_connect_object( GtkObject *objeto,
gchar *nombre,
GtkSignalFunc func,
GtkObject *slot_object );
</verb></tscreen>
gtk_signal_connect_object() es idéntica a gtk_signal_connect() excepto
en que la función de llamada sólo usa un argumento, un puntero a un
objeto GTK. Por tanto cuando usemos esta función para conectar
señales, la función de respuesta debe ser de la forma:
<tscreen><verb>
void callback_func( GtkObject *object );
</verb></tscreen>
Donde, por regla general, el objeto es un <em/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 <em/widget/
o un objeto como un argumento, tal y como se vio en el ejemplo hola
mundo.
¿Para qué sirve tener dos funciones para conectar señales? Simplemente
para permitir que las funciones de respuesta puedan tener un número
diferente de argumentos. Muchas funciones de GTK sólo aceptan un
puntero a un GtkWidget como argumento, por lo que tendrá que usar
gtk_signal_connect_object() con estas funciones, mientras que
probablemente tenga que suministrarle información adicional a sus
funciones.
<!-- XXX Completamente revisado hasta aquí -------------------------- -->
<sect1>Eventos
<p>
Además del mecanismo de señales descrito arriba existe otro conjunto
de <em>eventos</em> que reflejan como las X manejan los eventos. Se
pueden asignar funciones de respuesta a estos eventos. Los eventos
son:
<itemize>
<item> event
<item> button_press_event
<item> button_release_event
<item> motion_notify_event
<item> delete_event
<item> destroy_event
<item> expose_event
<item> key_press_event
<item> key_release_event
<item> enter_notify_event
<item> leave_notify_event
<item> configure_event
<item> focus_in_event
<item> focus_out_event
<item> map_event
<item> unmap_event
<item> property_notify_event
<item> selection_clear_event
<item> selection_request_event
<item> selection_notify_event
<item> proximity_in_event
<item> proximity_out_event
<item> drag_begin_event
<item> drag_request_event
<item> drag_end_event
<item> drop_enter_event
<item> drop_leave_event
<item> drop_data_available_event
<item> other_event
</itemize>
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 <tt/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:
<tscreen><verb>
void callback_func( GtkWidget *widget,
GdkEvent *event,
gpointer callback_data );
</verb></tscreen>
GdkEvent es una estructura <tt/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
<tt/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:
<tscreen><verb>
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 */
</verb></tscreen>
Por lo tanto para conectar una función de respuesta a uno de estos
eventos debemos usar algo como:
<tscreen><verb>
gtk_signal_connect( GTK_OBJECT(boton), "button_press_event",
GTK_SIGNAL_FUNC(button_press_callback),
NULL);
</verb></tscreen>
Por supuesto se asume que <tt/boton/ es un <em/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
<tt/button_press_callback/. Esta función puede declararse así:
<tscreen><verb>
static gint button_press_event (GtkWidget *widget,
GdkEventButton *event,
gpointer data);
</verb></tscreen>
Conviene destacar que se puede declarar el segundo argumento como
<tt/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 <ref id="sec_GDK_Event_Types" name="Tipos de
eventos GDK">.
<!-- ----------------------------------------------------------------- -->
<sect1>Aclaración de Hello World
<p>
Ahora que conocemos la teoría vamos a aclarar las ideas estudiando
en detalle el programa <tt/helloworld/.
Ésta es la función respuesta a la que se llamará cuando se
pulse el botón. En el ejemplo ignoramos tanto el <em/widget/ como
la información, pero no es difícil usarlos. El siguiente ejemplo
usará la información que recibe como argumento para decirnos que
botón fue presionado.
<tscreen><verb>
void hello (GtkWidget *widget, gpointer data)
{
g_print ("Hello World\n");
}
</verb></tscreen>
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».
<tscreen><verb>
gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("delete event occured\n");
return (TRUE);
}
</verb></tscreen>
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.
<tscreen><verb>
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
</verb></tscreen>
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.
<tscreen><verb>
int main (int argc, char *argv[])
</verb></tscreen>
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.
<tscreen><verb>
GtkWidget *ventana;
GtkWidget *boton;
</verb></tscreen>
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.
<tscreen><verb>
gtk_init (&amp;argc, &amp;argv);
</verb></tscreen>
Ahora vamos a crear una ventana. Simplemente reservamos memoria para
la estructura GtkWindow *ventana, con lo que ya tenemos una nueva
ventana, ventana que no se mostrará hasta que llamemos a
gtk_widget_show (ventana) hacia el final del programa.
<tscreen><verb>
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
</verb></tscreen>
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 <em/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.
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
</verb></tscreen>
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 <em/pixels/ de ancho donde no habrán <em/widgets/. Hay
funciones similares que serán tratadas con más detalle en la sección
<ref id="sec_setting_widget_attributes" name="Estableciendo los
atributos de los <em/widgets/">
De nuevo, GTK_CONTAINER es una macro que se encarga de la conversión
entre tipos
<tscreen><verb>
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
</verb></tscreen>
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 <tt/boton/ apunte a esta estructura. Su etiqueta
será: "Hola mundo".
<tscreen><verb>
boton = gtk_button_new_with_label ("Hola mundo");
</verb></tscreen>
Ahora hacemos que el botón sea útil, para ello enlazamos el botón con
el manejador de señales para que cuando emita la señal «clicked», se
llame a nuestra función hola(). Los datos adicionales serán
ignorados, por lo que simplemente le pasaremos NULL a la función
respuesta. Obviamente se emitirá la señal «clicked» cuando pulsemos
en el botón con el ratón.
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (hola), NULL);
</verb></tscreen>
XXX
Ahora vamos a usar el botón para terminar nuestro programa. Así
aclararemos cómo es posible que la señal «destroy» sea emitida tanto
por el gestor de ventanas como por nuestro programa. Cuando el botón
es pulsado, al igual que arriba, se llama a la primera función
respuesta hello() y después se llamará a esta función. Las funciones
respuesta serán ejecutadas en el orden en que sean conectadas. Como la
función gtk_widget_destroy() sólo acepta un GtkWidget como argumento,
utilizaremos gtk_signal_connect_object() en lugar de
gtk_signal_connect().
<tscreen><verb>
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (ventana));
</verb></tscreen>
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 <em/widget/. Existen otros <em/widgets/ (descritos
después) que sirven para contener y establecer la disposición de
varios <em/widgets/ de diferentes formas.
<tscreen><verb>
gtk_container_add (GTK_CONTAINER (ventana), boton);
</verb></tscreen>
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 <em/widgets/ en pantalla. El <em/widget/ ventana será
el último en mostrarse queremos que aparezca todo de golpe, en vez de
ver aparecer la ventana, y después ver aparecer el botón. De todas
formas con un ejemplo tan simple nunca se notaría cual es el orden de
aparición.
<tscreen><verb>
gtk_widget_show (boton);
gtk_widget_show (ventana);
</verb></tscreen>
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.
<tscreen><verb>
gtk_main ();
</verb></tscreen>
Por último el `return' final que devuelve el control cuando gtk_quit()
sea invocada.
<tscreen><verb>
return 0;
</verb></tscreen>
Cuando pulsemos el botón del ratón el <em/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 <em/widget/ asociado a
la ventana como argumento, lo que destruye al <em/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.
<!-- ***************************************************************** -->
<sect>Avanzando
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1>Tipos de datos
<p>
Existen algunos detalles de los ejemplos anteriores que hay que aclarar.
Los tipos gint, gchar, etc. que puede ver por ahí son typedefs a int y
a char respectivamente. Sirven para que no haya que tener en cuenta el
tamaño de cada uno de ellos a la hora de hacer cálculos.
Un buen ejemplo es <tt/gint32/ que es un entero de 32 bits independientemente
de la plataforma, bien sea un Alpha de 64 bits o un i386 de 32. Todas las
definiciones son muy intuitivas y se encuentran definidas en glib/glib.h
(que se incluye desde gtk.h).
Probablemente el lector se haya dado cuenta de que se puede usar GtkWidget
cuando la función llama a un GtkObject. Esto es debido a que GTK
está orienta a objetos y un <em/widget/ es un GtkObject.
<!-- ----------------------------------------------------------------- -->
<sect1>Más sobre el manejo de señales
<p>
Si estudiamos en mayor profundidad la declaración de
gtk_signal_connect:
<tscreen><verb>
gint gtk_signal_connect( GtkObject *object,
gchar *name,
GtkSignalFunc func,
gpointer func_data );
</verb></tscreen>
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:
<tscreen><verb>
void gtk_signal_disconnect( GtkObject *object,
gint id );
</verb></tscreen>
Por lo tanto podemos desconectar un manejador de señal pasándole
a la función anterior el <em/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:
<tscreen><verb>
void gtk_signal_handlers_destroy( GtkObject *object );
</verb></tscreen>
Esta llamada es bastante auto explicativa. Simplemente quitamos todos los
controladores de señales del objeto que pasamos como primer argumento.
<!-- ----------------------------------------------------------------- -->
<sect1>Un Hello World mejorado.
<p>
Vamos a mejorar el ejemplo para obtener una visión más amplia
sobre el manejo de señales y respuestas. También introduciremos
los <em/widgets/ usados para empaquetar.
<tscreen><verb>
/* principio del ejemplo helloworld2 */
#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 ();
}
int main (int argc, char *argv[])
{
/* GtkWidget es el tipo de almacenamiento usado para los wigtes*/
GtkWidget *ventana;
GtkWidget *boton;
GtkWidget *caja1;
/* Esta llamada está presente en todas las aplicaciones basadas
* en GTK. Los argumentos introducidos a la aplicación*/
gtk_init (&amp;argc, &amp;argv);
/* creamos una nueva ventana*/
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Esta función es nueva, pone como título de la ventana
* "¡Hola botones!"*/
gtk_window_set_title (GTK_WINDOW (ventana), "¡Hola botones!");
/* Establecemos el controlador para la llamada delete_event que
* termina la aplicación inmediatamente. */
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* Establecemos el ancho del borde de la ventana.*/
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* Creamos una caja donde empaquetaremos los widgets. El
* procedimiento de empaquetamiento se describe en detalle en la
* sección correspondiente. La caja no se ve realmente, sólo
* sirve para introducir los widgets. */
caja1 = gtk_hbox_new(FALSE, 0);
/* ponemos la caja en la ventana principal */
gtk_container_add (GTK_CONTAINER (ventana), caja1);
/* Creamos un nuevo botón con la etiqueta "Botón 1". */
boton = gtk_button_new_with_label ("Botón 1");
/* Cada vez que el botón sea pulsado llamamos a la función
* "callback" con un puntero a "botón 1" como argumento. */
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "botón 1");
/* En lugar de gtk_container_add empaquetamos el botón en la
* caja invisible, que a su vez ha sido empaquetado en la
* ventana. */
gtk_box_pack_start(GTK_BOX(caja1), boton, TRUE, TRUE, 0);
/* Siempre se debe realizar este paso. Sirve para decirle a GTK
* que los preparativos del botón ya se han finalizado y que
* por tanto puede ser mostrado. */
gtk_widget_show(boton);
/* hacemos lo mismo para crear un segundo botón. */
boton = gtk_button_new_with_label ("Botón 2");
/* Llamamos a la misma función de respuesta pero con diferente
* argumento: un puntero a "botón 2". */
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "botón 2");
gtk_box_pack_start(GTK_BOX(caja1), boton, TRUE, TRUE, 0);
/* El orden en que mostramos los botones no es realmente
* importante, pero se recomienda mostrar la ventana la última
* para que todo aparezca de golpe. */
gtk_widget_show(boton);
gtk_widget_show(caja1);
gtk_widget_show (ventana);
/* Esperamos en gtk_main a que comience el espectáculo.*/
gtk_main ();
return 0;
}
/* final del ejemplo*/
</verb></tscreen>
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).
<!-- ***************************************************************** -->
<sect><em/Widgets/ usados para empaquetar
<!-- ***************************************************************** -->
<p>
Al crear una aplicación normalmente se quiere que haya más de un
<em/widget/ por ventana. Nuestro primer ejemplo sólo usaba un
<em/widget/ por lo que usábamos la función gtk_container_add
para «empaquetar» el <em/widget/ en la ventana. Pero cuando cuando
se quiere poner más de un <em/widget/ en una ventana, ¿Cómo
podemos controlar donde aparecerá el <em/widget/?. Aquí es donde
entra el empaquetamiento.
<!-- ----------------------------------------------------------------- -->
<sect1>Empaquetamiento usando cajas
<p>
Normalmente para empaquetar se usan cajas, tal y como ya hemos
visto. Éstas son <em/widgets/ invisibles que pueden contener
nuestros <em/widgets/ de dos formas diferentes, horizontal o
verticalmente. Al hacerlo de la primera forma los objetos son
insertados de izquierda a derecha o al revés (dependiendo de que
llamada se use). Lo mismo ocurre en los verticales (de arriba a bajo o
al revés). Se pueden usar tantas cajas como se quieran para
conseguir cualquier tipo de efecto.
Para crear una caja horizontal llamamos a gtk_hbox_new() y para las
verticales gtk_vbox_new(). Las funciones usadas para introducir
objetos dentro son gtk_box_pack_start() y gtk_box_pack_end(). La
primera llenará de arriba a abajo o de izquierda a derecha. La
segunda lo hará al revés.
Usando estas funciones podemos ir metiendo <em/widgets/ con una
justificación a la izquierda o a la derecha y además podemos
mezclarlas de cualquier manera para conseguir el efecto
deseado. Nosotros usaremos gtk_box_pack_start() en la mayoria de
nuestros ejemplos. Un objeto puede ser otro contenedor o un
<em/widget/. De hecho, muchos <em/widgets/ son contenedores,
incluyendo el <em/widget/ botón (button) (aunque normalmente lo
único que meteremos dentro será una etiqueta de texto).
Mediante el uso de estas funciones le decimos a GTK dónde queremos
situar nuestros widgets, y GTK podrá, por ejemplo, cambiarles el
tamaño de forma automática y hacer otras cosas de
utilidad. También hay unas cuantas opciones que tienen que ver con
la forma en la que los <em/widgets/ serán empaquetados. Como puede
imaginarse, este método nos da una gran flexibilidad a la hora de
colocar y crear <em/widgets/.
<!-- ----------------------------------------------------------------- -->
<sect1>Detalles de la cajas.
<p>
Debido a esta flexibilidad el empaquetamiento puede ser confuso al
principio. Hay muchas opciones y no es obvio como encajan unas con
otras. Pero en la práctica sólo hay cinco estilos diferentes.
<? <CENTER> >
<?
<IMG SRC="gtk_tut_packbox1.gif" VSPACE="15" HSPACE="10" WIDTH="528"
HEIGHT="235" ALT="Imagen de ejemplo sobre el empaquetado con cajas">
>
<? </CENTER> >
Cada línea contiene una caja horizontal (hbox) con diferentes
botones. La llamada a gtk_box_pack es una manera de conseguir
empaquetar cada uno de los botones dentro de la caja. Eso sí, cada uno
de ellos se empaqueta de la misma forma que el resto (se llama con los
mismos argumentos a gtk_box_pack_start()).
Esta es la declaración de la función gtk_box_pack_start:
<tscreen><verb>
void gtk_box_pack_start( GtkBox *box,
GtkWidget *hijo,
gint expand,
gint fill,
gint padding );
</verb></tscreen>
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 <tt/expand/ de gtk_box_pack_start() y de
gtk_box_pack_end() controla si los <em/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 <em/expand/ podremos hacer que nuestros
<em/widgets/ tengan una justaficación a la derecha o a la
izquierda. En caso contrario, los <em/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 <tt/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:
<tscreen><verb>
GtkWidget *gtk_hbox_new (gint homogeneous,
gint spacing);
</verb></tscreen>
El argumento <tt/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 <tt/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.
<? <CENTER> >
<?
<IMG ALIGN="center" SRC="gtk_tut_packbox2.gif" WIDTH="509" HEIGHT="213"
VSPACE="15" HSPACE="10" ALT="Imagen de ejemplo sobre el empaquetado con cajas">
>
<? </CENTER> >
Estudiemos el código usado para crear las imágenes
anteriores. Con los comentarios no debería de haber ningún
problema para entenderlo.
<!-- ----------------------------------------------------------------- -->
<sect1>Programa demostración de empaquetamiento
<p>
<tscreen><verb>
/* principio del ejemplo packbox packbox.c */
#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. */
GtkWidget *make_box (gint homogeneous, gint spacing,
gint expand, gint fill, gint padding)
{
GtkWidget *box;
GtkWidget *boton;
char padstr[80];
/* creamos una nueva caja con los argumentos homogeneous y
* spacing */
box = gtk_hbox_new (homogeneous, spacing);
/* crear una serie de botones */
boton = gtk_button_new_with_label ("gtk_box_pack");
gtk_box_pack_start (GTK_BOX (box), boton, expand, fill, padding);
gtk_widget_show (boton);
boton = gtk_button_new_with_label ("(box,");
gtk_box_pack_start (GTK_BOX (box), boton, expand, fill, padding);
gtk_widget_show (boton);
boton = gtk_button_new_with_label ("boton,");
gtk_box_pack_start (GTK_BOX (box), boton, expand, fill, padding);
gtk_widget_show (boton);
/* Este botón llevará por etiqueta el valor de expand */
if (expand == TRUE)
boton = gtk_button_new_with_label ("TRUE,");
else
boton = gtk_button_new_with_label ("FALSE,");
gtk_box_pack_start (GTK_BOX (box), boton, expand, fill, padding);
gtk_widget_show (boton);
/* Este es el mismo caso que el de arriba, pero más compacto */
boton = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
gtk_box_pack_start (GTK_BOX (box), boton, expand, fill, padding);
gtk_widget_show (boton);
sprintf (padstr, "%d);", padding);
boton = gtk_button_new_with_label (padstr);
gtk_box_pack_start (GTK_BOX (box), boton, expand, fill, padding);
gtk_widget_show (boton);
return box;
}
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *boton;
GtkWidget *caja1;
GtkWidget *caja2;
GtkWidget *separator;
GtkWidget *etiqueta;
GtkWidget *quitbox;
int which;
/* ¡No olvidar la siguiente llamada! */
gtk_init (&amp;argc, &amp;argv);
if (argc != 2) {
fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n");
/* hacemos limpieza en GTK y devolvemos el valor de 1 */
gtk_exit (1);
}
which = atoi (argv[1]);
/* Creamos la ventana */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Siempre hay que conectar la señal de destrucción con la
* ventana principal. Esto es muy importante para que el
* comportamiento de la ventana sea intuitivo. */
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* Creamos una caja vertical donde empaquetaremos las cajas
* horizontales. Así podemos apilar las cajas horizontales
* llenas con botones una encima de las otras. */
caja1 = gtk_vbox_new (FALSE, 0);
/* Aclaramos cúal es el ejemplo a mostrar. Se corresponde con
* las imágenes anteriores. */
switch (which) {
case 1:
/* creamos una nueva etiqueta. */
etiqueta = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
/* Alineamos la etiqueta a la izquierda. Está función
* será discutida en detalle en la sección de los
* atributos de los widgets. */
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0);
/* Empaquetamos la etiqueta en la caja vertical (vbox
* caja1). Siempre hay que recordar que los widgets añadidos a
* una vbox serán empaquetados uno encimo de otro. */
gtk_box_pack_start (GTK_BOX (caja1), etiqueta, FALSE, FALSE, 0);
/* mostramos la etiqueta. */
gtk_widget_show (etiqueta);
/* llamada a la función que hace las cajas. Los argumentos
* son homogenous = FALSE, expand = FALSE, fill = FALSE,
* padding = 0 */
caja2 = make_box (FALSE, 0, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* Llamad a la función para hacer cajas -
* homogeneous = FALSE, spacing = 0, expand = FALSE,
* fill = FALSE, padding = 0 */
caja2 = make_box (FALSE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (FALSE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* creamos un separador. Más tarde aprenderemos más cosas
* sobre ellos, pero son bastante sencillos. */
separator = gtk_hseparator_new ();
/* empaquetamos el separador el la vbox. Los widgets serán
* apilados verticalmente. */
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
/* creamos una nueva etiqueta y la mostramos */
etiqueta = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0);
gtk_box_pack_start (GTK_BOX (caja1), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (TRUE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (TRUE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* un nuevo separador */
separator = gtk_hseparator_new ();
/* Los tres últimos argumentos son: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;
case 2:
/* Nueva etiqueta */
etiqueta = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0);
gtk_box_pack_start (GTK_BOX (caja1), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (FALSE, 10, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (FALSE, 10, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
separator = gtk_hseparator_new ();
/* Los argumentos son: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
etiqueta = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0);
gtk_box_pack_start (GTK_BOX (caja1), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (FALSE, 0, TRUE, FALSE, 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* Los argumentos son: homogeneous, spacing, expand, fill,
* padding */
caja2 = make_box (FALSE, 0, TRUE, TRUE, 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
separator = gtk_hseparator_new ();
/* Los argumentos son: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;
case 3:
/* Con esto demostramos como hay que usar gtk_box_pack_end ()
* para conseguir que los
widgets esten alineados a la izquierda. */
caja2 = make_box (FALSE, 0, FALSE, FALSE, 0);
/* la última etiqueta*/
etiqueta = gtk_label_new ("end");
/* la empaquetamos usando gtk_box_pack_end(), por lo que se
* sitúa en el lado derecho de la hbox.*/
gtk_box_pack_end (GTK_BOX (caja2), etiqueta, FALSE, FALSE, 0);
/* mostrar la etiqueta */
gtk_widget_show (etiqueta);
/* empaquetamos caja2 en caja1 */
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, FALSE, 0);
gtk_widget_show (caja2);
/* el separador para la parte de abajo. */
separator = gtk_hseparator_new ();
/* Así se determina el tamaño del separador a 400 pixels
* de largo por 5 de alto. La hbox también tendrá 400
* pixels de largo y la etiqueta "end" estará separada de
* las demás etiquetas en la hbox. Si no establecemos estos
* parámetros todos los widgets en la hbox serán
* empaquetados tan juntos como se pueda.*/
gtk_widget_set_usize (separator, 400, 5);
/* Empaquetamos el separador creado al principio de main() en
* la vbox (caja1). */
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
}
/* Creamos otra hbox... recordar que podemos crear tantas como
* queramos. */
quitbox = gtk_hbox_new (FALSE, 0);
/* El botón de salida. */
boton = gtk_button_new_with_label ("Quit");
/* Establecemos la señal de destrucción de la ventana.
* Recuerde que emitirá la señal de "destroy" que a su vez
* será procesada por el controlador de señales, tal y como
* ya hemos visto. */
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (gtk_main_quit),
GTK_OBJECT (ventana));
/* Empaquetamos el botón en la caja de salida (quitbox).
* los tres últimos argumentos de gtk_box_pack_start
* son:expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (caja1), quitbox, FALSE, FALSE, 0);
/* empaquetamos la vbox (caja1) que ya contiene todos los widgets
* en la ventana principal. */
gtk_container_add (GTK_CONTAINER (ventana), caja1);
/* mostramos todo aquello que faltaba por mostrar */
gtk_widget_show (boton);
gtk_widget_show (quitbox);
gtk_widget_show (caja1);
/* Si mostramos la ventana lo último todo aparece de golpe. */
gtk_widget_show (ventana);
/* por supuesto tenemos una función main. */
gtk_main ();
/* El programa llega aquí cuando se llama a gtk_main_quit(),
* pero no cuando se llama a gtk_exit(). */
return 0;
}
/* final del ejemplo*/
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Empaquetamiento usando tablas
<p>
Existe otra forma de empaquetar: usando tablas. Estas pueden llegar a
ser extremadamente útiles.
Usando tablas creamos una cuadrícula donde podemos poner los
widgets. Estos pueden ocupar tanto espacio como queramos.
La primera función que conviene estudiar es gtk_table_new:
<tscreen><verb>
GtkWidget *gtk_table_new( gint rows,
gint columns,
gint homogeneous );
</verb></tscreen>
Como es lógico el primer argumento es el número de filas y el
segundo el de columnas.
El tercero establece el tamaño de las celdas de la tabla. Si es TRUE
se fuerza a que el tamaño de las celdas sea igual al de la celda
mayor. Con FALSE se establece el ancho de toda una columna igual al de
la celda más ancha de esa columna, y la altura de una fila será
la de la celda más alta de esa fila.
El número de filas y columnas varía entre 0 y n, donde n es el
número especificado en la llamada a gtk_table_new. Así si se
especifica columnas = 2 y filas = 2 la apariencia será parecida a:
<tscreen><verb>
0 1 2
0+----------+----------+
| | |
1+----------+----------+
| | |
2+----------+----------+
</verb></tscreen>
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:
<tscreen><verb>
void gtk_table_attach( GtkTable *table,
GtkWidget *hijo,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach,
gint xoptions,
gint yoptions,
gint xpadding,
gint ypadding );
</verb></tscreen>
El primer argumento (<tt/table/) es el nombre de la tabla y el segundo
(<tt/hijo/) el <em/widget/ que quiere poner en la tabla.
Los argumentos <tt/left_attach/, <tt/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 <tt/xoptions/ e <tt/yoptions/ son usadas para especificar
como queremos el empaquetamiento y podemos utilizar multiples
opciones simultaneamente con OR.
Las opciones son:
<itemize>
<item>GTK_FILL - Si el relleno es más grande que el widget, y se
especifica GTK_FILL, el <em/widget/ se expandirá ocupando todo el
espacio disponible.
<item>GTK_SHRINK - En el caso de que hayamos dejado espacio sin usar
cuando el usuario reajuste el tamaño de la ventana los <em/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.
<item>GTK_EXPAND - Mediante esta opción la tabla se expande usando
todo el espacio libre de la ventana.
</itemize>
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:
<tscreen><verb>
void gtk_table_attach_defaults( GtkTable *table,
GtkWidget *widget,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach );
</verb></tscreen>
Las opciones X e Y se ponen por defecto a GTK_FILL | GTK_EXPAND, y el
relleno X e Y se pone a 0. El resto de los argumentos son identicos a
la función anterior.
Existen otras funciones como gtk_table_set_row_spacing() y
gtk_table_set_col_spacing(), que sirven para especificar el espaciado
entre las columnas/filas en la columna/fila que queramos.
<tscreen><verb>
void gtk_table_set_row_spacing( GtkTable *table,
gint row,
gint spacing );
</verb></tscreen>
y
<tscreen><verb>
void gtk_table_set_col_spacing ( GtkTable *table,
gint column,
gint spacing );
</verb></tscreen>
Conviene destacar que el espaciado se sitúa a la derecha de la
columna y debajo de la fila.
Tambien se puede forzar que el espaciado sea el mismo para las filas
y/o las columnas:
<tscreen><verb>
void gtk_table_set_row_spacings( GtkTable *table,
gint spacing );
</verb></tscreen>
y
<tscreen><verb>
void gtk_table_set_col_spacings( GtkTable *table,
gint spacing );
</verb></tscreen>
Usando estas funciones las últimas fila y columna no estarán
espaciadas.
<!-- ----------------------------------------------------------------- -->
<sect1>Ejemplo de empaquetamiento mediante tablas.
<p>
Haremos una ventana con tres botones en una tabla 2x2. Los dos
primeros botones ocuparán la fila de arriba, mientras que el
tercero (de salida) ocupará toda la fila de abajo. El resultado es
el siguiente:
<? <CENTER> >
<?
<IMG SRC="gtk_tut_table.gif" VSPACE="15" HSPACE="10"
ALT="Imagen de ejemplo sobre el empaquetado mediante tablas" WIDTH="180" HEIGHT="120">
>
<? </CENTER> >
Este es el código:
<tscreen><verb>
/* principio del ejemplo table table.c */
#include <gtk/gtk.h>
/* La respuesta, que además se imprime en stdout. */
void callback (GtkWidget *widget, gpointer data)
{
g_print ("Hello again - %s was pressed\n", (char *) data);
}
/* Con esta otra respuesta terminamos el programa. */
void delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *boton;
GtkWidget *table;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Table");
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 20);
table = gtk_table_new (2, 2, TRUE);
gtk_container_add (GTK_CONTAINER (ventana), table);
boton = gtk_button_new_with_label ("botón 1");
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "botón 1");
gtk_table_attach_defaults (GTK_TABLE(table), boton, 0, 1, 0, 1);
gtk_widget_show (boton);
boton = gtk_button_new_with_label ("botón 2");
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "botón 2");
gtk_table_attach_defaults (GTK_TABLE(table), boton, 1, 2, 0, 1);
gtk_widget_show (boton);
boton = gtk_button_new_with_label ("Quit");
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (delete_event), NULL);
gtk_table_attach_defaults (GTK_TABLE(table), boton, 0, 2, 1, 2);
gtk_widget_show (boton);
gtk_widget_show (table);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* final del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect>Estudio general de los <em/widgets/
<!-- ***************************************************************** -->
<p>
Los pasos generales a la hora de crear un <em/widget/ son:
<enum>
<item> Usar gtk_*_new - Una de las diferentes formas de crear un
<em/widget/. (Todas serán explicadas en esta sección).
<item> Connectar todas las señales y los eventos a los
controladores apropiados.
<item> Establecer los atributos del <em/widget/.
<item> Empaquetar el <em/widget/ en un contenedor usando las llamadas
apropiadas, como gtk_container_add() o gtk_box_pack_start().
<item> Mostrar el <em/widget/ usando gtk_widget_show().
</enum>
Mediante esta última llamada GTK `sabe' que hemos acabado de
establecer los atributos del <em/widget/, y que por lo tanto puede
mostrarse. Se puede usar gtk_widget_hide para hacer que desaparezca.
El orden en el que se muestran los <em/widgets/ no es importante, pero
se recomienda mostrar al final la ventana para que todo aparezca de
golpe. El hijo de un <em/widget/ no se muestra hasta que lo hace la
propia ventana (que en este caso es un <em/widget/ padre) mediante
gtk_widget_show().
<!-- ----------------------------------------------------------------- -->
<sect1> Conversión de tipos
<p>
GTK usa un sistema de conversión de tipos mediante macros que
comprueban si se puede realizar la conversión y en caso
afirmativo la hacen. Las más comunes son:
<itemize>
<item> GTK_WIDGET(widget)
<item> GTK_OBJECT(object)
<item> GTK_SIGNAL_FUNC(function)
<item> GTK_CONTAINER(container)
<item> GTK_WINDOW(ventana)
<item> GTK_BOX(box)
</itemize>
Todas son usadas para cambiar de tipo los argumentos de una función.
Aparecerán mucho en los ejemplos, para usarlas sólo hay que mirar la
declaración de la función.
Tal y como se puede ver en el árbol de clases (situado un poco
más adelante) todos los <em/widgets/ derivan de la clase base
GtkObject. Esto significa que siempre se puede usar un <em/widget/
como argumento de una función (que acepte un objeto, claro)
realizando la conversión de tipo GTK_OBJECT().
Por ejemplo:
<tscreen><verb>
gtk_signal_connect( GTK_OBJECT(boton), "clicked",
GTK_SIGNAL_FUNC(callback_function), callback_data);
</verb></tscreen>
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 <em/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
<em/widget/ leyendo las declaraciones de las funciones.
<!-- ----------------------------------------------------------------- -->
<sect1>Árbol formado por los <em/widgets/
<p>
A continuación se detallan todas las ramas del árbol que forman
los <em/widgets/.
<tscreen><verb>
GtkObject
+GtkWidget
| +GtkMisc
| | +GtkLabel
| | | +GtkAccelLabel
| | | `GtkTipsQuery
| | +GtkArrow
| | +GtkImage
| | `GtkPixmap
| +GtkContainer
| | +GtkBin
| | | +GtkAlignment
| | | +GtkFrame
| | | | `GtkAspectFrame
| | | +GtkButton
| | | | +GtkToggleButton
| | | | | `GtkCheckButton
| | | | | `GtkRadioButton
| | | | `GtkOptionMenu
| | | +GtkItem
| | | | +GtkMenuItem
| | | | | +GtkCheckMenuItem
| | | | | | `GtkRadioMenuItem
| | | | | `GtkTearoffMenuItem
| | | | +GtkListItem
| | | | `GtkTreeItem
| | | +GtkWindow
| | | | +GtkColorSelectionDialog
| | | | +GtkDialog
| | | | | `GtkInputDialog
| | | | +GtkDrawWindow
| | | | +GtkFileSelection
| | | | +GtkFontSelectionDialog
| | | | `GtkPlug
| | | +GtkEventBox
| | | +GtkHandleBox
| | | +GtkScrolledWindow
| | | `GtkViewport
| | +GtkBox
| | | +GtkButtonBox
| | | | +GtkHButtonBox
| | | | `GtkVButtonBox
| | | +GtkVBox
| | | | +GtkColorSelection
| | | | `GtkGammaCurve
| | | `GtkHBox
| | | +GtkCombo
| | | `GtkStatusbar
| | +GtkCList
| | | `GtkCTree
| | +GtkFixed
| | +GtkNotebook
| | | `GtkFontSelection
| | +GtkPaned
| | | +GtkHPaned
| | | `GtkVPaned
| | +GtkLayout
| | +GtkList
| | +GtkMenuShell
| | | +GtkMenuBar
| | | `GtkMenu
| | +GtkPacker
| | +GtkSocket
| | +GtkTable
| | +GtkToolbar
| | `GtkTree
| +GtkCalendar
| +GtkDrawingArea
| | `GtkCurve
| +GtkEditable
| | +GtkEntry
| | | `GtkSpinButton
| | `GtkText
| +GtkRuler
| | +GtkHRuler
| | `GtkVRuler
| +GtkRange
| | +GtkScale
| | | +GtkHScale
| | | `GtkVScale
| | `GtkScrollbar
| | +GtkHScrollbar
| | `GtkVScrollbar
| +GtkSeparator
| | +GtkHSeparator
| | `GtkVSeparator
| +GtkPreview
| `GtkProgress
| `GtkProgressBar
+GtkData
| +GtkAdjustment
| `GtkTooltips
`GtkItemFactory
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1><em/Widgets/ sin ventanas
<p>
Los siguientes <em/widgets/ no tienen ventanas asociadas. Si se
quieren capturar eventos se tendrá que utilizar GtkEventBox. En la
sección <ref id="sec_The_EventBox_Widget" name="El widget
EventBox"> se pueden encontrar más detalles sobre su uso.
<tscreen><verb>
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkAspectFrame
GtkFrame
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
</verb></tscreen>
Vamos a continuar la explicación describiendo cada uno de los
<em/widgets/ mediante ejemplos. También se puede consultar el
programa testgtk.c (Se encuentra en gtk/testgtk.c).
<!-- ***************************************************************** -->
<sect>El <em/widget/ Botón
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1>Botones normales <label id="sec_Radio_Buttons">
<p>
Ya hemos visto prácticamente todo lo que hay que saber a cerca de
este <em/widget/. Existen dos formas diferentes de crear un
botón. Se puede usar gtk_button_new_with_label() para conseguir un
botón con etiqueta o simplemente gtk_button_new(). Si se quiere se
puede añadir una etiqueta a este último empaquetándola,
primero se crea una nueva caja y luego se empaquetan los objetos que
se quieran mediante gtk_box_pack_start. Una vez finalizado esto se
relaciona la caja con el botón mediante gtk_container_add.
Estudiemos un ejemplo de gtk_button_new para crear un botón con una
imagen y una etiqueta. El código está dividido en dos para que
pueda ser reusado.
<tscreen><verb>
/* principio del ejemplo buttons buttons.c */
#include <gtk/gtk.h>
/* Creamos la caja con una imagen y una etiqueta empaquetadas. Se
* devuelve la caja. */
GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text)
{
GtkWidget *caja1;
GtkWidget *etiqueta;
GtkWidget *pixmapwid;
GdkPixmap *pixmap;
GdkBitmap *mask;
GtkStyle *style;
/* create box for xpm and etiqueta */
caja1 = gtk_hbox_new (FALSE, 0);
gtk_container_border_width (GTK_CONTAINER (caja1), 2);
/* obtenemos el estilo del botón (probablemente para el color
* de fondo, pero no estoy seguro) */
style = gtk_widget_get_style(parent);
/* cargamos el pixmap. Hay una sección que describe el proceso
* en detalle */
pixmap = gdk_pixmap_create_from_xpm (parent->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
xpm_filename);
pixmapwid = gtk_pixmap_new (pixmap, mask);
etiqueta = gtk_label_new (label_text);
gtk_box_pack_start (GTK_BOX (caja1),
pixmapwid, FALSE, FALSE, 3);
gtk_box_pack_start (GTK_BOX (caja1), etiqueta, FALSE, FALSE, 3);
gtk_widget_show(pixmapwid);
gtk_widget_show(etiqueta);
return (caja1);
}
/* respuesta */
void callback (GtkWidget *widget, gpointer data)
{
g_print ("Hola de nuevo. Se ha pulsado %s\n", (char *) data);
}
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *boton;
GtkWidget *caja1;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Botones con dibujos");
/* It's a good idea to do this for all windows. */
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
gtk_widget_realize(ventana);
boton = gtk_button_new ();
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (callback),
(gpointer) "botón divertido");
caja1 = xpm_label_box(ventana, "info.xpm", "botón divertido");
gtk_widget_show(caja1);
gtk_container_add (GTK_CONTAINER (boton), caja1);
gtk_widget_show(boton);
gtk_container_add (GTK_CONTAINER (ventana), boton);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* final del ejemplo */
</verb></tscreen>
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:
<itemize>
<item> pressed
<item> released
<item> clicked
<item> enter
<item> leave
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1> Botones de selección
<p>
Estos botones son muy similares a los normales. La única diferencia
es que sólo pueden estar en dos posiciones diferentes alternadas
mediante pulsaciones del ratón.
Los botones de selección son la base de otros tipos: los de
comprobación y los circulares. Por lo tanto muchas de sus llamadas
seran heredadas por estos.
Creamos un nuevo botón de selección:
<tscreen><verb>
GtkWidget *gtk_toggle_button_new( void );
GtkWidget *gtk_toggle_button_new_with_label( gchar *etiqueta );
</verb></tscreen>
Como se ha podido imaginar estas funciones son iguales a las de un
botón normal. La primera crea un botón, mientras que la segunda
crea un botón con una etiqueta.
Para saber cual es el estado de un botón de selección,
comprobación o circular se usa una de las macros del ejemplo
siguiente. En éstas se comprueba el estado del botón mediante
una respuesta. La señal que queremos recibir es
«toggled». Generalmente para comprobar el estado de una señal se
establece un controlador de señales y luego se usa la siguiente
macro. La función de respuesta debe ser de la forma:
<tscreen><verb>
void toggle_button_callback (GtkWidget *widget, gpointer data)
{
if (GTK_TOGGLE_BUTTON (widget)->active)
{
/* Si el control llega aquí el botón está pulsado */
} else {
/* El botón no está pulsado (sobresale) */
}
}
</verb></tscreen>
<tscreen><verb>
void gtk_toggle_button_set_state( GtkToggleButton *toggle_button,
gint state );
</verb></tscreen>
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».
<tscreen><verb>
void gtk_toggle_button_toggled (GtkToggleButton *toggle_button);
</verb></tscreen>
Cambia el estado del botón emitiendo la señal «toggled».
<!-- ----------------------------------------------------------------- -->
<sect1> Botones de comprobación
<p>
Los botones de comprobación son un poco diferentes a los anteriores, aunque
sus propiedades y funciones son bastante similares. En lugar de ser botones
con texto en su interior son pequeños cuadrados con texto a su derecha.
Normalmente son usados para (des)seleccionar opciones.
Las dos funciones que los crean son muy similares a las de los botones
normales.
<tscreen><verb>
GtkWidget *gtk_check_button_new( void );
GtkWidget *gtk_check_button_new_with_label ( gchar *etiqueta );
</verb></tscreen>
La función new_with_label crea un botón de comprobación con
una etiqueta dentro.
El proceso para comprobar el estado de un botón de este tipo es
igual al de los de comprobación.
<!-- ----------------------------------------------------------------- -->
<sect1> Botones circulares
<p>
Estos botones son similares a los de selección con la salvedad de
que están agrupados, de modo que sólo uno puede estar
seleccionado. Por tanto son usados para permitir al usuario
seleccionar algo de una lista de opciones mutuamente excluyentes.
Las llamadas para crear un botón circular son:
<tscreen><verb>
GtkWidget *gtk_radio_button_new( GSList *group );
GtkWidget *gtk_radio_button_new_with_label( GSList *group,
gchar *etiqueta );
</verb></tscreen>
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:
<tscreen><verb>
GSList *gtk_radio_button_group( GtkRadioButton *radio_button );
</verb></tscreen>
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:
<tscreen><verb>
void gtk_toggle_button_set_state( GtkToggleButton *toggle_button,
gint state );
</verb></tscreen>
El siguiente ejemplo crea un grupo de tres botones:
<tscreen><verb>
/* Principio del ejemplo radiobuttons.c */
#include <gtk/gtk.h>
#include <glib.h>
void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) {
gtk_main_quit();
}
main(int argc,char *argv[])
{
static GtkWidget *ventana = NULL;
GtkWidget *caja1;
GtkWidget *caja2;
GtkWidget *boton;
GtkWidget *separator;
GSList *group;
gtk_init(&amp;argc,&amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC(close_application),
NULL);
gtk_window_set_title (GTK_WINDOW (ventana), "radio buttons");
gtk_container_border_width (GTK_CONTAINER (ventana), 0);
caja1 = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (ventana), caja1);
gtk_widget_show (caja1);
caja2 = gtk_vbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
boton = gtk_radio_button_new_with_label (NULL, "botón1");
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
gtk_widget_show (boton);
group = gtk_radio_button_group (GTK_RADIO_BUTTON (boton));
boton = gtk_radio_button_new_with_label(group, "botón2");
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (boton), TRUE);
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
gtk_widget_show (boton);
group = gtk_radio_button_group (GTK_RADIO_BUTTON (boton));
boton = gtk_radio_button_new_with_label(group, "botón3");
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
gtk_widget_show (boton);
separator = gtk_hseparator_new ();
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 0);
gtk_widget_show (separator);
caja2 = gtk_vbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, TRUE, 0);
gtk_widget_show (caja2);
boton = gtk_button_new_with_label ("close");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC(close_application),
GTK_OBJECT (ventana));
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
GTK_WIDGET_SET_FLAGS (boton, GTK_CAN_DEFAULT);
gtk_widget_grab_default (boton);
gtk_widget_show (boton);
gtk_widget_show (ventana);
gtk_main();
return(0);
}
/* final del ejemplo */
</verb></tscreen>
<!-- TODO: checout out gtk_radio_button_new_from_widget function - TRG -->
<!-- ***************************************************************** -->
<sect>Ajustes (<em/Adjustment/) <label id="sec_Adjustment">
<!-- ***************************************************************** -->
<p>
Existen diferentes <em/widgets/ en GTK+ que pueden ser ajustados
visualmente por el usuario mediante el ratón o el teclado. Un
ejemplo son los <em/widgets/ de selección descritos en la
sección <ref id="sec_Range_Widgets" name="Widgets de selección
de rango">. También hay otros widgets que pueden ser ajustados
parcialmente, por ejemplo el <em/widget/ de texto o el <em/viewport/.
Como es lógico el programa tiene que poder reaccionar a los
cambios que el usuario realiza en los <em/widgets/ de selección de
rango. Una forma de hacer que el programa reaccione sería tener
cada <em/widget/ emitiendo su propio tipo de señal cuando cambie el
ajuste, y bien pasar el nuevo valor al manejador de señal o bien
obligarle a que mire dentro de la estructura de datos del <em/widget/
para conocer este valor. Pero también puede ser que quiera conectar
los ajustes de varios <em/widgets/, para que así cuando se ajuste
uno, los demás se ajusten automáticamente. El ejemplo más
obvio es conectar una barra de desplazamiento a una región con
texto. Si cada <em/widget/ posee su propia forma de establecer u
obtener sus valores de ajuste el programador puede que tenga que
escribir sus propios controladores de señales para traducir el
resultado de la señal producida por un <em/widget/ como el
argumento de una función usada para determinar valores en otro
<em/widget/.
Para resolver este problema GTK+ usa objetos del tipo GtkAdjustment.
Con ellos se consigue almacenar y traspasar información de una forma
abstracta y flexible. El uso más obvio es el de almacenes de
párametros para <em/widgets/ de escala (barras deslizantes y
escalas). Como los GtkAdjustment derivan de GtkObject poseen
cualidades intrínsecas que les permiten ser algo más que simples
estructuras de datos. Lo más importante es que pueden emitir
señales que a su vez pueden ser usadas tanto para reaccionar frente
al cambio de datos introducidos por el usuario como para transferir
los nuevos valores de forma transparente entre <em/widgets/ ajustables.
<sect1>Creando un ajuste
<p>
Los ajustes se pueden crear usando:
<tscreen><verb>
GtkObject *gtk_adjustment_new( gfloat value,
gfloat lower,
gfloat upper,
gfloat step_increment,
gfloat page_increment,
gfloat page_size );
</verb></tscreen>
El argumento <tt/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 <em/widget/ ajustable. El argumento
<tt/lower/ especifica los valores más pequeños que el ajuste
puede contener. A su vez con <tt/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 <tt/page_increment/
es el mayor. Con <tt/page_size/ se determina el valor visible de un
<em/widget/.
<!-- ----------------------------------------------------------------- -->
<sect1> Forma sencilla de usar los ajustes
<p>
Los <em/widgets/ ajustábles se pueden dividir en dos categorias
diferentes, aquellos que necesitan saber las unidades de la cantidad
almacenada y los que no. Este último grupo incluye los <em/widgets/
de tamaño (barras deslizantes, escalas, barras de estado, o botones
giratorios). Normalmente estos <em/widgets/ son ajustados
«directamente» por el usuario. Los argumentos <tt/lower/ y
<tt/upper/ serán los limites dentro de los cuales el usuario puede
manipular los ajustes. Por defecto sólo se modificará el
<tt/value/ (valor) de un ajuste.
El otro grupo incluye los <em/widgets/ de texto, la lista compuesta o
la ventana con barra deslizante. Estos <em/widgets/ usan valores en
pixels para sus ajustes, y normalmente son ajustados
«indirectamente» mediante barras deslizantes. Aunque todos los
<em/widgets/ pueden crear sus propios ajustes o usar otros creados por
el programador con el segundo grupo suele ser conveniente dejarles que
creen sus propios ajustes. Normalmente no tendrán en cuenta ninguno
de los valores de un ajuste proporcionado por el programador, excepto
<tt/value/, pero los resultados son, en general, indefinidos
(entiendase que tendrá que leer el código fuente para saber que
pasa con cada widget).
Probablemente ya se habrá dado cuenta de que como los <em/widgets/
de texto (y todos los <em/widgets/ del segundo grupo), insisten en
establecer todos los valores excepto <tt/value/, mientras que las
barras deslizantes sólo modifican <tt/value/, si se comparte un
objeto de ajuste entre una barra deslizante y un <em/widget/ de texto
al manipular la barra se modificará el <em/widget/ de texto. Ahora
queda completamente demostrada la utilidad de los ajustes. Veamos un
ejemplo:
<tscreen><verb>
/* creamos un ajuste */
text = gtk_text_new (NULL, NULL);
/* lo usamos con la barra deslizante */
vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);
</verb></tscreen>
</sect1>
<!-- ----------------------------------------------------------------- -->
<sect1> Descripción detallada de los ajustes
<p>
Puede que se esté preguntando cómo es posible crear sus propios
controladores para responder a las modificaciones producidas por
el usuario y cómo obtener el valor del ajuste hecho por este.
Para aclarar esto y otras cosas vamos a estudiar la estructura
del ajuste
<tscreen><verb>
struct _GtkAdjustment
{
GtkData data;
gfloat lower;
gfloat upper;
gfloat value;
gfloat step_increment;
gfloat page_increment;
gfloat page_size;
};
</verb></tscreen>
Lo primero que hay que aclarar es que no hay ninguna macro o función
de acceso que permita obtener el <tt/value/ de un GtkAdjustment, por
lo que tendrá que hacerlo usted mismo. Tampoco se preocupe mucho
porque la macro <tt>GTK_ADJUSTMENT (Object)</tt> 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 <tt/value/ de un ajuste normalmente se quiere
que cualquier <em/widget/ se entere del cambio producido. Para ello
GTK+ posee una función especial:
<tscreen><verb>
void gtk_adjustment_set_value( GtkAdjustment *adjustment,
gfloat value );
</verb></tscreen>
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 <em/widgets/.
Por tanto todos los <em/widgets/ ajustables deben conectar controladores
de señales a sus señales del tipo <tt/value_changed/. Esta es la
definición de la señal como viene en <tt/struct _GtkAdjustmentClass/
<tscreen><verb>
void (* value_changed) (GtkAdjustment *adjustment);
</verb></tscreen>
Todos los <em/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 <em/widget/ de escala habría que usar una respuesta
como esta:
<tscreen><verb>
void cb_rotate_picture (GtkAdjustment *adj, GtkWidget *picture)
{
set_picture_rotation (picture, adj->value);
...
</verb></tscreen>
y conectarla con el ajuste del <em/widget/ de escala mediante:
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
GTK_SIGNAL_FUNC (cb_rotate_picture), picture);
</verb></tscreen>
¿Qué pasa cuando un <em/widget/ reconfigura los valores
<tt/upper/ o <tt/lower/ (por ejemplo cuando se añade más texto)?
Simplemente que se emite la señal <tt/changed/, que debe ser
parecida a:
<tscreen><verb>
void (* changed) (GtkAdjustment *adjustment);
</verb></tscreen>
Los <em/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 <tt/lower/ y <tt/upper/.
Probablemente nunca tenga que conectar un controlador a esta señal
a no ser que esté escribiendo un nuevo tipo de <em/widget/. Pero si
cambia directamente alguno de los valores de GtkAdjustment debe hacer
que se emita la siguiente señal para reconfigurar todos aquellos
<em/widgets/ que usen ese ajuste:
<tscreen><verb>
gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");
</verb></tscreen>
</sect1>
</sect>
<!-- ***************************************************************** -->
<sect>Los <em/widgets/ de selección de rango <label id="sec_Range_Widgets">
<!-- ***************************************************************** -->
<p>
Este tipo de <em/widgets/ incluye a las barras de desplazamiento
(<em>scroollbar</em>) y la menos conocida escala
(<em/scale</em>). Ambos pueden ser usados para muchas cosas, pero como
sus funciones y su implementación son muy parecidas los describimos
al mismo tiempo. Principalmente se utilizan para permitirle al usuario
escoger un valor dentro de un rango ya prefijado.
Todos los <em/widgets/ de selección comparten elementos
gráficos, cada uno de los cuales tiene su propia ventana X window y
recibe eventos. Todos contienen una guía y un rectángulo para
determinar la posición dentro de la guía (en una procesador de
textos con entorno gráfico se encuentra situado a la derecha del
texto y sirve para situarnos en las diferentes partes del texto). Con
el ratón podemos subir o bajar el rectángulo, mientras que si
hacemos `click' dentro de la guía, pero no sobre el rectángulo,
este se mueve hacia donde hemos hecho el click. Dependiendo del
botón pulsado el rectángulo se moverá hasta la posición
del click o una cantidad prefijada de ante mano.
Tal y como se mencionó en <ref id="sec_Adjustment" name="Ajustes">
todos los <em/widgets/ usados para seleccionar un rango estan
asociados con un objeto de ajuste, a partir del cual calculan la
longitud de la barra y su posición. Cuando el usuario manipula la
barra de desplazamiento el widget cambiará el valor del ajuste.
<sect1>El <em/widget/ barra de desplazamiento
<p>
El <em/widget/ barra de desplazamiento solamente debe utilizarse para
hacer <em/scroll/ sobre otro <em/widget/, como una lista, una caja de
texto, o un puerto de visión (y en muchos es más fácil utilizar
el <em/widget/ scrolled window). Para el resto de los casos, debería
utilizar los <em/widgets/ de escala, ya son más sencillos de usar y
más potentes.
Hay dos tipos separados de barras de desplazamiento, según sea
horizontal o vertical. Realmente no hay mucho que añadir. Puede
crear estos <em/widgets/ utilizar las funciones siguientes, definidas
en <tt>&lt;gtk/gtkhscrollbar.h&gt;</tt> y
<tt>&lt;gtk/gtkvscrollbar.h&gt;</tt>:
<tscreen><verb>
GtkWidget* gtk_hscrollbar_new( GtkAdjustment *adjustment );
GtkWidget* gtk_vscrollbar_new( GtkAdjustment *adjustment );
</verb></tscreen>
y esto es todo lo que hay (si no me cree, ¡mire los ficheros de
cabecera!). El argumento <tt/adjustment/ puede ser un puntero a un
ajuste ya existente, o puede ser NULL, en cuyo caso se creará
uno. Es útil especificar NULL si quiere pasar el ajuste recién
creado a la función constructora de algún otro <em/widget/ (como
por ejemplo el <em/widget/ texto) que se ocupará de configurarlo
correctamente por usted.
<!-- ----------------------------------------------------------------- -->
<sect1><em/Widgets/ de escala
<p>
Los <em/widgets/ de escala se usan para determinar el valor de una
cantidad que se puede interpretar visualmente. El usuario
probablemente fijará el valor a ojo. Por ejemplo el <em/widget/
GtkColorSelection contiene <em/widgets/ de escala que controlan las
componentes del color a seleccionar. Normalmente el valor preciso es
menos importante que el efecto visual, por lo que el color se
selecciona con el ratón y no mediante un número concreto.
<!-- ----------------------------------------------------------------- -->
<sect2>Creación de un <em/widget/ de escala
<p>
Existen dos tipos de <em/widgets/ de escala: GtkHScale (que es
horizontal) y GtkVscale (vertical). Como funcionan de la misma manera
los vamos a describir a la vez. Las funciones definidas en
<tt>&lt;gtk/gtkvscale.h&gt;</tt> y <tt>&lt;gtk/gtkhscale.h&gt;</tt>,
crean <em/widgets/ de escala verticales y horizontales
respectivamente.
<tscreen><verb>
GtkWidget* gtk_vscale_new( GtkAdjustment *adjustment );
GtkWidget* gtk_hscale_new( GtkAdjustment *adjustment );
</verb></tscreen>
El <tt/ajuste/ (adjustment) puede ser tanto un ajuste creado
mediante <tt/gtk_adjustment_new()/ como <tt/NULL/. En este
último caso se crea un GtkAdjustment anónimo con todos sus
valores iguales a <tt/0.0/. Si no ha quedado claro el uso de esta
función consulte la sección <ref id="sec_Adjustment"
name="Ajustes"> para una discusión más detallada.
<!-- ----------------------------------------------------------------- -->
<sect2> Funciones y señales
<p>
Los <em/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:
<tscreen><verb>
void gtk_scale_set_draw_value( GtkScale *scale,
gint draw_value );
</verb></tscreen>
Los valores posibles de <tt/draw_value son/ son <tt/TRUE/ o <tt/FALSE/.
Con el primero se muestra el valor y con el segundo no.
El valor mostrado por un <em/widget/ de escala por defecto se redondea
a un valor decimal (igual que con <tt/value/ en un GtkAdjustment). Se
puede cambiar con:
<tscreen>
<verb>
void gtk_scale_set_digits( GtkScale *scale,
gint digits );
</verb>
</tscreen>
donde <tt/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:
<tscreen>
<verb>
void gtk_scale_set_value_pos( GtkScale *scale,
GtkPositionType pos );
</verb>
</tscreen>
Si ha leido la sección acerca del <em/widget/ libro de notas
entonces ya conoce cuales son los valores posibles de <tt/pos/. Estan
definidos en <tt>&lt;gtk/gtkscale.h&gt;</tt> como <tt/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:
<tt>&lt;gtk/gtkscale.h&gt;</tt>.
</sect2>
</sect1>
<!-- ----------------------------------------------------------------- -->
<sect1> Funciones comunes <label id="sec_funciones_range">
<p>
La descripción interna de la clase GtkRange es bastante complicada,
pero al igual que con el resto de las «clases base» sólo es
interesante si se quiere «hackear». Casi todas las señales y
funciones sólo son útiles para desarrollar derivados. Para un
usuario normal las funciones interesantes son aquellas definidas en:
<tt>&lt;gtk/gtkrange.h&gt;</tt> y funcionan igual en todos los
<em/widgets/ de rango.
<!-- ----------------------------------------------------------------- -->
<sect2> Estableciendo cada cúanto se actualizan
<p>
La política de actualización de un <em/widget/ define en que
puntos de la interacción con el usuario debe cambiar el valor
<tt/value/ en su GtkAdjustment y emitir la señal
«value_changed». Las actualizaciones definidas en
<tt>&lt;gtk/gtkenums.h&gt;</tt> como <tt>enum GtkUpdateType</tt>, son:
<itemize>
<item>GTK_UPDATE_POLICY_CONTINUOUS - Este es el valor por defecto.La
señal «value_changed» se emite continuamente, por ejemplo cuando
la barra deslizante se mueve incluso aunque sea un poquito.
</item>
<item>GTK_UPDATE_POLICY_DISCONTINUOUS - La señal «value_changed»
sólo se emite cuando se ha parado de mover la barra y el usuario ha
soltado el botón del ratón.
</item>
<item>GTK_UPDATE_POLICY_DELAYED - La señal sólo se emite cuando
el usuario suelta el botón del ratón o si la barra no se mueve
durante un periodo largo de tiempo.
</item>
</itemize>
Para establecer la política de actualización se usa la
conversión definida en la macro
<tscreen><verb>
void gtk_range_set_update_policy( GtkRange *range,
GtkUpdateType policy) ;
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2>Obteniendo y estableciendo Ajustes
<p>
Para obtener o establecer el ajuste de un <em/widget/ de rango se usa:
<tscreen><verb>
GtkAdjustment* gtk_range_get_adjustment( GtkRange *range );
void gtk_range_set_adjustment( GtkRange *range,
GtkAdjustment *adjustment );
</verb></tscreen>
La función <tt/gtk_range_get_adjustment()/ devuelve un puntero al
ajuste al que <tt/range/ esté conectado.
La función <tt/gtk_range_set_adjustment()/ no hace nada si se le
pasa como argumento el valor <tt/range/ del ajuste que esta siendo
usado (aunque se haya modificado algún valor). En el caso de que
sea un ajuste nuevo (GtkAdjustment) dejará de usar el antiguo
(probablemente lo destruirá) y conectará las señales
apropiadas al nuevo. A continuación llamará a la función
<tt/gtk_range_adjustment_changed()/ que en teoría recalculará el
tamaño y/o la posición de la barra, redibujándola en caso de
que sea necesario. Tal y como se mencionó en la sección de los
ajustes si se quiere reusar el mismo GtkAdjustment cuando se modifican
sus valores se debe emitir la señal «changed». Por ejemplo:
<tscreen><verb>
gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");
</verb></tscreen>
</sect2>
</sect1>
<!-- ----------------------------------------------------------------- -->
<sect1> Enlaces con el teclado y el ratón
<p>
Todos los <em/widgets/ de rango reaccionan más o menos de la misma
manera a las pulsaciones del ratón. Al pulsar el botón 1 sobre
el rectángulo de la barra el <tt/value/ del ajuste aumentará o
disminuirá según <tt/page_increment/. Con el botón 2 la barra
se desplazará al punto en el que el botón fue pulsado. Con cada
pulsación de cualquier botón sobre las flechas el valor del
ajuste se modifica una cantidad igual a <tt/step_increment/.
Acostumbrarse a que tanto las barras deslizantes como los <em/widgets/ de
escala puedan tomar la atención del teclado puede ser un proceso largo.
Si que se cree que los usuarios no lo van a entender se puede anular
mediante la función GTK_WIDGET_UNSET_FLAGS y con GTK_CAN_FOCUS como
argumento:
<tscreen><verb>
GTK_WIDGET_UNSET_FLAGS (scrollbar, GTK_CAN_FOCUS);
</verb></tscreen>
Los enlaces entre teclas (que sólo estan activos cuando el
<em/widget/ tiene la atención (focus)) se comportan de manera
diferente para los <em/widgets/ de rango horizontales que para los
verticales. También son diferentes para los <em/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)
<sect2><em/Widgets/ de rango vertical
<p>
Todos los <em/widgets/ de rango pueden ser manipulados con las teclas
arriba, abajo, <tt/Re Pág/, <tt/ Av Pág/. Las flechas mueven las
barras la cantidad fijada mediante <tt/step_increment/, mientras que
<tt/Re Pág/ y <tt/Av Pag/ lo hacen según <tt/page_increment/.
El usuario también puede mover la barra de un extremo al otro de la
guía mediante el teclado. Con el <em/widget/ GtkVScale podemos ir a
los extremos utilizando las teclas <tt/Inicio/ y <tt/Final/ mientras
que con el <em/widget/ GtkVScrollbar habrá que utilizar
<tt/Control-Re Pág/ y <tt/Control-Av Pág/.
<!-- ----------------------------------------------------------------- -->
<sect2><em/Widgets/ de rango horizontal
<p>
Las teclas izquierda y derecha funcionan tal y como espera que
funcionen en estos <em/widgets/: mueven la barra una cantidad dada por
<tt/step_increment/. A su vez <tt/Inicio/ y <tt/Final/ sirven para
pasar de un extremo al otro de la guía. Para el <em/widget/
GtkHScale el mover la barra una cantidad dada por <tt/page_increment/
se consigue mediante <tt>Control-Izquierda</tt> y
<tt>Control-derecha</tt>, mientras que para el <em/widget/
GtkHScrollbar se consigue con <tt/Control-Inicio/ y
<tt/Control-Final/.
</sect2>
</sect1>
<!-- ----------------------------------------------------------------- -->
<sect1> Ejemplo <label id="sec_Ejemplo_Rango">
<p>
Este ejemplo es una versión modificada del test «range controls»
que a su vez forma parte de <tt/testgtk.c/. Simplemente dibuja una
ventana con tres <em/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
<em/widgets/ al ser manipulados por el usuario.
<tscreen><verb>
/* principio del ejemplo widgets de selección de rango rangewidgets.c */
#include <gtk/gtk.h>
GtkWidget *hscale, *vscale;
void cb_pos_menu_select( GtkWidget *item,
GtkPositionType pos )
{
/* Establece el valor position en los widgets de escala */
gtk_scale_set_value_pos (GTK_SCALE (hscale), pos);
gtk_scale_set_value_pos (GTK_SCALE (vscale), pos);
}
void cb_update_menu_select( GtkWidget *item,
GtkUpdateType policy )
{
/* Establece la política de actualización para los widgets
* de escala */
gtk_range_set_update_policy (GTK_RANGE (hscale), policy);
gtk_range_set_update_policy (GTK_RANGE (vscale), policy);
}
void cb_digits_scale( GtkAdjustment *adj )
{
/* Establece el número de cifras decimales a las que se
* redondeará adj->value */
gtk_scale_set_digits (GTK_SCALE (hscale), (gint) adj->value);
gtk_scale_set_digits (GTK_SCALE (vscale), (gint) adj->value);
}
void cb_page_size( GtkAdjustment *get,
GtkAdjustment *set )
{
/* Establece el tamaño de la página y el incremento del
* ajuste al valor especificado en la escala "Page Size" */
set->page_size = get->value;
set->page_increment = get->value;
/* Ahora emite la señal "changed" para reconfigurar todos los
* widgets que están enlazados a este ajuste */
gtk_signal_emit_by_name (GTK_OBJECT (set), "changed");
}
void cb_draw_value( GtkToggleButton *boton )
{
/* Activa o desactiva el valor display en los widgets de escala
* dependiendo del estado del botón de comprobación */
gtk_scale_set_draw_value (GTK_SCALE (hscale), boton->active);
gtk_scale_set_draw_value (GTK_SCALE (vscale), boton->active);
}
/* Funciones varias */
GtkWidget *make_menu_item( gchar *name,
GtkSignalFunc callback,
gpointer data )
{
GtkWidget *item;
item = gtk_menu_item_new_with_label (name);
gtk_signal_connect (GTK_OBJECT (item), "activate",
callback, data);
gtk_widget_show (item);
return(item);
}
void scale_set_default_values( GtkScale *scale )
{
gtk_range_set_update_policy (GTK_RANGE (scale),
GTK_UPDATE_CONTINUOUS);
gtk_scale_set_digits (scale, 1);
gtk_scale_set_value_pos (scale, GTK_POS_TOP);
gtk_scale_set_draw_value (scale, TRUE);
}
/* crea la ventana principal */
void create_range_controls( void )
{
GtkWidget *ventana;
GtkWidget *caja1, *caja2, *caja3;
GtkWidget *boton;
GtkWidget *scrollbar;
GtkWidget *separator;
GtkWidget *opt, *menu, *item;
GtkWidget *etiqueta;
GtkWidget *scale;
GtkObject *adj1, *adj2;
/* creación estándar de una ventana */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
gtk_window_set_title (GTK_WINDOW (ventana), "range controls");
caja1 = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (ventana), caja1);
gtk_widget_show (caja1);
caja2 = gtk_hbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
/* value, lower, upper, step_increment, page_increment, page_size */
/* Observe que el valor de page_size solo sirve para los widgets
* barras de desplazamiento (scrollbar), y que el valor más
* alto que obtendrá será (upper - page_size). */
adj1 = gtk_adjustment_new (0.0, 0.0, 101.0, 0.1, 1.0, 1.0);
vscale = gtk_vscale_new (GTK_ADJUSTMENT (adj1));
scale_set_default_values (GTK_SCALE (vscale));
gtk_box_pack_start (GTK_BOX (caja2), vscale, TRUE, TRUE, 0);
gtk_widget_show (vscale);
caja3 = gtk_vbox_new (FALSE, 10);
gtk_box_pack_start (GTK_BOX (caja2), caja3, TRUE, TRUE, 0);
gtk_widget_show (caja3);
/* Reutilizamos el mismo ajuste */
hscale = gtk_hscale_new (GTK_ADJUSTMENT (adj1));
gtk_widget_set_usize (GTK_WIDGET (hscale), 200, 30);
scale_set_default_values (GTK_SCALE (hscale));
gtk_box_pack_start (GTK_BOX (caja3), hscale, TRUE, TRUE, 0);
gtk_widget_show (hscale);
/* Reutilizamos de nuevo el mismo ajuste */
scrollbar = gtk_hscrollbar_new (GTK_ADJUSTMENT (adj1));
/* Observe que con esto conseguimos que la escala siempre se
* actualice de una forma continua cuando se mueva la barra de
* desplazamiento */
gtk_range_set_update_policy (GTK_RANGE (scrollbar),
GTK_UPDATE_CONTINUOUS);
gtk_box_pack_start (GTK_BOX (caja3), scrollbar, TRUE, TRUE, 0);
gtk_widget_show (scrollbar);
caja2 = gtk_hbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
/* Un botón para comprobar si el valor se muestra o no*/
boton = gtk_check_button_new_with_label("Display value on scale widgets");
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (boton), TRUE);
gtk_signal_connect (GTK_OBJECT (boton), "toggled",
GTK_SIGNAL_FUNC(cb_draw_value), NULL);
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
gtk_widget_show (boton);
caja2 = gtk_hbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
/* Una opción en el menú para cambiar la posición del
* valor */
etiqueta = gtk_label_new ("Scale Value Position:");
gtk_box_pack_start (GTK_BOX (caja2), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
opt = gtk_option_menu_new();
menu = gtk_menu_new();
item = make_menu_item ("Top",
GTK_SIGNAL_FUNC(cb_pos_menu_select),
GINT_TO_POINTER (GTK_POS_TOP));
gtk_menu_append (GTK_MENU (menu), item);
item = make_menu_item ("Bottom", GTK_SIGNAL_FUNC (cb_pos_menu_select),
GINT_TO_POINTER (GTK_POS_BOTTOM));
gtk_menu_append (GTK_MENU (menu), item);
item = make_menu_item ("Left", GTK_SIGNAL_FUNC (cb_pos_menu_select),
GINT_TO_POINTER (GTK_POS_LEFT));
gtk_menu_append (GTK_MENU (menu), item);
item = make_menu_item ("Right", GTK_SIGNAL_FUNC (cb_pos_menu_select),
GINT_TO_POINTER (GTK_POS_RIGHT));
gtk_menu_append (GTK_MENU (menu), item);
gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
gtk_box_pack_start (GTK_BOX (caja2), opt, TRUE, TRUE, 0);
gtk_widget_show (opt);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
caja2 = gtk_hbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
/* Sí, otra opción de menú, esta vez para la política
* de actualización de los widgets */
etiqueta = gtk_label_new ("Scale Update Policy:");
gtk_box_pack_start (GTK_BOX (caja2), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
opt = gtk_option_menu_new();
menu = gtk_menu_new();
item = make_menu_item ("Continuous",
GTK_SIGNAL_FUNC (cb_update_menu_select),
GINT_TO_POINTER (GTK_UPDATE_CONTINUOUS));
gtk_menu_append (GTK_MENU (menu), item);
item = make_menu_item ("Discontinuous",
GTK_SIGNAL_FUNC (cb_update_menu_select),
GINT_TO_POINTER (GTK_UPDATE_DISCONTINUOUS));
gtk_menu_append (GTK_MENU (menu), item);
item = make_menu_item ("Delayed",
GTK_SIGNAL_FUNC (cb_update_menu_select),
GINT_TO_POINTER (GTK_UPDATE_DELAYED));
gtk_menu_append (GTK_MENU (menu), item);
gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
gtk_box_pack_start (GTK_BOX (caja2), opt, TRUE, TRUE, 0);
gtk_widget_show (opt);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
caja2 = gtk_hbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
/* Un widget GtkHScale para ajustar el número de dígitos en
* la escala. */
etiqueta = gtk_label_new ("Scale Digits:");
gtk_box_pack_start (GTK_BOX (caja2), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
adj2 = gtk_adjustment_new (1.0, 0.0, 5.0, 1.0, 1.0, 0.0);
gtk_signal_connect (GTK_OBJECT (adj2), "value_changed",
GTK_SIGNAL_FUNC (cb_digits_scale), NULL);
scale = gtk_hscale_new (GTK_ADJUSTMENT (adj2));
gtk_scale_set_digits (GTK_SCALE (scale), 0);
gtk_box_pack_start (GTK_BOX (caja2), scale, TRUE, TRUE, 0);
gtk_widget_show (scale);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
caja2 = gtk_hbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
/* Y un último widget GtkHScale para ajustar el tamaño de la
* página de la barra de desplazamiento. */
etiqueta = gtk_label_new ("Scrollbar Page Size:");
gtk_box_pack_start (GTK_BOX (caja2), etiqueta, FALSE, FALSE, 0);
gtk_widget_show (etiqueta);
adj2 = gtk_adjustment_new (1.0, 1.0, 101.0, 1.0, 1.0, 0.0);
gtk_signal_connect (GTK_OBJECT (adj2), "value_changed",
GTK_SIGNAL_FUNC (cb_page_size), adj1);
scale = gtk_hscale_new (GTK_ADJUSTMENT (adj2));
gtk_scale_set_digits (GTK_SCALE (scale), 0);
gtk_box_pack_start (GTK_BOX (caja2), scale, TRUE, TRUE, 0);
gtk_widget_show (scale);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
separator = gtk_hseparator_new ();
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 0);
gtk_widget_show (separator);
caja2 = gtk_vbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, TRUE, 0);
gtk_widget_show (caja2);
boton = gtk_button_new_with_label ("Quit");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
GTK_WIDGET_SET_FLAGS (boton, GTK_CAN_DEFAULT);
gtk_widget_grab_default (boton);
gtk_widget_show (boton);
gtk_widget_show (ventana);
}
int main( int argc,
char *argv[] )
{
gtk_init(&amp;argc, &amp;argv);
create_range_controls();
gtk_main();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
Observe que el programa no llama a <tt/gtk_signal_connect/ para
conectar el «delete_event», y que sólo conecta la señal
«destroy». Con esto seguimos realizando la función deseada, ya que
un «delete_event» no manejado desenboca en una señal «destroy»
para la ventana.
</sect1>
</sect>
<!-- ***************************************************************** -->
<sect><em/Widgets/ varios
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1> Etiquetas
<p>
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 <em/widget/
EventBox o un <em/widget/ botón.
Para crear una nueva etiqueta se usa:
<tscreen><verb>
GtkWidget *gtk_label_new( char *str );
</verb></tscreen>
El único argumento es la cadena de texto que se quiere mostrar.
Para cambiarla después de que haya sido creada se usa:
<tscreen><verb>
void gtk_label_set( GtkLabel *etiqueta,
char *str );
</verb></tscreen>
En este caso el primer argumento es la etiqueta ya creada (cambiado su
tipo mediante la macro <tt/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:
<tscreen><verb>
void gtk_label_get( GtkLabel *etiqueta,
char **str );
</verb></tscreen>
El primer argumento es la etiqueta, mientras que el segundo es el
valor devuelto para la cadena. No libere la memoria de la cadena
devuelta, ya que se utiliza internamente por GTK.
El texto de la etiqueta se puede justificar utilizando:
<tscreen><verb>
void gtk_label_set_justify( GtkLabel *etiqueta,
GtkJustification jtype );
</verb></tscreen>
Los valores posibles para <tt/jtype/ son:
<itemize>
<item> GTK_JUSTIFY_LEFT
<item> GTK_JUSTIFY_RIGHT
<item> GTK_JUSTIFY_CENTER (the default)
<item> GTK_JUSTIFY_FILL
</itemize>
El <em/widget/ etiqueta también es capaz de separar el texto de forma
automática cuando se llega al final de una linea. Esto se puede
conseguir utilizando:
<tscreen><verb>
void gtk_label_set_line_wrap (GtkLabel *etiqueta,
gboolean wrap);
</verb></tscreen>
El argumento <tt/wrap/ toma el valor TRUE o FALSE.
Si quiere que su etiqueta salga subrayada, puede especificar un motivo
para el subrayado con:
<tscreen><verb>
void gtk_label_set_pattern (GtkLabel *etiqueta,
const gchar *pattern);
</verb></tscreen>
El argumento <tt/pattern/ indica cual debe ser el aspecto del
subrayado. Consiste en una cadena de espacios en blanco y carácteres
de subrayado. Por ejemplo, la cadena <tt/"__ __"/ debe hacer que
se subrayen los dos primeros y el octavo y el noveno carácter.
A continuación tenemos un pequeño ejemplo que ilustra el uso de estas
funciones. Este ejemplo utiliza el <em/widget/ marco (<em/frame/) para
hacer una mejor demostración de los estilos de la etiqueta. Por ahora
puede ignorarlo, ya que el <em/widget/ <ref id="sec_Frames"
name="Frame"> se explicará más tarde.
<tscreen><verb>
/* principio del ejemplo label label.c */
#include <gtk/gtk.h>
int main( int argc,
char *argv[] )
{
static GtkWidget *ventana = NULL;
GtkWidget *hbox;
GtkWidget *vbox;
GtkWidget *frame;
GtkWidget *etiqueta;
/* Inicializa GTK */
gtk_init(&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
gtk_window_set_title (GTK_WINDOW (ventana), "Etiqueta");
vbox = gtk_vbox_new (FALSE, 5);
hbox = gtk_hbox_new (FALSE, 5);
gtk_container_add (GTK_CONTAINER (ventana), hbox);
gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (ventana), 5);
frame = gtk_frame_new ("Normal Label");
etiqueta = gtk_label_new ("This is a Normal label");
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
frame = gtk_frame_new ("Multi-line Label");
etiqueta = gtk_label_new ("This is a Multi-line label.\nSecond line\n" \
"Third line");
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
frame = gtk_frame_new ("Left Justified Label");
etiqueta = gtk_label_new ("This is a Left-Justified\n" \
"Multi-line label.\nThird line");
gtk_label_set_justify (GTK_LABEL (etiqueta), GTK_JUSTIFY_LEFT);
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
frame = gtk_frame_new ("Right Justified Label");
etiqueta = gtk_label_new ("This is a Right-Justified\nMulti-line label.\n" \
"Fourth line, (j/k)");
gtk_label_set_justify (GTK_LABEL (etiqueta), GTK_JUSTIFY_RIGHT);
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
vbox = gtk_vbox_new (FALSE, 5);
gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
frame = gtk_frame_new ("Line wrapped label");
etiqueta = gtk_label_new ("This is an example of a line-wrapped label. It " \
"should not be taking up the entire " /* big space to test spacing */\
"width allocated to it, but automatically " \
"wraps the words to fit. " \
"The time has come, for all good men, to come to " \
"the aid of their party. " \
"The sixth sheik's six sheep's sick.\n" \
" It supports multiple paragraphs correctly, " \
"and correctly adds "\
"many extra spaces. ");
gtk_label_set_line_wrap (GTK_LABEL (etiqueta), TRUE);
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
frame = gtk_frame_new ("Filled, wrapped label");
etiqueta = gtk_label_new ("This is an example of a line-wrapped, filled label. " \
"It should be taking "\
"up the entire width allocated to it. " \
"Here is a seneance to prove "\
"my point. Here is another sentence. "\
"Here comes the sun, do de do de do.\n"\
" This is a new paragraph.\n"\
" This is another newer, longer, better " \
"paragraph. It is coming to an end, "\
"unfortunately.");
gtk_label_set_justify (GTK_LABEL (etiqueta), GTK_JUSTIFY_FILL);
gtk_label_set_line_wrap (GTK_LABEL (etiqueta), TRUE);
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
frame = gtk_frame_new ("Underlined label");
etiqueta = gtk_label_new ("This label is underlined!\n"
"This one is underlined in quite a funky fashion");
gtk_label_set_justify (GTK_LABEL (etiqueta), GTK_JUSTIFY_LEFT);
gtk_label_set_pattern (GTK_LABEL (etiqueta),
"_________________________ _ _________ _ ______ __ _______ ___");
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show_all (ventana);
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Flechas
<p>
En <em/widget/ flecha (<em/arrow/) dibuja la punta de una flecha,
con un estilo y hacia una dirección a escoger. Puede ser muy útil
en muchas aplicaciones cuando se coloca en un botón.
Sólo hay dos funciones para manipular el <em/widget/ flecha:
<tscreen><verb>
GtkWidget *gtk_arrow_new( GtkArrowType arrow_type,
GtkShadowType shadow_type );
void gtk_arrow_set( GtkArrow *arrow,
GtkArrowType arrow_type,
GtkShadowType shadow_type );
</verb></tscreen>
La primera crea un nuevo <em/widget/ flecha del tipo y apariencia
indicados. La segunda permite alterar posteriormente estos valores. El
argumento <tt/arrow_type/ puede tomar uno de los valores siguientes:
<itemize>
<item> GTK_ARROW_UP
<item> GTK_ARROW_DOWN
<item> GTK_ARROW_LEFT
<item> GTK_ARROW_RIGHT
</itemize>
Naturalmente, estos valores indican la dirección a la que debe apuntar
la flecha. El argumento <tt/shadow_type/ puede tomar uno de los
valores siguientes:
<itemize>
<item> GTK_SHADOW_IN
<item> GTK_SHADOW_OUT (por defecto)
<item> GTK_SHADOW_ETCHED_IN
<item> GTK_SHADOW_ETCHED_OUT
</itemize>
Aquí tenemos un pequeño ejemplo para ilustrar la utilización de la
flecha.
<tscreen><verb>
/* principio del ejemplo arrow arrow.c */
#include <gtk/gtk.h>
/* Crea un widget flecha con los parámetros especificados
* y lo empaqueta en un botón */
GtkWidget *create_arrow_button( GtkArrowType arrow_type,
GtkShadowType shadow_type )
{
GtkWidget *boton;
GtkWidget *arrow;
boton = gtk_button_new();
arrow = gtk_arrow_new (arrow_type, shadow_type);
gtk_container_add (GTK_CONTAINER (boton), arrow);
gtk_widget_show(boton);
gtk_widget_show(arrow);
return(boton);
}
int main( int argc,
char *argv[] )
{
/* GtkWidget es el tipo utilizado para los widgets */
GtkWidget *ventana;
GtkWidget *boton;
GtkWidget *box;
/* Inicializa el toolkit */
gtk_init (&amp;argc, &amp;argv);
/* Crea una nueva ventana */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Arrow Buttons");
/* Es una buena idea hacer esto con todas las ventanas. */
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
/* Establece el ancho del borde de la ventana. */
gtk_container_set_border_width (GTK_CONTAINER (ventana), 10);
/* Crea una caja para almacenar las flechas/botones */
box = gtk_hbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (box), 2);
gtk_container_add (GTK_CONTAINER (ventana), box);
/* Empaqueta y muestra todos nuestros widgets */
gtk_widget_show(box);
boton = create_arrow_button(GTK_ARROW_UP, GTK_SHADOW_IN);
gtk_box_pack_start (GTK_BOX (box), boton, FALSE, FALSE, 3);
boton = create_arrow_button(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
gtk_box_pack_start (GTK_BOX (box), boton, FALSE, FALSE, 3);
boton = create_arrow_button(GTK_ARROW_LEFT, GTK_SHADOW_ETCHED_IN);
gtk_box_pack_start (GTK_BOX (box), boton, FALSE, FALSE, 3);
boton = create_arrow_button(GTK_ARROW_RIGHT, GTK_SHADOW_ETCHED_OUT);
gtk_box_pack_start (GTK_BOX (box), boton, FALSE, FALSE, 3);
gtk_widget_show (ventana);
/* Nos quedamos en gtk_main y ¡esperamos que empiece la diversión! */
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>El <em/widget/ de información rápida (<em/tooltip/)
<p>
Estos <em/widgets/ son las pequeñas etiquetas que texto que
aparecen cuando se sitúa el puntero del ratón sobre un botón
u otro <em/widget/ durante algunos segundos. Son bastante fáciles
de usar, así que no se dará ningún ejemplo. Si quiere ver
algún ejemplo se recomienda leer el programa testgtk.c que
acompaña a GTK.
Algunos <em/widgets/ (como la etiqueta) no pueden llevar asociado un
<em/tooltip/.
Para cada función sólo hay que hacer una llamada para conseguir
un <em/tooltip/. El objeto <tt/GtkTooltip/ que devuelve la siguiente
función puede ser usado para crear múltiples <em/widgets/.
<tscreen><verb>
GtkTooltips *gtk_tooltips_new( void );
</verb></tscreen>
Una vez que el <em/tooltip/ ha sido creado (y el <em/widget/ sobre el
que se quiere usar) simplemente hay que usar la siguiente llamada para
pegarlo:
<tscreen><verb>
void gtk_tooltips_set_tip( GtkTooltips *tooltips,
GtkWidget *widget,
const gchar *tip_text,
const gchar *tip_private );
</verb></tscreen>
El primer argumento es el <em/tooltip/ que ya ha creado, seguido del
<em/widget/ al que se desea asociar el <em/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.
<!-- TODO: sort out what how to do the context sensitive help -->
Veamos un ejemplo:
<tscreen><verb>
GtkTooltips *tooltips;
GtkWidget *boton;
...
tooltips = gtk_tooltips_new ();
boton = gtk_button_new_with_label ("botón 1");
...
gtk_tooltips_set_tip (tooltips, boton, "Este es el botón 1", NULL);
</verb></tscreen>
Existen otras funciones que pueden ser usadas con los <em/tooltips/.
Solamente vamos a enumerlarlas añadiendo una pequeña descripción
de que hace cada una.
<tscreen><verb>
void gtk_tooltips_enable( GtkTooltips *tooltips );
</verb></tscreen>
Permite que funcionen un conjunto de <em/tooltips/
<tscreen><verb>
void gtk_tooltips_disable( GtkTooltips *tooltips );
</verb></tscreen>
Oculta un conjunto de <em/tooltips/ para que no pueda ser mostrado.
<tscreen><verb>
void gtk_tooltips_set_delay( GtkTooltips *tooltips,
gint delay );
</verb></tscreen>
Establece cuantos milisegundos tiene que estar el puntero sobre el
<em/widget/ para que aparezca el <em/tooltip/. Por defecto se usan 1000
milisegundos (1 segundo).
<tscreen><verb>
void gtk_tooltips_set_colors( GtkTooltips *tooltips,
GdkColor *background,
GdkColor *foreground );
</verb></tscreen>
Establece el color del texto y del fondo del <em/tooltip/. No se como
se especifica el color.
<!-- ----------------------------------------------------------------- -->
<sect1> Barras de progreso <label id="sec_ProgressBar">
<p>
Estas barras se usan para mostrar el estado de una operación. Son
bastante sencillas de utilizar, tal y como se verá en los ejemplos
siguientes. Pero primero vamos a ver cuales son las funciones que hay
que utilizar para crear una nueva barra de progreso.
Hay dos formas de crear una nueva barra de progreso, la sencilla no
necesita de argumentos, y la otra recibe un objeto GtkAdjustment. Si
se utiliza la primera forma, la barra de progreso creará su propio
GtkAdjustment.
<tscreen><verb>
GtkWidget *gtk_progress_bar_new( void );
GtkWidget *gtk_progress_bar_new_with_adjustment( GtkAdjustment *adjustment );
</verb></tscreen>
El segundo método tiene la ventaja de que podemos utilizar el objeto
adjustment para especificar nuestro propio rango de parámetros para la
barra de progreso.
El ajuste de una barra de progreso se puede cambiar de forma dinámica
utilizando:
<tscreen><verb>
void gtk_progress_set_adjustment( GtkProgress *progress,
GtkAdjustment *adjustment );
</verb></tscreen>
Ahora que hemos creado la barra de progreso ya podemos utilizarla.
<tscreen><verb>
void gtk_progress_bar_update( GtkProgressBar *pbar,
gfloat percentage );
</verb></tscreen>
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.
GTK+ v1.2 ha añadido una nueva característica a la barra de progreso,
y es que ahora permite mostrar su valor de varias maneras distintas, e
informar al usuario del valor y rango actual.
Una barra de progreso puede mostrarse con distintas orientaciones
utilizando la función
<tscreen><verb>
void gtk_progress_bar_set_orientation( GtkProgressBar *pbar,
GtkProgressBarOrientation orientation );
</verb></tscreen>
Donde el argumento <tt/orientación/ puede tomar uno de los valores que
vienen a continuación para indicar la dirección en la que se mueve la
barra de progreso:
<itemize>
<item> GTK_PROGRESS_LEFT_TO_RIGHT
<item> GTK_PROGRESS_RIGHT_TO_LEFT
<item> GTK_PROGRESS_BOTTOM_TO_TOP
<item> GTK_PROGRESS_TOP_TO_BOTTOM
</itemize>
Cuando se utiliza como una medida de cuanto se ha completado de un
proceso, la barra de progreso puede configurarse para que muestre su
valor de una forma continua o discreta. En modo continuo, la barra de
progreso se actualiza mediante un número discreto de bloques, el
número de bloques también es configurable.
Se puede configurar el estilo de la barra de progreso utilizando la
siguiente función:
<tscreen><verb>
void gtk_progress_bar_set_bar_style( GtkProgressBar *pbar,
GtkProgressBarStyle style );
</verb></tscreen>
El parámetro <tt/style/ puede tomar uno de los dos valores siguientes:
<itemize>
<item>GTK_PROGRESS_CONTINUOUS
<item>GTK_PROGRESS_DISCRETE
</itemize>
El número de bloques se puede establecer utilizando
<tscreen><verb>
void gtk_progress_bar_set_discrete_blocks( GtkProgressBar *pbar,
guint blocks );
</verb></tscreen>
La barra de progreso también se puede utilizar, a parte de para
indicar lo «avanzado» de una tarea, para indicar que hay algún tipo
de actividad. Esto puede ser útil en situaciones donde no se pueda
medir el progreso de una tarea con un rango de valores. Para el modo
actividad, no sirve el estilo de barra que se ha descrito más
arriba. Este modo hay que seleccionarlo utilizando la siguiente
función:
<tscreen><verb>
void gtk_progress_set_activity_mode( GtkProgress *progress,
guint activity_mode );
</verb></tscreen>
El tamaño del paso del indicador de actividad, y el número de bloques
se indican usando las siguientes funciones:
<tscreen><verb>
void gtk_progress_bar_set_activity_step( GtkProgressBar *pbar,
guint step );
void gtk_progress_bar_set_activity_blocks( GtkProgressBar *pbar,
guint blocks );
</verb></tscreen>
Cuando estamos en modo continuo, la barra de progreso puede mostrar un
texto configurable dentro la barra misma, utilizando la función
siguiente:
<tscreen><verb>
void gtk_progress_set_format_string( GtkProgress *progress,
gchar *format);
</verb></tscreen>
El argumento <tt/format/ es parecido al que se utiliza en una orden
<tt/printf/ de C. Se pueden utilizar las siguientes opciones para el
formateado de la cadena:
<itemize>
<item> %p - porcentaje
<item> %v - valor
<item> %l - valor inferior del rango
<item> %u - valor superior del rango
</itemize>
Puede activar o desactivar el texto utilizando:
<tscreen><verb>
void gtk_progress_set_show_text( GtkProgress *progress,
gint show_text );
</verb></tscreen>
El argumento <tt/show_text/ es un valor booleano TRUE/FALSE. La
apariencia del texto puede modificarse utilizando:
<tscreen><verb>
void gtk_progress_set_text_alignment( GtkProgress *progress,
gfloat x_align,
gfloat y_align );
</verb></tscreen>
Los argumentos <tt/x_align/ y <tt/y_align/ toman un valor entre 0.0 y
1.0. Este valor indica la posición de la cadena de texto dentro de la
barra. Si ponemos 0.0 en los dos sitios la cadena de texto aparecerá
en la esquina superior izquierda; un valor de 0.5 (el que se utiliza
por defecto) centra el texto, y un valor de 1.0 coloca el texto en la
esquina inferior derecha.
Se pueden leer los parámetros actuales del texto de un objeto barra
de progreso utilizando las dos funciones que se muestran a
continuación. La cadena de carácteres devuelta por estas funciones
debe liberarse en la aplicación (utilizando la función
g_free()). Estas funciones devuelven el texto formateado que se
mostrará en la barra.
<tscreen><verb>
gchar *gtk_progress_get_current_text( GtkProgress *progress );
gchar *gtk_progress_get_text_from_value( GtkProgress *progress,
gfloat value );
</verb></tscreen>
Hay otra forma de cambiar el rango y el valor de un objeto barra de
progreso utilizando la función:
<tscreen><verb>
void gtk_progress_configure( GtkProgress *progress,
gfloat value,
gfloat min,
gfloat max );
</verb></tscreen>
Esta función proporciona una interfaz sencilla al rango y valor de una
barra de progreso.
Las funciones restantes se pueden utilizar para obtener y establecer
el valor actual de una barra de progreso utilizando distintos tipos y
formatos para el valor.
<tscreen><verb>
void gtk_progress_set_percentage( GtkProgress *progress,
gfloat percentage );
void gtk_progress_set_value( GtkProgress *progress,
gfloat value );
gfloat gtk_progress_get_value( GtkProgress *progress );
gfloat gtk_progress_get_current_percentage( GtkProgress *progress );
gfloat gtk_progress_get_percentage_from_value( GtkProgress *progress,
gfloat value );
</verb></tscreen>
Estas funciones son autoexplicatorias. La última función utiliza el
ajuste de la barra de progreso especificada para calcular el
porcentaje dentro del rango de valores de la barra.
Las barras de progreso se usan con otras funciones como los tiempos de
espera (<em/timeouts/), sección <ref id="sec_timeouts"
name="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.
<tscreen><verb>
/* comienzo del programa-ejemplo progressbar.c */
#include <gtk/gtk.h>
#include <gtk/gtk.h>
typedef struct _ProgressData {
GtkWidget *ventana;
GtkWidget *pbar;
int timer;
} ProgressData;
/* Actualiza el valor de la barra de progreso para que
* podamos ver algún movimiento */
gint progress_timeout( gpointer data )
{
gfloat new_val;
GtkAdjustment *adj;
/* Calcula el valor de la barra de progreso utilizando
* el rango de valores establecido en el ajuste de la
* barra */
new_val = gtk_progress_get_value( GTK_PROGRESS(data) ) + 1;
adj = GTK_PROGRESS (data)->adjustment;
if (new_val > adj->upper)
new_val = adj->lower;
/* Establece el nuevo valor */
gtk_progress_set_value (GTK_PROGRESS (data), new_val);
/* Como esta es una función de espera, devolvemos TRUE
* para que continue siendo llamada */
return(TRUE);
}
/* Función de llamada que activa/desactiva el texto de dentro
* de la barra de progreso */
void toggle_show_text( GtkWidget *widget,
ProgressData *pdata )
{
gtk_progress_set_show_text (GTK_PROGRESS (pdata->pbar),
GTK_TOGGLE_BUTTON (widget)->active);
}
/* Función de llamada que activa/desactiva el modo actividad
* de la barra de progreso */
void toggle_activity_mode( GtkWidget *widget,
ProgressData *pdata )
{
gtk_progress_set_activity_mode (GTK_PROGRESS (pdata->pbar),
GTK_TOGGLE_BUTTON (widget)->active);
}
/* Función de llamada que activa/desactiva el modo continuo
* de la barra de progreso */
void set_continuous_mode( GtkWidget *widget,
ProgressData *pdata )
{
gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR (pdata->pbar),
GTK_PROGRESS_CONTINUOUS);
}
/* Función de llamada que activa/desactiva el modo discreto
* de la barra de progreso */
void set_discrete_mode( GtkWidget *widget,
ProgressData *pdata )
{
gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR (pdata->pbar),
GTK_PROGRESS_DISCRETE);
}
/* Libera la memoria y elimina el temporizador */
void destroy_progress( GtkWidget *widget,
ProgressData *pdata)
{
gtk_timeout_remove (pdata->timer);
pdata->timer = 0;
pdata->ventana = NULL;
g_free(pdata);
gtk_main_quit();
}
int main( int argc,
char *argv[])
{
ProgressData *pdata;
GtkWidget *align;
GtkWidget *separator;
GtkWidget *table;
GtkAdjustment *adj;
GtkWidget *boton;
GtkWidget *check;
GtkWidget *vbox;
gtk_init (&amp;argc, &amp;argv);
/* Reserva memoria para los datos que se le pasan a las funciones
* de llamada */
pdata = g_malloc( sizeof(ProgressData) );
pdata->ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_policy (GTK_WINDOW (pdata->ventana), FALSE, FALSE, TRUE);
gtk_signal_connect (GTK_OBJECT (pdata->ventana), "destroy",
GTK_SIGNAL_FUNC (destroy_progress),
pdata);
gtk_window_set_title (GTK_WINDOW (pdata->ventana), "GtkProgressBar");
gtk_container_set_border_width (GTK_CONTAINER (pdata->ventana), 0);
vbox = gtk_vbox_new (FALSE, 5);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
gtk_container_add (GTK_CONTAINER (pdata->ventana), vbox);
gtk_widget_show(vbox);
/* Crea un objeto de alineamiento centrado */
align = gtk_alignment_new (0.5, 0.5, 0, 0);
gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 5);
gtk_widget_show(align);
/* Crea un objeto GtkAdjusment para albergar el rango de la barra
* de progreso */
adj = (GtkAdjustment *) gtk_adjustment_new (0, 1, 150, 0, 0, 0);
/* Crea la GtkProgressBar utilizando el ajuste */
pdata->pbar = gtk_progress_bar_new_with_adjustment (adj);
/* Establece el formato de la cadena de texto que puede mostrarse
* en la barra de progreso:
* %p - porcentaje
* %v - valor
* %l - valor inferior del rango
* %u - valor superior del rango */
gtk_progress_set_format_string (GTK_PROGRESS (pdata->pbar),
"%v from [%l-%u] (=%p%%)");
gtk_container_add (GTK_CONTAINER (align), pdata->pbar);
gtk_widget_show(pdata->pbar);
/* Añade un temporizador para la actualización del valor de la
* barra de progreso */
pdata->timer = gtk_timeout_add (100, progress_timeout, pdata->pbar);
separator = gtk_hseparator_new ();
gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
gtk_widget_show(separator);
/* filas, columnas, homogéneo */
table = gtk_table_new (2, 3, FALSE);
gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0);
gtk_widget_show(table);
/* Añade un botón de comprobación para seleccionar si se debe
* mostrar el texto dentro de la barra */
check = gtk_check_button_new_with_label ("Show text");
gtk_table_attach (GTK_TABLE (table), check, 0, 1, 0, 1,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
5, 5);
gtk_signal_connect (GTK_OBJECT (check), "clicked",
GTK_SIGNAL_FUNC (toggle_show_text),
pdata);
gtk_widget_show(check);
/* Añade un botón de comprobación para activar/desactivar el modo
* actividad */
check = gtk_check_button_new_with_label ("Activity mode");
gtk_table_attach (GTK_TABLE (table), check, 0, 1, 1, 2,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
5, 5);
gtk_signal_connect (GTK_OBJECT (check), "clicked",
GTK_SIGNAL_FUNC (toggle_activity_mode),
pdata);
gtk_widget_show(check);
separator = gtk_vseparator_new ();
gtk_table_attach (GTK_TABLE (table), separator, 1, 2, 0, 2,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
5, 5);
gtk_widget_show(separator);
/* Añade un botón circular para seleccionar el modo continuo */
boton = gtk_radio_button_new_with_label (NULL, "Continuous");
gtk_table_attach (GTK_TABLE (table), boton, 2, 3, 0, 1,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
5, 5);
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (set_continuous_mode),
pdata);
gtk_widget_show (boton);
/* Añade un botón circular para seleccionar el modo discreto */
boton = gtk_radio_button_new_with_label(
gtk_radio_button_group (GTK_RADIO_BUTTON (boton)),
"Discrete");
gtk_table_attach (GTK_TABLE (table), boton, 2, 3, 1, 2,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
5, 5);
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (set_discrete_mode),
pdata);
gtk_widget_show (boton);
separator = gtk_hseparator_new ();
gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
gtk_widget_show(separator);
/* Añade un botón para salir del programa */
boton = gtk_button_new_with_label ("close");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (pdata->ventana));
gtk_box_pack_start (GTK_BOX (vbox), boton, FALSE, FALSE, 0);
/* Esto hace que este botón sea el botón pueda utilizarse por
* defecto defecto. */
GTK_WIDGET_SET_FLAGS (boton, GTK_CAN_DEFAULT);
/* Esto marca este botón para que sea el botón por
* defecto. Simplemente utilizando la tecla "Intro" haremos que se
* active este botón. */
gtk_widget_grab_default (boton);
gtk_widget_show(boton);
gtk_widget_show (pdata->ventana);
gtk_main ();
return(0);
}
/* final del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Cuadros de diálogo
<p>
El <em/widget/ del cuadro de diálogo es bastante simple, sólo es una
ventana con algunas cosas ya preempaquetadas. Su estructura es la
siguiente:
<tscreen><verb>
struct GtkDialog
{
GtkWindow ventana;
GtkWidget *vbox;
GtkWidget *action_area;
};
</verb></tscreen>
Simplemente se crea una ventana en la cual se empaqueta una vbox, un
separador y una hbox llamada «action_area».
Este tipo de <em/widgets/ pueden ser usados como mensages <em/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:
<tscreen><verb>
GtkWidget *gtk_dialog_new( void );
</verb></tscreen>
Para crear un nuevo cuadro de diálogo hay que llamar a:
<tscreen><verb>
GtkWidget *ventana;
ventana = gtk_dialog_new ();
</verb></tscreen>
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í:
<tscreen><verb>
boton = ...
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (ventana)->action_area), boton,
TRUE, TRUE, 0);
gtk_widget_show (boton);
</verb></tscreen>
Otra cosa que nos puede interesar es empaquetar una etiqueta en la
vbox:
<tscreen><verb>
etiqueta = gtk_label_new ("Dialogs are groovy");
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (ventana)->vbox), etiqueta, TRUE,
TRUE, 0);
gtk_widget_show (etiqueta);
</verb></tscreen>
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
<em/widget/ en cualquiera de las cajas (p.j. una tabla en una vbox).
<!-- ----------------------------------------------------------------- -->
<sect1> <em/Pixmaps/ <label id="sec_Pixmaps">
<p>
Los <em/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 <em/bitmap/ es un <em/pixmap/ que sólo tiene dos colores, y hay
unas cuantas rutinas especiales para controlar este caso particular.
Para comprender los <em/pixmaps/, puede ayudar entender como funciona
X-windows. Bajo X-windows, las aplicaciones no tienen porque estar
ejecutándose en el ordenador que está interactuando con el
usuario. Las distintas aplicaciones, llamadas «clientes», comunican
con un programa que muestra los gráficos y que controla el tecledo y
el ratón. Este programa que interactua directamente con el usuario se
llama un «<em/display server/» o «servidor X». Como la
comunicación entre el servidor y el cliente puede llevarse a cabo
mediante una red, es importante mantener alguna información en el
servidor X. Los <em/pixmaps/ por ejemplo, se almacenan en la memoria
del servidor X. Esto significa que una vez que se establecen los
valores del <em/pixmap/, no tienen que estar transmitiéndose por la
red; en su lugar lo único que hay que enviar es una orden del estilo
«mostrar <em/pixmap/ número XYZ aquí». Incluso si no está utilizando
X-windows con GTK, al utilizar construcciones como los <em/pixmaps/
conseguirá que sus programas funciones de forma aceptable bajo
X-windows.
Para usar un <em/pixmap/ en GTK primero tiene que construir una
estructura del tipo GdkPixmap usando rutinas de GDK. Los <em/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.
<tscreen><verb>
GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *ventana,
gchar *data,
gint width,
gint height );
</verb></tscreen>
Esta rutina se utiliza para crear un <em/bitmap/ a partir de datos
almacenados en la memoria. Cada bit de información indica si el
<em/pixel/ luce o no. Tanto la altura como la anchura estan expresadas
en <em/pixels/. El puntero del tipo GdkWindow indica la ventana en
cuestión, ya que los <em/pixmaps/ sólo tienen sentido dentro de
la pantalla en la que van a ser mostrados.
<tscreen><verb>
GdkPixmap *gdk_pixmap_create_from_data( GdkWindow *ventana,
gchar *data,
gint width,
gint height,
gint depth,
GdkColor *fg,
GdkColor *bg );
</verb></tscreen>
Con esto creamos un <em/pixmap/ con la profundidad (número de
colores) especificada en los datos del <em/bitmap/. Los valores
<tt/fg/ y <tt/bg/ son los colores del frente y del fondo
respectivamente.
<tscreen><verb>
GdkPixmap *gdk_pixmap_create_from_xpm( GdkWindow *ventana,
GdkBitmap **mask,
GdkColor *transparent_color,
const gchar *filename );
</verb></tscreen>
El formato XPM es una representacion de los <em/pixmaps/ para el
sistema X Window. Es bastante popular y existen muchos programas para
crear imágenes en este formato. El archivo especificado mediante
<tt/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 <tt/transparent_color/. Más adelante veremos un
ejemplo.
<tscreen><verb>
GdkPixmap *gdk_pixmap_create_from_xpm_d( GdkWindow *ventana,
GdkBitmap **mask,
GdkColor *transparent_color,
gchar **data );
</verb></tscreen>
Se pueden incorporar imágenes pequeñas dentro de un programa en
formato XPM. Un <em/pixmap/ se crea usando esta información, en
lugar de leerla de un archivo. Un ejemplo sería:
<tscreen><verb>
/* 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. ",
" ......... ",
" ",
" "};
</verb></tscreen>
Cuando hayamos acabado de usar un <em/pixmap/ y no lo vayamos a usar
durante un tiempo suele ser conveniente liberar el recurso mediante
gdk_pixmap_unref(). (Los <em/pixmaps/ deben ser considerados recursos
preciosos).
Una vez que hemos creado el <em/pixmap/ lo podemos mostrar como un
<em/widget/ GTK. Primero tenemos que crear un <em/widget pixmap/ que
contenga un <em/pixmap/ GDK. Esto se hace usando:
<tscreen><verb>
GtkWidget *gtk_pixmap_new( GdkPixmap *pixmap,
GdkBitmap *mask );
</verb></tscreen>
Las otras funciones del <em/widget pixmap/ son:
<tscreen><verb>
guint gtk_pixmap_get_type( void );
void gtk_pixmap_set( GtkPixmap *pixmap,
GdkPixmap *val,
GdkBitmap *mask );
void gtk_pixmap_get( GtkPixmap *pixmap,
GdkPixmap **val,
GdkBitmap **mask);
</verb></tscreen>
La función gtk_pixmap_set se usa para cambiar los datos del
<em/pixmap/ que el <em/widget/ está manejando en ese
momento. <tt/val/ es el <em/pixmap/ creado usando GDK.
El ejemplo siguiente usa un <em/pixmap/ en un botón:
<tscreen><verb>
/* comienzo del ejemplo pixmap.c */
#include <gtk/gtk.h>
/* Datos en formato XPM del icono de apertura de archivo */
static const char * xpm_data[] = {
"16 16 3 1",
" c None",
". c #000000000000",
"X c #FFFFFFFFFFFF",
" ",
" ...... ",
" .XXX.X. ",
" .XXX.XX. ",
" .XXX.XXX. ",
" .XXX..... ",
" .XXXXXXX. ",
" .XXXXXXX. ",
" .XXXXXXX. ",
" .XXXXXXX. ",
" .XXXXXXX. ",
" .XXXXXXX. ",
" .XXXXXXX. ",
" ......... ",
" ",
" "};
/* Cuando se llama a esta función (usando signal delete_event) se
* termina la aplicación*/
void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) {
gtk_main_quit();
}
/* Al presionar el botón aparece el mensaje */
void button_clicked( GtkWidget *widget, gpointer data ) {
printf( "botón pulsado\n" );
}
int main( int argc, char *argv[] )
{
GtkWidget *ventana, *pixmapwid, *boton;
GdkPixmap *pixmap;
GdkBitmap *mask;
GtkStyle *style;
/* Creamos la ventana principal y relacionamos la señal
* delete_event con acabar el programa.*/
gtk_init( &amp;argc, &amp;argv );
ventana = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_signal_connect( GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (close_application), NULL );
gtk_container_border_width( GTK_CONTAINER (ventana), 10 );
gtk_widget_show( ventana );
/* Ahora para el pixmap de gdk */
style = gtk_widget_get_style( ventana );
pixmap = gdk_pixmap_create_from_xpm_d( ventana->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
(gchar **)xpm_data );
/* Un pixmap widget que contendrá al pixmap */
pixmapwid = gtk_pixmap_new( pixmap, mask );
gtk_widget_show( pixmapwid );
/* Un botón para contener al pixmap */
boton = gtk_button_new();
gtk_container_add( GTK_CONTAINER(boton), pixmapwid );
gtk_container_add( GTK_CONTAINER(ventana), boton );
gtk_widget_show( boton );
gtk_signal_connect( GTK_OBJECT(boton), "clicked",
GTK_SIGNAL_FUNC(button_clicked), NULL );
/* mostramos la ventana */
gtk_main ();
return 0;
}
/* final del ejemplo */
</verb></tscreen>
Para cargar un archivo llamado icon0.xpm con la información XPM (que
se encuentra en en directorio actual) habríamos usado:
<tscreen><verb>
/* cargar un pixmap desde un fichero */
pixmap = gdk_pixmap_create_from_xpm( ventana->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
"./icon0.xpm" );
pixmapwid = gtk_pixmap_new( pixmap, mask );
gtk_widget_show( pixmapwid );
gtk_container_add( GTK_CONTAINER(ventana), pixmapwid );
</verb></tscreen>
Una desventaja de los <em/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 (<em/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.
<tscreen><verb>
/* 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",
"&amp; 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&amp; ",
" *=-;#::o+ ",
" >,<12#:34 ",
" 45671#:X3 ",
" +89<02qwo ",
"e* >,67;ro ",
"ty> 459@>+&amp;&amp; ",
"$2u+ ><ipas8* ",
"%$;=* *3:.Xa.dfg> ",
"Oh$;ya *3d.a8j,Xe.d3g8+ ",
" Oh$;ka *3d$a8lz,,xxc:.e3g54 ",
" Oh$;kO *pd$%svbzz,sxxxxfX..&amp;wn> ",
" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ",
" Oh$@g&amp; *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ",
" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5&amp; ",
" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ",
" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ",
" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ",
" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo",
" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g",
" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&amp;en",
" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>",
" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ",
" 3206Bwxxszx%et.eaAp77m77mmmf3&amp;eeeg* ",
" @26MvzxNzvlbwfpdettttttttttt.c,n&amp; ",
" *;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&amp;&amp; ",
" &amp;i0ycm6n4 ogk17,0<6666g ",
" N-k-<> >=01-kuu666> ",
" ,6ky&amp; &amp;46-10ul,66, ",
" Ou0<> o66y<ulw<66&amp; ",
" *kk5 >66By7=xu664 ",
" <<M4 466lj<Mxu66o ",
" *>> +66uv,zN666* ",
" 566,xxj669 ",
" 4666FF666> ",
" >966666M ",
" oM6668+ ",
" *4 ",
" ",
" "};
void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) {
gtk_main_quit();
}
int main (int argc, char *argv[])
{
GtkWidget *ventana, *pixmap, *fixed;
GdkPixmap *gdk_pixmap;
GdkBitmap *mask;
GtkStyle *style;
GdkGC *gc;
/* Creamos la ventana principal y relacionamos la señal
* delete_event para terminar la aplicación. Conviene destacar
* que la ventana no tendrá título puesto que es popup.*/
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new( GTK_WINDOW_POPUP );
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (close_application), NULL);
gtk_widget_show (ventana);
style = gtk_widget_get_default_style();
gc = style->black_gc;
gdk_pixmap = gdk_pixmap_create_from_xpm_d( ventana->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
WheelbarrowFull_xpm );
pixmap = gtk_pixmap_new( gdk_pixmap, mask );
gtk_widget_show( pixmap );
fixed = gtk_fixed_new();
gtk_widget_set_usize( fixed, 200, 200 );
gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 );
gtk_container_add( GTK_CONTAINER(ventana), fixed );
gtk_widget_show( fixed );
/* Con esto cubrimos todo menos la imagen */
gtk_widget_shape_combine_mask( ventana, mask, 0, 0 );
/* mostramos la ventana */
gtk_widget_set_uposition( ventana, 20, 400 );
gtk_widget_show( ventana );
gtk_main ();
return 0;
}
/* final del ejemplo */
</verb></tscreen>
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.
<tscreen><verb>
gtk_widget_set_events( ventana,
gtk_widget_get_events( ventana ) |
GDK_BUTTON_PRESS_MASK );
gtk_signal_connect( GTK_OBJECT(ventana), "button_press_event",
GTK_SIGNAL_FUNC(close_application), NULL );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Reglas
<p>
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:
<tscreen><verb>
GtkWidget *gtk_hruler_new( void ); /* horizontal */
GtkWidget *gtk_vruler_new( void ); /* vertical */
</verb></tscreen>
Las unidades de la regla pueden ser pixels, pulgadas o centímetros
(GKD_PIXELS, GDK_INCHES, GDK_CENTIMETRES). Esto se hace usando:
<tscreen><verb>
void gtk_ruler_set_metric( GtkRuler *ruler,
GtkMetricType metric );
</verb></tscreen>
El valor por defecto es GTK_PIXELS.
<tscreen><verb>
gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS );
</verb></tscreen>
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:
<tscreen><verb>
void gtk_ruler_set_range( GtkRuler *ruler,
gfloat lower,
gfloat upper,
gfloat posicion,
gfloat max_size );
</verb></tscreen>
Los argumentos <tt/lower/ (valor más bajo) y <tt/upper/ (más
alto) delimitan la extensión de la regla. El argumento
<tt/max_size/ es el número más alto que será mostrado. Como
es lógico <tt/posicion/ define la posición inicial del indicador
dentro de la regla.
Una regla vertical puede puede llegar a ser de 800 pixels:
<tscreen><verb>
gtk_ruler_set_range( GTK_RULER(vruler), 0, 800, 0, 800);
</verb></tscreen>
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:
<tscreen><verb>
gtk_ruler_set_range( GTK_RULER(vruler), 7, 16, 0, 20);
</verb></tscreen>
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:
<tscreen><verb>
#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x
gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event",
(GtkSignalFunc)EVENT_METHOD(ruler, motion_notify_event),
GTK_OBJECT(ruler) );
</verb></tscreen>
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
<em/pixels/. La regla horizontal oscila entre 7 y 13 con marcas cada
100 <em/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.
<tscreen><verb>
/* comienzo del ejemplo rulers.c */
#include <gtk/gtk.h>
#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x
#define XSIZE 600
#define YSIZE 400
/* Esta rutina toma el control cuando se pulsa el botón close
*/
void close_application( GtkWidget *widget, GdkEvent *event, gpointer data ) {
gtk_main_quit();
}
int main( int argc, char *argv[] ) {
GtkWidget *ventana, *table, *area, *hrule, *vrule;
gtk_init( &amp;argc, &amp;argv );
ventana = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC( close_application ), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* creación de la tabla donde pondremos las reglas y la zona de
* dibujo */
table = gtk_table_new( 3, 2, FALSE );
gtk_container_add( GTK_CONTAINER(ventana), table );
area = gtk_drawing_area_new();
gtk_drawing_area_size( (GtkDrawingArea *)area, XSIZE, YSIZE );
gtk_table_attach( GTK_TABLE(table), area, 1, 2, 1, 2,
GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0 );
gtk_widget_set_events( area, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK );
/* La regla horizontal está arriba. Cuando el ratón se mueve
* a lo largo de la zona de dibujo el controlador de eventos de la
* regla recibe motion_notify_event. */
hrule = gtk_hruler_new();
gtk_ruler_set_metric( GTK_RULER(hrule), GTK_PIXELS );
gtk_ruler_set_range( GTK_RULER(hrule), 7, 13, 0, 20 );
gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event",
(GtkSignalFunc)EVENT_METHOD(hrule,
motion_notify_event),
GTK_OBJECT(hrule) );
/* GTK_WIDGET_CLASS(GTK_OBJECT(hrule)->klass)->motion_notify_event, */
gtk_table_attach( GTK_TABLE(table), hrule, 1, 2, 0, 1,
GTK_EXPAND|GTK_SHRINK|GTK_FILL, GTK_FILL, 0, 0 );
/* la zona de dibujo el controlador de eventos de la regla recibe
* motion_notify_event. */
vrule = gtk_vruler_new();
gtk_ruler_set_metric( GTK_RULER(vrule), GTK_PIXELS );
gtk_ruler_set_range( GTK_RULER(vrule), 0, YSIZE, 10, YSIZE );
gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event",
(GtkSignalFunc)
GTK_WIDGET_CLASS(GTK_OBJECT(vrule)->klass)->
motion_notify_event,
GTK_OBJECT(vrule) );
gtk_table_attach( GTK_TABLE(table), vrule, 0, 1, 1, 2,
GTK_FILL, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0 );
/* mostramos todo */
gtk_widget_show( area );
gtk_widget_show( hrule );
gtk_widget_show( vrule );
gtk_widget_show( table );
gtk_widget_show( ventana );
gtk_main();
return 0;
}
/* final del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Barras de estado
<p>
Las barras de estado son widgets usados para mostrar un mensaje. Todo
aquello que haya sido mostrado se guarda en una pila, con lo que es
muy fácil repetir el último mensaje.
Para permitir que diferentes partes del programa usen la misma barra
de estado éstas usan Identificadores por Contexto (Context
Identifiers) para identificar a los `usuarios'. El mensaje que está
en lo alto de la pila será el siguiente en mostrarse, sin importar
el contexto en el que se esté. Los mensajes se almacenan en el
orden el último en entrar es el primero en salir, y el
Identificador por Contexto no influye en este orden.
Las barras de estado se crean con una llamada a:
<tscreen><verb>
GtkWidget *gtk_statusbar_new( void );
</verb></tscreen>
Se pide un nuevo Identificador por Contexto con una pequeña
descripción textual del contexto y una llamada a la función:
<tscreen><verb>
guint gtk_statusbar_get_context_id( GtkStatusbar *statusbar,
const gchar *context_description );
</verb></tscreen>
Hay tres funciones que pueden manipular las barras de estado:
<tscreen><verb>
guint gtk_statusbar_push( GtkStatusbar *statusbar,
guint context_id,
gchar *text );
void gtk_statusbar_pop( GtkStatusbar *statusbar)
guint context_id );
void gtk_statusbar_remove( GtkStatusbar *statusbar,
guint context_id,
guint message_id );
</verb></tscreen>
La primera, gtk_statusbar_push, se utiliza para añadir un nuevo
mensaje a la barra de estado. Devuelve un Identificador de Mensaje,
que podemos pasarle más tarde a la función gtk_statusbar_remove para
eliminar el mensaje con los Identificadores de Contexto y de Mensaje
que hay en la pila de barras de estado.
La función gtk_statusbar_pop elimina el mensaje que se encuentra
más alto en pila y que contiene el Identificador por Contexto
especificado.
El ejemplo siguiente crea una barra de estado y dos botones, uno para
meter un elemento en la barra y el otro para sacar el último elemento
introducido.
<tscreen><verb>
/* Principio del ejemplo de barras de estado statusbar.c */
#include <gtk/gtk.h>
#include <glib.h>
GtkWidget *status_bar;
void push_item (GtkWidget *widget, gpointer data)
{
static int count = 1;
char buff[20];
g_snprintf(buff, 20, "Item %d", count++);
gtk_statusbar_push( GTK_STATUSBAR(status_bar), GPOINTER_TO_INT(data), buff);
return;
}
void pop_item (GtkWidget *widget, gpointer data)
{
gtk_statusbar_pop( GTK_STATUSBAR(status_bar), GPOINTER_TO_INT(data) );
return;
}
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *vbox;
GtkWidget *boton;
int context_id;
gtk_init (&amp;argc, &amp;argv);
/* crear una nueva ventana */
ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize( GTK_WIDGET (ventana), 200, 100);
gtk_window_set_title(GTK_WINDOW (ventana), "GTK Statusbar Example");
gtk_signal_connect(GTK_OBJECT (ventana), "delete_event",
(GtkSignalFunc) gtk_exit, NULL);
vbox = gtk_vbox_new(FALSE, 1);
gtk_container_add(GTK_CONTAINER(ventana), vbox);
gtk_widget_show(vbox);
status_bar = gtk_statusbar_new();
gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0);
gtk_widget_show (status_bar);
context_id = gtk_statusbar_get_context_id(
GTK_STATUSBAR(status_bar), "Statusbar example");
boton = gtk_button_new_with_label("push item");
gtk_signal_connect(GTK_OBJECT(boton), "clicked",
GTK_SIGNAL_FUNC (push_item), &amp;context_id);
gtk_box_pack_start(GTK_BOX(vbox), boton, TRUE, TRUE, 2);
gtk_widget_show(boton);
boton = gtk_button_new_with_label("pop last item");
gtk_signal_connect(GTK_OBJECT(boton), "clicked",
GTK_SIGNAL_FUNC (pop_item), &amp;context_id);
gtk_box_pack_start(GTK_BOX(vbox), boton, TRUE, TRUE, 2);
gtk_widget_show(boton);
/* siempre mostramos la ventana en el último paso para que todo se
* dibuje en la pantalla de un golpe. */
gtk_widget_show(ventana);
gtk_main ();
return 0;
}
/* Final del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Entrada de texto
<p>
El <em/widget/ Entry permite mostrar e introducir texto en una línea
de un cuadro de diálogo. El texto se puede poner con llamadas a funciones
que permiten reemplazar, preañadir o añadir el texto al contenido
actual del <em/widget/ Entry.
Hay dos funciones para crear un <em/widget/ Entry:
<tscreen><verb>
GtkWidget *gtk_entry_new( void );
GtkWidget *gtk_entry_new_with_max_length( guint16 max );
</verb></tscreen>
La primera sirve para crear un nuevo <em/widget/ Entry, mientras que la
segunda crea el <em/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 <em/widget/ Entry.
<tscreen><verb>
void gtk_entry_set_text( GtkEntry *entry,
const gchar *text );
void gtk_entry_append_text( GtkEntry *entry,
const gchar *text );
void gtk_entry_prepend_text( GtkEntry *entry,
const gchar *text );
</verb></tscreen>
La función <tt/gtk_entry_set_text/ cambia el contenido actual del
<em/widget/ Entry. Las funciones <tt/gtk_entry_append_text/ y
<tt/gtk_entry_prepend_text/ permiten añadir o preañadir texto.
Las función siguientes permiten decir donde poner el punto de inserción.
<tscreen><verb>
void gtk_entry_set_position( GtkEntry *entry,
gint posicion );
</verb></tscreen>
Se pueden obtener los contenidos del <em/widget/ llamando a la función
que se describe a continuación. Obtener los contenidos del <em/widget/
puede ser útil en las funciones de llamada descritas más adelante.
<tscreen><verb>
gchar *gtk_entry_get_text( GtkEntry *entry );
</verb></tscreen>
Si quiere impedir que alguien cambie el contenido del <em/widget/ escribiendo
en él, utilice la función
<tscreen><verb>
void gtk_entry_set_editable( GtkEntry *entry,
gboolean editable );
</verb></tscreen>
Esta función permite camiar el estado de edición de un <em/widget/ Entry,
siendo el argumento <tt/editable/ TRUE o FALSE.
Si estamos utilizando el <em/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.
<tscreen><verb>
void gtk_entry_set_visibility( GtkEntry *entry,
gboolean visible );
</verb></tscreen>
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 <em/widget/, haciéndole fácil al usuario eliminar este texto.
<tscreen><verb>
void gtk_entry_select_region( GtkEntry *entry,
gint start,
gint end );
</verb></tscreen>
Si queremos saber el momento en el que el usuario ha introducido el texto,
podemos conectar con las señales <tt/activate/ o <tt/changed/. <tt/activate/
se activa cuando el usuario aprieta la tecla enter en el <em/widget/.
<tt/changed/ se activa cuando cambia algo del texto, p.e. cuando se introduce
o se elimina algún carácter.
<tscreen><verb>
/* Principio del ejemplo entry entry.c */
#include <gtk/gtk.h>
void enter_callback(GtkWidget *widget, GtkWidget *entry)
{
gchar *entry_text;
entry_text = gtk_entry_get_text(GTK_ENTRY(entry));
printf("Entry contents: %s\n", entry_text);
}
void entry_toggle_editable (GtkWidget *checkbutton,
GtkWidget *entry)
{
gtk_entry_set_editable(GTK_ENTRY(entry),
GTK_TOGGLE_BUTTON(checkbutton)->active);
}
void entry_toggle_visibility (GtkWidget *checkbutton,
GtkWidget *entry)
{
gtk_entry_set_visibility(GTK_ENTRY(entry),
GTK_TOGGLE_BUTTON(checkbutton)->active);
}
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *vbox, *hbox;
GtkWidget *entry;
GtkWidget *boton;
GtkWidget *check;
gtk_init (&amp;argc, &amp;argv);
/* crear una nueva ventana */
ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize( GTK_WIDGET (ventana), 200, 100);
gtk_window_set_title(GTK_WINDOW (ventana), "GTK Entry");
gtk_signal_connect(GTK_OBJECT (ventana), "delete_event",
(GtkSignalFunc) gtk_exit, NULL);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (ventana), vbox);
gtk_widget_show (vbox);
entry = gtk_entry_new_with_max_length (50);
gtk_signal_connect(GTK_OBJECT(entry), "activate",
GTK_SIGNAL_FUNC(enter_callback),
entry);
gtk_entry_set_text (GTK_ENTRY (entry), "hello");
gtk_entry_append_text (GTK_ENTRY (entry), " world");
gtk_entry_select_region (GTK_ENTRY (entry),
0, GTK_ENTRY(entry)->text_length);
gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
gtk_widget_show (entry);
hbox = gtk_hbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
gtk_widget_show (hbox);
check = gtk_check_button_new_with_label("Editable");
gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0);
gtk_signal_connect (GTK_OBJECT(check), "toggled",
GTK_SIGNAL_FUNC(entry_toggle_editable), entry);
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
gtk_widget_show (check);
check = gtk_check_button_new_with_label("Visible");
gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0);
gtk_signal_connect (GTK_OBJECT(check), "toggled",
GTK_SIGNAL_FUNC(entry_toggle_visibility), entry);
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
gtk_widget_show (check);
boton = gtk_button_new_with_label ("Close");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC(gtk_exit),
GTK_OBJECT (ventana));
gtk_box_pack_start (GTK_BOX (vbox), boton, TRUE, TRUE, 0);
GTK_WIDGET_SET_FLAGS (boton, GTK_CAN_DEFAULT);
gtk_widget_grab_default (boton);
gtk_widget_show (boton);
gtk_widget_show(ventana);
gtk_main();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Botones <em/spin/
<p>
El <em/widget/ botón <em/spin/ se utiliza normalmente para permitir
que el usuario elija un valor de un rango de valores. Consiste en una
caja para la entrada de texto con una flecha para arriba y otra para
abajo justo al lado de la caja. Si utilizamos alguna de las flechas
haremos que el valor suba o baje dentro del rango de los valores
posibles. También podemos introducir directamente un valor específico
(utilizando la caja de texto).
El <em/widget/ botón <em/spin/ permite tener valores con un número de
cifras decimales (o sin cifras decimales) y la posibilidad de
incrementarlo/decrementarlo en pasos configurables. La acción de
matener pulsado uno de los botones puede resultar (es opcional) en una
aceleración del cambio en el valor de acuerdo con el tiempo que se
mantenga pulsado.
El botón <em/spin/ utiliza un objeto <ref id="sec_Adjustment"
name="Ajuste"> para conservar la información referente al rango de
valores que puede tomar el botón <em/spin/. Esto hace que el
<em/widget/ botón <em/spin/ sea muy poderoso.
Recuerde que un <em/widget/ ajuste puede crearse con la siguiente
función, que ilustra la información que se guarda:
<tscreen><verb>
GtkObject *gtk_adjustment_new( gfloat valor,
gfloat inferior,
gfloat superior,
gfloat paso,
gfloat incremento_pagina,
gfloat tamano_pagina );
</verb></tscreen>
Estos atributos de un ajuste se utilizan en un botón <em/spin/ de la
forma siguiente:
<itemize>
<item> <tt/valor/: valor inicial del botón <em/spin/
<item> <tt/inferior/: valor inferior del rango
<item> <tt/superior/: valor superior del rango
<item> <tt/paso/: valor a incrementar/decrementar cuando pulsemos el
botón 1 en una flecha
<item> <tt/incremento_pagina/: valor a incrementar/decrementar cuando
pulsemos el botón 2 en una flecha
<item> <tt/tamano_pagina/: no se utiliza
</itemize>
Además, se puede utilizar el botón 3 para saltar directamente a los
valores <tt/superior/ o <tt/inferior/ cuando se pulsa en una de las
flechas. Veamos como crear un botón <em/spin/:
<tscreen><verb>
GtkWidget *gtk_spin_button_new( GtkAdjustment *ajuste,
gfloat aceleracion,
guint digitos );
</verb></tscreen>
El argumento <tt/aceleracion/ toma un valor entre 0.0 y 1.0 e indica
la aceleración que tendrá el botón <em/spin/. El argumento
<tt/digitos/ especifica el número de cifras decimales con que se
mostrará el valor.
Se puede reconfigurar un botón <em/spin/ después de su creación
utilizando la función:
<tscreen><verb>
void gtk_spin_button_configure( GtkSpinButton *boton_spin,
GtkAdjustment *ajuste,
gfloat aceleracion,
guint digitos );
</verb></tscreen>
El argumento <tt/boton_spin/ especifica el botón <em/spin/ que va a
reconfigurarse. El resto de argumentos son los que acabamos de
explicar.
Podemos establecer y obtener el ajuste utilizando las dos funciones
siguientes:
<tscreen><verb>
void gtk_spin_button_set_adjustment( GtkSpinButton *boton_spin,
GtkAdjustment *ajuste );
GtkAdjustment *gtk_spin_button_get_adjustment( GtkSpinButton *boton_spin );
</verb></tscreen>
El número de cifras decimales también puede alterarse utilizando:
<tscreen><verb>
void gtk_spin_button_set_digits( GtkSpinButton *boton_spin,
guint digitos) ;
</verb></tscreen>
El valor que un botón <em/spin/ está mostrando actualmente puede
cambiarse utilizando las siguientes funciones:
<tscreen><verb>
void gtk_spin_button_set_value( GtkSpinButton *boton_spin,
gfloat valor );
</verb></tscreen>
El valor actual de un botón <em/spin/ puede obtenerse como un entero o
como un flotante con las funciones siguientes:
<tscreen><verb>
gfloat gtk_spin_button_get_value_as_float( GtkSpinButton *boton_spin );
gint gtk_spin_button_get_value_as_int( GtkSpinButton *boton_spin );
</verb></tscreen>
Si quiere alterar el valor de un <em/spin/ de forma relativa a su
valor actual, puede utilizar la siguiente función:
<tscreen><verb>
void gtk_spin_button_spin( GtkSpinButton *boton_spin,
GtkSpinType direccion,
gfloat incremento );
</verb></tscreen>
El parámetro <tt/direccion/ puede tomar uno de los valores siguientes:
<itemize>
<item> GTK_SPIN_STEP_FORWARD
<item> GTK_SPIN_STEP_BACKWARD
<item> GTK_SPIN_PAGE_FORWARD
<item> GTK_SPIN_PAGE_BACKWARD
<item> GTK_SPIN_HOME
<item> GTK_SPIN_END
<item> GTK_SPIN_USER_DEFINED
</itemize>
Trataré de explicar todas las posibilidades que ofrece esta
función. Algunos de los valores que puede utilizar <tt/direccion/
hacen que se utilicen valores que están almacenados en el objeto
Ajuste que está asociado con el botón <em/spin/.
GTK_SPIN_STEP_FORWARD y GTK_SPIN_STEP_BACKWARD aumentan o disminuyen
(respectivamente) el valor del botón <em/spin/ por la cantidad
especificada por <tt/incremento/, a menos que <tt/incremento/ sea
igual a 0, en cuyo caso el valor se aumentará o disminuirá por el
valor especificado en <tt/paso/ dentro del Ajuste.
GTK_SPIN_PAGE_FORWARD y GTK_SPIN_PAGE_BACKWARD sencillamente alteran
el valor del botón <em/spin/ por la cantidad <tt/incremento/.
GTK_SPIN_HOME hace que el botón <em/spin/ tenga el mismo valor que el
valor inferior del rango Ajuste.
GTK_SPIN_END hace que el botón <em/spin/ tenga el mismo valor que el
valor superior del rango Ajuste.
GTK_SPIN_USER_DEFINED cambia el valor del botón <em/spin/ por la
cantidad especificada.
Ahora vamos a dejar de lado las funciones para establecer y obtener el
rango de los atributos del botón <em/spin/, y vamos a pasar a las
funciones que afectan a la apariencia y al comportamiento del
<em/widget/ botón <em/spin/ en sí mismo.
La primera de estas funciones se utiliza para restringir el contenido
de la caja de texto de un botón <em/spin/ a un valor numérico. Esto
evita que un usuario introduzca cualquier valor no númerico.
<tscreen><verb>
void gtk_spin_button_set_numeric( GtkSpinButton *boton_spin,
gboolean numerico );
</verb></tscreen>
Puede indicar si un botón <em/spin/ pasará del límite superior al
inferior utilizando la siguiente función:
<tscreen><verb>
void gtk_spin_button_set_wrap( GtkSpinButton *boton_spin,
gboolean wrap );
</verb></tscreen>
Puede hacer que un botón <em/spin/ redondee su valor al <tt/paso/ más
cercano, que se indica cuando creamos el Ajuste que se utiliza con el
botón <em/spin/. Para hacer que redondee tenemos que utilizar la
función siguiente:
<tscreen><verb>
void gtk_spin_button_set_snap_to_ticks( GtkSpinButton *boton_spin,
gboolean redondear );
</verb></tscreen>
Para política de actualización de un botón <em/spin/ puede cambiarse
con la siguiente función:
<tscreen><verb>
void gtk_spin_button_set_update_policy( GtkSpinButton *boton_spin,
GtkSpinButtonUpdatePolicy politica );
</verb></tscreen>
<!-- TODO: find out what this does - TRG -->
Los valores posibles de <tt/politica/ son o GTK_UPDATE_ALWAYS o
GTK_UPDATE_IF_VALID.
Estas políticas afectan al comportamiento de un botón <em/spin/ cuando
se lee el texto insertado en la caja de texto y se sincroniza con los
valores del Ajuste.
En el caso de GTK_UPDATE_IF_VALID el valor de un botón <em/spin/
cambiará si el texto introducido es un valor numérico contenido dentro
del rango especificado por el Ajuste. En caso contrario el texto
introducido se convierte al valor del botón <em/spin/.
En caso de utilizar GTK_UPDATE_ALWAYS se ignorarán los errores que
puedan ocurrir en la conversión del texto en un valor numérico.
El aspecto de los botones utilizados en un botón <em/spin/ pueden
cambiarse utilizando las siguientes funciones:
<tscreen><verb>
void gtk_spin_button_set_shadow_type( GtkSpinButton *boton_spin,
GtkShadowType tipo_sombra );
</verb></tscreen>
Como siempre, el <tt/tipo_sombra/ puede ser uno de los siguientes:
<itemize>
<item> GTK_SHADOW_IN
<item> GTK_SHADOW_OUT
<item> GTK_SHADOW_ETCHED_IN
<item> GTK_SHADOW_ETCHED_OUT
</itemize>
Finalmente, puede pedir de forma explícita que un botón <em/spin/ se
actualice a sí mismo:
<tscreen><verb>
void gtk_spin_button_update( GtkSpinButton *boton_spin );
</verb></tscreen>
Es hora de un nuevo ejemplo.
<tscreen><verb>
/* principio del ejemplo spinbutton spinbutton.c */
#include <gtk/gtk.h>
static GtkWidget *spinner1;
void toggle_snap( GtkWidget *widget,
GtkSpinButton *spin )
{
gtk_spin_button_set_snap_to_ticks (spin, GTK_TOGGLE_BUTTON (widget)->active);
}
void toggle_numeric( GtkWidget *widget,
GtkSpinButton *spin )
{
gtk_spin_button_set_numeric (spin, GTK_TOGGLE_BUTTON (widget)->active);
}
void change_digits( GtkWidget *widget,
GtkSpinButton *spin )
{
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spinner1),
gtk_spin_button_get_value_as_int (spin));
}
void get_value( GtkWidget *widget,
gpointer data )
{
gchar buf[32];
GtkLabel *etiqueta;
GtkSpinButton *spin;
spin = GTK_SPIN_BUTTON (spinner1);
etiqueta = GTK_LABEL (gtk_object_get_user_data (GTK_OBJECT (widget)));
if (GPOINTER_TO_INT (data) == 1)
sprintf (buf, "%d", gtk_spin_button_get_value_as_int (spin));
else
sprintf (buf, "%0.*f", spin->digits,
gtk_spin_button_get_value_as_float (spin));
gtk_label_set_text (etiqueta, buf);
}
int main( int argc,
char *argv[] )
{
GtkWidget *ventana;
GtkWidget *frame;
GtkWidget *hbox;
GtkWidget *main_vbox;
GtkWidget *vbox;
GtkWidget *vbox2;
GtkWidget *spinner2;
GtkWidget *spinner;
GtkWidget *boton;
GtkWidget *etiqueta;
GtkWidget *val_label;
GtkAdjustment *adj;
/* Inicializar GTK */
gtk_init(&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit),
NULL);
gtk_window_set_title (GTK_WINDOW (ventana), "Spin Button");
main_vbox = gtk_vbox_new (FALSE, 5);
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 10);
gtk_container_add (GTK_CONTAINER (ventana), main_vbox);
frame = gtk_frame_new ("Not accelerated");
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
gtk_container_add (GTK_CONTAINER (frame), vbox);
/* spin del día, mes y año */
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 5);
vbox2 = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5);
etiqueta = gtk_label_new ("Day :");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0.5);
gtk_box_pack_start (GTK_BOX (vbox2), etiqueta, FALSE, TRUE, 0);
adj = (GtkAdjustment *) gtk_adjustment_new (1.0, 1.0, 31.0, 1.0,
5.0, 0.0);
spinner = gtk_spin_button_new (adj, 0, 0);
gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner), TRUE);
gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinner),
GTK_SHADOW_OUT);
gtk_box_pack_start (GTK_BOX (vbox2), spinner, FALSE, TRUE, 0);
vbox2 = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5);
etiqueta = gtk_label_new ("Month :");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0.5);
gtk_box_pack_start (GTK_BOX (vbox2), etiqueta, FALSE, TRUE, 0);
adj = (GtkAdjustment *) gtk_adjustment_new (1.0, 1.0, 12.0, 1.0,
5.0, 0.0);
spinner = gtk_spin_button_new (adj, 0, 0);
gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner), TRUE);
gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinner),
GTK_SHADOW_ETCHED_IN);
gtk_box_pack_start (GTK_BOX (vbox2), spinner, FALSE, TRUE, 0);
vbox2 = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5);
etiqueta = gtk_label_new ("Year :");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0.5);
gtk_box_pack_start (GTK_BOX (vbox2), etiqueta, FALSE, TRUE, 0);
adj = (GtkAdjustment *) gtk_adjustment_new (1998.0, 0.0, 2100.0,
1.0, 100.0, 0.0);
spinner = gtk_spin_button_new (adj, 0, 0);
gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner), FALSE);
gtk_spin_button_set_shadow_type (GTK_SPIN_BUTTON (spinner),
GTK_SHADOW_IN);
gtk_widget_set_usize (spinner, 55, 0);
gtk_box_pack_start (GTK_BOX (vbox2), spinner, FALSE, TRUE, 0);
frame = gtk_frame_new ("Accelerated");
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
gtk_container_add (GTK_CONTAINER (frame), vbox);
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
vbox2 = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5);
etiqueta = gtk_label_new ("Value :");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0.5);
gtk_box_pack_start (GTK_BOX (vbox2), etiqueta, FALSE, TRUE, 0);
adj = (GtkAdjustment *) gtk_adjustment_new (0.0, -10000.0, 10000.0,
0.5, 100.0, 0.0);
spinner1 = gtk_spin_button_new (adj, 1.0, 2);
gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner1), TRUE);
gtk_widget_set_usize (spinner1, 100, 0);
gtk_box_pack_start (GTK_BOX (vbox2), spinner1, FALSE, TRUE, 0);
vbox2 = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 5);
etiqueta = gtk_label_new ("Digits :");
gtk_misc_set_alignment (GTK_MISC (etiqueta), 0, 0.5);
gtk_box_pack_start (GTK_BOX (vbox2), etiqueta, FALSE, TRUE, 0);
adj = (GtkAdjustment *) gtk_adjustment_new (2, 1, 5, 1, 1, 0);
spinner2 = gtk_spin_button_new (adj, 0.0, 0);
gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner2), TRUE);
gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
GTK_SIGNAL_FUNC (change_digits),
(gpointer) spinner2);
gtk_box_pack_start (GTK_BOX (vbox2), spinner2, FALSE, TRUE, 0);
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
boton = gtk_check_button_new_with_label ("Snap to 0.5-ticks");
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (toggle_snap),
spinner1);
gtk_box_pack_start (GTK_BOX (vbox), boton, TRUE, TRUE, 0);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (boton), TRUE);
boton = gtk_check_button_new_with_label ("Numeric only input mode");
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (toggle_numeric),
spinner1);
gtk_box_pack_start (GTK_BOX (vbox), boton, TRUE, TRUE, 0);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (boton), TRUE);
val_label = gtk_label_new ("");
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
boton = gtk_button_new_with_label ("Value as Int");
gtk_object_set_user_data (GTK_OBJECT (boton), val_label);
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (get_value),
GINT_TO_POINTER (1));
gtk_box_pack_start (GTK_BOX (hbox), boton, TRUE, TRUE, 5);
boton = gtk_button_new_with_label ("Value as Float");
gtk_object_set_user_data (GTK_OBJECT (boton), val_label);
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (get_value),
GINT_TO_POINTER (2));
gtk_box_pack_start (GTK_BOX (hbox), boton, TRUE, TRUE, 5);
gtk_box_pack_start (GTK_BOX (vbox), val_label, TRUE, TRUE, 0);
gtk_label_set_text (GTK_LABEL (val_label), "0");
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, TRUE, 0);
boton = gtk_button_new_with_label ("Close");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (ventana));
gtk_box_pack_start (GTK_BOX (hbox), boton, TRUE, TRUE, 5);
gtk_widget_show_all (ventana);
/* Entramos dentro del bucle de eventos */
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Caja combinada (<em/Combo Box/)
<p>
La caja combinada o <em/combo box/ es otro sencillo <em/widget/
exclusivamente compuesto por otros <em/widgets/. Desde el punto de
vista del usuario, el <em/widget/ consiste en una caja para la
introducción de texto y un menú desplegable desde el que el usuario
puede seleccionar una de un conjunto predefinido de entradas. De forma
alternativa, el usuario puede introducir una opción diferente en la
caja de texto.
El siguiente extracto de la estructura que define un Combo Box
identifica algunos de sus componentes:
<tscreen><verb>
struct _GtkCombo {
GtkHBox hbox;
GtkWidget *entry;
GtkWidget *boton;
GtkWidget *popup;
GtkWidget *popwin;
GtkWidget *list;
... };
</verb></tscreen>
Como puede ver, el Combo Box tiene dos partes principales que tiene
que conocer: un <em/widget entry/ y un <em/widget list/ (lista).
Lo primero, para crear un combo box, utilice:
<tscreen><verb>
GtkWidget *gtk_combo_new( void );
</verb></tscreen>
Ahora, si quiere indicar la cadena que debe aparecer en la sección
entry del combo box, podrá hacerlo manipulando directamente el
<em/widget/ <tt/entry/:
<tscreen><verb>
gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), "Mi cadena.");
</verb></tscreen>
Para introducir valores en la lista desplegable, puede utilizar la
función:
<tscreen><verb>
void gtk_combo_set_popdown_strings( GtkCombo *combo,
GList *cadenas );
</verb></tscreen>
Antes de llamar a esta función, tiene que ensamblar una GList con las
cadenas que quiere. GList es una implementación de una lista enlazada
que forma parte de <ref id="sec_glib" name="glib">, una biblioteca
base de GTK. Por el momento, la explicación fea y rápida es que tiene
que crear un puntero GList, hacerlo igual a NULL, y añadirle cadenas
de texto con la función
<tscreen><verb>
GList *g_list_append( GList *glist,
gpointer datos );
</verb></tscreen>
Es importante que inicialice el puntero GList a NULL antes de
utilizarlo. El valor devuelto por la función g_list_append debe
utilizarse como el nuevo puntero a la GList.
Aquí tenemos un trozo de código típico para crear un conjunto de
opciones:
<tscreen><verb>
GList *glist=NULL;
glist = g_list_append(glist, "Cadena 1");
glist = g_list_append(glist, "Cadena 2");
glist = g_list_append(glist, "Cadena 3");
glist = g_list_append(glist, "Cadena 4");
gtk_combo_set_popdown_strings( GTK_COMBO(combo), glist) ;
</verb></tscreen>
A partir de este momento tendrá un combo box completo funcionando. Hay
unos cuantos aspectos de su funcionamiento que puede cambiar. Para
hacerlo tiene las funciones siguientes:
<tscreen><verb>
void gtk_combo_set_use_arrows( GtkCombo *combo,
gint valor );
void gtk_combo_set_use_arrows_always( GtkCombo *combo,
gint valor );
void gtk_combo_set_case_sensitive( GtkCombo *combo,
gint valor );
</verb></tscreen>
<tt/gtk_combo_set_use_arrows()/ le deja al usuario cambiar el valor
del combo box utilizando las flechas de arriba/abajo. Utilizando estas
teclas no haremos salir la lista, pero se reemplazará el texto actual
del combo box con el siguiente elemento de la lista (superior o
inferior, según la tecla que se pulse). Esto se consigue buscando en
la lista el elemento correspondiente al valor actual del combo box y
seleccionando el anterior o el posterior (según corresponda).
Normalmente en una caja de entrada de texto las flechas se utilizan
para cambiar el foco (ie. el <em/widget/ que recibe la entrada del
teclado), pero en este caso será el TAB quien se ocupe. Cuando el
elemento actual sea el último de la lista y presione la flecha abajo
se cambiará el foco (lo mismo se aplica cuando estamos sobre el primer
elemento y pulsamos la tecla arriba).
Si el valor actual en la caja de entrada de texto no está en la lista,
entonces se desactiva la función de <tt/gtk_combo_set_use_arrows()/.
<tt/gtk_combo_set_use_arrows_always()/ igualmente permite la
utilización de las flechas arriba/abajo para cambiar el elemento
seleccionado por el siguiente/anterior de la lista, pero además trata
la lista como si fuese circular (ie. pasa del último al primer
elemento), desactivando completamente la utilidad de las teclas arriba
y abajo para cambiar el foco.
<tt/gtk_combo_set_case_sensitive()/ cambia entre una búsqueda por la
lista que discrimine entre mayúsculas y minúsculas y una búsqueda que
no discrimine. Se utiliza cuando se quiere que el <em/widget/ combo
complete el valor que se está introduciendo con un valor de la
lista. Dependiendo de esta función, se completará distinguiendo entre
mayúsculas y minúsculas o no. El <em/widget/ combo completará la
entrada actual si el usuario presiona la combinación de teclas MOD-1 y
`Tab'. MOD-1 normalmente es la tecla `Alt'. Hay algunos
administradores de ventanas que también utilizan esta combinación de
teclas, con lo que perderemos su posible utilización por parte de GTK.
Ahora que tenemos un combo box que actua como queremos que actue, todo
lo que nos queda es saber como hay que hacer para obtener los datos
que nos puede proporcionar. Esto es relativamente sencillo. La mayoría
del tiempo, de lo único que tiene que preocuparse es de obtener datos
de la caja de texto. Podemos acceder a la caja de texto mediante
GTK_ENTRY(GTK_COMBO(combo)->entry). Las dos cosas que son interesantes
hacer con esta caja son: enlazarla con la señal <tt/activate/, que
indica que el usuario ha presionado la tecla «Intro», y leer el
texto. Lo primero podemos hacerlo utilizando algo así:
<tscreen><verb>
gtk_signal_connect(GTK_OBJECT(GTK_COMB(combo)->entry), "activate",
GTK_SIGNAL_FUNC (mi_funcion_respuesta), mis_datos);
</verb></tscreen>
Para conseguir el texto que hay en la caja en cualquier momento sólo
tenemos que utilizar la función siguiente:
<tscreen><verb>
gchar *gtk_entry_get_text(GtkEntry *entry);
</verb></tscreen>
De esta forma:
<tscreen><verb>
char *cadena;
cadena = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(combo)->entry));
</verb></tscreen>
Esto es todo lo interesante. Existe otra función,
<tscreen><verb>
void gtk_combo_disable_activate(GtkCombo *combo);
</verb></tscreen>
que permite desactivar la señal <tt/activate/ en el <em/widget/ entry
dentro del combo box. Personalmente no se me ocurre ningún motivo para
utilizarla, pero existir existe.
<!-- There are also a function to set the string on a particular item, void
gtk_combo_set_item_string(GtkCombo *combo, GtkItem *item, const gchar
*item_value), but this requires that you have a pointer to the
appropriate GtkItem. Frankly, I have no idea how to do that.
-->
<!-- ************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1> Selección de Color
<p>
El <em/widget/ selección de color, nos permite (¡sorpresa!) la selección
interactiva de colores. Este <em/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 <em/widget/ de selección de color emite (por ahora) sólo una señal,
<tt/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 <tt/gtk_color_selection_set_color()/.
Echémosle un vistazo a lo que nos ofrece el <em/widget/ de selección de color.
El <em/widget/ tiene dos «sabores» diferentes; <tt/gtk_color_selection/
y <tt/gtk_color_selection_dialog/:
<tscreen><verb>
GtkWidget *gtk_color_selection_new( void );
</verb></tscreen>
Probablemente no utilizará este constructor directamente. Crea un <em/widget/
GtkColorSelection huérfano al que le tendrá que asignarle un padre. El
<em/widget/ GtkColorSelection está heredado del <em/widget/ GtkVBox.
<tscreen><verb>
GtkWidget *gtk_color_selection_dialog_new( const gchar *title );
</verb></tscreen>
Éste es el constructor del cuadro de selección de color más común. Crea un
<tt/GtkColorSelectionDialog/, heredado de un <tt/GtkDialog/. Consiste en un
<tt/GtkFrame/ con un <tt/GtkColorSelection/, un <tt/GtkHSeparator/ y un
<tt/GtkHBox/ con tres botones, «Aceptar», «Cancelar» y «Ayuda».
Puede utilizar estos botones accediendo a los <em/widgets/ <tt/ok_button/,
<tt/cancel_button/ y <tt/help_button/ de la estructura GtkColorSelectionDialog,
(es decir GTK_COLOR_SELECTION_DIALOG(colorseldialog)->ok_button).
<tscreen><verb>
void gtk_color_selection_set_update_policy( GtkColorSelection *colorsel,
GtkUpdateType policy );
</verb></tscreen>
Esta función se utiliza para indicar la política de actuación. La política
por defecto es <tt/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 <tt/GTK_UPDATE_DISCONTINUOUS/ o
<tt/GTK_UPDATE_DELAYED/.
<tscreen><verb>
void gtk_color_selection_set_opacity( GtkColorSelection *colorsel,
gint use_opacity );
</verb></tscreen>
El <em/widget/ de selección de color admite el ajuste de la transparencia
de un color (también conocido como el canal alfa). Esta opción está
desactivada por defecto. Si se llama a esta función con <tt/use_opacity/
como TRUE se activa la transparencia. Si se utiliza <tt/use_opacity/ como
FALSE se desactiva la transparencia.
<tscreen><verb>
void gtk_color_selection_set_color( GtkColorSelection *colorsel,
gdouble *color );
</verb></tscreen>
Puede poner el color actual explicitamente haciendo uso de esta función con
un puntero a un vector de colores (de tipo <tt/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 <tt/gtk_color_selection_set_opacity()/). Todos los
valores se encuentran entre 0.0 y 1.0.
<tscreen><verb>
void gtk_color_selection_get_color( GtkColorSelection *colorsel,
gdouble *color );
</verb></tscreen>
Cuando necesite preguntar por el color actual, normalmente cuando haya
recibido una señal <tt/color_changed/, utilice esta función. <tt/color/
es un puntero al vector de colores que se rellenará. Ver la descripción
de la función <tt/gtk_color_selection_set_color()/ para conocer la
estructura de este vector.
<!-- Need to do a whole section on DnD - TRG
Drag and drop
-------------
The color sample areas (right under the hue-saturation wheel) supports
drag and drop. The type of drag and drop is "application/x-color". The
message data consists of an array of 4 (or 5 if opacity is enabled)
gdouble values, where the value at position 0 is 0.0 (opacity on) or
1.0 (opacity off) followed by the red, green and blue values at
positions 1,2 and 3 respectively. If opacity is enabled, the opacity
is passed in the value at position 4.
-->
Aquí tenemos un pequeño ejemplo que muestra el uso de
<tt/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.
<tscreen><verb>
/* principio del ejemplo colorsel colorsel.c */
#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
GtkWidget *colorseldlg = NULL;
GtkWidget *drawingarea = NULL;
/* Manejador del cambio de color */
void color_changed_cb (GtkWidget *widget, GtkColorSelection *colorsel)
{
gdouble color[3];
GdkColor gdk_color;
GdkColormap *colormap;
/* Obtener el mapa de colores de la zona de dibujo */
colormap = gdk_window_get_colormap (drawingarea->window);
/* Obtener el color actual */
gtk_color_selection_get_color (colorsel,color);
/* Meterlo en un entero sin signo de 16 bits (0..65535) e insertarlo
en la estructura GdkColor */
gdk_color.red = (guint16)(color[0]*65535.0);
gdk_color.green = (guint16)(color[1]*65535.0);
gdk_color.blue = (guint16)(color[2]*65535.0);
/* Pedir memoria para el color */
gdk_color_alloc (colormap, &amp;gdk_color);
/* Poner el color de fondo de la ventana */
gdk_window_set_background (drawingarea->window, &amp;gdk_color);
/* Limpiar la ventana */
gdk_window_clear (drawingarea->window);
}
/* Manejador del evento Drawingarea */
gint area_event (GtkWidget *widget, GdkEvent *event, gpointer client_data)
{
gint handled = FALSE;
GtkWidget *colorsel;
/* Comprobar si hemos recibido un evento de pulsación de botón */
if (event->type == GDK_BUTTON_PRESS &amp;&amp; colorseldlg == NULL)
{
/* Sí, ¡tenemos un evento y todavía no está el colorseldlg! */
handled = TRUE;
/* Crear el cuadro de diálogo de selección del color */
colorseldlg = gtk_color_selection_dialog_new("Select background color");
/* Obtener el widget GtkColorSelection */
colorsel = GTK_COLOR_SELECTION_DIALOG(colorseldlg)->colorsel;
/* Conectar con la señal «color_changed», darle al dato del
cliente el valor del widget colorsel */
gtk_signal_connect(GTK_OBJECT(colorsel), "color_changed",
(GtkSignalFunc)color_changed_cb, (gpointer)colorsel);
/* Mostrar el cuadro de diálogo */
gtk_widget_show(colorseldlg);
}
return handled;
}
/* Manipulador de los eventos cerrar y salir */
void destroy_window (GtkWidget *widget, gpointer client_data)
{
gtk_main_quit ();
}
/* Principal */
gint main (gint argc, gchar *argv[])
{
GtkWidget *ventana;
/* Inicializa el toolkit, y elimina las opciones relacionadas con
gtk incluidas en la línea de órdenes */
gtk_init (&amp;argc,&amp;argv);
/* Crea la ventana de más alto nivel, le da título y la política */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW(ventana), "Color selection test");
gtk_window_set_policy (GTK_WINDOW(ventana), TRUE, TRUE, TRUE);
/* Enlaza con los eventos «delete» y «destroy», para que podamos
salir */
gtk_signal_connect (GTK_OBJECT(ventana), "delete_event",
(GtkSignalFunc)destroy_window, (gpointer)ventana);
gtk_signal_connect (GTK_OBJECT(ventana), "destroy",
(GtkSignalFunc)destroy_window, (gpointer)ventana);
/* Crea la zona de dibujo, pone el tamaño y caza los eventos de los
botones */
drawingarea = gtk_drawing_area_new ();
gtk_drawing_area_size (GTK_DRAWING_AREA(drawingarea), 200, 200);
gtk_widget_set_events (drawingarea, GDK_BUTTON_PRESS_MASK);
gtk_signal_connect (GTK_OBJECT(drawingarea), "event",
(GtkSignalFunc)area_event, (gpointer)drawingarea);
/* Add drawingarea to window, then show them both */
gtk_container_add (GTK_CONTAINER(ventana), drawingarea);
gtk_widget_show (drawingarea);
gtk_widget_show (ventana);
/* Entrar en el bucle principal de gtk (nunca sale de aquí) */
gtk_main ();
/* Para satisfacer a los compiladores pijos */
return 0;
}
/* final del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Selección de ficheros
<p>
El <em/widget/ de selección de ficheros nos proporciona una forma
rápida y sencilla de mostrar un cuadro de diálogo para la selección de
un fichero. Ya viene con los botones Aceptar, Cancelar y Ayuda. Una
magnifica ayuda para acortar el tiempo de programación.
Para crear un nuevo cuadro de diálogo de selección de ficheros
utilice:
<tscreen><verb>
GtkWidget *gtk_file_selection_new( gchar *title );
</verb></tscreen>
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:
<tscreen><verb>
void gtk_file_selection_set_filename( GtkFileSelection *filesel,
gchar *filename );
</verb></tscreen>
Para obtener el texto que el usuario ha introducido, utilice la función:
<tscreen><verb>
gchar *gtk_file_selection_get_filename( GtkFileSelection *filesel );
</verb></tscreen>
También hay punteros a los <em/widgets/ que contiene el cuadro de
diálogo. Son los siguientes:
<itemize>
<item>dir_list
<item>file_list
<item>selection_entry
<item>selection_text
<item>main_vbox
<item>ok_button
<item>cancel_button
<item>help_button
</itemize>
Lo más probable es que sólo utilice los punteros <tt/ok_button/,
<tt/cancel_button/, y <tt/help_button/ para controlar cuando se pulsan.
Aquí incluímos un ejemplo robado de <tt/testgtk.c/, modificado
para que se puede ejecutar independientemente. Como puede ver, no es
muy complicado crear un <em/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.
<tscreen><verb>
/* principio del ejemplo filesel filesel.c */
#include <gtk/gtk.h>
/* Obtener el nombre del fichero e imprimirlo en la consola */
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
{
g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
}
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *filew;
gtk_init (&amp;argc, &amp;argv);
/* Crear un nuevo widget de selección de ficheros */
filew = gtk_file_selection_new ("File selection");
gtk_signal_connect (GTK_OBJECT (filew), "destroy",
(GtkSignalFunc) destroy, &amp;filew);
/* Conectar el ok_button con la función file_ok_sel */
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
"clicked", (GtkSignalFunc) file_ok_sel, filew );
/* Conectar el cancel_button con la destrucción del widget */
gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button),
"clicked", (GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (filew));
/* Damos el nombre del fichero, como si fuese un cuadro de diálogo para
grabar ficheros y estuviesemos dando un nombre por defecto */
gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew),
"penguin.png");
gtk_widget_show(filew);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> El <em/widget/ contenedor
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1> El <em/widget/ EventBox<label id="sec_EventBox">
<label id="sec_The_EventBox_Widget">
<p>
Algunos <em/widget/ gtk no tienen asociada una ventana X, por lo que
sólo pueden dibujar en la de su padre. Debido a esto, no pueden
recibir ningún evento y si tienen un tamaño incorrecto, no se
recortarán correctamente por lo que puede que se sobreescriban ciertas
zonas, etc... Si necesita este tipo de <em/widgets/, el EventBox es
para usted.
Cuando se ve por primera vez, el <em/widget/ EventBox puede parecer
completamente inútil. No dibuja nada en la pantalla y no responde
a ningún evento. Sin embargo, tiene una utilidad - proporciona una
ventana X para su <em/widget/ hijo. Esto es importante ya que
muchos <em/widgets/ GTK no tienen una ventana X asociada. No tener una
ventana X ahorra memoria y mejora el rendimiento, pero tiene sus
desventajas. Un <em/widget/ sin una ventana X no puede recibir
eventos, y no realizará ningún recorte en sus contenidos. Aunque el
nombre <em/EventBox/ enfatiza su función de manejador de eventos, el
<em/widget/ también puede utilizarse para hacer los recortes.
(Y más... ver el ejemplo más abajo.)
Para crear un nuevo <em/widget/ EventBox, utilice:
<tscreen><verb>
GtkWidget *gtk_event_box_new( void );
</verb></tscreen>
Un <em/widget/ hijo puede añadirse a su EventBox así:
<tscreen><verb>
gtk_container_add( GTK_CONTAINER(event_box), widget );
</verb></tscreen>
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.
<tscreen><verb>
/* principio del ejemplo eventbox eventbox.c */
#include <gtk/gtk.h>
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *event_box;
GtkWidget *etiqueta;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Event Box");
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* Crea un EventBox y lo añade a nuestra ventana superior */
event_box = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER(ventana), event_box);
gtk_widget_show (event_box);
/* Crea una larga etiqueta */
etiqueta = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
gtk_container_add (GTK_CONTAINER (event_box), etiqueta);
gtk_widget_show (etiqueta);
/* La recortamos. */
gtk_widget_set_usize (etiqueta, 110, 20);
/* Y enlazamos una acción con la etiqueta */
gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Otra cosa más que necesita una ventana X ... */
gtk_widget_realize (event_box);
gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* Final del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>El <em/widget/ alineamiento <label id="sec_Alignment">
<p>
El <em/widget/ alineamiento (<em/alignment/) le permitirá colocar un
<em/widget/ dentro de su ventana utilizando una posición y un tamaño
relativos al mismo <em/widget/ de alineamiento. Por ejemplo, puede ser
muy útil para centrar un <em/widget/ en la ventana.
Sólo hay dos funciones asociadas con el <em/widget/ alineamiento:
<tscreen><verb>
GtkWidget* gtk_alignment_new( gfloat xalign,
gfloat yalign,
gfloat xscale,
gfloat yscale );
void gtk_alignment_set( GtkAlignment *alignment,
gfloat xalign,
gfloat yalign,
gfloat xscale,
gfloat yscale );
</verb></tscreen>
La primera función crea un nuevo <em/widget/ alineamiento con los
parámetros especificados. La segunda función permite alterar los
parámetros de un <em/widget/ alineamiento ya existente.
Los cuatro parámetros de alineamiento son números en coma flotante que
pueden tener variar entre 0.0 y 1.0. Los argumentos <tt/xalign/ e
<tt/yalign/ afectan a la posición del <em/widget/ colocado dentro del
<em/widget/ de alineamiento. Los argumentos <tt/xscale/ e <tt/yscale/
afectan a la cantidad de espacio que ocupa el <em/widget/.
Se le puede añadir un <em/widget/ hijo a un alineamiento utilizando:
<tscreen><verb>
gtk_container_add( GTK_CONTAINER(alignment), child_widget );
</verb></tscreen>
Para ver un ejemplo de utilización del <em/widget/ alineamiento,
diríjase al ejemplo del <em/widget/ <ref id="sec_ProgressBar"
name="Barra de progreso">.
<!-- ----------------------------------------------------------------- -->
<sect1> Contenedor fijo
<p>
El contenedor fijo le permite situar <em/widgets/ en una posición fija
dentro de su ventana, relativa a la esquina superior izquierda. La
posición de los <em/widgets/ puede cambiarse dinámicamente.
Sólo hay tres funciones asociadas al <em/widget/ contenedor fijo:
<tscreen><verb>
GtkWidget* gtk_fixed_new( void );
void gtk_fixed_put( GtkFixed *fixed,
GtkWidget *widget,
gint16 x,
gint16 y );
void gtk_fixed_move( GtkFixed *fixed,
GtkWidget *widget,
gint16 x,
gint16 y );
</verb></tscreen>
La función <tt/gtk_fixed_new/ permite la creación de un nuevo
contenedor fijo.
<tt/gtk_fixed_put/ situa <tt/widget/ dentro del contenedor <tt/fixed/
en la posición especificada por <tt/x/ e <tt/y/.
<tt/gtk_fixed_move/ permite que mover hacia una nuevo posición el
<em/widget/ especificado.
El ejemplo siguiente muestra como utilizar el contenedor fijo.
<tscreen><verb>
/* principio del ejemplo fixed fixed.c */
#include <gtk/gtk.h>
/* Voy a ser un poco torpe y utilizar algunas variables
* globales para almacenar la posición del widget que
* hay dentro del contenedor */
gint x=50;
gint y=50;
/* Esta función de llamada mueve el botón a una nueva
* posición dentro del contenedor fijo. */
void move_button( GtkWidget *widget,
GtkWidget *fixed )
{
x = (x+30)%300;
y = (y+50)%300;
gtk_fixed_move( GTK_FIXED(fixed), widget, x, y);
}
int main( int argc,
char *argv[] )
{
/* GtkWidget es el tipo de almacenamiento para los widgets */
GtkWidget *ventana;
GtkWidget *fixed;
GtkWidget *boton;
gint i;
/* Inicializa GTK */
gtk_init(&amp;argc, &amp;argv);
/* Crear una nueva ventana */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(ventana), "Fixed Container");
/* Aquí conectamos el evento "destroy" al manejador de la señal */
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
/* Establecemos el ancho del borde la ventana */
gtk_container_set_border_width (GTK_CONTAINER (ventana), 10);
/* Creamos un contenedor fijo */
fixed = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(ventana), fixed);
gtk_widget_show(fixed);
for (i = 1 ; i <= 3 ; i++) {
/* Crea un nuevo botón con la etiqueta "Press me" */
boton = gtk_button_new_with_label ("Press me");
/* Cuando el botón reciba la señal "pulsado", llamará a la función
* move_button() pasándole el contenedor fijo como argumento. */
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (move_button), fixed);
/* Esto mete el botón dentro de la ventana del window contenedor
* fijo. */
gtk_fixed_put (GTK_FIXED (fixed), boton, i*50, i*50);
/* El paso final es mostrar el widget recien creado */
gtk_widget_show (boton);
}
/* Mostrar la ventana */
gtk_widget_show (ventana);
/* Entrar en el bucle principal */
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Contenedor capa
<p>
El contenedor capa es similar al contenedor fijo, excepto que permite
implementar una zona de <em/scroll/ infinita (donde infinito significa
menor de 2^32). Xwindows tiene una limitación en la que las ventanas
pueden tener un máximo de 32767 <em/pixels/ de alto o de ancho. El
contenedor capa sortea esta limitación con una exótica combinación de
ventanas y <em/bits/ de gravedad, <!-- Si alguien entiende que
significa esto: e98cuenc@criens.u-psud.fr --> para que puede tener un
suave <em/scroll/ aún cuando utilice una gran cantidad de <em/widgets/
hijos dentro de su zona de <em/scroll/.
Podrá crear un contenedor capa utilizando:
<tscreen><verb>
GtkWidget *gtk_layout_new( GtkAdjustment *hadjustment,
GtkAdjustment *vadjustment );
</verb></tscreen>
Como puede observar, podrá especificar (de forma opcional) los objetos
de ajuste que utilizará el <em/widget/ capa para hacer su <em/scroll/.
Puede añadir y mover <em/widgets/ dentro del contenedor capa
utilizando las dos funciones siguientes:
<tscreen><verb>
void gtk_layout_put( GtkLayout *layout,
GtkWidget *widget,
gint x,
gint y );
void gtk_layout_move( GtkLayout *layout,
GtkWidget *widget,
gint x,
gint y );
</verb></tscreen>
El tamaño del contenedor capa se puede establecer utilizando la
siguiente función:
<tscreen><verb>
void gtk_layout_set_size( GtkLayout *layout,
guint width,
guint height );
</verb></tscreen>
Los contenedores capa son uno de los poquísimos <em/widgets/ dentro de
GTK que se repintan ellos mismos en la pantalla cuando se cambian
utilizando las funciones anteriores (la inmensa mayoria de los
<em/widgets/ mandan una petición a la cola que será procesada cuando
se devuelva el control a la función <tt/gtk_main()/).
Cuando quiera hacer una gran cantidad de cambios dentro del contenedor
capa, podrá utilizar las dos funciones siguientes para desactivar y
reactivar la característica de repintado:
<tscreen><verb>
void gtk_layout_freeze( GtkLayout *layout );
void gtk_layout_thaw( GtkLayout *layout );
</verb></tscreen>
Las cuatro funciones finales a utilizar con los <em/widgets/capa son
para la manipulación de los <em/widgets/ de ajuste horizontal y
vertical:
<tscreen><verb>
GtkAdjustment* gtk_layout_get_hadjustment( GtkLayout *layout );
GtkAdjustment* gtk_layout_get_vadjustment( GtkLayout *layout );
void gtk_layout_set_hadjustment( GtkLayout *layout,
GtkAdjustment *adjustment );
void gtk_layout_set_vadjustment( GtkLayout *layout,
GtkAdjustment *adjustment);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Marcos <label id="sec_Frames">
<p>
Los marcos pueden utilizarse para meter uno o un grupo de
<em/widgets/dentro de una caja puede ser (de forma opcional)
etiquetada. La posición de la etiqueta y el estilo de la caja pueden
modificarse.
Se puede crear un marco con la siguiente función:
<tscreen><verb>
GtkWidget *gtk_frame_new( const gchar *etiqueta );
</verb></tscreen>
La etiqueta se coloca por defecto en la esquina superior izquierda del
marco. Si el argumento <tt/etiqueta/ es NULL no se mostrará ninguna
etiqueta. Puede cambiarse el texto de la etiqueta utilizando la
función siguiente.
<tscreen><verb>
void gtk_frame_set_label( GtkFrame *frame,
const gchar *etiqueta );
</verb></tscreen>
La posición de la etiqueta se puede cambiar utilizado la función:
<tscreen><verb>
void gtk_frame_set_label_align( GtkFrame *frame,
gfloat xalign,
gfloat yalign );
</verb></tscreen>
<tt/xalign/ e <tt/yalign/ toman valores entre 0.0 y 1.0. <tt/yalign/
no se actualmente no se utiliza. El valor por defecto de <tt/xalign/
es 0.0, lo que coloca la etiqueta a la izquierda del marco.
La siguiente función altera el estilo de la caja que se utiliza para
señalar el marco.
<tscreen><verb>
void gtk_frame_set_shadow_type( GtkFrame *frame,
GtkShadowType type);
</verb></tscreen>
El argumento <tt/type/ puede tomar uno de los valores siguientes:
<itemize>
<item> GTK_SHADOW_NONE
<item> GTK_SHADOW_IN
<item> GTK_SHADOW_OUT
<item> GTK_SHADOW_ETCHED_IN (the default)
<item> GTK_SHADOW_ETCHED_OUT
</itemize>
El código siguiente ilustra la utilización del <em/widget/ marco.
<tscreen><verb>
/* principio del ejemplo frame frame.c */
#include <gtk/gtk.h>
int main( int argc,
char *argv[] )
{
/* GtkWidget es el tipo de almacenamiento para los widgets */
GtkWidget *ventana;
GtkWidget *frame;
GtkWidget *boton;
gint i;
/* Inicializa GTK */
gtk_init(&amp;argc, &amp;argv);
/* Crea una nueva ventana */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(ventana), "Frame Example");
/* Aquí conectamos el evento "destroy"al manejador de señal */
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
gtk_widget_set_usize(ventana, 300, 300);
/* Establecemos el ancho del borde de la ventana */
gtk_container_set_border_width (GTK_CONTAINER (ventana), 10);
/* Crea un marco */
frame = gtk_frame_new(NULL);
gtk_container_add(GTK_CONTAINER(ventana), frame);
/* Establece la etiqueta del marco */
gtk_frame_set_label( GTK_FRAME(frame), "GTK Frame Widget" );
/* Alinea la etiqueta a la derecha del marco */
gtk_frame_set_label_align( GTK_FRAME(frame), 1.0, 0.0);
/* Establece el estilo del marco */
gtk_frame_set_shadow_type( GTK_FRAME(frame), GTK_SHADOW_ETCHED_OUT);
gtk_widget_show(frame);
/* Muestra la ventana */
gtk_widget_show (ventana);
/* Entra dentro del bucle principal */
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Marcos con proporciones fijas
<p>
El <em/widget aspect frame/ (marco proporcional) es como el <em/widget
frame/ (marco), excepto que conserva las proporciones (esto es, la
relación entre el ancho y el alto) del <em/widget/ hijo, añadiendo
espacio extra en caso de ser necesario. Esto es útil, por ejemplo, si
quiere hacer una vista previa de una gran imagen. El tamaño de la
vista previa debería variar cuando el usuario redimensione la ventana,
pero la proporción tiene que coincidir con la de la imagen original.
Para crear un nuevo marco proporcional utilice:
<tscreen><verb>
GtkWidget *gtk_aspect_frame_new( const gchar *etiqueta,
gfloat xalign,
gfloat yalign,
gfloat ratio,
gint obey_child);
</verb></tscreen>
<tt/xalign/ e <tt/yalign/ indican la alineación exactamente igual que
con los <em/widgets Alignment/. Si <tt/obey_child/ es TRUE, la
proporción de un <em/widget/ hijo será la misma que la proporción del
tamaño ideal que éste pida. En caso contrario, vendrá dada por
<tt/ratio/.
Para cambiar las opciones de un marco proporcional ya existente, puede
utilizar:
<tscreen><verb>
void gtk_aspect_frame_set( GtkAspectFrame *aspect_frame,
gfloat xalign,
gfloat yalign,
gfloat ratio,
gint obey_child);
</verb></tscreen>
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.
<tscreen><verb>
/* principio del ejemplo aspectframe aspectframe.c */
#include <gtk/gtk.h>
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *aspect_frame;
GtkWidget *drawing_area;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Aspect Frame");
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* Crear un aspect_frame y añadirlo a nuestra ventana superior */
aspect_frame = gtk_aspect_frame_new ("2x1", /* etiqueta */
0.5, /* centro x */
0.5, /* centro y */
2, /* tamañox/tamañoy = 2 */
FALSE /* ignorar el aspecto del hijo */);
gtk_container_add (GTK_CONTAINER(ventana), aspect_frame);
gtk_widget_show (aspect_frame);
/* Añadir un widget hijo al marco proporcional */
drawing_area = gtk_drawing_area_new ();
/* Pediremos una ventana de 200x200, pero el marco proporcional
* sólo no dejará una ventana de 200x100, ya que tenemos una
* relación de 2x1 */
gtk_widget_set_usize (drawing_area, 200, 200);
gtk_container_add (GTK_CONTAINER(aspect_frame), drawing_area);
gtk_widget_show (drawing_area);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> El <em/widget/ ventana dividida (<em/Paned Window/)
<p>
El <em/widget/ ventana dividida es útil para cuando se quiere dividir
una zona en dos partes, con un tamaño relativo controlado por el
usuario. Entre las dos porciones de la ventana se dibuja un separador
con un botoncito que el usuario puede arrastrar para cambiar el tamaño
de cada zona. La división puede ser horizontal (HPaned) o vertical
(VPaned).
Para crear una nueva ventana dividida, utilice una de las siguientes
funciones:
<tscreen><verb>
GtkWidget *gtk_hpaned_new (void);
GtkWidget *gtk_vpaned_new (void);
</verb></tscreen>
Después de crear el <em/widget/ ventana dividida, tiene que añadirle
un <em/widget/ hijo a cada mitad. Para hacerlo, utilice:
<tscreen><verb>
void gtk_paned_add1 (GtkPaned *paned, GtkWidget *hijo);
void gtk_paned_add2 (GtkPaned *paned, GtkWidget *hijo);
</verb></tscreen>
<tt/gtk_paned_add1()/ añade el <em/widget/ hijo a la mitad que se
encuentra en la parte izquierda o superior de la ventana
dividida. <tt/gtk_paned_add2()/ añade el <em/widget/ a la mitad que
hay en la parte derecha o inferior de la ventana.
Por ejemplo, si queremos crear una parte del interface de usuario de
un programa de correo-e imaginario. Dividiremos verticalmente una
ventana en dos partes, teniendo en la parte superior una lista de los
mensajes de correo-e y en la parte inferior el texto de uno de estos
mensajes. El programa es bastante fácil de entender. Solo un par de
cosillas: no se puede añadir texto en un <em/widget/ de texto (Text)
si no se ha hecho antes <tt/gtk_widget_realize()/, pero como
demostración de una técnica alternativa, para añadir el texto
conectaremos un manipulador a la señal «realize». Y tenemos que
añadir la opción <tt/GTK_SHRINK/ a algunos de los elementos que hay en
la tabla con la ventana de texto y sus barras de desplazamiento, así
cuando la porción de abajo se haga más pequeña, se encogerá
correctamente en lugar de desaparecer por la parte de abajo de la
ventana.
<tscreen><verb>
/* principio del ejemplo paned paned.c */
#include <gtk/gtk.h>
/* Crear la lista de "messages" */
GtkWidget *
create_list (void)
{
GtkWidget *scrolled_window;
GtkWidget *list;
GtkWidget *list_item;
int i;
char buffer[16];
/* Crear una nueva ventana con barras de desplazamiento si hacen
falta */
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
/* Crear una nueva lista y poner en ella la ventana con barras */
list = gtk_list_new ();
gtk_container_add (GTK_CONTAINER(scrolled_window), list);
gtk_widget_show (list);
/* Añadir algunos mensajes a la ventana */
for (i=0; i<10; i++) {
sprintf(buffer,"Message #%d",i);
list_item = gtk_list_item_new_with_label (buffer);
gtk_container_add (GTK_CONTAINER(list), list_item);
gtk_widget_show (list_item);
}
return scrolled_window;
}
/* Añadir algún texto a nuestro widget de texto - esta función se
invoca cada vez que se produce una señal realize en la
ventana. Podemos forzar esta señal mediante gtk_widget_realize, pero
primero tiene que formar parte de una jerarquía */
void
realize_text (GtkWidget *text, gpointer data)
{
gtk_text_freeze (GTK_TEXT (text));
gtk_text_insert (GTK_TEXT (text), NULL, &amp;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);
gtk_text_thaw (GTK_TEXT (text));
}
/* Creamos una zona con texto que muestra un "message" */
GtkWidget *
create_text (void)
{
GtkWidget *table;
GtkWidget *text;
GtkWidget *hscrollbar;
GtkWidget *vscrollbar;
/* Crea una tabla para contener el widget de texto y las barras de
desplazamiento */
table = gtk_table_new (2, 2, FALSE);
/* Pone un widget de texto en la esquina superior izquierda.
Observe la utilización de GTK_SHRINK en la dirección y */
text = gtk_text_new (NULL, NULL);
gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1,
GTK_FILL | GTK_EXPAND,
GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);
gtk_widget_show (text);
/* Pone una HScrollbar en la esquina inferior izquierda */
hscrollbar = gtk_hscrollbar_new (GTK_TEXT (text)->hadj);
gtk_table_attach (GTK_TABLE (table), hscrollbar, 0, 1, 1, 2,
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
gtk_widget_show (hscrollbar);
/* Y una VScrollbar en la esquina superior derecha */
vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj);
gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1,
GTK_FILL, GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0, 0);
gtk_widget_show (vscrollbar);
/* Y un manejador para poner un mensaje en el widget de texto
cuando reciba realize */
gtk_signal_connect (GTK_OBJECT (text), "realize",
GTK_SIGNAL_FUNC (realize_text), NULL);
return table;
}
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *vpaned;
GtkWidget *list;
GtkWidget *text;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Paned Windows");
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* crea un widget vpaned y lo añade a nuestra ventana superior */
vpaned = gtk_vpaned_new ();
gtk_container_add (GTK_CONTAINER(ventana), vpaned);
gtk_widget_show (vpaned);
/* Ahora crea los contenidos de las dos mitades de la ventana */
list = create_list ();
gtk_paned_add1 (GTK_PANED(vpaned), list);
gtk_widget_show (list);
text = create_text ();
gtk_paned_add2 (GTK_PANED(vpaned), text);
gtk_widget_show (text);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- XXX -->
<!-- ----------------------------------------------------------------- -->
<sect1> <em/Viewports/ <label id="sec_Viewports">
<p>
Probablemente nunca le llegue a hacer falta utilizar el <em/widget/
Viewport directamente. Será mucho más probable que tenga que utilizar
el <em/widget/ <ref id="sec_ScrolledWindows" name="Ventanas con barras
de desplazamiento"> que a su vez hace uso de <em/viewport/.
Un <em/widget viewport/ le permite meter dentro un gran <em/widget/,
de forma que sólo verá una parte del mismo. Utiliza
<ref id="sec_Adjustment" name="ajustes"> para definir la zona que se
está viendo actualmente.
Para crear un <em/viewport/ hay que utilizar la función:
<tscreen><verb>
GtkWidget *gtk_viewport_new( GtkAdjustment *hadjustment,
GtkAdjustment *vadjustment );
</verb></tscreen>
Como puede observar, se pueden especificar los ajustes horizontal y
vertical que el <em/widget/ va a utilizar en el mismo momento de su
creación. El <em/widget/ creará sus propios ajustes en caso de que
reciba como argumento un valor NULL.
Puede obtener y establecer los ajustes después de que se haya
creado el <em/widget/ utilizado las cuatro funciones siguientes:
<tscreen><verb>
GtkAdjustment *gtk_viewport_get_hadjustment (GtkViewport *viewport );
GtkAdjustment *gtk_viewport_get_vadjustment (GtkViewport *viewport );
void gtk_viewport_set_hadjustment( GtkViewport *viewport,
GtkAdjustment *adjustment );
void gtk_viewport_set_vadjustment( GtkViewport *viewport,
GtkAdjustment *adjustment );
</verb></tscreen>
La única función relativa al <em/viewport/ que queda que altera su
apariencia es:
<tscreen><verb>
void gtk_viewport_set_shadow_type( GtkViewport *viewport,
GtkShadowType type );
</verb></tscreen>
Los valores posibles para el argumento <tt/type/ son:
<itemize>
<item> GTK_SHADOW_NONE,
<item> GTK_SHADOW_IN,
<item> GTK_SHADOW_OUT,
<item> GTK_SHADOW_ETCHED_IN,
<item> GTK_SHADOW_ETCHED_OUT
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1>Ventanas con barras de desplazamiento <label id="sec_ScrolledWindows">
<p>
Las ventanas con barras de desplazamiento se utilizan para crear una zona
con barras de desplazamiento dentro de una ventana real. Puede insertar
cualquier tipo de <em/widget/ en una ventana con barras de
desplazamiento, y podrá utilizarlo sin importar su tamaño gracias a
las barras de desplazamiento.
La función siguiente se utiliza para crear una nueva ventana con
barras de desplazamiento.
<tscreen><verb>
GtkWidget *gtk_scrolled_window_new( GtkAdjustment *hadjustment,
GtkAdjustment *vadjustment );
</verb></tscreen>
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.
<tscreen><verb>
void gtk_scrolled_window_set_policy( GtkScrolledWindow *scrolled_window,
GtkPolicyType hscrollbar_policy,
GtkPolicyType vscrollbar_policy );
</verb></tscreen>
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.
<tscreen><verb>
/* principio del ejemplo scrolledwin scrolledwin.c */
#include <gtk/gtk.h>
void destroy(GtkWidget *widget, gpointer data)
{
gtk_main_quit();
}
int main (int argc, char *argv[])
{
static GtkWidget *ventana;
GtkWidget *scrolled_window;
GtkWidget *table;
GtkWidget *boton;
char buffer[32];
int i, j;
gtk_init (&amp;argc, &amp;argv);
/* Crea un nuevo cuadro de diálogo para que la ventana con barras de
* desplazamiento se meta dentro. Un cuadro de diálogo es como una
* ventana normal excepto que tiene dentro una vbox y un separador
* horizontal. Es sólo un atajo para crear cuadros de diálogo */
ventana = gtk_dialog_new ();
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
(GtkSignalFunc) destroy, NULL);
gtk_window_set_title (GTK_WINDOW (ventana), "dialog");
gtk_container_border_width (GTK_CONTAINER (ventana), 0);
gtk_widget_set_usize(ventana, 300, 300);
/* crea una nueva ventana con barras de desplazamiento. */
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10);
/* la política es GTK_POLICY_AUTOMATIC, o GTK_POLICY_ALWAYS.
* GTK_POLICY_AUTOMATIC decidirá automáticamente si necesita
* barras de desplazamiento, mientras que GTK_POLICY_ALWAYS pondrá
* siempre las barras de desplazamiento. El primer argumento se
* refiere a la barra horizontal, el segundo a la vertical. */
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
/* El cuadro de diálogo se crea con una vbox dentro de él. */
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(ventana)->vbox), scrolled_window,
TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
/* crea una tabla de 10 por 10 casillas. */
table = gtk_table_new (10, 10, FALSE);
/* pone el espacio en x y en y a 10 */
gtk_table_set_row_spacings (GTK_TABLE (table), 10);
gtk_table_set_col_spacings (GTK_TABLE (table), 10);
/* empaqueta la tabla en la ventana con barras de desplazamiento */
gtk_container_add (GTK_CONTAINER (scrolled_window), table);
gtk_widget_show (table);
/* crea una rejilla de botones de selección en la tabla para
* demostrar la ventana con barras de desplazamiento. */
for (i = 0; i < 10; i++)
for (j = 0; j < 10; j++) {
sprintf (buffer, "botón (%d,%d)\n", i, j);
boton = gtk_toggle_button_new_with_label (buffer);
gtk_table_attach_defaults (GTK_TABLE (table), boton,
i, i+1, j, j+1);
gtk_widget_show (boton);
}
/* Añade un botón "close" en la parte de abajo del cuadro de
* diálogo */
boton = gtk_button_new_with_label ("close");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (ventana));
/* hace que el botón puede ser elegido por defecto. */
GTK_WIDGET_SET_FLAGS (boton, GTK_CAN_DEFAULT);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (ventana)->action_area), boton, TRUE, TRUE, 0);
/* Hace que el botón sea el elegido por defecto. Con pulsar la
* tecla "Enter" se activará este botón. */
gtk_widget_grab_default (boton);
gtk_widget_show (boton);
gtk_widget_show (ventana);
gtk_main();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
Juegue un poco redimensionando la ventana. Vea como actuan las barras
de desplazamiento. También puede utilizar la función
<tt/gtk_widget_set_usize()/ para poner el tamaño por defecto de la
ventana o de cualquier otro <em/widget/.
<!-- XXX -->
<!-- ----------------------------------------------------------------- -->
<sect1>Cajas de botones
<p>
Las cajas de botones son útiles para crear grupos de botones. Hay
cajas horizontales y verticales. Puede crear una nueva caja de botones
utilizando alguna de las funciones siguientes, que crean
respectivamente una caja horizontal y otra vertical:
<tscreen><verb>
GtkWidget *gtk_hbutton_box_new( void );
GtkWidget *gtk_vbutton_box_new( void );
</verb></tscreen>
Los únicos atributos pertenecientes a las cajas de botones son los
que definen como se distribuyen los botones. Puede cambiar el
espaciado que hay entre los botones con:
<tscreen><verb>
void gtk_hbutton_box_set_spacing_default( gint spacing );
void gtk_vbutton_box_set_spacing_default( gint spacing );
</verb></tscreen>
Igualmente, se pueden obtener los actuales valores para el espaciado
utilizando:
<tscreen><verb>
gint gtk_hbutton_box_get_spacing_default( void );
gint gtk_vbutton_box_get_spacing_default( void );
</verb></tscreen>
El segundo atributo al que podemos acceder afecta al esquema de los
botones dentro de la caja. Se establece utilizando:
<tscreen><verb>
void gtk_hbutton_box_set_layout_default( GtkButtonBoxStyle layout );
void gtk_vbutton_box_set_layout_default( GtkButtonBoxStyle layout );
</verb></tscreen>
El argumento <tt/layout/ puede tomar uno de los siguientes valores:
<itemize>
<item> GTK_BUTTONBOX_DEFAULT_STYLE
<item> GTK_BUTTONBOX_SPREAD
<item> GTK_BUTTONBOX_EDGE
<item> GTK_BUTTONBOX_START
<item> GTK_BUTTONBOX_END
</itemize>
Puede obtenerse el esquema actual utilizando:
<tscreen><verb>
GtkButtonBoxStyle gtk_hbutton_box_get_layout_default( void );
GtkButtonBoxStyle gtk_vbutton_box_get_layout_default( void );
</verb></tscreen>
Podemos añadir botones a una caja de botones utilizando (como
siempre) la función:
<tscreen><verb>
gtk_container_add( GTK_CONTAINER(button_box), child_widget );
</verb></tscreen>
Aquí hay un ejemplo que ilustra todos los diferentes esquemas que
podemos utilizar con las cajas de botones.
<tscreen><verb>
/* principio del ejemplo buttonbox buttonbox.c */
#include <gtk/gtk.h>
/* Crear una Caja de Botones con los parámetros
* especificados */
GtkWidget *create_bbox (gint horizontal,
char* title,
gint spacing,
gint child_w,
gint child_h,
gint layout)
{
GtkWidget *frame;
GtkWidget *bbox;
GtkWidget *boton;
frame = gtk_frame_new (title);
if (horizontal)
bbox = gtk_hbutton_box_new ();
else
bbox = gtk_vbutton_box_new ();
gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
gtk_container_add (GTK_CONTAINER (frame), bbox);
/* Establece la apariencia de la Caja de Botones */
gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), layout);
gtk_button_box_set_spacing (GTK_BUTTON_BOX (bbox), spacing);
gtk_button_box_set_child_size (GTK_BUTTON_BOX (bbox), child_w, child_h);
boton = gtk_button_new_with_label ("OK");
gtk_container_add (GTK_CONTAINER (bbox), boton);
boton = gtk_button_new_with_label ("Cancel");
gtk_container_add (GTK_CONTAINER (bbox), boton);
boton = gtk_button_new_with_label ("Help");
gtk_container_add (GTK_CONTAINER (bbox), boton);
return(frame);
}
int main( int argc,
char *argv[] )
{
static GtkWidget* ventana = NULL;
GtkWidget *main_vbox;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *frame_horz;
GtkWidget *frame_vert;
/* Inicializa GTK */
gtk_init( &amp;argc, &amp;argv );
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Button Boxes");
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
gtk_container_set_border_width (GTK_CONTAINER (ventana), 10);
main_vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (ventana), main_vbox);
frame_horz = gtk_frame_new ("Horizontal Button Boxes");
gtk_box_pack_start (GTK_BOX (main_vbox), frame_horz, TRUE, TRUE, 10);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
gtk_container_add (GTK_CONTAINER (frame_horz), vbox);
gtk_box_pack_start (GTK_BOX (vbox),
create_bbox (TRUE, "Spread (spacing 40)", 40, 85, 20, GTK_BUTTONBOX_SPREAD),
TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (vbox),
create_bbox (TRUE, "Edge (spacing 30)", 30, 85, 20, GTK_BUTTONBOX_EDGE),
TRUE, TRUE, 5);
gtk_box_pack_start (GTK_BOX (vbox),
create_bbox (TRUE, "Start (spacing 20)", 20, 85, 20, GTK_BUTTONBOX_START),
TRUE, TRUE, 5);
gtk_box_pack_start (GTK_BOX (vbox),
create_bbox (TRUE, "End (spacing 10)", 10, 85, 20, GTK_BUTTONBOX_END),
TRUE, TRUE, 5);
frame_vert = gtk_frame_new ("Vertical Button Boxes");
gtk_box_pack_start (GTK_BOX (main_vbox), frame_vert, TRUE, TRUE, 10);
hbox = gtk_hbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 10);
gtk_container_add (GTK_CONTAINER (frame_vert), hbox);
gtk_box_pack_start (GTK_BOX (hbox),
create_bbox (FALSE, "Spread (spacing 5)", 5, 85, 20, GTK_BUTTONBOX_SPREAD),
TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (hbox),
create_bbox (FALSE, "Edge (spacing 30)", 30, 85, 20, GTK_BUTTONBOX_EDGE),
TRUE, TRUE, 5);
gtk_box_pack_start (GTK_BOX (hbox),
create_bbox (FALSE, "Start (spacing 20)", 20, 85, 20, GTK_BUTTONBOX_START),
TRUE, TRUE, 5);
gtk_box_pack_start (GTK_BOX (hbox),
create_bbox (FALSE, "End (spacing 20)", 20, 85, 20, GTK_BUTTONBOX_END),
TRUE, TRUE, 5);
gtk_widget_show_all (ventana);
/* Entra dentro del bucle de eventos */
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Barras de herramientas
<p>
Las barras de herramientas acostumbran a agrupar un conjunto de
<em>widgets</em> para hacer más sencilla la personalización
de su aspecto y composición. Típicamente una barra de herramientas
consiste en botones con iconos, etiquetas y <em/tips/ para los iconos
(pequeño texto descriptivo que aparece cuando se mantiene el ratón
sobre el icono), pero en realidad en una barra se puede poner
cualquier tipo de <em>widget</em>. Finalmente, los elementos se pueden
disponer de forma horizontal o vertical, y los botones pueden mostrar
iconos, etiquetas o ambos.
La creación de una barra de herramientas se hace (como puede que ya
haya sospechado) mediante la función siguiente:
<tscreen><verb>
GtkWidget *gtk_toolbar_new( GtkOrientation orientation,
GtkToolbarStyle style );
</verb></tscreen>
donde <tt/orientation/ puede ser:
<tscreen><verb>
GTK_ORIENTATION_HORIZONTAL
GTK_ORIENTATION_VERTICAL
</verb></tscreen>
y <tt/style/:
<tscreen><verb>
GTK_TOOLBAR_TEXT
GTK_TOOLBAR_ICONS
GTK_TOOLBAR_BOTH
</verb></tscreen>
La variable <tt/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 <em>widgets</em> 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 <em/tip/, un texto para el <em/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:
<tscreen><verb>
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 );
</verb></tscreen>
Si quiere utilizar <tt/gtk_toolbar_insert_item/, el único parámetro
adicional que debería especificar es la posición en la que quiere que
se introduzca el elemento.
Para añadir un espacio en blanco entre los elementos de la barra de
herramientas, puede utilizar la función siguiente:
<tscreen><verb>
void gtk_toolbar_append_space( GtkToolbar *toolbar );
void gtk_toolbar_prepend_space( GtkToolbar *toolbar );
void gtk_toolbar_insert_space( GtkToolbar *toolbar,
gint posicion );
</verb></tscreen>
Y el tamaño del espacio en blanco puede establecerse globalmente
para toda una barra de herramientas con la función:
<tscreen><verb>
void gtk_toolbar_set_space_size( GtkToolbar *toolbar,
gint space_size) ;
</verb></tscreen>
Si tiene que establecer la orientación de una barra de herramientas y
su estilo, puede hacerlo `al vuelo' con las funciones siguientes:
<tscreen><verb>
void gtk_toolbar_set_orientation( GtkToolbar *toolbar,
GtkOrientation orientation );
void gtk_toolbar_set_style( GtkToolbar *toolbar,
GtkToolbarStyle style );
void gtk_toolbar_set_tooltips( GtkToolbar *toolbar,
gint enable );
</verb></tscreen>
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):
<tscreen><verb>
#include <gtk/gtk.h>
#include "gtk.xpm"
/* Esta función está conectada al botón Close o a la acción de cerrar
* la ventana desde el WM */
void delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
gtk_main_quit ();
}
</verb></tscreen>
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.
<tscreen><verb>
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
</verb></tscreen>
En realidad no necesitamos todos los <em>widgets</em> que acabo de
poner, pero para aclarar las cosas un poco más los he puesto todos.
<tscreen><verb>
/* 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);
}
/* todavía más fácil, sólo hay que comprobar el botón de selección
* y activar/desactivar los tooltips */
void toggle_event (GtkWidget *widget, gpointer data)
{
gtk_toolbar_set_tooltips (GTK_TOOLBAR ( data ),
GTK_TOGGLE_BUTTON (widget)->active );
}
</verb></tscreen>
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)
<tscreen><verb>
int main (int argc, char *argv[])
{
/* Aquí está nuestra ventana principal (un cuadro de diálogo) y una
* caja flotante */
GtkWidget* dialog;
GtkWidget* handlebox;
/* De acuerdo, necesitamos una barra de herramientas, un icono con
* una máscara (una para todos los botones) y un widget icono donde
* meter el icono (crearemos un widget diferente para cada botón) */
GtkWidget * toolbar;
GdkPixmap * icon;
GdkBitmap * mask;
GtkWidget * iconw;
/* a esta función se le llama en todas las aplicación GTK */
gtk_init (&amp;argc, &amp;argv);
/* crear una ventana nueva con un título y el tamaño adecuado */
dialog = gtk_dialog_new ();
gtk_window_set_title ( GTK_WINDOW ( dialog ) , "GTKToolbar Tutorial");
gtk_widget_set_usize( GTK_WIDGET ( dialog ) , 600 , 300 );
GTK_WINDOW ( dialog ) ->allow_shrink = TRUE;
/* salimos si alguien intenta cerrarnos */
gtk_signal_connect ( GTK_OBJECT ( dialog ), "delete_event",
GTK_SIGNAL_FUNC ( delete_event ), NULL);
/* tenemos que mandar la señalo realize porque utilizamos pixmaps
* para los elementos que hay en la barra de herramientas */
gtk_widget_realize ( dialog );
/* para hacerlo más bonito ponemos la barra de herramientas en la
* caja flotante, para que así se pueda desatar de la ventana
* principal */
handlebox = gtk_handle_box_new ();
gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG(dialog)->vbox ),
handlebox, FALSE, FALSE, 5 );
</verb></tscreen>
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 <em>widgets</em>. 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
<em>widgets</em> suelen aparecer juntos.
<tscreen><verb>
/* la barra de herramientas será horizontal, con iconos y texto, y
* con un espacio de 5pxl entre elementos y finalmente, la ponemos en
* nuestra caja flotante */
toolbar = gtk_toolbar_new ( GTK_ORIENTATION_HORIZONTAL,
GTK_TOOLBAR_BOTH );
gtk_container_border_width ( GTK_CONTAINER ( toolbar ) , 5 );
gtk_toolbar_set_space_size ( GTK_TOOLBAR ( toolbar ), 5 );
gtk_container_add ( GTK_CONTAINER ( handlebox ) , toolbar );
/* ahora creamos el icono con la máscara: utilizaremos el widget
* icon con todos los elementos de la barra de herramientas */
icon = gdk_pixmap_create_from_xpm_d ( dialog->window, &amp;mask,
&amp;dialog->style->white, gtk_xpm );
</verb></tscreen>
Bien, lo que acabamos de escribir es la inicialización del
<em>widget</em> de la barra de herramientas y la creación de un
<em>pixmap</em> GDK con su máscara. Si quiere saber algo más sobre la
utilización de <em>pixmaps</em>, vea la documentación de GDK o la
sección <ref id="sec_Pixmaps" name="Pixmaps"> en este tutorial.
<tscreen><verb>
/* nuestro primer elemento es el botón <close> */
iconw = gtk_pixmap_new ( icon, mask ); // icon widget
close_button =
gtk_toolbar_append_item ( GTK_TOOLBAR (toolbar), // nuestra barra
"Close", // etiqueta del botón
"Closes this app", // tooltip para el botón
"Private", // cadena privada del tooltip
iconw, // widget del icono
GTK_SIGNAL_FUNC (delete_event), // una señal
NULL );
gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) ); // espacio después del elemento
</verb></tscreen>
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 <em>widget
pixmap</em> 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, <tt/gtk_toolbar_append_item/ devuelve un
puntero al <em>widget</em> de nuestro nuevo botón recien creado, por
lo que podremos trabajar con él como siempre.
<tscreen><verb>
/* 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 ) );
</verb></tscreen>
Aquí empezamos creando un grupo de botones circulares. Para hacerlo
hemos utilizado <tt/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
<ref id="sec_Radio_Buttons" name="Botones circulares"> que se encuentra
más adelante en este tutorial).
<tscreen><verb>
/* los botones circulares que vienen a continuación están
relacionados con los anteriores */
iconw = gtk_pixmap_new ( icon, mask );
text_button =
gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
GTK_TOOLBAR_CHILD_RADIOBUTTON,
icon_button,
"Text",
"Only texts in toolbar",
"Private",
iconw,
GTK_SIGNAL_FUNC (radio_event),
toolbar);
gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );
iconw = gtk_pixmap_new ( icon, mask );
both_button =
gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
GTK_TOOLBAR_CHILD_RADIOBUTTON,
text_button,
"Both",
"Icons and text in toolbar",
"Private",
iconw,
GTK_SIGNAL_FUNC (radio_event),
toolbar);
gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(both_button),TRUE);
</verb></tscreen>
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).
<tscreen><verb>
/* 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);
</verb></tscreen>
Un botón de selección puede crearse de una forma obvia (si ya sabe como
crear botones circulares).
<tscreen><verb>
/* para empaquetar un widget en la barra de herramientas, sólo
* tenemos que crearlo y añadirlo en la barra con el tooltip
* apropiado */
entry = gtk_entry_new ();
gtk_toolbar_append_widget( GTK_TOOLBAR (toolbar),
entry,
"This is just an entry",
"Private" );
/* bien, no se ha creado con la barra, así que debemos mostrarlo
* explicitamente */
gtk_widget_show ( entry );
</verb></tscreen>
Como puede ver, añadir cualquier tipo de <em>widget</em> a la barra
de herramientas es fácil. Lo único que debe recordar es que este
<em>widget</em> debe mostrarse manualmente (al contrario que los demás
elementos que se mostrarán junto con la barra de herramientas).
<tscreen><verb>
/* ¡ Eso es ! mostremos algo. */
gtk_widget_show ( toolbar );
gtk_widget_show (handlebox);
gtk_widget_show ( dialog );
/* quedémonos en gtk_main y ¡esperemos a que empiece la diversión! */
gtk_main ();
return 0;
}
</verb></tscreen>
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:
<tscreen><verb>
/* XPM */
static char * gtk_xpm[] = {
"32 39 5 1",
". c none",
"+ c black",
"@ c #3070E0",
"# c #F05050",
"$ c #35E035",
"................+...............",
"..............+++++.............",
"............+++++@@++...........",
"..........+++++@@@@@@++.........",
"........++++@@@@@@@@@@++........",
"......++++@@++++++++@@@++.......",
".....+++@@@+++++++++++@@@++.....",
"...+++@@@@+++@@@@@@++++@@@@+....",
"..+++@@@@+++@@@@@@@@+++@@@@@++..",
".++@@@@@@+++@@@@@@@@@@@@@@@@@@++",
".+#+@@@@@@++@@@@+++@@@@@@@@@@@@+",
".+##++@@@@+++@@@+++++@@@@@@@@$@.",
".+###++@@@@+++@@@+++@@@@@++$$$@.",
".+####+++@@@+++++++@@@@@+@$$$$@.",
".+#####+++@@@@+++@@@@++@$$$$$$+.",
".+######++++@@@@@@@++@$$$$$$$$+.",
".+#######+##+@@@@+++$$$$$$@@$$+.",
".+###+++##+##+@@++@$$$$$$++$$$+.",
".+###++++##+##+@@$$$$$$$@+@$$@+.",
".+###++++++#+++@$$@+@$$@++$$$@+.",
".+####+++++++#++$$@+@$$++$$$$+..",
".++####++++++#++$$@+@$++@$$$$+..",
".+#####+++++##++$$++@+++$$$$$+..",
".++####+++##+#++$$+++++@$$$$$+..",
".++####+++####++$$++++++@$$$@+..",
".+#####++#####++$$+++@++++@$@+..",
".+#####++#####++$$++@$$@+++$@@..",
".++####++#####++$$++$$$$$+@$@++.",
".++####++#####++$$++$$$$$$$$+++.",
".+++####+#####++$$++$$$$$$$@+++.",
"..+++#########+@$$+@$$$$$$+++...",
"...+++########+@$$$$$$$$@+++....",
".....+++######+@$$$$$$$+++......",
"......+++#####+@$$$$$@++........",
".......+++####+@$$$$+++.........",
".........++###+$$$@++...........",
"..........++##+$@+++............",
"...........+++++++..............",
".............++++..............."};
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Libros de notas (<em/Notebooks/)
<p>
El <em/widget/ Notebook es una colección de `páginas' que se solapan
las unas a las otras, cada una con un contenido diferente. Este
<em/widget/ se ha vuelto cada vez más común últimamente en la
programación de interfaces gráficos de usuario (GUI en inglés), y es
una buena forma de mostrar bloques de información similar que
necesitan aparecer de forma separada.
La primera función que necesita conocer, como probablemente ya habrá
adivinado, se utiliza para crear un nuevo <em/widget/ notebook.
<tscreen><verb>
GtkWidget *gtk_notebook_new( void );
</verb></tscreen>
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.
<tscreen><verb>
void gtk_notebook_set_tab_pos( GtkNotebook *notebook,
GtkPositionType pos );
</verb></tscreen>
<tt/GtkPositionType/ debe tener uno de los valores siguientes (su significado
está bastante claro):
<itemize>
<item> GTK_POS_LEFT
<item> GTK_POS_RIGHT
<item> GTK_POS_TOP
<item> GTK_POS_BOTTOM
</itemize>
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 <em/widget/. Veamos las dos primeras
formas (son muy parecidas).
<tscreen><verb>
void gtk_notebook_append_page( GtkNotebook *notebook,
GtkWidget *hijo,
GtkWidget *tab_label );
void gtk_notebook_prepend_page( GtkNotebook *notebook,
GtkWidget *hijo,
GtkWidget *tab_label );
</verb></tscreen>
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). <tt/hijo/ es el <em/widget/ que se mete en la página
del libro de notas, y <tt/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.
<tscreen><verb>
void gtk_notebook_insert_page( GtkNotebook *notebook,
GtkWidget *hijo,
GtkWidget *tab_label,
gint posicion );
</verb></tscreen>
Los parámetros son los mismos que habían en las funciones _append_ y
_prepend_ excepto que hay uno más que antes, <tt/posicion/. 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.
<tscreen><verb>
void gtk_notebook_remove_page( GtkNotebook *notebook,
gint page_num );
</verb></tscreen>
Esta función coge la página especificada por <tt/page_num/ y la
elimina del <em/widget/ al que apunta <tt/notebook/.
Para saber cual es la página actual del libro de notas utilice la
función:
<tscreen><verb>
gint gtk_notebook_current_page( GtkNotebook *notebook );
</verb></tscreen>
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 <em/widget/ notebook que queremos manipular. Nota:
cuando el libro de notas está en la última página y se llama a
<tt/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
<tt/gtk_notebook_prev_page/, no se pasará a la última página.
<tscreen><verb>
void gtk_notebook_next_page( GtkNoteBook *notebook );
void gtk_notebook_prev_page( GtkNoteBook *notebook );
</verb></tscreen>
La siguiente función establece la página `activa'. Si quiere que se
abra el libro de notas por la página 5, por ejemplo, debe utilizar
esta función. Si no utiliza esta función el libro de notas empezará
por defecto en la primera página.
<tscreen><verb>
void gtk_notebook_set_page( GtkNotebook *notebook,
gint page_num );
</verb></tscreen>
Las dos funciones siguientes añaden o eliminan los indicadores de las
páginas o el borde del libro, respectivamente.
<tscreen><verb>
void gtk_notebook_set_show_tabs( GtkNotebook *notebook,
gint show_tabs);
void gtk_notebook_set_show_border( GtkNotebook *notebook,
gint show_border );
</verb></tscreen>
<tt/show_tabs/ y <tt/show_border/ puede ser TRUE o FALSE.
Ahora echémosle un vistaza a un ejemplo, sacado del código de
<tt/testgtk.c/ que viene con la distribución de GTK, y que muestra
la utilización de las 13 funciones. Este pequeño programa crea una
ventana con un libro de notas y seis botones. El libro de notas
contiene 11 páginas, incluidas de tres formas diferentes, añadidas,
insertadas, y preañadidas. Los botones le permiten rotar las
posiciones de los indicadores, añadir y eliminar los indicadores y el
borde, eliminar una página, cambiar páginas hacia delante y hacia
detrás, y salir del programa.
<tscreen><verb>
/* principio del ejemplo notebook notebook.c */
#include <gtk/gtk.h>
/* Esta función rota la posición de los indicadores */
void rotate_book (GtkButton *boton, GtkNotebook *notebook)
{
gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4);
}
/* Añade/Elimina los indicadores de la página y los bordes */
void tabsborder_book (GtkButton *boton, GtkNotebook *notebook)
{
gint tval = FALSE;
gint bval = FALSE;
if (notebook->show_tabs == 0)
tval = TRUE;
if (notebook->show_border == 0)
bval = TRUE;
gtk_notebook_set_show_tabs (notebook, tval);
gtk_notebook_set_show_border (notebook, bval);
}
/* Elimina una página del libro de notas */
void remove_book (GtkButton *boton, GtkNotebook *notebook)
{
gint page;
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);
}
void delete (GtkWidget *widget, GtkWidget *event, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *boton;
GtkWidget *table;
GtkWidget *notebook;
GtkWidget *frame;
GtkWidget *etiqueta;
GtkWidget *checkbutton;
int i;
char bufferf[32];
char bufferl[32];
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (ventana), "delete_event",
GTK_SIGNAL_FUNC (delete), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
table = gtk_table_new(2,6,TRUE);
gtk_container_add (GTK_CONTAINER (ventana), table);
/* Crea un nuevo libro de notas, indicando la posición de los
indicadores */
notebook = gtk_notebook_new ();
gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1);
gtk_widget_show(notebook);
/* le añadimos un montón de páginas al libro de notas */
for (i=0; i < 5; i++) {
sprintf(bufferf, "Append Frame %d", i+1);
sprintf(bufferl, "Page %d", i+1);
frame = gtk_frame_new (bufferf);
gtk_container_border_width (GTK_CONTAINER (frame), 10);
gtk_widget_set_usize (frame, 100, 75);
gtk_widget_show (frame);
etiqueta = gtk_label_new (bufferf);
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_widget_show (etiqueta);
etiqueta = gtk_label_new (bufferl);
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, etiqueta);
}
/* Ahora añadimos una página en punto específico */
checkbutton = gtk_check_button_new_with_label ("Check me please!");
gtk_widget_set_usize(checkbutton, 100, 75);
gtk_widget_show (checkbutton);
etiqueta = gtk_label_new ("Add spot");
gtk_container_add (GTK_CONTAINER (checkbutton), etiqueta);
gtk_widget_show (etiqueta);
etiqueta = gtk_label_new ("Add page");
gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, etiqueta, 2);
/* Y finalmente preañadimos páginas en el libro de notas */
for (i=0; i < 5; i++) {
sprintf(bufferf, "Prepend Frame %d", i+1);
sprintf(bufferl, "PPage %d", i+1);
frame = gtk_frame_new (bufferf);
gtk_container_border_width (GTK_CONTAINER (frame), 10);
gtk_widget_set_usize (frame, 100, 75);
gtk_widget_show (frame);
etiqueta = gtk_label_new (bufferf);
gtk_container_add (GTK_CONTAINER (frame), etiqueta);
gtk_widget_show (etiqueta);
etiqueta = gtk_label_new (bufferl);
gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, etiqueta);
}
/* Decimos en que página empezar (página 4) */
gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3);
/* creamos un montón de botones */
boton = gtk_button_new_with_label ("close");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (delete), NULL);
gtk_table_attach_defaults(GTK_TABLE(table), boton, 0,1,1,2);
gtk_widget_show(boton);
boton = gtk_button_new_with_label ("next page");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) gtk_notebook_next_page,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), boton, 1,2,1,2);
gtk_widget_show(boton);
boton = gtk_button_new_with_label ("prev page");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) gtk_notebook_prev_page,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), boton, 2,3,1,2);
gtk_widget_show(boton);
boton = gtk_button_new_with_label ("tab position");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) rotate_book, GTK_OBJECT(notebook));
gtk_table_attach_defaults(GTK_TABLE(table), boton, 3,4,1,2);
gtk_widget_show(boton);
boton = gtk_button_new_with_label ("tabs/border on/off");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) tabsborder_book,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), boton, 4,5,1,2);
gtk_widget_show(boton);
boton = gtk_button_new_with_label ("remove page");
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
(GtkSignalFunc) remove_book,
GTK_OBJECT(notebook));
gtk_table_attach_defaults(GTK_TABLE(table), boton, 5,6,1,2);
gtk_widget_show(boton);
gtk_widget_show(table);
gtk_widget_show(ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
Espero que la explicación le ayude de alguna manera a crear libros de
notas en sus aplicaciones GTK.
<!-- ***************************************************************** -->
<sect> El <em/widget/ GtkCList
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<p>
El <em>widget</em> GtkCList ha reemplazado al <em>widget</em> GtkList
(que sigue estando disponible).
El <em>widget</em> GtkCList es un <em>widget</em> 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.
<!-- ----------------------------------------------------------------- -->
<sect1>Creando un <em>widget</em> GtkCList
<p>
Crear un GtkCList es algo bastante sencillo, una vez que sabe como
crear un <em>widget</em> en general. Se proporcionan al menos dos
formas estándar de crearlo, la forma fácil y la forma difícil. Pero
antes de crear una GtkCList, hay una cosa que debemos saber: ¿Cuántas
columnas va a tener?
No todas las columnas tienen que ser visibles y pueden utilizarse para
almacenar datos que estén relacionados con una cierta celda de la
lista.
<tscreen><verb>
GtkWidget *gtk_clist_new ( gint columns );
GtkWidget *gtk_clist_new_with_titles( gint columns,
gchar *titles[] );
</verb></tscreen>
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.
<!-- ----------------------------------------------------------------- -->
<sect1>Modos de operación
<p>
Hay varios atributos que pueden utilizarse para alterar el aspecto
de un GtkCList. Primero tenemos
<tscreen><verb>
void gtk_clist_set_selection_mode( GtkCList *clist,
GtkSelectionMode mode );
</verb></tscreen>
que, como el propio nombre indica, establece el modo de selección de la
lista GtkCList. El primer argumento es el <em>widget</em> GtkCList, y el
segundo especifica el modo de selección de la celda (están definidos
en <tt/gtkenums.h/). En el momento de escribir esto, estaban
disponibles los siguientes modos:
<itemize>
<item> GTK_SELECTION_SINGLE - La selección o es NULL o contiene un
puntero GList a un elemento seleccionado.
<item> GTK_SELECTION_BROWSE - La selección es NULL si la lista no
contiene <em>widgets</em> 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.
<item> 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.<!-- FIXME: Todo esto no se si tiene sentido -->
Éste apunta de nuevo a una estructura GList para el segundo elemento
seleccionado y continua así. Éste es, actualmente, el modo por
<bf>defecto</bf> para el <em>widget</em> GtkCList.
<item> GTK_SELECTION_EXTENDED - La selección siempre es NULL.
</itemize>
Puede que se añadan otros modos en versiones posteriores de GTK.
También tenemos
<tscreen><verb>
void gtk_clist_set_policy (GtkCList *clist,
GtkPolicyType vscrollbar_policy,
GtkPolicyType hscrollbar_policy);
</verb></tscreen>
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:
<itemize>
<item> GTK_POLICY_ALWAYS - La barra de desplazamiento siempre está ahí.
<item> 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 <em>widget</em>.
</itemize>
También podemos definir como debería ser el aspecto del borde del
<em>widget</em> GtkCList. Esto lo podemos hacer mediante
<tscreen><verb>
void gtk_clist_set_border( GtkCList *clist,
GtkShadowType border );
</verb></tscreen>
Y los posibles valores para el segundo argumento son
<itemize>
<item> GTK_SHADOW_NONE
<item> GTK_SHADOW_IN
<item> GTK_SHADOW_OUT
<item> GTK_SHADOW_ETCHED_IN
<item> GTK_SHADOW_ETCHED_OUT
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1>Trabajando con los títulos
<p>
Cuando cree un <em>widget</em> GtkCList, también obtendrá
automáticamente un conjunto de botones título. Vivirán en lo alto de
una ventana CList, y pueden actuar como botones normales que responden
cuando se pulsa sobre ellos, o bien pueden ser pasivos, en cuyo caso
no serán nada más que un título. Hay cuatro llamadas diferentes que
nos ayudarán a establecer el estado de los botones título.
<tscreen><verb>
void gtk_clist_column_title_active( GtkCList *clist,
gint column );
void gtk_clist_column_title_passive( GtkCList *clist,
gint column );
void gtk_clist_column_titles_active( GtkCList *clist );
void gtk_clist_column_titles_passive( GtkCList *clist );
</verb></tscreen>
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
<tt/column/, mientras que las dos llamadas siguientes
activarán/desactivarán todos los botones título que hayan en el
<em>widget</em> <tt/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:
<tscreen><verb>
void gtk_clist_column_titles_show( GtkCList *clist );
void gtk_clist_column_titles_hide( GtkCList *clist );
</verb></tscreen>
Para que los títulos sean realmente útiles necesitamos un mecanismo
que nos permita darles el valor que nosotros queramos y cambiar ese
valor, y podremos hacerlo mediante
<tscreen><verb>
void gtk_clist_set_column_title( GtkCList *clist,
gint column,
gchar *title );
</verb></tscreen>
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 <tt/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 <em>widget</em>
entero, por ejemplo un <em>pixmap</em>. Todo esto se hace mediante
<tscreen><verb>
void gtk_clist_set_column_widget( GtkCList *clist,
gint column,
GtkWidget *widget );
</verb></tscreen>
que no debería necesitar de explicaciones adicionales.
<!-- ----------------------------------------------------------------- -->
<sect1>Manipulando la lista en sí.
<p>
Es posible cambiar la justificación de una columna, y esto se hace
mediante
<tscreen><verb>
void gtk_clist_set_column_justification( GtkCList *clist,
gint column,
GtkJustification justification );
</verb></tscreen>
El tipo GtkJustification puede tomar los valores siguientes:
<itemize>
<item>GTK_JUSTIFY_LEFT - El texto en la columna empezará desde el lado
izquierdo.
<item>GTK_JUSTIFY_RIGHT - El texto en la columna empezará desde el
lado derecho.
<item>GTK_JUSTIFY_CENTER - El texto se colocará en el centro de la
columna.
<item>GTK_JUSTIFY_FILL - El texto utilizará todo el espacio disponible
en la columna. Normalmente se hace añadiendo espacios en blanco entre
las palabras (o entre letras por separado, si se trata de una sola
palabra). Más o menos de la misma forma en la que lo hace un
procesador de textos WYSIWYG.
</itemize>
La siguiente función es muy importante, y debería ser un estándar
para inicializar todos los <em>widgets</em> GtkCList. Cuando se crea
la lista, los anchos de las distintas columnas se eligen para que
coincidan con sus títulos, y éste es el ancho adecuado que tenemos que
poner, utilizando
<tscreen><verb>
void gtk_clist_set_column_width( GtkCList *clist,
gint column,
gint width );
</verb></tscreen>
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
<tscreen><verb>
void gtk_clist_set_row_height( GtkCList *clist,
gint height );
</verb></tscreen>
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.
<tscreen><verb>
void gtk_clist_moveto( GtkCList *clist,
gint row,
gint column,
gfloat row_align,
gfloat col_align );
</verb></tscreen>
Es importante comprender bien el significado de <tt/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, <tt/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
<tscreen><verb>
GtkVisibility gtk_clist_row_is_visible( GtkCList *clist,
gint row );
</verb></tscreen>
El valor devuelto es uno de los siguientes:
<itemize>
<item>GTK_VISIBILITY_NONE
<item>GTK_VISIBILITY_PARTIAL
<item>GTK_VISIBILITY_FULL
</itemize>
Como puede ver, sólo nos dice si una fila es visible. Actualmente no hay
ninguna forma de obtener el mismo dato para una columna. Sin embargo
podemos obtener información parcial, porque si el valor devuelto es
GTK_VISIBILITY_PARTIAL, entonces es que alguna parte está oculta,
pero no sabemos si es la fila que está cortada por la parte de abajo
de la lista, o si la fila tiene columnas que están fuera.
También podemos cambiar el color del primer y del segundo plano de una
fila en particular. Esto es útil para marcar la fila seleccionada por
el usuario, y las dos funciones que hay que utilizar son
<tscreen><verb>
void gtk_clist_set_foreground( GtkCList *clist,
gint row,
GdkColor *color );
void gtk_clist_set_background( GtkCList *clist,
gint row,
GdkColor *color );
</verb></tscreen>
Cuidado, ya que los colores deben estar asignados previamente en la
memoria.
<!-- ----------------------------------------------------------------- -->
<sect1>Añadiendo filas a la lista
<p>
Podemos añadir filas de dos formas. Se pueden añadir al final de la lista
utilizando
<tscreen><verb>
gint gtk_clist_append( GtkCList *clist,
gchar *text[] );
</verb></tscreen>
o podemos insertar una fila en un lugar determinado utilizando
<tscreen><verb>
void gtk_clist_insert( GtkCList *clist,
gint row,
gchar *text[] );
</verb></tscreen>
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 <tt/text[]/ es NULL, entonces no habrá texto en las columnas
de la fila. Esto sería útil, por ejemplo, si queremos añadir
<em>pixmaps</em> 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
<tscreen><verb>
void gtk_clist_remove( GtkCList *clist,
gint row );
</verb></tscreen>
Hay también una llamada que elimina todas las filas en la lista.
Es mucho más rápido que llamar a <tt/gtk_clist_remove/ una vez por
cada fila, que sería la única alternativa.
<tscreen><verb>
void gtk_clist_clear( GtkCList *clist );
</verb></tscreen>
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.
<tscreen><verb>
void gtk_clist_freeze( GtkCList * clist );
void gtk_clist_thaw( GtkCList * clist );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Poniendo texto y <em>pixmaps</em> en las celdas
<p>
Una celda puede contener un <em>pixmap</em>, texto o ambos. Para ponerlos
en las celdas, podemos utilizar las siguientes funciones.
<tscreen><verb>
void gtk_clist_set_text( GtkCList *clist,
gint row,
gint column,
gchar *text );
void gtk_clist_set_pixmap( GtkCList *clist,
gint row,
gint column,
GdkPixmap *pixmap,
GdkBitmap *mask );
void gtk_clist_set_pixtext( GtkCList *clist,
gint row,
gint column,
gchar *text,
guint8 spacing,
GdkPixmap *pixmap,
GdkBitmap *mask );
</verb></tscreen>
Son bastante sencillas de entender. Todas las llamadas tienen la
GtkCList como primer argumento, seguidas por la fila y la columna
de la celda, y seguidas por el dato que debe ponerse en la celda. El
argumento <tt/gint8 spacing/ en <tt/gtk_clist_set_pixtext/ es el
número de <em>pixels</em> entre el <em>pixmap</em> y el principio del
texto.
Para leer los datos que hay en una celda, podemos utilizar
<tscreen><verb>
gint gtk_clist_get_text( GtkCList *clist,
gint row,
gint column,
gchar **text );
gint gtk_clist_get_pixmap( GtkCList *clist,
gint row,
gint column,
GdkPixmap **pixmap,
GdkBitmap **mask );
gint gtk_clist_get_pixtext( GtkCList *clist,
gint row,
gint column,
gchar **text,
guint8 *spacing,
GdkPixmap **pixmap,
GdkBitmap **mask );
</verb></tscreen>
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 <tt/clist/) pueden ser
NULL. Por lo que si sólo queremos leer el texto de una celda que es de
tipo <tt/pixtext/, deberíamos hacer lo siguiente, suponiendo que
<tt/clist/, <tt/row/ y <tt/column/ ya existan:
<tscreen><verb>
gchar *mytext;
gtk_clist_get_pixtext(clist, row, column, &amp;mytext, NULL, NULL, NULL);
</verb></tscreen>
Hay una rutina más que está relacionada con lo que está dentro
de una celda de una <tt/clist/, y es
<tscreen><verb>
GtkCellType gtk_clist_get_cell_type( GtkCList *clist,
gint row,
gint column );
</verb></tscreen>
que devuelve el tipo de datos que hay en la celda. El valor devuelto es
uno de los siguientes
<itemize>
<item>GTK_CELL_EMPTY
<item>GTK_CELL_TEXT
<item>GTK_CELL_PIXMAP
<item>GTK_CELL_PIXTEXT
<item>GTK_CELL_WIDGET
</itemize>
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 <tt/gint/, viene dado en <em>pixeles</em>, y puede ser positivo o
negativo.
<tscreen><verb>
void gtk_clist_set_shift( GtkCList *clist,
gint row,
gint column,
gint vertical,
gint horizontal );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Almacenando punteros a datos
<p>
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
<tscreen><verb>
void gtk_clist_set_row_data( GtkCList *clist,
gint row,
gpointer data );
void gtk_clist_set_row_data_full( GtkCList *clist,
gint row,
gpointer data,
GtkDestroyNotify destroy );
gpointer gtk_clist_get_row_data( GtkCList *clist,
gint row );
gint gtk_clist_find_row_from_data( GtkCList *clist,
gpointer data );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Trabajando con la selección
<p>
También hay funciones que nos permiten forzar la (de)selección de una
fila. Son
<tscreen><verb>
void gtk_clist_select_row( GtkCList *clist,
gint row,
gint column );
void gtk_clist_unselect_row( GtkCList *clist,
gint row,
gint column );
</verb></tscreen>
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.
<tscreen><verb>
gint gtk_clist_get_selection_info( GtkCList *clist,
gint x,
gint y,
gint *row,
gint *column );
</verb></tscreen>
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...
<!-- ----------------------------------------------------------------- -->
<sect1>Las señales que lo hacen todo
<p>
Como con el resto de <em>widgets</em>, hay unas cuantas señales que
podemos utilizar. El <em>widget</em> GtkCList está derivado del
<em>widget</em> GtkContainer, y por tanto tiene las mismas
señales que éste, pero además añade las siguientes:
<itemize>
<item><tt/select_row/ - Esta señal enviará la siguiente información,
en este orden: GtkCList *clist, gint row, gint column, GtkEventButton
*event
<item><tt/unselect_row/ - Cuando el usuario deselecciona una fila, se
activará esta señal. Envia la misma información que <tt/select_row/
<item><tt/click_column/ - Envia GtkCList *clist, gint column
</itemize>
Por tanto si queremos conectar una llamada a <tt/select_row/, la
llamada se deberá declarar como
<tscreen><verb>
void select_row_callback(GtkWidget *widget,
gint row,
gint column,
GdkEventButton *event,
gpointer data);
</verb></tscreen>
La llamada se conectará, como siempre, con
<tscreen><verb>
gtk_signal_connect(GTK_OBJECT( clist),
"select_row"
GTK_SIGNAL_FUNC(select_row_callback),
NULL);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Un ejemplo GtkCList
<p>
<tscreen><verb>
/* principio del ejemplo clist clist.c */
#include <gtk/gtk.h>
#include <glib.h>
/* Aquí tenemos algunos prototipos de las funciones de llamada */
void button_add_clicked( GtkWidget *boton, gpointer data);
void button_clear_clicked( GtkWidget *boton, gpointer data);
void button_hide_show_clicked( GtkWidget *boton, gpointer data);
void selection_made( GtkWidget *clist, gint row, gint column,
GdkEventButton *event, gpointer data);
gint main (int argc, gchar *argv[])
{
GtkWidget *ventana;
GtkWidget *vbox, *hbox;
GtkWidget *clist;
GtkWidget *button_add, *button_clear, *button_hide_show;
gchar *titles[2] = {"Ingredients","Amount"};
gtk_init(&amp;argc, &amp;argv);
ventana=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize(GTK_WIDGET(ventana), 300, 150);
gtk_window_set_title(GTK_WINDOW(ventana), "GtkCList Example");
gtk_signal_connect(GTK_OBJECT(ventana),
"destroy",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
vbox=gtk_vbox_new(FALSE, 5);
gtk_container_border_width(GTK_CONTAINER(vbox), 5);
gtk_container_add(GTK_CONTAINER(ventana), vbox);
gtk_widget_show(vbox);
/* Crear el GtkCList. Para este ejemplo utilizaremos 2 columnas */
clist = gtk_clist_new_with_titles( 2, titles);
/* Cuando se hace una selección, queremos saber algo acerca de
* ella. La función de llamada utilizada es selection_made, y su
* código lo podemos encontrar más abajo */
gtk_signal_connect(GTK_OBJECT(clist), "select_row",
GTK_SIGNAL_FUNC(selection_made),
NULL);
/* No es necesario ponerle sombra al borde, pero es bonito :) */
gtk_clist_set_border(GTK_CLIST(clist), GTK_SHADOW_OUT);
/* Lo que sí que es importante, es poner el ancho de las columnas
* ya no tendrán el valor correcto en caso contrario. Recuerde que
* las columnas se numeran desde el 0 en adelante (hasta el 1 en
* este caso).
*/
gtk_clist_set_column_width (GTK_CLIST(clist), 0, 150);
/* Scollbars _only when needed_ */
gtk_clist_set_policy(GTK_CLIST(clist), GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
/* Añade el widget GtkCList a la caja vertical y lo muestra. */
gtk_box_pack_start(GTK_BOX(vbox), clist, TRUE, TRUE, 0);
gtk_widget_show(clist);
/* Crea los botones y los añade a la ventana. Ver la parte del
* tutorial sobre botones para ver más ejemplos y comentarios
* acerca de todo esto.
*/
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
gtk_widget_show(hbox);
button_add = gtk_button_new_with_label("Add List");
button_clear = gtk_button_new_with_label("Clear List");
button_hide_show = gtk_button_new_with_label("Hide/Show titles");
gtk_box_pack_start(GTK_BOX(hbox), button_add, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), button_clear, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), button_hide_show, TRUE, TRUE, 0);
/* Conectar nuestras funciones de llamada a los tres botones */
gtk_signal_connect_object(GTK_OBJECT(button_add), "clicked",
GTK_SIGNAL_FUNC(button_add_clicked),
(gpointer) clist);
gtk_signal_connect_object(GTK_OBJECT(button_clear), "clicked",
GTK_SIGNAL_FUNC(button_clear_clicked),
(gpointer) clist);
gtk_signal_connect_object(GTK_OBJECT(button_hide_show), "clicked",
GTK_SIGNAL_FUNC(button_hide_show_clicked),
(gpointer) clist);
gtk_widget_show(button_add);
gtk_widget_show(button_clear);
gtk_widget_show(button_hide_show);
/* Ahora hemos terminado el interface y sólo nos queda mostrar la
* ventana y entrar en el bucle gtk_main.
*/
gtk_widget_show(ventana);
gtk_main();
return 0;
}
/* El usuario pulsó el botón "Add List". */
void button_add_clicked( GtkWidget *boton, gpointer data)
{
int indx;
/* Algo tonto que añadir a la lista. 4 filas con 2 columnas cada
* una
*/
gchar *drink[4][2] = {{"Milk", "3 Oz"},
{"Water", "6 l"},
{"Carrots", "2"},
{"Snakes", "55"}};
/* Aquí hacemos la adición del texto. Se hace una vez por cada
* fila.
*/
for( indx=0; indx < 4; indx++)
gtk_clist_append( (GtkCList*) data, drink[indx]);
return;
}
/* El usuario pulsó el botón "Clear List" */
void button_clear_clicked( GtkWidget *boton, gpointer data)
{
/* Borrar la lista utilizando gtk_clist_clear. Esto es mucho más
* rápido que llamar a gtk_clist_remove una vez por cada fila.
*/
gtk_clist_clear((GtkCList*) data);
return;
}
/* El usuario pulsó el botón "Hide/Show titles". */
void button_hide_show_clicked( GtkWidget *boton, gpointer data)
{
/* Una bandera para recordar el estado. 0 = actualmente visible */
static short int flag = 0;
if (flag == 0)
{
/* Oculta los títulos y pone la bandera a 1 */
gtk_clist_column_titles_hide((GtkCList*) data);
flag++;
}
else
{
/* Muestra los títulos y pone la bandera a 0 */
gtk_clist_column_titles_show((GtkCList*) data);
flag--;
}
return;
}
/* Se llegamos aquí, entonces el usuario ha seleccionado una fila de
* la lista.
*/
void selection_made( GtkWidget *clist, gint row, gint column,
GdkEventButton *event, gpointer data)
{
gchar *text;
/* Obtiene el texto que se ha almacenado en la fila y columna
* sobre las que se ha pulsado. Lo recibiremos como un puntero en
* el argumento text.
*/
gtk_clist_get_text(GTK_CLIST(clist), row, column, &amp;text);
/* Imprime alguna información sobre la fila seleccionada */
g_print("You selected row %d. More specifically you clicked in column %d, and the text in this cell is %s\n\n", row, column, text);
return;
}
/* final del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> El <em>widget</em> árbol<label id="sec_Tree_Widgets">
<!-- ***************************************************************** -->
<p>
El propósito del <em>widget</em> GtkTree es mostrar datos organizados
de forma jerárquica. El <em>widget</em> GtkTree en sí es un contenedor
vertical para los <em>widgets</em> 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 <em>widgets</em> GtkTree que en los GtkList. La
diferencia es que los <em>widgets</em> GtkTree pueden anidarse
dentro de otros <em>widgets</em> GtkTree. Vamos a verlo de forma
resumida.
El <em>widget</em> 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.
<sect1> Creando un árbol
<p>
Puede crear un GtkTree de la forma usual, utilizando:
<tscreen><verb>
GtkWidget* gtk_tree_new( void );
</verb></tscreen>
Como el <em>widget</em> 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 <tt/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. <ref id="sec_Tree_Item_Widget" name="El
widget elemento de árbol"> más adelante explica todos los
detalles de GtkTreeItem. Por ahora, es suficiente con saber como crear
uno, utilizando:
<tscreen><verb>
GtkWidget* gtk_tree_item_new_with_label( gchar *etiqueta );
</verb></tscreen>
Puede añadirlo al árbol utilizando una de las siguientes funciones
(ver <ref id="sec_GtkTree_Functions" name="Funciones y macros">
más adelante para leer más opciones):
<tscreen><verb>
void gtk_tree_append( GtkTree *arbol,
GtkWidget *elemento_arbol );
void gtk_tree_prepend( GtkTree *arbol,
GtkWidget *elemento_arbol );
</verb></tscreen>
Observe que debe añadir elementos a un GtkTree de uno en uno - no
hay un equivalente a <tt/gtk_list_*_items()/.
<sect1> Añadiendo un Subárbol
<p>
Un subárbol se crea como cualquier otro <em>widget</em> GtkTree. Un
subárbol se añade a otro árbol bajo un elemento del mismo, utilizando:
<tscreen><verb>
void gtk_tree_item_set_subtree( GtkTreeItem *elemento_arbol,
GtkWidget *subarbol );
</verb></tscreen>
No necesita llamar a <tt/gtk_widget_show()/ en un subárbol ni antes ni
después de añadirlo a GtkTreeItem. Sin embargo, <em>deberá</em> haber
añadido el GtkTreeItem en cuestión a un árbol padre antes de llamar a
<em/gtk_tree_item_set_subtree()/. Esto se debe a que, técnicamente,
el padre del subárbol <em>no</em> es el GtkTreeItem «propietario»,
sino el GtkTree que contiene al GtkTreeItem.
Cuando le añade un subárbol a un GtkTreeItem, aparece el signo de un
más o de un menos a su lado, donde puede pinchar el usuario para
«expandirlo» u «contraerlo», o sea, para mostrar u ocultar su
subárbol. Los GtkTreeItems están contraídos por defecto. Observe que
cuando contrae un GtkTreeItem, cualquier elemento seleccionado en el
subárbol permanece seleccionado, que puede no coincidir con lo que el
usuario espera.
<sect1> Manejando la lista de selección
<p>
Como con GtkList, GtkTree tiene un campo <tt>selection</tt>, y
es posible controlar el comportamiento del árbol (de alguna manera)
estableciendo el tipo de selección, utilizando:
<tscreen><verb>
void gtk_tree_set_selection_mode( GtkTree *arbol,
GtkSelectionMode mode );
</verb></tscreen>
La semántica asociada con los distintos modos de selección está
descrita en la sección del <em>widget</em> GtkList. Como ocurría con
el <em>widget</em> GtkList, se enviarán las señales <tt/select_child/,
<tt/unselect_child/ (realmente no - ver <ref id="sec_GtkTree_Signals"
name="Señales"> más adelante para una explicación), y
<tt/selection_changed/ cuando los elementos de la lista sean
seleccionados o deseleccionados. Sin embargo, para aprovechar estas
señales, necesita conocer por medio <em>de que</em> <em>widget</em>
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 <em>widgets</em> GtkTree
son creados iguales, algunos son más iguales que otros. Todos los
<em>widgets</em> 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 <em>widget</em>
GtkTree superior de la jerarquia, conocido como el «árbol raíz».
Por tanto no es una buena idea acceder al campo <tt>selection</tt>
directamente en un <em>widget</em> GtkTree arbitrario, a menos que
<em>sepa</em> que es el árbol raíz. En vez de eso, utilice la
macro GTK_TREE_SELECTION (arbol), que da la lista selección del árbol
raíz como un puntero <tt/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 <tt/select_child/ (y tt/unselect_child/, en
teoría) son emitidas por todos los árboles, pero la señal
<em/selection_changed/ es emitida sólo por el árbol raíz. En
consecuencia, si quiere manipular la señal <tt/select_child/ de un
árbol y todos sus subárboles, tendrá que llamar a
<tt/gtk_signal_connect()/ una vez por cada subárbol.
<sect1> Estructura interna del <em>widget</em> árbol
<p>
La definición de la estructura GtkTree es ls siguiente:
<tscreen><verb>
struct _GtkTree
{
GtkContainer container;
GList *child;
GtkTree* root_tree; /* propietario de la lista de selección */
GtkWidget* tree_owner;
GList *selection;
guint level;
guint indent_value;
guint current_indent;
guint selection_mode : 2;
guint view_mode : 1;
guint view_line : 1;
};
</verb></tscreen>
Ya se han mencionado los peligros asociados con el acceso directo al
campo <tt>selection</tt>. Se puede acceder a los otros campos
importantes de la estructura mediante macros manipuladoras o
funciones de clase. GTK_TREE_IS_ROOT_TREE (arbol) devuelve un valor
booleano que indica si un árbol es árbol raíz de una jerarquia
GtkTree, mientras que GTK_TREE_ROOT_TREE (arbol) devuelve el árbol
raíz, un objeto de tipo GtkTree (recuerde transformarlo utilizando
GTK_WIDGET (arbol) si quiere utilizar con él alguna de la funciones
<tt/gtk_widget_*()/).
En lugar de acceder directamente al campo hijo de un <em>widget</em>
GtkTree, probablemente sea mejor transformarlo utilizando
GTK_CONTAINER (arbol), y pasárselo a la función
<tt/gtk_container_children()/. Con esto crearemos un duplicado de la
lista original, por lo que deberá eliminarlo de la memoria utilizando
<tt/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í:
<tscreen><verb>
hijo = gtk_container_children (GTK_CONTAINER (arbol));
while (hijo) {
do_something_nice (GTK_TREE_ITEM (hijo->data));
hijo = g_list_remove_link (hijo, hijo);
}
</verb></tscreen>
El campo <tt>tree_owner</tt> sólo está definido en subárboles, donde
apunta al <em>widget</em> GtkTreeItem que contiene al árbol en
cuestión. El campo <tt>level</tt> 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 <em>widget</em> GtkTree se dibuje en la pantalla.
<sect2> Señales<label id="sec_GtkTree_Signals">
<p>
<tscreen><verb>
void selection_changed( GtkTree *arbol );
</verb></tscreen>
Esta señal se emitirá cuando cambie el campo <tt>selection</tt> de
un GtkTree. Esto ocurre cuando se selecciona o deselecciona un hijo del
GtkTree.
<tscreen><verb>
void select_child( GtkTree *arbol,
GtkWidget *hijo );
</verb></tscreen>
Esta señal se emite cuando se está seleccionando un hijo del GtkTree.
Esto ocurre en las llamadas a <tt/gtk_tree_select_item()/,
<tt/gtk_tree_select_child()/, en <em>todas</em> las pulsaciones de
botón y llamadas a <tt/gtk_tree_item_toggle()/ y
<tt/gtk_item_toggle()/. Puede que a veces se invoque indirectamente en
otras ocasiones, cuando el hijo se añada o elimine del GtkTree.
<tscreen><verb>
void unselect_child (GtkTree *arbol,
GtkWidget *hijo);
</verb></tscreen>
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
<tt/gtk_tree_unselect_item()/ o a <tt/gtk_tree_unselect_child()/, y quizás
en otras ocasiones, pero <em>no</em> cuando la pulsación de un botón
deselecciona un hijo, y tampoco por la emisión de la señal «toggle»
por <tt/gtk_item_toggle()/.
<sect2> Funciones y macros<label id="sec_GtkTree_Functions">
<p>
<tscreen><verb>
guint gtk_tree_get_type( void );
</verb></tscreen>
Devuelve el identificador de tipo de `GtkTree'.
<tscreen><verb>
GtkWidget* gtk_tree_new( void );
</verb></tscreen>
Crea un nuevo objeto GtkTree. El nuevo <em>widget</em> se devuelve como
un puntero a un objeto GtkWidget. Se devolverá NULL si se produce algún
error.
<tscreen><verb>
void gtk_tree_append( GtkTree *arbol,
GtkWidget *elemento_arbol );
</verb></tscreen>
Añade un árbol a un GtkTree.
<tscreen><verb>
void gtk_tree_prepend( GtkTree *arbol,
GtkWidget *elemento_arbol );
</verb></tscreen>
Preañade un árbol a un GtkTree.
<tscreen><verb>
void gtk_tree_insert( GtkTree *arbol,
GtkWidget *elemento_arbol,
gint posicion );
</verb></tscreen>
Inserta un árbol en un GtkTree en la posición de la lista especificada
por <tt>posicion.</tt>
<tscreen><verb>
void gtk_tree_remove_items( GtkTree *arbol,
GList *items );
</verb></tscreen>
Elimina una lista de elementos (en forma de una <tt/GList */) de un
GtkTree. Eliminar un elemento de un árbol lo dereferencia (y por tanto
normalmente) lo destruye (""), a él <em>y</em> a su subárbol, de
haberlo, <em>y</em> a todos los subárboles que contenga ese
subárbol. Si quiere eliminar sólo un elemento, deberá utilizar
<tt/gtk_container_remove()/.
<tscreen><verb>
void gtk_tree_clear_items( GtkTree *arbol,
gint start,
gint end );
</verb></tscreen>
Elimina los elementos de un GtkTree desde la posición <tt>start</tt>
hasta la posición <tt>end</tt>. De nuevo hay que llevarse cuidado
con donde se aplica la dereferencia, ya que <tt/gtk_tree_clear_items()/
simplemente construye una lista y se la pasa a
<tt/gtk_tree_remove_items()/.
<tscreen><verb>
void gtk_tree_select_item( GtkTree *arbol,
gint item );
</verb></tscreen>
Emite la señal <tt/select_item/ para el hijo que se encuentra en la
posición <tt>item</tt>, y por tanto selecciona a ese hijo (a menos que
lo deseleccione en un manejador de señal...)
<tscreen><verb>
void gtk_tree_unselect_item( GtkTree *arbol,
gint item );
</verb></tscreen>
Emite la señal <tt/unselect_item/ para el hijo en la posición
<tt>item</tt>, y por tanto deselecciona al hijo.
<tscreen><verb>
void gtk_tree_select_child( GtkTree *arbol,
GtkWidget *elemento_arbol );
</verb></tscreen>
Emite la señal <tt/select_item/ para el hijo <tt>elemento_arbol</tt>, y por tanto
lo selecciona.
<tscreen><verb>
void gtk_tree_unselect_child( GtkTree *arbol,
GtkWidget *elemento_arbol );
</verb></tscreen>
Emite la señal <tt/unselect_item/ para el hijo <tt>elemento_arbol</tt>, y por
tanto lo deselecciona.
<tscreen><verb>
gint gtk_tree_child_position( GtkTree *arbol,
GtkWidget *hijo );
</verb></tscreen>
Devuelve la posición en el árbol de <tt>child</tt>, a menos que
<tt>child</tt> no esté en el árbol, en cuya caso devuelve -1.
<tscreen><verb>
void gtk_tree_set_selection_mode( GtkTree *arbol,
GtkSelectionMode mode );
</verb></tscreen>
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.
<tscreen><verb>
void gtk_tree_set_view_mode( GtkTree *arbol,
GtkTreeViewMode mode );
</verb></tscreen>
Establece el «modo de visión», que puede ser o GTK_TREE_VIEW_LINE
(por defecto) o GTK_TREE_VIEW_ITEM. El modo de visión se propaga
de un árbol a sus subárboles, y no puede establecerse en exclusiva
para un subárbol (esto no es exacto del todo - vea los comentarios en el
código de ejemplo).
El termino «modo de visión» es algo ambiguo - básicamente, controla
la forma en que se resalta a uno de los hijos del árbol cuando es
seleccionado. Si es GTK_TREE_VIEW_LINE, se resaltará el
<em>widget</em> GtkTreeItem completo, mientras que si es
GTK_TREE_VIEW_ITEM, sólo se resaltará el <em>widget</em> hijo (es
decir, lo que normalmente es la etiqueta).
<tscreen><verb>
void gtk_tree_set_view_lines( GtkTree *arbol,
guint flag );
</verb></tscreen>
Controla si se dibujarán las líneas de conexión entre los elementos
del árbol. <tt>flag</tt> es o TRUE, en cuyo caso se dibujarán, o
FALSE, en cuyo caso no se dibujarán.
<tscreen><verb>
GtkTree *GTK_TREE (gpointer obj);
</verb></tscreen>
Convierte un puntero genérico a `GtkTree *'.
<tscreen><verb>
GtkTreeClass *GTK_TREE_CLASS (gpointer class);
</verb></tscreen>
Convierte un puntero genérico a `GtkTreeClass *'.
<tscreen><verb>
gint GTK_IS_TREE (gpointer obj);
</verb></tscreen>
Determina si un puntero genérico se refiere a un objeto `GtkTree'.
<tscreen><verb>
gint GTK_IS_ROOT_TREE (gpointer obj)
</verb></tscreen>
Determina si un puntero genérico se refiere a un objeto `GtkTree'
<em>y</em> 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.
<tscreen><verb>
GtkTree *GTK_TREE_ROOT_TREE (gpointer obj)
</verb></tscreen>
Devuelve el árbol raíz de un puntero a un objeto `GtkTree'. Seguimos
con el mismo problema que en el caso anterior.
<tscreen><verb>
GList *GTK_TREE_SELECTION(gpointer obj)
</verb></tscreen>
Devuelve la lista de selección del árbol raíz de un objeto
`GtkTree'. Seguimos con el mismo problema que antes.
<sect1> El <em>widget</em> elemento de árbol<label id="sec_Tree_Item_Widget">
<p>
El <em>widget</em> GtkTreeItem, cómo el GtkListItem, está derivado
de GtkItem, que de nuevo, está derivado de GtkBin. Sin embargo, el
elemento en sí mismo es un contenedor genérico que contiene un
<em>widget</em> hijo, que puede ser de cualquier tipo. El <em>widget</em>
GtkTreeItem tiene ciertos campos extra, pero el único que nos
interesa ahora es el campo <em>subárbol</em>.
La definición de la estructura GtkTreeItem es así:
<tscreen><verb>
struct _GtkTreeItem
{
GtkItem item;
GtkWidget *subtree;
GtkWidget *pixmaps_box;
GtkWidget *plus_pix_widget, *minus_pix_widget;
GList *pixmaps /* nodo pixmap para esta profundidad de color */
guint expanded : 1;
};
</verb></tscreen>
El campo <tt>pixmaps_box</tt> es un GtkEventBox que caza las pulsaciones
en el símbolo más/menos que controla la expansión y contracción. El
campo <tt>pixmaps</tt> 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
<em>realmente</em> sepa que es lo que está haciendo.
Ya que está derivado directamente de un GtkItem, puede tratarse como
tal utilizando la macro GTK_ITEM (ElementoArbol). 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():
<tscreen><verb>
elemento_arbol = gtk_tree_item_new ();
etiqueta_widget = gtk_label_new (etiqueta);
gtk_misc_set_alignment (GTK_MISC (etiqueta_widget), 0.0, 0.5);
gtk_container_add (GTK_CONTAINER (elemento_arbol), etiqueta_widget);
gtk_widget_show (etiqueta_widget);
</verb></tscreen>
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í:
<tscreen><verb>
gtk_widget_ref (arbol);
propietario = GTK_TREE(arbol)->tree_owner;
gtk_container_remove (GTK_CONTAINER(arbol), item);
if (arbol->parent == NULL){
gtk_tree_item_expand (GTK_TREE_ITEM(propietario));
gtk_tree_item_set_subtree (GTK_TREE_ITEM(propietario), arbol);
}
else
gtk_widget_unref (arbol);
</verb></tscreen>
Finalmente, hay que mencionar que la opción de drag-n-drop (arrastar y
soltar) <em>funciona</em> 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 <em>widget</em> padre tiene a su vez
un padre, y así hasta llegar al nivel más alto o ventana de diálogo,
cuando llamamos a <tt/gtk_widget_dnd_drag_set()/ o
<tt/gtk_widget_dnd_drop_set()/. En caso contrario, podrían ocurrir
cosas extrañas.
<sect2> Señales
<p>
GtkTreeItem hereda las señales <tt/select/, <tt/deselect/, y
<tt/toggle/ de GtkItem. Además, añade dos señales propias, <tt/expand/
y <tt/collapse/.
<tscreen><verb>
void select( GtkItem *elemento_arbol );
</verb></tscreen>
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 <tt/gtk_tree_item_select()/,
<tt/gtk_item_select()/, o a <tt/gtk_tree_select_child()/.
<tscreen><verb>
void deselect( GtkItem *elemento_arbol );
</verb></tscreen>
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 <tt/gtk_tree_item_deselect()/ o a
<tt/gtk_item_deselect()/. En el caso de GtkTreeItems, también se
emitirá por <tt/gtk_tree_unselect_child()/, y a veces por
<tt/gtk_tree_select_child()/.
<tscreen><verb>
void toggle( GtkItem *elemento_arbol );
</verb></tscreen>
Esta señal se emite cuando el programa llama a <tt/gtk_item_toggle()/. El
efecto que tiene cuando se emite en un GtkTreeItem es llamar a
<tt/gtk_tree_select_child()/ (y nunca a
<tt/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.
<tscreen><verb>
void expand( GtkTreeItem *elemento_arbol );
</verb></tscreen>
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
<tt/gtk_tree_item_expand()/.
<tscreen><verb>
void collapse( GtkTreeItem *elemento_arbol );
</verb></tscreen>
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
<tt/gtk_tree_item_collapse()/.
<sect2> Funciones y Macros
<p>
<tscreen><verb>
guint gtk_tree_item_get_type( void );
</verb></tscreen>
Devuelve el identificador de tipo de `GtkTreeItem'.
<tscreen><verb>
GtkWidget* gtk_tree_item_new( void );
</verb></tscreen>
Crea un nuevo objeto GtkTreeItem. El nuevo <em>widget</em> se devuelve
como un puntero a un objeto GtkWidget. Se devolverá NULL si hay algún
fallo.
<tscreen><verb>
GtkWidget* gtk_tree_item_new_with_label (gchar *etiqueta);
</verb></tscreen>
Crea un nuevo objeto GtkTreeItem, teniendo una simple GtkLabel
como único hijo. El nuevo <em>widget</em> se devolverá como
un puntero a un objeto GtkWidget. Se devolverá NULL en caso
de haber algún fallo.
<tscreen><verb>
void gtk_tree_item_select( GtkTreeItem *elemento_arbol );
</verb></tscreen>
Esta función es básicamente un recubrimiento de una llamada a
gtk_item_select (GTK_ITEM (elemento_arbol)) que emitirá la
señal select.
<tscreen><verb>
void gtk_tree_item_deselect( GtkTreeItem *elemento_arbol );
</verb></tscreen>
Esta función es básicamente un recubrimiento de una llamada a
gtk_item_deselect (GTK_ITEM (elemento_arbol)) que emitirá la
señal deselect.
<tscreen><verb>
void gtk_tree_item_set_subtree( GtkTreeItem *elemento_arbol,
GtkWidget *subarbol );
</verb></tscreen>
Esta función añade <tt/subarbol/ a <tt/elemento_arbol/, mostrándolo si
<tt/elemento_arbol/ está expandido, u ocultándolo si <tt/elemento_arbol/ está
contraído. De nuevo, recuerde que el <tt/elemento_arbol/ ya debe de haber
sido añadido a un árbol para que esto funcione.
<tscreen><verb>
void gtk_tree_item_remove_subtree( GtkTreeItem *elemento_arbol );
</verb></tscreen>
Esto elimina todos los hijos de los subárboles del <tt/elemento_arbol/
(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.
<tscreen><verb>
void gtk_tree_item_expand( GtkTreeItem *elemento_arbol );
</verb></tscreen>
Esto emite la señal «expand» para el <tt/elemento_arbol/, que lo
expande.
<tscreen><verb>
void gtk_tree_item_collapse( GtkTreeItem *elemento_arbol );
</verb></tscreen>
Esto emite la señal «collapse» en el <tt/elemento_arbol/, que lo
contrae.
<tscreen><verb>
GtkTreeItem *GTK_TREE_ITEM (gpointer obj)
</verb></tscreen>
Convierte un puntero genérico en un `GtkTreeItem *'.
<tscreen><verb>
GtkTreeItemClass *GTK_TREE_ITEM_CLASS (gpointer obj)
</verb></tscreen>
Convierte un puntero genérico en un `GtkTreeItemClass'.
<tscreen><verb>
gint GTK_IS_TREE_ITEM (gpointer obj)
</verb></tscreen>
Determina si un puntero genérico se refiere a un objeto `GtkTreeItem'.
<tscreen><verb>
GtkWidget GTK_TREE_ITEM_SUBTREE (gpointer obj)
</verb></tscreen>
Devuelve un subárbol del elemento (<tt/obj/ debería apuntar a un
objeto `GtkTreeItem').
<sect1> Árbol ejemplo
<p>
Este ejemplo es muy parecido al árbol ejemplo que hay en
<tt/testgtk.c/, pero mucho menos completo (aunque mucho mejor
comentado). Pone una ventana con un árbol, y conecta todas las señales
de los objetos relevantes, con lo que podrá ver cuando se emiten.
<!-- Hay un comentario en el código que no se traducir -->
<tscreen><verb>
/* principio del ejemplo tree tree.c */
#include <gtk/gtk.h>
/* para todas las señales GtkItem:: y GtkTreeItem:: */
static void cb_itemsignal (GtkWidget *item, gchar *signame)
{
gchar *name;
GtkLabel *etiqueta;
/* Es un GtkBin, por lo que tiene un hijo, que sabemos que es una
* etiqueta, por lo que la cogemos */
etiqueta = GTK_LABEL (GTK_BIN (item)->child);
/* Conseguimos el texto de la etiqueta */
gtk_label_get (etiqueta, &amp;name);
/* Conseguimos el nivel del árbol en el que se encuentra el elemento */
g_print ("%s called for item %s->%p, level %d\n", signame, name,
item, GTK_TREE (item->parent)->level);
}
/* nunca se llamará a esta función */
static void cb_unselect_child (GtkWidget *arbol_raiz, GtkWidget *hijo,
GtkWidget *subarbol)
{
g_print ("unselect_child called for root tree %p, subtree %p, child %p\n",
arbol_raiz, subarbol, hijo);
}
/* Se llamará a esta función cada vez que el usuario pulse en un
* elemento, esté o no seleccionado. */
whether it is already selected or not. */
static void cb_select_child (GtkWidget *arbol_raiz, GtkWidget *hijo,
GtkWidget *subarbol)
{
g_print ("select_child called for root tree %p, subtree %p, child %p\n",
arbol_raiz, subarbol, hijo);
}
static void cb_selection_changed (GtkWidget *arbol)
{
GList *i;
g_print ("selection_change called for tree %p\n", arbol);
g_print ("selected objects are:\n");
i = GTK_TREE_SELECTION(arbol);
while (i){
gchar *name;
GtkLabel *etiqueta;
GtkWidget *item;
/* Get a GtkWidget pointer from the list node */
item = GTK_WIDGET (i->data);
etiqueta = GTK_LABEL (GTK_BIN (item)->child);
gtk_label_get (etiqueta, &amp;name);
g_print ("\t%s on level %d\n", name, GTK_TREE
(item->parent)->level);
i = i->next;
}
}
int main (int argc, char *argv[])
{
GtkWidget *ventana, *scrolled_win, *arbol;
static gchar *itemnames[] = {"Foo", "Bar", "Baz", "Quux",
"Maurice"};
gint i;
gtk_init (&amp;argc, &amp;argv);
/* una ventana general */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT(ventana), "delete_event",
GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
gtk_container_border_width (GTK_CONTAINER(ventana), 5);
/* una ventana con barras de desplazamiento */
scrolled_win = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_widget_set_usize (scrolled_win, 150, 200);
gtk_container_add (GTK_CONTAINER(ventana), scrolled_win);
gtk_widget_show (scrolled_win);
/* Crear el árbol raíz */
arbol = gtk_tree_new();
g_print ("root tree is %p\n", arbol);
/* connect all GtkTree:: signals */
gtk_signal_connect (GTK_OBJECT(arbol), "select_child",
GTK_SIGNAL_FUNC(cb_select_child), arbol);
gtk_signal_connect (GTK_OBJECT(arbol), "unselect_child",
GTK_SIGNAL_FUNC(cb_unselect_child), arbol);
gtk_signal_connect (GTK_OBJECT(arbol), "selection_changed",
GTK_SIGNAL_FUNC(cb_selection_changed), arbol);
/* Añadirlo a la ventana con barras de desplazamiento */
gtk_container_add (GTK_CONTAINER(scrolled_win), arbol);
/* Poner el modo de selección */
gtk_tree_set_selection_mode (GTK_TREE(arbol),
GTK_SELECTION_MULTIPLE);
/* mostrar el árbol */
gtk_widget_show (arbol);
for (i = 0; i < 5; i++){
GtkWidget *subarbol, *item;
gint j;
/* Crear un elemento del árbol */
item = gtk_tree_item_new_with_label (itemnames[i]);
/* Conectar todas las señales GtkItem:: y GtkTreeItem:: */
gtk_signal_connect (GTK_OBJECT(item), "select",
GTK_SIGNAL_FUNC(cb_itemsignal), "select");
gtk_signal_connect (GTK_OBJECT(item), "deselect",
GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
gtk_signal_connect (GTK_OBJECT(item), "toggle",
GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
gtk_signal_connect (GTK_OBJECT(item), "expand",
GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
gtk_signal_connect (GTK_OBJECT(item), "collapse",
GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
/* Añadirlo al árbol padre */
gtk_tree_append (GTK_TREE(arbol), item);
/* Mostrarlo - esto se puede hacer en cualquier momento */
gtk_widget_show (item);
/* Crear el subárbol de este elemento */
subarbol = gtk_tree_new();
g_print ("-> item %s->%p, subtree %p\n", itemnames[i], item,
subarbol);
/* Esto todavía es necesario si quiere que se llamen a están
* señales en el subárbol hijo. Note that selection_change will
* be signalled for the root tree regardless. */
gtk_signal_connect (GTK_OBJECT(subarbol), "select_child",
GTK_SIGNAL_FUNC(cb_select_child), subarbol);
gtk_signal_connect (GTK_OBJECT(subarbol), "unselect_child",
GTK_SIGNAL_FUNC(cb_unselect_child), subarbol);
/* Esto no tiene absolutamente ningún efecto, ya que se ignora
* completamente en los subárboles */
gtk_tree_set_selection_mode (GTK_TREE(subarbol),
GTK_SELECTION_SINGLE);
/* Esto tampoco hace nada, pero por una razón diferente - los
* valores view_mode y view_line de un árbol se propagan a los
* subárboles cuando son mapeados. Por tanto, establecer los
* valores después actualmente tendría (algún impredecible) efecto
*/
gtk_tree_set_view_mode (GTK_TREE(subarbol), GTK_TREE_VIEW_ITEM);
/* Establecer este subárbol del elemento - ¡Recuerde que no puede
* hacerlo hasta que se haya añadido a su árbol padre! */
gtk_tree_item_set_subtree (GTK_TREE_ITEM(item), subarbol);
for (j = 0; j < 5; j++){
GtkWidget *subitem;
/* Crea un elemento subárbol, más o menos lo mismo de antes */
subitem = gtk_tree_item_new_with_label (itemnames[j]);
/* Conectar todas las señales GtkItem:: y GtkTreeItem:: */
gtk_signal_connect (GTK_OBJECT(subitem), "select",
GTK_SIGNAL_FUNC(cb_itemsignal), "select");
gtk_signal_connect (GTK_OBJECT(subitem), "deselect",
GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
gtk_signal_connect (GTK_OBJECT(subitem), "toggle",
GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
gtk_signal_connect (GTK_OBJECT(subitem), "expand",
GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
gtk_signal_connect (GTK_OBJECT(subitem), "collapse",
GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
g_print ("-> -> item %s->%p\n", itemnames[j], subitem);
/* Añadirlo a su árbol padre */
gtk_tree_append (GTK_TREE(subarbol), subitem);
/* Mostrarlo */
gtk_widget_show (subitem);
}
}
/* Mostrar la ventana y entrar en el bucle final */
gtk_widget_show (ventana);
gtk_main();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> El <em>widget</em> menú
<!-- ***************************************************************** -->
<p>
Hay dos formas de crear menús, la fácil, y la difícil. Ambas tienen su
utilidad, aunque lo más probable es que normalmente utilice la
menufactory (la forma fácil). La forma «difícil» consiste en crear
todos los menús utilizando las llamadas directamente. La forma fácil
consiste en utilizar las llamadas de <tt/gtk_item_factory/. Es mucho
más fácil, pero aun así cada aproximación tiene sus ventajas y sus
inconvenientes.
La menufactory es mucho más fácil de utilizar, y tambíen es más fácil
añadir nuevos menús, aunque a larga, escribiendo unas cuántas
funciones de recubrimiento para crear menús utilizando el método
manual puede acabar siendo más útil. Con la itemfactory, no es posible
añadir imágenes o el carácter `/' a los menús.
<!-- ----------------------------------------------------------------- -->
<sect1>Creación manual de menús
<p>
Siguiendo la auténtica tradición de la enseñanza, vamos a enseñarle
primero la forma difícil. <tt>:)</tt>
Se utilizan tres <em>widgets</em> para hacer una barra de menús y
submenús:
<itemize>
<item>un elemento del menú, que es lo que el usuario quiere seleccionar,
p.e. 'Guardar'
<item>un menú, que actua como un contenedor para los elementos del menú, y
<item>una barra de menú, que es un contenedor para cada uno de los menús,
</itemize>
Todo esto se complica ligeramente por el hecho de que los
<em>widgets</em> de los elementos del menú se utilizan para dos cosas
diferentes. Están los <em>widgets</em> que se empaquetan en el menú, y
los que se empaquetan en una barra de menús, que cuando se selecciona,
activa el menú.
Vamos a ver las funciones que se utilizan para crear menús y barras
de menús. ésta primera función se utiliza para crear una barra de menús.
<tscreen><verb>
GtkWidget *gtk_menu_bar_new( void );
</verb></tscreen>
Como el propio nombre indica, esta función crea una nueva barra de
menús. Utilice <tt/gtk_container_add/ para empaquetarla en una
ventana, o las funciones <tt/box_pack/ para empaquetarla en una caja -
exactamente igual que si fuesen botones.
<tscreen><verb>
GtkWidget *gtk_menu_new( void );
</verb></tscreen>
Esta función devuelve un puntero a un nuevo menú, que no se debe
mostrar nunca (no hace falta utilizar <tt/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ú).
<tscreen><verb>
GtkWidget *gtk_menu_item_new( void );
</verb></tscreen>
y
<tscreen><verb>
GtkWidget *gtk_menu_item_new_with_label( const char *etiqueta );
</verb></tscreen>
Estas llamadas se utilizan para crear los elementos del menú que
van a mostrarse. Recuerde que hay que distinguir entre un «menú»
creado con <tt/gtk_menu_new/ y un «elemento del menú» creado con las
funciones <tt/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 <tt/gtk_menu_new_with_label/ y <tt/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 <tt/gtk_menu_append/. Para capturar
el momento en el que el elemento se selecciona por el usuario,
necesitamos conectar con la señal <tt/activate/ de la forma usual. Por
tanto, si quiere crear un menú estándar <tt/File/, con las opciones
<tt/Open/, <tt/Save/ y <tt/Quit/ el código debería ser algo como
<tscreen><verb>
file_menu = gtk_menu_new(); /* No hay que mostrar menús */
/* Crear los elementos del menú */
open_item = gtk_menu_item_new_with_label("Open");
save_item = gtk_menu_item_new_with_label("Save");
quit_item = gtk_menu_item_new_with_label("Quit");
/* Añadirlos al menú */
gtk_menu_append( GTK_MENU(file_menu), open_item);
gtk_menu_append( GTK_MENU(file_menu), save_item);
gtk_menu_append( GTK_MENU(file_menu), quit_item);
/* Enlazar las función de llamada a la señal "activate" */
gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");
/* Podemos enlazar el elemento de menú Quit con nuestra función de
* salida */
gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");
/* Tenemos que mostrar los elementos del menú */We do need to show menu items */
gtk_widget_show( open_item );
gtk_widget_show( save_item );
gtk_widget_show( quit_item );
</verb></tscreen>
En este momento tendremos nuestro menú. Ahora necesitamos crear una
barra de menús y un elemento de menú para el elemento <tt/File/, que
vamos a añadir a nuestro menú. El código es el siguiente
<tscreen><verb>
menu_bar = gtk_menu_bar_new();
gtk_container_add( GTK_CONTAINER(ventana), menu_bar);
gtk_widget_show( menu_bar );
file_item = gtk_menu_item_new_with_label("File");
gtk_widget_show(file_item);
</verb></tscreen>
Ahora necesitamos asociar el menú con <tt/file_item/. Esto se hace con
la función
<tscreen>
void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
GtkWidget *submenu );
</tscreen>
Por lo que nuestro ejemplo continua con
<tscreen><verb>
gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );
</verb></tscreen>
Todo lo que queda por hacer es añadir el menú a la barra de menús, que
se hace mediante la función
<tscreen>
void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);
</tscreen>
que en nuestro caso habrá que utilizar así:
<tscreen><verb>
gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );
</verb></tscreen>
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 <tt/file_item/ en el ejemplo actual)
antes de enlazarla en la barra de menú.
<tscreen><verb>
void gtk_menu_item_right_justify( GtkMenuItem *menu_item );
</verb></tscreen>
Aquí hay un resumen de los pasos que son necesarios para crear una
barra de menús con los menús correspondientes ya enlazados:
<itemize>
<item> Crear un nuevo menú utilizando <tt/gtk_menu_new()/
<item> Utilizar multiples llamadas a <tt/gtk_menu_item_new()/ para
cada elemento que desee tener en su menú. Y utilizar
<tt/gtk_menu_append()/ para poner cada uno de esos nuevos elementos en
el menú.
<item> Crear un elemento de menú utilizando
<tt/gtk_menu_item_new()/. Ésta será la raíz del menú, el texto que
aparezca aquí estará en la barra de menús.
<item> Utilizar <tt/gtk_menu_item_set_submenu()/ para enlazar el menú
al elemento del menú raíz (el creado en el paso anterior).
<item> Crear una nueva barra de menús utilizando
<tt/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.
<item> Utilizar <tt/gtk_menu_bar_append/ para poner el menú raíz en la
barra de menús.
</itemize>
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 <tt/gtk_menu_popup()/
utilizando, por ejemplo, un evento de pulsación de botón. Siga los
pasos siguientes:
<itemize>
<item>Cree una función manejadora de eventos. Tiene que tener el
siguiente prototipo
<tscreen>
static gint handler( GtkWidget *widget,
GdkEvent *event );
</tscreen>
y utilice el evento para encontrar donde debe aparecer el menú.
<item>En el manejador de eventos, si el evento es una pulsación de un
botón del ratón, tratar <tt>event</tt> como un evento de botón
(que lo es) y utilizarlo como se indica en el código ejemplo para
pasarle información a <tt/gtk_menu_popup()/.
<item>Enlazar este manejador de eventos con el <em>widget</em> con
<tscreen>
gtk_signal_connect_object(GTK_OBJECT(widget), "event",
GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
</tscreen>
donde <tt>widget</tt> es el <em>widget</em> con el que esta conectando,
<tt>handler</tt> es la función manejadora, y <tt>menu</tt> es un menú
creado con <tt/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.
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1>Ejemplo de la creación manual de un menú
<p>
Esto debería funcionar. Échele un vistazo al ejemplo para aclarar los
conceptos.
<tscreen><verb>
/* principio del ejemplo menu menu.c */
#include <gtk/gtk.h>
static gint button_press (GtkWidget *, GdkEvent *);
static void menuitem_response (gchar *);
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *menu;
GtkWidget *menu_bar;
GtkWidget *root_menu;
GtkWidget *menu_items;
GtkWidget *vbox;
GtkWidget *boton;
char buf[128];
int i;
gtk_init (&amp;argc, &amp;argv);
/* crear una nueva ventana */
ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize( GTK_WIDGET (ventana), 200, 100);
gtk_window_set_title(GTK_WINDOW (ventana), "GTK Menu Test");
gtk_signal_connect(GTK_OBJECT (ventana), "delete_event",
(GtkSignalFunc) gtk_main_quit, NULL);
/* Inicializar el widget-menu, y recuerde -- ¡¡Nunca haga
* gtk_show_widget() con el widget menu!!
* Éste es el menú que contiene todos los elementos del menú, el
* que se desplegará cuando pulse en el "Root Menu" en la
* aplicación
*/
menu = gtk_menu_new();
/* Ahora hacemos un pequeño bucle que crea tres elementos de menú
* para "test-menu". Recuerde llamar a gtk_menu_append. Aquí
* estamos añadiendo una lista de elementos de menú a nuestro
* menú. Normalmente tendríamos que cazar aquí la señal "clicked"
* de cada uno de los elementos del menú y le deberíamos dar una
* función de llamada a cada uno, pero lo vamos a omitimos para
* ahorrar espacio. */
for(i = 0; i < 3; i++)
{
/* Copia los nombres al búfer. */
sprintf(buf, "Test-undermenu - %d", i);
/* Crea un nuevo elemento de menú con un nombre... */
menu_items = gtk_menu_item_new_with_label(buf);
/* ...y lo añade al menú. */
gtk_menu_append(GTK_MENU (menu), menu_items);
/* Hace algo interesante cuando se selecciona el menuitem */
gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));
/* Muestra el widget */
gtk_widget_show(menu_items);
}
/* Ésta es el menú raíz, y será la etiqueta mostrada en la
* barra de menús. No habrá ningún manejador de señal conectado, ya que
* lo único que hace es desplegar el resto del menú. */
root_menu = gtk_menu_item_new_with_label("Root Menu");
gtk_widget_show(root_menu);
/* Ahora especificamos que queremos que el recien creado "menu"
* sea el menú para el "root menu" */
gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
/* Un vbox para poner dentro un menú y un botón */
vbox = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(ventana), vbox);
gtk_widget_show(vbox);
/* Crear una barra de menú para que contenga al menú y la añadamos
* a nuestra ventana principal */
menu_bar = gtk_menu_bar_new();
gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
gtk_widget_show(menu_bar);
/* Crear un botón al que atar los menús como un popup */
boton = gtk_button_new_with_label("press me");
gtk_signal_connect_object(GTK_OBJECT(boton), "event",
GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
gtk_box_pack_end(GTK_BOX(vbox), boton, TRUE, TRUE, 2);
gtk_widget_show(boton);
/* Y finalmente añadimos el elemento de menú y la barra de menú --
* éste es el elemento de menú "raíz" sobre el que he estado
* delirando =) */
gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
/* siempre mostramos la ventana como último paso para que todo se
* pongo en pantalla a la vez. */
gtk_widget_show(ventana);
gtk_main ();
return 0;
}
/* Responde a una pulsación del botón enviando un menú como un widget
* Recuerde que el argumento "widget" es el menú que se está enviando,
* NO el botón que se ha pulsado.
*/
static gint button_press (GtkWidget *widget, GdkEvent *event)
{
if (event->type == GDK_BUTTON_PRESS) {
GdkEventButton *bevent = (GdkEventButton *) event;
gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
bevent->button, bevent->time);
/* Le dice al que llamó a la rutina que hemos manejado el
* evento; la historia termina aquí. */
return TRUE;
}
/* Le dice al que llamó a la rutina que no hemos manejado el
* evento. */
return FALSE;
}
/* Imprime una cadena cuando se selecciona un elemento del menú */
static void menuitem_response (gchar *string)
{
printf("%s\n", string);
}
/* final del ejemplo */
</verb></tscreen>
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ú.
<!-- XXX Las dos sect1 que vienen han cambiado -->
<!-- ----------------------------------------------------------------- -->
<sect1>Utilizando GtkItemFactory
<p>
Ahora que le hemos enseñado la forma difícil, le mostraremos como
utilizar las llamadas <tt/gtk_item_factory/.
<!-- ----------------------------------------------------------------- -->
<sect1>Ejemplo de la fábrica de elementos
<p>
Aquí hay un ejemplo de cómo utilizar la fábrica de elementos
GTK.
<tscreen><verb>
/* principio del ejemplo menu itemfactory.h */
#include <gtk/gtk.h>
#include <strings.h>
/* La obligatoria función de llamada */
static void print_hello( GtkWidget *w,
gpointer data )
{
g_message ("Hello, World!\n");
}
/* Esta es la estructura GtkItemFactoryEntry utilizada para crear
nuevos menúes.
This is the GtkItemFactoryEntry structure used to generate new menus.
Elemento 1: La dirección del menú. La letra que hay
después del subrayado indica una tecla aceleradora
una vez que el menú esté abierto.
Elemento 2: La tecla aceleradora para la entrada del menú.
Elemento 3: La función de llamada.
Elemento 4: La acción de llamada. Cambia los parámetros que
se le pasan a la función de llamada. El valor por
defecto es 0.
Elemento 5: El tipo de elemento, se utiliza para definir de que
tipo de elemento se trata. Los valores posibles son:
NULL -> "<Item>"
"" -> "<Item>"
"<Title>" -> crea un elemento título
"<Item>" -> crea un simple elemento
"<CheckItem>" -> crea un elemento de comprobación
"<ToggleItem>" -> crea un elemento de selección
"<RadioItem>" -> crea un elemento circular
<path> -> dirección de un elemento circular
con el que enlazar
"<Separator>" -> crea un separador
"<Branch>" -> crea un elemento para contener
subelementos (de forma opcional)
"<LastBranch>" -> crea una rama justificada a la
derecha
*/
static GtkItemFactoryEntry menu_items[] = {
{ "/_File", NULL, NULL, 0, "<Branch>" },
{ "/File/_New", "<control>N", print_hello, 0, NULL },
{ "/File/_Open", "<control>O", print_hello, 0, NULL },
{ "/File/_Save", "<control>S", print_hello, 0, NULL },
{ "/File/Save _As", NULL, NULL, 0, NULL },
{ "/File/sep1", NULL, NULL, 0, "<Separator>" },
{ "/File/Quit", "<control>Q", gtk_main_quit, 0, NULL },
{ "/_Options", NULL, NULL, 0, "<Branch>" },
{ "/Options/Test", NULL, NULL, 0, NULL },
{ "/_Help", NULL, NULL, 0, "<LastBranch>" },
{ "/_Help/About", NULL, NULL, 0, NULL },
};
void get_main_menu( GtkWidget *ventana,
GtkWidget **menubar )
{
GtkItemFactory *item_factory;
GtkAccelGroup *accel_group;
gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
accel_group = gtk_accel_group_new ();
/* Esta función inicializa la fábrica de elementos
Param 1: El tipo de menú - puede ser GTK_TYPE_MENU_BAR,
GTK_TYPE_MENU, o GTK_TYPE_OPTION_MENU.
Param 2: La dirección del menú.
Param 3: Un puntero a un gtk_accel_group. La fábrica de
elementos actualiza la tabla de teclas aceleradoras
mientras genera los menúes.
*/
item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>",
accel_group);
/* Esta función genera los elementos de menú. Pasa la
fábrica de elementos (item_factory), el número de elementos
del vector, el vector en sí, y cualquier dato de llamada para
los elementos de menú. */
gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL);
/* Enlaza el nuevo grupo acelerador a la ventana. */
gtk_accel_group_attach (accel_group, GTK_OBJECT (ventana));
if (menubar)
/* Finalmente, devuelve la barra de menú creada por la
* fábrica de elementos. */
*menubar = gtk_item_factory_get_widget (item_factory, "<main>");
}
int main( int argc,
char *argv[] )
{
GtkWidget *ventana;
GtkWidget *main_vbox;
GtkWidget *menubar;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit),
"WM destroy");
gtk_window_set_title (GTK_WINDOW(ventana), "Item Factory");
gtk_widget_set_usize (GTK_WIDGET(ventana), 300, 200);
main_vbox = gtk_vbox_new (FALSE, 1);
gtk_container_border_width (GTK_CONTAINER (main_vbox), 1);
gtk_container_add (GTK_CONTAINER (ventana), main_vbox);
gtk_widget_show (main_vbox);
get_main_menu (ventana, &amp;menubar);
gtk_box_pack_start (GTK_BOX (main_vbox), menubar, FALSE, TRUE, 0);
gtk_widget_show (menubar);
gtk_widget_show (ventana);
gtk_main ();
return(0);
}
/* fin del ejemplo */
</verb></tscreen>
Por ahora, sólo está este ejemplo. Ya llegará una
explicación del mismo y más comentarios.
<!-- ***************************************************************** -->
<sect> El <em>widget</em> texto
<!-- ***************************************************************** -->
<p>
El <em>widget</em> texto permite mostrar y editar multiples líneas de
texto. Admite texto en varios colores y con varios tipos de letra,
permitiendo mezclarlos de cualquier forma que desee. También hay un
gran número de teclas para la edición de textos, que son compatibles
con Emacs.
El <em>widget</em> texto admite copiar-y-pegar, incluyendo la
utilización de doble y triple-click para seleccionar una palabra y una
línea completa, respectivamente.
<!-- ----------------------------------------------------------------- -->
<sect1>Creando y configurando un cuadro de texto
<p>
Sólo hay una función para crear un nuevo <em>widget</em> texto.
<tscreen><verb>
GtkWidget *gtk_text_new( GtkAdjustment *hadj,
GtkAdjustment *vadj );
</verb></tscreen>
Los argumentos nos permitirán dar al <em>widget</em> texto punteros a
<tt/GtkAdjustement/ que pueden ser utilizados para controlar la visión
de la posición del <em>widget</em>. Si le ponemos un valor NULL en
cualquiera de los dos argumentos (o en los dos), la función
<tt/gtk_text_new/ creará su propio ajuste.
<tscreen><verb>
void gtk_text_set_adjustments( GtkText *text,
GtkAdjustment *hadj,
GtkAdjustment *vadj );
</verb></tscreen>
La función de arriba permite cambiar en cualquier momento el ajuste
horizontal y vertical de un <em>widget</em> texto.
El <em>widget</em> 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 <em>display</em> nosotros mismos.
<tscreen><verb>
vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);
gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0);
gtk_widget_show (vscrollbar);
</verb></tscreen>
El trozo de código de arriba crea una nueva barra de desplazamiento
vertical, y la conecta con el ajuste vertical del <em>widget</em>
de texto, <tt/text/. Entonces la empaqueta en un cuadro de la forma
usual.
Observe que, actualmente el <em>widget</em> GtkText no admite barras
de desplazamiento horizontal.
Principalmente hay dos maneras de utilizar un <em>widget</em> 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 <em>widget</em> de texto tiene las
siguientes funciones:
<tscreen><verb>
void gtk_text_set_editable( GtkText *text,
gint editable );
</verb></tscreen>
El argumento <tt/editable/ es un valor TRUE o FALSE que especifica si se
permite al usuario editar los contenidos del <em>widget</em> texto.
Cuando el <em>widget</em> texto se pueda editar, mostrará un cursor
en la posición actual de inserción.
Sin embargo la utilización del <em>widget</em> en estos dos modos no
es algo permanente, ya que puede cambiar el estado editable del
<em>widget</em> texto e insertar texto en cualquier momento.
El <em>widget</em> 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:
<tscreen><verb>
void gtk_text_set_word_wrap( GtkText *text,
gint word_wrap );
</verb></tscreen>
Utilizando esta función podremos especificar que el <em>widget</em>
texto debería cortar las líneas largas en los límites de las
palabras. El argumento <tt/word_wrap/ es un valor TRUE o FALSE.
<!-- ----------------------------------------------------------------- -->
<sect1>Manipulación de texto
<P>
El punto actual de inserción en un <em>widget</em> texto puede
cambiarse utilizando
<tscreen><verb>
void gtk_text_set_point( GtkText *text,
guint index );
</verb></tscreen>
donde <tt/index/ es la posición en la que poner el punto de inserción.
Análogamente tenemos la función para obtener la posición del punto
de inserción:
<tscreen><verb>
guint gtk_text_get_point( GtkText *text );
</verb></tscreen>
Una función que es útil en combinación con las dos anteriores es
<tscreen><verb>
guint gtk_text_get_length( GtkText *text );
</verb></tscreen>
que devuelve la longitud actual del <em>widget</em> 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 <tt/gtk_text_insert/, que nos permitirá
especificar los colores de fondo y de la letra y un tipo de letra para
el texto.
<tscreen><verb>
void gtk_text_insert( GtkText *text,
GdkFont *font,
GdkColor *fore,
GdkColor *back,
const char *chars,
gint length );
</verb></tscreen>
Pasar un valor <tt/NULL/ como el color de la letra (<tt/fore/), el
color de fondo (<tt/back/) o el tipo de letra (<tt/font/) hará que
se utilicen los valores que indiquen el estilo del <em>widget</em>.
Utilizar un valor de <tt/-1/ para el parámetro <tt/length/ hará
que se inserte todo el texto.
El <em/widget/ texto es uno de los pocos de GTK que se redibuja
a sí mismo dinámicamente, fuera de la función <tt/gtk_main/. Esto
significa que todos los cambios en el contenido del <em/widget/ texto
se manifestarán inmediatamente. Para permitirnos realizar varias
actualizaciones del <em/widget/ de texto sin que se redibuje
continuamente, podemos congelar el <em/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 <em/widget/:
<tscreen><verb>
void gtk_text_freeze( GtkText *text );
void gtk_text_thaw( GtkText *text );
</verb></tscreen>
Se puede borrar el texto que se encuentra en el punto actual de
inserción del <em/widget/ de texto mediante dos funciones. El valor
devuelto es TRUE o FALSE en función del éxito de la operación.
<tscreen><verb>
gint gtk_text_backward_delete( GtkText *text,
guint nchars );
gint gtk_text_forward_delete ( GtkText *text,
guint nchars );
</verb></tscreen>
Si quiere recuperar el contenido del <em/widget/ de texto, entonces
la macro <tt/GTK_TEXT_INDEX(t, index)/ le permitirá obtener el
carácter que se encuentra en la posición <tt/index/ del <em/widget/
de texto <tt/t/.
Para obtener mayores bloques de texto, podemos utilizar la función
<tscreen><verb>
gchar *gtk_editable_get_chars( GtkEditable *editable,
gint start_pos,
gint end_pos );
</verb></tscreen>
Esta es una función de la clase padre del <em/widget/ texto. Un valor
de -1 en <tt/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
<tt/g_free/ cuando haya acabado el bloque.
<!-- ----------------------------------------------------------------- -->
<sect1>Atajos por teclado
<p>
El <em/widget/ texto tiene ciertos atajos de teclado preinstalados
para las funciones de edición estándar, movimiento y selección. Pueden
utilizarse mediante combinaciones de las teclas Control y Alt.
Además, si se mantiene apretada la tecla de Control y se utilizan las
teclas de movimiento, el cursor se moverá por palabras en lugar de por
caracteres. Manteniendo apretada la tecla Shift, las teclas de movimiento
harán que se extienda la selección.
<sect2>Atajos para el movimiento
<p>
<itemize>
<item> Ctrl-A Principio de línea
<item> Ctrl-E Final de línea
<item> Ctrl-N Línea siguiente
<item> Ctrl-P Línea anterior
<item> Ctrl-B Retrasarse un carácter
<item> Ctrl-F Adelantarse un carácter
<item> Alt-B Retrasarse una palabra
<item> Alt-F Adelantarse una palabra
</itemize>
<sect2>Atajos para la edición
<p>
<itemize>
<item> Ctrl-H Borrar el carácter anterior (Backspace)
<item> Ctrl-D Borrar el carácter siguiente (Suprimir)
<item> Ctrl-W Borrar la palabra anterior
<item> Alt-D Borrar la palabra siguiente
<item> Ctrl-K Borrar hasta el fin de la línea
<item> Ctrl-U Borrar la línea
</itemize>
<sect2>Atajos de selección
<p>
<itemize>
<item> Ctrl-X Cortar al portapapeles
<item> Ctrl-C Copiar al portapapeles
<item> Ctrl-V Pegar desde el portapapeles
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1>Un ejemplo de GtkText
<p>
<tscreen><verb>
/* principio del ejemplo text text.c */
/* 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);
}
void text_toggle_word_wrap (GtkWidget *checkbutton,
GtkWidget *text)
{
gtk_text_set_word_wrap(GTK_TEXT(text),
GTK_TOGGLE_BUTTON(checkbutton)->active);
}
void close_application( GtkWidget *widget, gpointer data )
{
gtk_main_quit();
}
int main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *caja1;
GtkWidget *caja2;
GtkWidget *hbox;
GtkWidget *boton;
GtkWidget *check;
GtkWidget *separator;
GtkWidget *table;
GtkWidget *vscrollbar;
GtkWidget *text;
GdkColormap *cmap;
GdkColor colour;
GdkFont *fixed_font;
FILE *infile;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize (ventana, 600, 500);
gtk_window_set_policy (GTK_WINDOW(ventana), TRUE, TRUE, FALSE);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC(close_application),
NULL);
gtk_window_set_title (GTK_WINDOW (ventana), "Text Widget Example");
gtk_container_border_width (GTK_CONTAINER (ventana), 0);
caja1 = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (ventana), caja1);
gtk_widget_show (caja1);
caja2 = gtk_vbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, TRUE, TRUE, 0);
gtk_widget_show (caja2);
table = gtk_table_new (2, 2, FALSE);
gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2);
gtk_table_set_col_spacing (GTK_TABLE (table), 0, 2);
gtk_box_pack_start (GTK_BOX (caja2), table, TRUE, TRUE, 0);
gtk_widget_show (table);
/* Crear el widget GtkText */
text = gtk_text_new (NULL, NULL);
gtk_text_set_editable (GTK_TEXT (text), TRUE);
gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1,
GTK_EXPAND | GTK_SHRINK | GTK_FILL,
GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
gtk_widget_show (text);
/* Añadir una barra de desplazamiento vertical al widget GtkText */
vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj);
gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1,
GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
gtk_widget_show (vscrollbar);
/* Obtener el mapa de colores del sistema y conseguir el color rojo */
cmap = gdk_colormap_get_system();
colour.red = 0xffff;
colour.green = 0;
colour.blue = 0;
if (!gdk_color_alloc(cmap, &amp;colour)) {
g_error("couldn't allocate colour");
}
/* Cargar un fuente de tamaño fijo */
fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*");
/* Al enviar la señal relize a un widget se crea una ventana para el
* mismo, y nos permite insertar texto */
gtk_widget_realize (text);
/* Congela el widget text, lo que nos permite hacer varias
* actualizaciones */
gtk_text_freeze (GTK_TEXT (text));
/* Insertamos algún texto coloreado */
gtk_text_insert (GTK_TEXT (text), NULL, &amp;text->style->black, NULL,
"Supports ", -1);
gtk_text_insert (GTK_TEXT (text), NULL, &amp;colour, NULL,
"colored ", -1);
gtk_text_insert (GTK_TEXT (text), NULL, &amp;text->style->black, NULL,
"text and different ", -1);
gtk_text_insert (GTK_TEXT (text), fixed_font, &amp;text->style->black, NULL,
"fonts\n\n", -1);
/* Cargamos el fichero text.c en la ventana de texto */
infile = fopen("text.c", "r");
if (infile) {
char buffer[1024];
int nchars;
while (1)
{
nchars = fread(buffer, 1, 1024, infile);
gtk_text_insert (GTK_TEXT (text), fixed_font, NULL,
NULL, buffer, nchars);
if (nchars < 1024)
break;
}
fclose (infile);
}
/* Descongelamos el widget text, permitiéndonos ver todos los
* cambios */
gtk_text_thaw (GTK_TEXT (text));
hbox = gtk_hbutton_box_new ();
gtk_box_pack_start (GTK_BOX (caja2), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
check = gtk_check_button_new_with_label("Editable");
gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT(check), "toggled",
GTK_SIGNAL_FUNC(text_toggle_editable), text);
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
gtk_widget_show (check);
check = gtk_check_button_new_with_label("Wrap Words");
gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, TRUE, 0);
gtk_signal_connect (GTK_OBJECT(check), "toggled",
GTK_SIGNAL_FUNC(text_toggle_word_wrap), text);
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), FALSE);
gtk_widget_show (check);
separator = gtk_hseparator_new ();
gtk_box_pack_start (GTK_BOX (caja1), separator, FALSE, TRUE, 0);
gtk_widget_show (separator);
caja2 = gtk_vbox_new (FALSE, 10);
gtk_container_border_width (GTK_CONTAINER (caja2), 10);
gtk_box_pack_start (GTK_BOX (caja1), caja2, FALSE, TRUE, 0);
gtk_widget_show (caja2);
boton = gtk_button_new_with_label ("close");
gtk_signal_connect (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC(close_application),
NULL);
gtk_box_pack_start (GTK_BOX (caja2), boton, TRUE, TRUE, 0);
GTK_WIDGET_SET_FLAGS (boton, GTK_CAN_DEFAULT);
gtk_widget_grab_default (boton);
gtk_widget_show (boton);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> Los <em>widgets</em> no documentados
<!-- ***************************************************************** -->
<p>
¡Todos ellos necesitan de gente que los documente! :) Por favor,
considere el contribuir a nuestro tutorial.
Si debe utilizar uno de estos <em/widgets/, que permanecen no
documentados, le sugiero que le eche un vistazo a su fichero de
cabecera respectivo en la distribución GTK. Los nombre de las
funciones GTK son muy descriptivos. Una vez haya comprendido como
funcionan las cosas, no le será difícil ver como hay que utilizar un
<em/widget/ simplemente mirando su declaración de funciones. Con esto,
y unos cuántos ejemplos del código de otros, no debería tener
problemas.
Cuando haya comprendido todas las funciones de un nuevo <em/widget/
no documentado, por favor considere el hecho de escribir un tutorial
para él, para que así otros se puedan beneficiar del tiempo que usted
gastó.
<!-- ----------------------------------------------------------------- -->
<sect1> Calendar
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> CTree
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Curves
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Drawing Area
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Font Selection Dialog
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Gamma Curve
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Image
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Packer
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Plugs and Sockets
<p>
<!-- ----------------------------------------------------------------- -->
<sect1> Preview
<p>
<!--
(This may need to be rewritten to follow the style of the rest of the tutorial)
<tscreen><verb>
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!
But there's more. Previews allow for side-by-side pre-previews. In other
words, you write a plug-in (e.g. the filterpack simulation) which would have
a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.
An approach like this acts as a sort of a preview palette and is very
effective for subtle changes. Let's go previews!
There's more. For certain plug-ins real-time image-specific human
intervention maybe necessary. In the SuperNova plug-in, for example, the
user is asked to enter the coordinates of the center of the future
supernova. The easiest way to do this, really, is to present the user with a
preview and ask him to interactively select the spot. Let's go previews!
Finally, a couple of misc uses. One can use previews even when not 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!
When Not to Use 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!
Without any ado:
/* Create a preview widget,
set its size, an show it */
GtkWidget *preview;
preview=gtk_preview_new(GTK_PREVIEW_COLOR)
/*Other option:
GTK_PREVIEW_GRAYSCALE);*/
gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
gtk_widget_show(preview);
my_preview_rendering_function(preview);
Oh yeah, like I said, previews look good inside frames, so how about:
GtkWidget *create_a_preview(int Width,
int Height,
int Colorfulness)
{
GtkWidget *preview;
GtkWidget *frame;
frame = gtk_frame_new(NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_container_set_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);
And here's the result:
[Image]
Misc
Making a preview clickable is achieved most easily by placing it in a
boton. It also adds a nice border around the preview and you may not even
need to place it in a frame. See the Filter Pack Simulation plug-in for an
example.
This is pretty much it as far as GTK is concerned.
Filling In a Preview
In order to familiarize ourselves with the basics of filling in previews,
let's create the following pattern (contrived by trial and error):
[Image]
void
my_preview_rendering_function(GtkWidget *preview)
{
#define SIZE 100
#define HALF (SIZE/2)
guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
gint i, j; /* Coordinates */
double r, alpha, x, y;
if (preview==NULL) return; /* I usually add this when I want */
/* to avoid silly crashes. You */
/* should probably make sure that */
/* everything has been nicely */
/* initialized! */
for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */
/* glib.h contains ABS(x). */
row[i*3+0] = sqrt(1-r)*255; /* Define Red */
row[i*3+1] = 128; /* Define Green */
row[i*3+2] = 224; /* Define Blue */
} /* "+0" is for alignment! */
else {
row[i*3+0] = r*255;
row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
}
}
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
/* Insert "row" into "preview" starting at the point with */
/* coordinates (0,j) first column, j_th row extending SIZE */
/* pixels to the right */
}
free(row); /* save some space */
gtk_widget_draw(preview,NULL); /* what does this do? */
gdk_flush(); /* or this? */
}
Non-GIMP users can have probably seen enough to do a lot of things already.
For the GIMP users I have a few pointers to add.
Image Preview
It is probably wise to keep a reduced version of the image around with just
enough pixels to fill the preview. This is done by selecting every n'th
pixel where n is the ratio of the size of the image to the size of the
preview. All further operations (including filling in the previews) are then
performed on the reduced number of pixels only. The following is my
implementation of reducing the image. (Keep in mind that I've had only basic
C!)
(UNTESTED CODE ALERT!!!)
typedef struct {
gint width;
gint height;
gint bbp;
guchar *rgb;
guchar *mask;
} ReducedImage;
enum {
SELECTION_ONLY,
SELECTION_IN_CONTEXT,
ENTIRE_IMAGE
};
ReducedImage *Reduce_The_Image(GDrawable *drawable,
GDrawable *mask,
gint LongerSize,
gint Selection)
{
/* This function reduced the image down to the the selected preview size */
/* The preview size is determine by LongerSize, i.e. the greater of the */
/* two dimensions. Works for RGB images only! */
gint RH, RW; /* Reduced height and reduced width */
gint width, height; /* Width and Height of the area being reduced */
gint bytes=drawable->bpp;
ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
gint i, j, whichcol, whichrow, x1, x2, y1, y2;
GPixelRgn srcPR, srcMask;
gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */
/* image. */
gimp_drawable_mask_bounds (drawable->id, &amp;x1, &amp;y1, &amp;x2, &amp;y2);
width = x2-x1;
height = y2-y1;
/* If there's a SELECTION, we got its bounds!)
if (width != drawable->width &amp;&amp; height != drawable->height)
NoSelectionMade=FALSE;
/* Become aware of whether the user has made an active selection */
/* This will become important later, when creating a reduced mask. */
/* If we want to preview the entire image, overrule the above! */
/* Of course, if no selection has been made, this does nothing! */
if (Selection==ENTIRE_IMAGE) {
x1=0;
x2=drawable->width;
y1=0;
y2=drawable->height;
}
/* If we want to preview a selection with some surrounding area we */
/* have to expand it a little bit. Consider it a bit of a riddle. */
if (Selection==SELECTION_IN_CONTEXT) {
x1=MAX(0, x1-width/2.0);
x2=MIN(drawable->width, x2+width/2.0);
y1=MAX(0, y1-height/2.0);
y2=MIN(drawable->height, y2+height/2.0);
}
/* How we can determine the width and the height of the area being */
/* reduced. */
width = x2-x1;
height = y2-y1;
/* The lines below determine which dimension is to be the longer */
/* side. The idea borrowed from the supernova plug-in. I suspect I */
/* could've thought of it myself, but the truth must be told. */
/* Plagiarism stinks! */
if (width>height) {
RW=LongerSize;
RH=(float) height * (float) LongerSize/ (float) width;
}
else {
RH=LongerSize;
RW=(float)width * (float) LongerSize/ (float) height;
}
/* The entire image is stretched into a string! */
tempRGB = (guchar *) malloc(RW*RH*bytes);
tempmask = (guchar *) malloc(RW*RH);
gimp_pixel_rgn_init (&amp;srcPR, drawable, x1, y1, width, height,
FALSE, FALSE);
gimp_pixel_rgn_init (&amp;srcMask, mask, x1, y1, width, height,
FALSE, FALSE);
/* Grab enough to save a row of image and a row of mask. */
src_row = (guchar *) malloc (width*bytes);
src_mask_row = (guchar *) malloc (width);
for (i=0; i < RH; i++) {
whichrow=(float)i*(float)height/(float)RH;
gimp_pixel_rgn_get_row (&amp;srcPR, src_row, x1, y1+whichrow, width);
gimp_pixel_rgn_get_row (&amp;srcMask, src_mask_row, x1, y1+whichrow, width);
for (j=0; j < RW; j++) {
whichcol=(float)j*(float)width/(float)RW;
/* No selection made = each point is completely selected! */
if (NoSelectionMade)
tempmask[i*RW+j]=255;
else
tempmask[i*RW+j]=src_mask_row[whichcol];
/* Add the row to the one long string which now contains the image! */
tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
/* Hold on to the alpha as well */
if (bytes==4)
tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
}
}
temp->bpp=bytes;
temp->width=RW;
temp->height=RH;
temp->rgb=tempRGB;
temp->mask=tempmask;
return temp;
}
The following is a preview function which used the same ReducedImage type!
Note that it uses fakes transparency (if one is present by means of
fake_transparency which is defined as follows:
gint fake_transparency(gint i, gint j)
{
if ( ((i%20)- 10) * ((j%20)- 10)>0 )
return 64;
else
return 196;
}
Now here's the preview function:
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);;
for (i=0; i < RH; i++) {
for (j=0; j < RW; j++) {
row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
if (bytes==4)
for (k=0; k<3; k++) {
float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
}
}
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
}
free(a);
gtk_widget_draw(preview,NULL);
gdk_flush();
}
Applicable Routines
guint gtk_preview_get_type (void);
/* No idea */
void gtk_preview_uninit (void);
/* No idea */
GtkWidget* gtk_preview_new (GtkPreviewType type);
/* Described above */
void gtk_preview_size (GtkPreview *preview,
gint width,
gint height);
/* Allows you to resize an existing preview. */
/* Apparently there's a bug in GTK which makes */
/* this process messy. A way to clean up a mess */
/* is to manually resize the window containing */
/* the preview after resizing the preview. */
void gtk_preview_put (GtkPreview *preview,
GdkWindow *ventana,
GdkGC *gc,
gint srcx,
gint srcy,
gint destx,
gint desty,
gint width,
gint height);
/* No idea */
void gtk_preview_put_row (GtkPreview *preview,
guchar *src,
guchar *dest,
gint x,
gint y,
gint w);
/* No idea */
void gtk_preview_draw_row (GtkPreview *preview,
guchar *data,
gint x,
gint y,
gint w);
/* Described in the text */
void gtk_preview_set_expand (GtkPreview *preview,
gint expand);
/* No idea */
/* No clue for any of the below but */
/* should be standard for most widgets */
void gtk_preview_set_gamma (double gamma);
void gtk_preview_set_color_cube (guint nred_shades,
guint ngreen_shades,
guint nblue_shades,
guint ngray_shades);
void gtk_preview_set_install_cmap (gint install_cmap);
void gtk_preview_set_reserved (gint nreserved);
GdkVisual* gtk_preview_get_visual (void);
GdkColormap* gtk_preview_get_cmap (void);
GtkPreviewInfo* gtk_preview_get_info (void);
That's all, folks!
</verb></tscreen>
-->
<!-- ***************************************************************** -->
<sect>Estableciendo los atributos de un <em/widget/<label id="sec_setting_widget_attributes">
<!-- ***************************************************************** -->
<p>
En este capítulo se describen las funciones utilizadas para manejar los
<em/widgets/. Pueden utilizarse para establecer el estilo, relleno,
tamaño, etc...
(Puede que deba hacer una sección completa para los aceleradores.)
<tscreen><verb>
void gtk_widget_install_accelerator( GtkWidget *widget,
GtkAcceleratorTable *table,
gchar *signal_name,
gchar key,
guint8 modifiers );
void gtk_widget_remove_accelerator ( GtkWidget *widget,
GtkAcceleratorTable *table,
gchar *signal_name);
void gtk_widget_activate( GtkWidget *widget );
void gtk_widget_set_name( GtkWidget *widget,
gchar *name );
gchar *gtk_widget_get_name( GtkWidget *widget );
void gtk_widget_set_sensitive( GtkWidget *widget,
gint sensitive );
void gtk_widget_set_style( GtkWidget *widget,
GtkStyle *style );
GtkStyle *gtk_widget_get_style( GtkWidget *widget );
GtkStyle *gtk_widget_get_default_style( void );
void gtk_widget_set_uposition( GtkWidget *widget,
gint x,
gint y );
void gtk_widget_set_usize( GtkWidget *widget,
gint width,
gint height );
void gtk_widget_grab_focus( GtkWidget *widget );
void gtk_widget_show( GtkWidget *widget );
void gtk_widget_hide( GtkWidget *widget );
</verb></tscreen>
<!-- ***************************************************************** -->
<sect>Tiempos de espera, ES (<em/IO/) y funciones ociosas (<em/idle/)<label id="sec_timeouts">
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1>Tiempos de espera
<p>
Puede que se esté preguntando como hacer que GTK haga algo útil
cuando se encuentra en <tt/gtk_main/. Bien, tiene varias
opciones. Utilizando las rutinas siguientes puede crear una función
a la que se llamará cada <tt/interval/ milisegundos.
<tscreen><verb>
gint gtk_timeout_add( guint32 interval,
GtkFunction function,
gpointer data );
</verb></tscreen>
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:
<tscreen><verb>
void gtk_timeout_remove( gint tag );
</verb></tscreen>
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:
<tscreen><verb>
gint timeout_callback( gpointer data );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Monitorizando la ES
<p>
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 <tt/open(2)/ o <tt/socket(2)/). Esto es especialmente
útil para las aplicaciones de red. La función:
<tscreen><verb>
gint gdk_input_add( gint source,
GdkInputCondition condition,
GdkInputFunction function,
gpointer data );
</verb></tscreen>
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:
<itemize>
<item>GDK_INPUT_READ - Llama a su función cuando hay datos listos para
leerse del fichero.
<item>GDK_INPUT_WRITE - Llama a su función cuando el descriptor del
fichero está listo para la escritura.
</itemize>
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
<tscreen><verb>
void gdk_input_remove( gint tag );
</verb></tscreen>
La función a la que quiere que se llame deberá declararse así:
<tscreen><verb>
void input_callback( gpointer data,
gint source,
GdkInputCondition condition );
</verb></tscreen>
Donde <tt/source/ y <tt/condition/ están especificados más arriba.
<!-- ----------------------------------------------------------------- -->
<sect1>Funciones ociosas
<p>
<!-- Need to check on idle priorities - TRG -->
¿Qué le parece si tuviese una función a la que se llamase cuando
no ocurriese nada?
<tscreen><verb>
gint gtk_idle_add( GtkFunction function,
gpointer data );
</verb></tscreen>
Esto hace que GTK llame a la función especificada cuando no ocurra
nada más.
<tscreen><verb>
void gtk_idle_remove( gint tag );
</verb></tscreen>
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 <tt/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.
<!-- ***************************************************************** -->
<sect>Manejo avanzado de eventos y señales<label id="sec_Adv_Events_and_Signals">
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1>Funciones señal
<!-- ----------------------------------------------------------------- -->
<sect2>Conectando y desconectando los manejadores de señal
<p>
<tscreen><verb>
guint gtk_signal_connect( GtkObject *object,
const gchar *name,
GtkSignalFunc func,
gpointer func_data );
guint gtk_signal_connect_after( GtkObject *object,
const gchar *name,
GtkSignalFunc func,
gpointer func_data );
guint gtk_signal_connect_object( GtkObject *object,
const gchar *name,
GtkSignalFunc func,
GtkObject *slot_object );
guint gtk_signal_connect_object_after( GtkObject *object,
const gchar *name,
GtkSignalFunc func,
GtkObject *slot_object );
guint gtk_signal_connect_full( GtkObject *object,
const gchar *name,
GtkSignalFunc func,
GtkCallbackMarshal marshal,
gpointer data,
GtkDestroyNotify destroy_func,
gint object_signal,
gint after );
guint gtk_signal_connect_interp( GtkObject *object,
const gchar *name,
GtkCallbackMarshal func,
gpointer data,
GtkDestroyNotify destroy_func,
gint after );
void gtk_signal_connect_object_while_alive( GtkObject *object,
const gchar *signal,
GtkSignalFunc func,
GtkObject *alive_object );
void gtk_signal_connect_while_alive( GtkObject *object,
const gchar *signal,
GtkSignalFunc func,
gpointer func_data,
GtkObject *alive_object );
void gtk_signal_disconnect( GtkObject *object,
guint handler_id );
void gtk_signal_disconnect_by_func( GtkObject *object,
GtkSignalFunc func,
gpointer data );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2>Bloqueando y desbloqueando los manejadores de señal
<p>
<tscreen><verb>
void gtk_signal_handler_block( GtkObject *object,
guint handler_id);
void gtk_signal_handler_block_by_func( GtkObject *object,
GtkSignalFunc func,
gpointer data );
void gtk_signal_handler_block_by_data( GtkObject *object,
gpointer data );
void gtk_signal_handler_unblock( GtkObject *object,
guint handler_id );
void gtk_signal_handler_unblock_by_func( GtkObject *object,
GtkSignalFunc func,
gpointer data );
void gtk_signal_handler_unblock_by_data( GtkObject *object,
gpointer data );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2>Emitiendo y deteniendo señales
<p>
<tscreen><verb>
void gtk_signal_emit( GtkObject *object,
guint signal_id,
... );
void gtk_signal_emit_by_name( GtkObject *object,
const gchar *name,
... );
void gtk_signal_emitv( GtkObject *object,
guint signal_id,
GtkArg *params );
void gtk_signal_emitv_by_name( GtkObject *object,
const gchar *name,
GtkArg *params );
guint gtk_signal_n_emissions( GtkObject *object,
guint signal_id );
guint gtk_signal_n_emissions_by_name( GtkObject *object,
const gchar *name );
void gtk_signal_emit_stop( GtkObject *object,
guint signal_id );
void gtk_signal_emit_stop_by_name( GtkObject *object,
const gchar *name );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Emisión y propagación de señales
<p>
La emisión de señales es el proceso mediante el cual GTK+ ejecuta
todos los manejadores de un objeto y una señal en especial.
Primero, observe que el valor devuelto por la emisión de una
señal es el mismo que el valor devuelto por el <em>último</em>
manipulador ejecutado. Ya que las señales de los eventos son todas
del tipo GTK_RUN_LAST, el manejador por defecto (proporcionado por
GTK+) será de este tipo, a menos que lo conecte con
<tt/gtk_signal_connect_after()/.
La forma en que se maneja un evento (digamos GTK_BUTTON_PRESS), es la
siguiente:
<itemize>
<item>Empieza con el <em>widget</em> donde ocurrió el evento.
<item>Emite la señal genérica <tt/event/. Si esta señal devuelve un
valor TRUE, detiene todo el proceso.
<item>En caso contrario, emite una señal especifica,
«button_press_event» en nuestro caso. Si ésta devuelve TRUE, detiene
todo el proceso.
<item>En caso contrario, va al <em>widget</em> padre y repite los
pasos anteriores.
<item>Continua hasta que algún manejador de señal devuelva TRUE, o
hasta que se llegue al <em>widget</em> de más alto nivel.
</itemize>
Algunas consecuencias son:
<itemize>
<item>El valor que devuelva su manejador no tendrá ningún efecto si
hay un manejador por defecto, a menos que lo conecte mediante
<tt/gtk_signal_connect_after()/.
<item>Para evitar que el manejador por defecto se ejecute, necesita
conectar mediante <tt/gtk_signal_connect()/ y utilizar
<tt/gtk_signal_emit_stop_by_name()/ - el valor devuelto sólo se ve
afectado si la señal se propaga, y no sólo por el hecho de emitirse.
</itemize>
<!-- ***************************************************************** -->
<sect>Manejando selecciones
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1> Contenido
<p>
Un tipo de comunicación entre procesos que se puede utilizar con GTK
son las <em/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 <em/display/ (la <em>propietaria</em>) 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
<em/objetivos/. Puede haber cualquier número de selecciones, pero la
mayoría de las aplicacion X sólo pueden manejar una, la <em/selección
primaria/.
En muchos casos, no es necesario para una aplicación GTK tratar por
sí misma con las selecciones. Los <em/widgets/ estándar, como el
<em/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 <em/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 <em/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 <em>átomo</em>. Un átomo es un entero que
identifica de una manera unívoca una cadena (en un cierto
<em/display/). Ciertos átomos están predefinidos por el servidor X, y
en algunos casos hay constantes en <tt>gtk.h</tt> que corresponden a
estos átomos. Por ejemplo la constante <tt>GDK_PRIMARY_SELECTION</tt>
corresponde a la cadena «PRIMARY». En otros casos, debería utilizar
las funciones <tt>gdk_atom_intern()</tt>, para obtener el átomo
correspondiente a una cadena, y <tt>gdk_atom_name()</tt>, para obtener
el nombre de un átomo. Ambas, selecciones y objetivos, están
identificados por átomos.
<!-- ----------------------------------------------------------------- -->
<sect1> Recuperando la selección
<p>
Recuperar la selección es un proceso asíncrono. Para comenzar el
proceso, deberá llamar a:
<tscreen><verb>
gint gtk_selection_convert( GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
guint32 time );
</verb</tscreen>
Este proceso <em/convierte/ la selección en la forma especificada por
<tt/target/. Si es posible, el campo <tt/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 <tt>GDK_CURRENT_TIME</tt>.
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
<tt>GtkSelectionData</tt>, que se define como:
<tscreen><verb>
struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};
</verb></tscreen>
<tt>selection</tt> y <tt>target</tt> son los valores que dió en su
llamada a <tt>gtk_selection_convert()</tt>. <tt>type</tt> es un átomo
que identifica el tipo de datos devueltos por el propietario de la
selección. Algunos valores posibles son «STRING», un cadena de
caracteres latin-1, «ATOM», una serie de átomos, «INTEGER», un
entero, etc. Muchos objetivos sólo pueden devolver un
tipo. <tt/format/ da la longitud de las unidades (por ejemplo
caracteres) en bits. Normalmente, no tiene porque preocuparse de todo
esto cuando recibe datos. <tt/data/ es un puntero a los datos
devueltos, y <tt/length/ da la longitud de los datos devueltos, en
bytes. Si <tt/length/ es negativo, es que a ocurrido un error y no se
puede obtener la selección. Esto podría ocurrir si no hay ninguna
aplicación que sea la propietaria de la selección, o si pide un
objetivo que la aplicación no admite. Actualmente se garantiza que el
búfer tendrá un byte más que <tt/length/; el byte extra siempre será
cero, por lo que no es necesario hacer una copia de las cadenas sólo
para añadirles un carácter nulo al final.
En el siguiente ejemplo, recuperamos el objetivo especial «TARGETS»,
que es una lista de todos los objetivos en los que se puede convertir
la selección.
<tscreen><verb>
/* principio del ejemplo selection gettargets.c */
#include <gtk/gtk.h>
void selection_received (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
/* Manejador de señal invocado cuando el usuario pulsa en el botón
"Get Targets" */
void
get_targets (GtkWidget *widget, gpointer data)
{
static GdkAtom targets_atom = GDK_NONE;
/* Obtener el atom correpondiente a la cadena "TARGETS" */
if (targets_atom == GDK_NONE)
targets_atom = gdk_atom_intern ("TARGETS", FALSE);
/* Y pide el objetivo "TARGETS" de la selección primaria */
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
GDK_CURRENT_TIME);
}
/* Manipulador de señal llamado cuando el propietario de la señal
* devuelve los datos */
void
selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
gpointer data)
{
GdkAtom *atoms;
GList *item_list;
int i;
/* **** IMPORTANTE **** Comprobar si se da la recuperación de los
* datos */
if (selection_data->length < 0)
{
g_print ("Selection retrieval failed\n");
return;
}
/* Asegurarse de que obtenemos los datos de la forma esperada */
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
{
g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
return;
}
/* Imprimir los atoms que hemos recibido */
atoms = (GdkAtom *)selection_data->data;
item_list = NULL;
for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
{
char *name;
name = gdk_atom_name (atoms[i]);
if (name != NULL)
g_print ("%s\n",name);
else
g_print ("(bad atom)\n");
}
return;
}
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *boton;
gtk_init (&amp;argc, &amp;argv);
/* Crear la ventana superior */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Event Box");
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Crear un botón que el usuario puede pulsar para obtener los
* objetivos */
boton = gtk_button_new_with_label ("Get Targets");
gtk_container_add (GTK_CONTAINER (ventana), boton);
gtk_signal_connect (GTK_OBJECT(boton), "clicked",
GTK_SIGNAL_FUNC (get_targets), NULL);
gtk_signal_connect (GTK_OBJECT(boton), "selection_received",
GTK_SIGNAL_FUNC (selection_received), NULL);
gtk_widget_show (boton);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Proporcionando la selección
<p>
Proporcionar la selección es un poco más complicado. Debe registrar
los manejadores a los que se llamarán cuando se le pida la
selección. Por cada par selección/objetivo que quiera manejar, deberá
hacer una llamada a:
<tscreen><verb>
void gtk_selection_add_handler( GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data );
</verb></tscreen>
<tt/widget/, <tt/selection/, y <tt/target/ identifican las peticiones
que este manejador puede manipular. Si <tt/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 <tt/data/.
La función de llamada tiene el prototipo:
<tscreen><verb>
typedef void (*GtkSelectionFunction)( GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data );
</verb></tscreen>
El <tt/GtkSelectionData/ es el mismo que hay más arriba, pero esta
vez, seremos nosotros los responsables de rellenar los campos
<tt/type/, <tt/format/, <tt/data/, y <tt/length/. (El campo
<tt/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:
<tscreen><verb>
void gtk_selection_data_set( GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length );
</verb></tscreen>
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 <tt/GtkSelectionData/ a mano.)
Cuando haga falta, puede pedir el propietario de la selección llamando
a:
<tscreen><verb>
gint gtk_selection_owner_set( GtkWidget *widget,
GdkAtom selection,
guint32 time );
</verb></tscreen>
Si otra aplicación pide el propietario de la selección, recibira un
«selection_clear_event».
Como ejemplo de proporciar la selección, el programa siguiente le añade
la posibilidad de selección a un botón de comprobación. Cuando se presione
el botón de comprobación, el programa pedirá la selección primaria. El
único objetivo que admite es un objetivo «STRING» (aparte de ciertos
objetivos como "TARGETS", proporcionados por GTK). Cuando se pida este
objetivo, se devolverá una representación del tiempo.
<tscreen><verb>
/* principio del ejemplo selection setselection.c */
#include <gtk/gtk.h>
#include <time.h>
/* Función de llamada para cuando el usuario cambia la selección */
void
selection_toggled (GtkWidget *widget, gint *have_selection)
{
if (GTK_TOGGLE_BUTTON(widget)->active)
{
*have_selection = gtk_selection_owner_set (widget,
GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
/* Si la demanda de la selección ha fallado, ponemos el botón en
* estado apagado */
if (!*have_selection)
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
}
else
{
if (*have_selection)
{
/* Antes de eliminar la seleción poniendo el propietario a
* NULL, comprobamos si somos el propietario actual */
if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
*have_selection = FALSE;
}
}
}
/* Llamado cuando otra aplicación pide la selección */
gint
selection_clear (GtkWidget *widget, GdkEventSelection *event,
gint *have_selection)
{
*have_selection = FALSE;
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
return TRUE;
}
/* Proporciona el tiempo actual como selección. */
void
selection_handle (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data)
{
gchar *timestr;
time_t current_time;
current_time = time (NULL);
timestr = asctime (localtime(&amp;current_time));
/* Cuando devolvemos una cadena, no debe terminar en NULL. La
* función lo hará por nosotros */
gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
8, timestr, strlen(timestr));
}
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *selection_button;
static int have_selection = FALSE;
gtk_init (&amp;argc, &amp;argv);
/* Crear la ventana superior */
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Event Box");
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Crear un botón de selección para que actue como la selección */
selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
gtk_container_add (GTK_CONTAINER (ventana), selection_button);
gtk_widget_show (selection_button);
gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
GTK_SIGNAL_FUNC (selection_toggled), &amp;have_selection);
gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
GTK_SIGNAL_FUNC (selection_clear), &amp;have_selection);
gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
GDK_SELECTION_TYPE_STRING,
selection_handle, NULL);
gtk_widget_show (selection_button);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect>Glib<label id="sec_glib">
<!-- ***************************************************************** -->
<p>
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.
<!-- ----------------------------------------------------------------- -->
<sect1>Definiciones
<p>
Las definiciones para los límites de muchos de los tipos estándar son:
<tscreen><verb>
G_MINFLOAT
G_MAXFLOAT
G_MINDOUBLE
G_MAXDOUBLE
G_MINSHORT
G_MAXSHORT
G_MININT
G_MAXINT
G_MINLONG
G_MAXLONG
</verb></tscreen>
Y también, los siguientes <tt/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.
<tscreen><verb>
char gchar;
short gshort;
long glong;
int gint;
char gboolean;
unsigned char guchar;
unsigned short gushort;
unsigned long gulong;
unsigned int guint;
float gfloat;
double gdouble;
long double gldouble;
void* gpointer;
gint8
guint8
gint16
guint16
gint32
guint32
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Listas doblemente enlazadas
<p>
Las funciones siguientes se utilizan para crear, manipular, y destruir
listas doblemente enlazadas. Supondré que sabe lo que son las listas
enlazadas, ya que explicarlas va más allá del objetivo de este
documento. Por supuesto, no es necesario que conozca como manejar
todo esto para utilizar GTK, pero siempre es bonito aprender cosas.
<tscreen><verb>
GList *g_list_alloc( void );
void g_list_free( GList *list );
void g_list_free_1( GList *list );
GList *g_list_append( GList *list,
gpointer data );
GList *g_list_prepend( GList *list,
gpointer data );
GList *g_list_insert( GList *list,
gpointer data,
gint posicion );
GList *g_list_remove( GList *list,
gpointer data );
GList *g_list_remove_link( GList *list,
GList *link );
GList *g_list_reverse( GList *list );
GList *g_list_nth( GList *list,
gint n );
GList *g_list_find( GList *list,
gpointer data );
GList *g_list_last( GList *list );
GList *g_list_first( GList *list );
gint g_list_length( GList *list );
void g_list_foreach( GList *list,
GFunc func,
gpointer user_data );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Listas simplemente enlazadas
<p>
Muchas de las funciones para las listas enlazadas son idénticas a las
de más arriba. Aquí hay una lista completa:
<tscreen><verb>
GSList *g_slist_alloc( void );
void g_slist_free( GSList *list );
void g_slist_free_1( GSList *list );
GSList *g_slist_append( GSList *list,
gpointer data );
GSList *g_slist_prepend( GSList *list,
gpointer data );
GSList *g_slist_insert( GSList *list,
gpointer data,
gint posicion );
GSList *g_slist_remove( GSList *list,
gpointer data );
GSList *g_slist_remove_link( GSList *list,
GSList *link );
GSList *g_slist_reverse( GSList *list );
GSList *g_slist_nth( GSList *list,
gint n );
GSList *g_slist_find( GSList *list,
gpointer data );
GSList *g_slist_last( GSList *list );
gint g_slist_length( GSList *list );
void g_slist_foreach( GSList *list,
GFunc func,
gpointer user_data );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Control de la memoria
<p>
<tscreen><verb>
gpointer g_malloc( gulong size );
</verb></tscreen>
Reemplaza a <tt/malloc()/. No necesita comprobar el valor devuelto, ya
que ya lo hace por usted esta función.
<tscreen><verb>
gpointer g_malloc0( gulong size );
</verb></tscreen>
Lo mismo que antes, pero rellena con ceros la memoria antes de
devolver un puntero a ella.
<tscreen><verb>
gpointer g_realloc( gpointer mem,
gulong size );
</verb></tscreen>
Vuelve a reservar <tt/size/ bites de memoria empezando en
<tt/mem/. Obviamente, la memoria debe haber sido previamente reservada.
<tscreen><verb>
void g_free( gpointer mem );
</verb></tscreen>
Libera la memoria. Fácil.
<tscreen><verb>
void g_mem_profile( void );
</verb></tscreen>
Crea un fichero donde vuelca la memoria que se está utilizando, pero
tiene que añadir <tt/#define MEM_PROFILE/ en lo alto de
<tt>glib/gmem.c</tt> y tendrá que hacer un make y un make install.
<tscreen><verb>
void g_mem_check( gpointer mem );
</verb></tscreen>
Comprueba que una dirección de memoria es válida. Tiene que añadir
<tt/#define MEM_CHECK/ en lo alto de <tt/gmem.c/ y tiene que hacer un
make y un make install.
<!-- ----------------------------------------------------------------- -->
<sect1>Timers
<p>
Temporizadores...
<tscreen><verb>
GTimer *g_timer_new( void );
void g_timer_destroy( GTimer *timer );
void g_timer_start( GTimer *timer );
void g_timer_stop( GTimer *timer );
void g_timer_reset( GTimer *timer );
gdouble g_timer_elapsed( GTimer *timer,
gulong *microseconds );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Manejo de cadenas de texto
<p>
Un puñado de funciones para manejar cadenas de texto. Parecen muy
interesantes, y probablemente sean mejores en muchos aspectos que las
funciones estándar de C, pero necesitan documentación.
<tscreen><verb>
GString *g_string_new( gchar *init );
void g_string_free( GString *string,
gint free_segment );
GString *g_string_assign( GString *lval,
gchar *rval );
GString *g_string_truncate( GString *string,
gint len );
GString *g_string_append( GString *string,
gchar *val );
GString *g_string_append_c( GString *string,
gchar c );
GString *g_string_prepend( GString *string,
gchar *val );
GString *g_string_prepend_c( GString *string,
gchar c );
void g_string_sprintf( GString *string,
gchar *fmt,
...);
void g_string_sprintfa ( GString *string,
gchar *fmt,
... );
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>Funciones de error y funciones varias
<p>
<tscreen><verb>
gchar *g_strdup( const gchar *str );
</verb></tscreen>
Reemplaza a la función <tt/strdup/. Copia el contenido de la cadena
original en un nuevo lugar en memoria, y devuelve un puntero al nuevo
lugar.
<tscreen><verb>
gchar *g_strerror( gint errnum );
</verb></tscreen>
Recomiendo utilizar esta función para todos los mensages de error. Es
mucho más bonita, y más portable que <tt/perror()/ y demás funciones
clásicas. La salida es normalmente de la forma:
<tscreen><verb>
nombre del programa:función que falló:fichero o descripción adicional:strerror
</verb></tscreen>
Aquí hay un ejemplo de una llamada utilizada en nuestro programa
<tt/hello_world/:
<tscreen><verb>
g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));
</verb></tscreen>
<tscreen><verb>
void g_error( gchar *format, ... );
</verb></tscreen>
Imprime un mensaje de error. El formato es como el de <tt/printf/,
pero le añade <tt/** ERROR **: / a su mensaje, y sale del
programa. Sólo para errores fatales.
<tscreen><verb>
void g_warning( gchar *format, ... );
</verb></tscreen>
El mismo que el anterior, pero añade "** WARNING **: ", y no sale del
programa.
<tscreen><verb>
void g_message( gchar *format, ... );
</verb></tscreen>
Imprime <tt/message: / antes de la cadena que le pase.
<tscreen><verb>
void g_print( gchar *format, ... );
</verb></tscreen>
Reemplazo de <tt/printf()/.
Y nuestra última función:
<tscreen><verb>
gchar *g_strsignal( gint signum );
</verb></tscreen>
Imprime el nombre de la señal del sistema Unix que corresponde con el
número <tt/signum/. Útil para las funciones genéricas de manejo de señal.
Todo lo anterior está más o menos robado de <tt/glib.h/. Si alguien
quiere documentar una función, ¡sólo tiene que enviarme un correo-e!
<!-- ***************************************************************** -->
<sect>Ficheros rc de GTK
<!-- ***************************************************************** -->
<p>
GTK tiene su propia forma de conseguir los valores por defecto de una
aplicación, y es utilizando los ficheros <tt/rc/. Pueden ser
utilizados para poner los colores de cualquier <em/widget/, y también
pueden utilizarse para poner imágenes como fondos de algunos <em/widgets/.
<!-- ----------------------------------------------------------------- -->
<sect1>Funciones para los ficheros <tt/rc/
<p>
Cuando empiece su aplicación, debería incluir una llamada a:
<tscreen><verb>
void gtk_rc_parse( char *filename );
</verb></tscreen>
Poniendo el nombre del fichero de su rc. Esto hará que GTK analice
este fichero, y utilice el estilo para los <em/widgets/ que se definan
ahí.
Si desea tener un conjunto especial de <em/widgets/ con un estilo
diferente de los otros, o realizar cualquier otra división lógica de
los <em/widgets/, haga una llamada a:
<tscreen><verb>
void gtk_widget_set_name( GtkWidget *widget,
gchar *name );
</verb></tscreen>
Pasándole su nuevo <em/widget/ como primer argumento, y el nombre que
desea darle como el segundo. Mediante este nombre podrá cambiar los
atributos de ese <em/widget/.
Si hacemos algo así:
<tscreen><verb>
boton = gtk_button_new_with_label ("Botón especial");
gtk_widget_set_name (boton, "botón especial");
</verb></tscreen>
El botón tendrá el nombre «botón especial» y podría hacersele
referencia en el fichero <tt/rc/ como «botón especial.GtkButton».
[<--- ¡Verificadme! ]
El fichero de ejemplo <tt/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:
<tscreen><verb>
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_name (ventana, "main window");
</verb></tscreen>
Y el estilo se define en el fichero <tt/rc/ utilizando:
<tscreen><verb>
widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
Qué hace que todos los <em/widgets/ GtkButton de la «main window»
(ventana principal) tengan el estilo "main_buttons" tal y como se
define en el fichero <tt/rc/.
Como puede ver, es un sistema muy poderoso y flexible. Utilice su
imaginación para aprovecharse al máximo de este sistema.
<!-- ----------------------------------------------------------------- -->
<sect1>Formato de los ficheros <tt/rc/ de GTK
<p>
El formato de los ficheros GTK se muestra en el ejemplo de más
abajo. Éste es el fichero <tt/testgtkrc/ de la distribución GTK, pero he
añadido unos cuantos comentarios y alguna cosilla. Puede que quiera
incluir esta explicación en su aplicación para permitir al usuario
personalizar su aplicación.
Hay varias directivas para cambiar los atributos de un <em/widget/.
<itemize>
<item>fg - Establece el color de primer plano de un <em/widget/.
<item>bg - Establece el color de fondo de un <em/widget/.
<item>bg_pixmap - Establece la imagen que servirá de fondo al
<em/widget/ (como mosaico).
<item>font - Establece el tipo de letra que se utilizará con el
<em/widget/.
</itemize>
Además de esto, hay varios estados en el que puede estar un
<em/widget/, y puede especificar diferentes colores, imágenes y tipos
de letra para cada estado. Estos estados son:
<itemize>
<item>NORMAL - El estado normal de un <em/widget/, sin el ratón sobre
él, y no siendo presionado, etc...
<item>PRELIGHT - Cuando el ratón esté sobre este <em/widget/ se
utilizarán los colores definidos para este estado.
<item>ACTIVE - Cuando se presiona o se pulsa sobre el <em/widget/,
estará activo, y los atributos asignados por está etiqueta serán
utilizados.
<item>INSENSITIVE - Cuando un <em/widget/ es insensible, y no se puede
activar, tomará estos atributos.
<item>SELECTED - Cuando se seleccione un objeto, tomará estos atributos.
</itemize>
Cuando se utilizan las directivas «fg» y «bg» para poner los colores de
los <em/widgets/, se utilizará el formato siguiente:
<tscreen><verb>
fg[<STATE>] = { Red, Green, Blue }
</verb></tscreen>
Donde <tt/STATE/ es uno de los estados anteriores (PRELIGHT, ACTIVE,
etc...), y el <tt/Red/, <tt/Green/ y <tt/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.
<tt/bg_pixmap/ es muy similar al de arriba, salvo que los colores se
reemplazan por un nombre de fichero.
<tt/pixmap_path/ es una lista de los caminos (<em/paths/) separados por
«:». Estos caminos se utilizarán para buscar cualquier imagen que
indique.
La directiva sobre el tipo de letra es simplemente:
<tscreen><verb>
font = "<nombre del tipo de letra>"
</verb></tscreen>
Donde lo único difícil es saber la cadena del tipo de letra a
elegir. Utilizar <tt/xfontsel/ o un programa similar debería ayudar.
El <tt/widget_class/ establece el estilo de una clase de
<em/widgets/. Estas clases se muestran en el resumen de <em/widgets/
dentro de la jerarquía de clases.
La directiva <tt/widget/ hace que un conjunto específico de
<em/widgets/ tenga un estido determinado, sobreescribiendo cualquier
estilo anterior que tuviese esa clase de <em/widgets/. Estos
<em/widgets/ se registran dentro de la aplicación utilizando una
llamada a <tt/gtk_widget_set_name()/. Esto le permitirá especificar
los atributos de un <em/widget/ uno a uno, en vez de establecer los
atributos de toda una clase <em/widget/. Deberá documentar cualquiera
de estos <em/widgets/ especiales para que los usuarios puedan
personalizarlos.
Cuando la palabra clave <tt/parent/ se utiliza como un atributo, el
<em/widget/ tomará los atributos de su padre en la aplicación.
Puede asignar los atributos de un estilo previamente definido a uno
nuevo.
<tscreen><verb>
style "main_button" = "button"
{
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
bg[PRELIGHT] = { 0.75, 0, 0 }
}
</verb></tscreen>
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 <em/widget/ esté en estado <tt/PRELIGHT/.
Por supuesto, muchos de estos atributos no se aplican a todos los
<em/widgets/. Realmente es una cuestión de sentido común. Se utilizará
cualquier atributo que se pueda aplicar.
<!-- ----------------------------------------------------------------- -->
<sect1>Fichero <tt/rc/ de ejemplo
<p>
<!-- Esto hay que traducirlo -->
<tscreen><verb>
# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
#
pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
#
# style <name> [= <name>]
# {
# <option>
# }
#
# widget <widget_set> style <style_name>
# widget_class <widget_class_set> style <style_name>
# Here is a list of all the possible states. Note that some do not apply to
# certain widgets.
#
# NORMAL - The normal state of a widget, without the mouse over top of
# it, and not being pressed etc.
#
# PRELIGHT - When the mouse is over top of the widget, colors defined
# using this state will be in effect.
#
# ACTIVE - When the widget is pressed or clicked it will be active, and
# the attributes assigned by this tag will be in effect.
#
# INSENSITIVE - When a widget is set insensitive, and cannot be
# activated, it will take these attributes.
#
# SELECTED - When an object is selected, it takes these attributes.
#
# Given these states, we can set the attributes of the widgets in each of
# these states using the following directives.
#
# fg - Sets the foreground color of a widget.
# fg - Sets the background color of a widget.
# bg_pixmap - Sets the background of a widget to a tiled pixmap.
# font - Sets the font to be used with the given widget.
#
# This sets a style called "button". The name is not really important, as
# it is assigned to the actual widgets at the bottom of the file.
style "window"
{
#This sets the padding around the window to the pixmap specified.
#bg_pixmap[<STATE>] = "<pixmap filename>"
bg_pixmap[NORMAL] = "warning.xpm"
}
style "scale"
{
#Sets the foreground color (font color) to red when in the "NORMAL"
#state.
fg[NORMAL] = { 1.0, 0, 0 }
#Sets the background pixmap of this widget to that of its parent.
bg_pixmap[NORMAL] = "<parent>"
}
style "button"
{
# This shows all the possible states for a button. The only one that
# doesn't apply is the SELECTED state.
fg[PRELIGHT] = { 0, 1.0, 1.0 }
bg[PRELIGHT] = { 0, 0, 1.0 }
bg[ACTIVE] = { 1.0, 0, 0 }
fg[ACTIVE] = { 0, 1.0, 0 }
bg[NORMAL] = { 1.0, 1.0, 0 }
fg[NORMAL] = { .99, 0, .99 }
bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
fg[INSENSITIVE] = { 1.0, 0, 1.0 }
}
# In this example, we inherit the attributes of the "button" style and then
# override the font and background color when prelit to create a new
# "main_button" style.
style "main_button" = "button"
{
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
bg[PRELIGHT] = { 0.75, 0, 0 }
}
style "toggle_button" = "button"
{
fg[NORMAL] = { 1.0, 0, 0 }
fg[ACTIVE] = { 1.0, 0, 0 }
# This sets the background pixmap of the toggle_button to that of its
# parent widget (as defined in the application).
bg_pixmap[NORMAL] = "<parent>"
}
style "text"
{
bg_pixmap[NORMAL] = "marble.xpm"
fg[NORMAL] = { 1.0, 1.0, 1.0 }
}
style "ruler"
{
font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
}
# pixmap_path "~/.pixmaps"
# These set the widget types to use the styles defined above.
# The widget types are listed in the class hierarchy, but could probably be
# just listed in this document for the users reference.
widget_class "GtkWindow" style "window"
widget_class "GtkDialog" style "window"
widget_class "GtkFileSelection" style "window"
widget_class "*Gtk*Scale" style "scale"
widget_class "*GtkCheckButton*" style "toggle_button"
widget_class "*GtkRadioButton*" style "toggle_button"
widget_class "*GtkButton*" style "button"
widget_class "*Ruler" style "ruler"
widget_class "*GtkText" style "text"
# This sets all the buttons that are children of the "main window" to
# the main_button style. These must be documented to be taken advantage of.
widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
<!-- ***************************************************************** -->
<sect>Escribiendo sus propios <em/widgets/
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1> Visión general
<p>
Aunque la distribución de GTK viene con muchos tipos de <em/widgets/
que debería cubrir todas la mayoría de las necesidades básicas, puede
que haya llegado el momento en que necesite crear su propio
<em/widget/. Debido a que GTK utiliza mucho la herencia de
<em/widgets/, y si ya hay un <em/widget/ que se acerque lo suficiente
a lo que quiere, tal vez pueda hacer un nuevo <em/widget/ con tan solo
unas cuantas líneas de código. Pero antes de empezar a trabajar en un
nuevo <em/widget/, asegúrese primero de que no hay nadie que ya haya
hecho otro parecido. Así evitará la duplicación de esfuerzo y
mantendrá el número de <em/widgets/ GTK en su valor mínimo, lo que
ayudará a que el código y la interfaz de las diferentes aplicaciones
sea consistente. Por otra parte, cuando haya acabado su <em/widget/,
anúncielo al mundo entreo para que todo el mundo se pueda
beneficiar. Probablemente el mejor lugar para hacerlo sea la
<tt>gtk-list</tt>.
Las fuentes completas de los <em/widgets/ de ejemplo están disponibles
en el mismo lugar en el que consiguió este tutorial, o en:
<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
name="http://www.gtk.org/~otaylor/gtk/tutorial/">
<!-- ----------------------------------------------------------------- -->
<sect1> La anatomía de un <em/widget/
<p>
Para crear un nuevo <em/widget/, es importante conocer como funcionan
los objetos de GTK. Esta sección es sólo un breve resumen. Ver la
documentación a la que se hace referencia para obtener más detalles.
Los widgets GTK están implementados siguiendo una orientación a
objetos. Sin embargo, están implementados en C estándar. De esta forma
se mejora enormemente la portabilidad y la estabilidad con respecto a
la actual generación de compiladores C++; sin embargo, con todo esto
no queremos decir que el creador de <em/widgets/ tenga que prestar
atención a ninguno de los detalles de implementación. La información
que es común a todos los <em/widgets/ de una clase de <em/widgets/
(p.e., a todos los <em/widgets/ botón) se almacena en la
<em>estructura de clase</em>. Sólo hay una copia de ésta en la que se
almacena información sobre las señales de la clase (que actuan como
funciones virtuales en C). Para permitir la herencia, el primer campo
en la estructura de la clase debe ser una copia de la estructura de la
clase del padre. La declaración de la estructura de la clase de
GtkButton debe ser algo así:
<tscreen><verb>
struct _GtkButtonClass
{
GtkContainerClass parent_class;
void (* pressed) (GtkButton *button);
void (* released) (GtkButton *button);
void (* clicked) (GtkButton *button);
void (* enter) (GtkButton *button);
void (* leave) (GtkButton *button);
};
</verb></tscreen>
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 <em/widget/. Esta
estructura tiene campos para almacenar la información que es diferente
para cada copia del <em/widget/. Nosotros llamaremos a esta estructura
la <em>estructura objeto</em>. Para la clase botón, es así:
<tscreen><verb>
struct _GtkButton
{
GtkContainer container;
GtkWidget *child;
guint in_button : 1;
guint button_down : 1;
};
</verb></tscreen>
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.
<!-- ----------------------------------------------------------------- -->
<sect1> Creando un <em/widget/ compuesto
<!-- ----------------------------------------------------------------- -->
<sect2> Introducción
<p>
Un tipo de widget que puede interesarnos es uno que sea un mero
agregado de otros <em/widgets/ GTK. Este tipo de <em/widget/ no hace
nada que no pueda hacerse sin la necesidad de crear un nuevo
<em/widget/, pero proporciona una forma conveniente de empaquetar los
elementos del interfaz de usuario para su reutilización. Los
<em/widgets/ <tt/FileSelection/ y <tt/ColorSelection/ incluidos en la
distribución estándar son ejemplos de este tipo de <em/widgets/.
El <em/widget/ ejemplo que hemos creado en esta sección es el
<em/widget/ Tictactoe, una matriz de 3x3 de botones de selección que
lanza una señal cuando están deseleccionados tres botones en una misma
fila, columna, o diagonal.
<!-- ----------------------------------------------------------------- -->
<sect2> Escogiendo una clase padre
<p>
Normalmente la clase padre para un <em/widget/ compuesto es la clase
contenedor que tenga todos los elementos del <em/widget/
compuesto. Por ejemplo, la clase padre del <em/widget/
<tt/FileSelection/ es la clase <tt/Dialog/. Ya que nuestros botones se
ordenarán en una tabla, parece natural hacer que nuestra clase padre
sea la clase <tt/GtkTable/. Desafortunadamente, esto no
funcionaría. La creación de un <em/widget/ se divide en dos funciones
- una función <tt/NOMBREWIDGET_new()/ que utilizará el usuario, y una
función <tt/NOMBREWIDGET_init()/ que hará el trabajo básico de
inicializar el <em/widget/ que es independiente de los argumentos que
se le pasen a la función <tt/_new()/. Los <em/widgets/ derivados sólo
llaman a la función <tt/_init/ de su <em/widget/ padre. Pero esta
división del trabajo no funciona bien con las tablas, que necesitan
saber en el momento de su creación el número de filas y de columnas
que deben tener. A menos que queramos duplicar la mayor parte de lo
hecho en <tt/gtk_table_new()/ en nuestro <em/widget/ Tictactoe,
haremos mejor si evitamos derivar de GtkTable. Por esta razón,
derivaremos de <tt/GtkVBox/, y meteremos nuestra tabla dentro de la
caja vertical.
<!-- ----------------------------------------------------------------- -->
<sect2> El fichero de cabecera
<p>
Cada clase <em/widget/ tiene un fichero de cabecera que declara el
objeto y las estructuras de clase para ese <em/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:
<tscreen><verb>
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */
</verb></tscreen>
Y para que los programas en C++ incluyan sin problemas el fichero de
cabecera, pondremos:
<tscreen><verb>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
.
.
.
#ifdef __cplusplus
}
#endif /* __cplusplus */
</verb></tscreen>
Con las funciones y las estructuras, declararemos tres macros estándar
en nuestro fichero de cabecera, <tt/TICTACTOE(obj)/,
<tt/TICTACTOE_CLASS(class)/, y <tt/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 <em/widget/
Tictactoe.
Aquí está el fichero de cabecera al completo:
<tscreen><verb>
/* tictactoe.h */
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#include <gdk/gdk.h>
#include <gtk/gtkvbox.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
typedef struct _Tictactoe Tictactoe;
typedef struct _TictactoeClass TictactoeClass;
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *botones[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
void (* tictactoe) (Tictactoe *ttt);
};
guint tictactoe_get_type (void);
GtkWidget* tictactoe_new (void);
void tictactoe_clear (Tictactoe *ttt);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2> La función <tt/_get_type()/.
<p>
Ahora continuaremos con la implementación de nuestro <em/widget/. Una
función del núcleo de todo <em/widget/ es
<tt/NOMBREWIDGET_get_type()/. Cuando se llame a esta función por
vez primera, le informará a GTK sobre la clase del <em/widget/, y
devolverá un ID que identificará unívocamente la clase <em/widget/. En
las llamadas siguientes, lo único que hará será devolver el ID.
<tscreen><verb>
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
};
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &amp;ttt_info);
}
return ttt_type;
}
</verb></tscreen>
La estructura GtkTypeInfo tiene la definición siguiente:
<tscreen><verb>
struct _GtkTypeInfo
{
gchar *type_name;
guint object_size;
guint class_size;
GtkClassInitFunc class_init_func;
GtkObjectInitFunc object_init_func;
GtkArgSetFunc arg_set_func;
GtkArgGetFunc arg_get_func;
};
</verb></tscreen>
Los utilidad de cada campo de esta estructura se explica por su propio
nombre. Ignoraremos por ahora los campos <tt/arg_set_func/
y <tt/arg_get_func/: son importantes, pero todavía es raro
utilizarlos, su papel es permitir que las opciones de los <em/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 <em/widget/.
<!-- ----------------------------------------------------------------- -->
<sect2> La función <tt/_class_init()/
<p>
La función <tt/NOMBREWIDGET_class_init()/ inicializa los campos de la
estructura clase del <em/widget/, y establece las señales de la
clase. Para nuestro <em/widget/ Tictactoe será una cosa así:
<tscreen><verb>
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
class->tictactoe = NULL;
}
</verb></tscreen>
Nuestro <em/widget/ sólo tiene una señal, la señal <tt/tictactoe/ que
se invoca cuando una fila, columna, o diagonal se rellena
completamente. No todos los <em/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:
<tscreen><verb>
gint gtk_signal_new( const gchar *name,
GtkSignalRunType run_type,
GtkType object_type,
gint function_offset,
GtkSignalMarshaller marshaller,
GtkType return_val,
guint nparams,
...);
</verb></tscreen>
crea una nueva señal. Los parámetros son:
<itemize>
<item> <tt/name/: El nombre de la señal.
<item> <tt/run_type/: Si el manejador por defecto se ejecuta antes o
despues del manejador de usuario. Normalmente debe ser
<tt/GTK_RUN_FIRST/, o <tt/GTK_RUN_LAST/, aunque hay otras
posibilidades.
<item> <tt/object_type/: El ID del objeto al que se le aplica esta
señal. (También se aplicará a los descendientes de los objetos)
<item> <tt/function_offset/: El desplazamiento en la estructura de la
clase de un puntero al manejador por defecto.
<item> <tt/marshaller/: Una función que se utiliza para invocar al
manejador de señal. Para los manejadores de señal que no tengan más
argumentos que el objeto que emitió la señal podemos utilizar la
función marshaller por defecto <tt/gtk_signal_default_marshaller/.
<item> <tt/return_val/: El tipo del valor devuelto.
<item> <tt/nparams/: El número de parámetros del manejador de señal
(distintos de los dos por defecto que hemos mencionado arriba).
<item> <tt/.../: Los tipos de los parámetros.
</itemize>
Cuando se especifican los tipos, se utilizará la enumeración
<tt/GtkType/:
<tscreen><verb>
typedef enum
{
GTK_TYPE_INVALID,
GTK_TYPE_NONE,
GTK_TYPE_CHAR,
GTK_TYPE_BOOL,
GTK_TYPE_INT,
GTK_TYPE_UINT,
GTK_TYPE_LONG,
GTK_TYPE_ULONG,
GTK_TYPE_FLOAT,
GTK_TYPE_DOUBLE,
GTK_TYPE_STRING,
GTK_TYPE_ENUM,
GTK_TYPE_FLAGS,
GTK_TYPE_BOXED,
GTK_TYPE_FOREIGN,
GTK_TYPE_CALLBACK,
GTK_TYPE_ARGS,
GTK_TYPE_POINTER,
/* it'd be great if the next two could be removed eventually */
GTK_TYPE_SIGNAL,
GTK_TYPE_C_CALLBACK,
GTK_TYPE_OBJECT
} GtkFundamentalType;
</verb></tscreen>
<tt/gtk_signal_new()/ devuelve un identificador entero único para la
señal, que almacenamos en el vector <tt/tictactoe_signals/, que
indexaremos utilizando una enumeración. (Convencionalmente, los
elementos de la enumeración son el nombre de la señal, en mayúsculas,
pero aquí tendríamos un conflicto con la macro <tt/TICTACTOE()/, por
lo que lo llamaremos <tt/TICTACTOE_SIGNAL/.
Después de crear nuestras señales, necesitamos llamar a GTK para
asociarlas con la clase Tictactoe. Hacemos esto llamando a
<tt/gtk_object_class_add_signals()/. Entonces haremos que el puntero
que apunta al manejador por defecto para la señal `tictactoe' sea NULL,
indicando que no hay ninguna acción por defecto.
<!-- ----------------------------------------------------------------- -->
<sect2> La función <tt/_init()/.
<p>
Cada clase <em/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 <em/widgets/ de composición, esta
función también crea los distintos <em/widgets/ componentes.
<tscreen><verb>
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint i,j;
table = gtk_table_new (3, 3, TRUE);
gtk_container_add (GTK_CONTAINER(ttt), table);
gtk_widget_show (table);
for (i=0;i<3; i++)
for (j=0;j<3; j++)
{
ttt->buttons[i][j] = gtk_toggle_button_new ();
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
i, i+1, j, j+1);
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
gtk_widget_show (ttt->buttons[i][j]);
}
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2> Y el resto...
<p>
Hay una función más que cada <em/widget/ (excepto los <em/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 <tt/NOMBREWIDGET_new()/. En algunos
<em/widgets/, que no es el caso del <em/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 <em/widget/
Tictactoe.
<tt/tictactoe_clear()/ es una función pública que reinicia todos los
botones en el <em/widget/ a la posición alta. Observe la utilización
de <tt/gtk_signal_handler_block_by_data()/ para hacer que no se
ejecute nuestro manejador de señal innecesariamente por cambios en los
botones.
<tt/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».
<tscreen><verb>
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;
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 1, 2 }, { 0, 1, 2 } };
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 2, 1, 0 } };
int success, found;
for (k=0; k<8; k++)
{
success = TRUE;
found = FALSE;
for (i=0;i<3;i++)
{
success = success &amp;&amp;
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
found = found ||
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
}
if (success &amp;&amp; found)
{
gtk_signal_emit (GTK_OBJECT (ttt),
tictactoe_signals[TICTACTOE_SIGNAL]);
break;
}
}
}
</verb></tscreen>
Y finalmente, un programa ejemplo que utiliza nuestro <em/widget/
Tictactoe:
<tscreen><verb>
#include <gtk/gtk.h>
#include "tictactoe.h"
/* Invocado cuando se completa una fila, columna o diagonal */
void
win (GtkWidget *widget, gpointer data)
{
g_print ("Yay!\n");
tictactoe_clear (TICTACTOE (widget));
}
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *ttt;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Aspect Frame");
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
/* Create a new Tictactoe widget */
ttt = tictactoe_new ();
gtk_container_add (GTK_CONTAINER (ventana), ttt);
gtk_widget_show (ttt);
/* And attach to its "tictactoe" signal */
gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
GTK_SIGNAL_FUNC (win), NULL);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Creando un <em/widget/ desde cero.
<!-- ----------------------------------------------------------------- -->
<sect2> Introducción
<p>
En esta sección, averiguaremos como se dibujan los <em/widgets/ a sí
mismos en pantalla y como interactuan con los eventos. Como ejemplo,
crearemos un marcador analógico con un puntero que el usuario
podrá arrastrar para hacer que el marcador tenga un valor dado.
<!-- ----------------------------------------------------------------- -->
<sect2> Mostrando un <em/widget/ en la pantalla
<p>
Hay varios pasos que están involucrados en el dibujado en pantalla.
Después de que el <em/widget/ se cree con una llamada a
<tt/NOMBREWIDGET_new()/, se necesitarán muchas más funciones:
<itemize>
<item> <tt/NOMBREWIDGET_realize()/ es la responsable de crear una
ventana X para el <em/widget/, si tiene alguna.
<item> <tt/NOMBREWIDGET_map()/ se invoca después de las llamadas del
usuario
<tt/gtk_widget_show()/. Es la responsable de asegurarse de que el
<em/widget/ está dibujado (<em/mapeado/) en la pantalla. Para una
clase contenedor, también deberá ocuparse de llamar a las funciones
<tt/map()/ de cada <em/widget/ hijo.
<item> <tt/NOMBREWIDGET_draw()/ se invoca cuando se llama a
<tt/gtk_widget_draw()/ desde el <em/widget/ de uno de sus
antepasados. Hace las llamadas necesarias a las funciones de dibujo
para dibujar el <em/widget/ en la pantalla. Para los <em/widgets/
contenedores, esta función debe llamar a las <tt/gtk_widget_draw/ de
sus <em/widgets/ hijos.
<item> <tt/NOMBREWIDGET_expose()/ es un manejador de los eventos
<tt/expose/ del <em/widget/. Hace las llamadas necesarias a las
funciones de dibujo para dibujar la parte expuesta en la
pantalla. Para los <em/widgets/ contenedores, esta función debe
generar los eventos <tt/expose/ de sus <em/widgets/ hijos que no
tengan su propia ventana. (Si tuviesen su propia ventana, X generaría
los eventos <tt/expose/ necesarios)
</itemize>
Las últimas dos funciones son bastante similares - ambas son
responsables de dibujar el <em/widget/ en pantalla. De hecho en muchos
<em/widgets/ realmente no importa la diferencia que hay entre ambas
funciones. La función <em/draw()/ que hay por defecto en
la clase <em/widget/ simplemente genera un evento <tt/expose/
artificial de la zona a redibujar. Sin embargo, algunos tipos de
<em/widgets/ puede ahorrarse trabajo distinguiendo entre las dos
funciones. Por ejemplo, si un <em/widget/ tiene varias ventanas X,
entonces, como los eventos <tt/expose/ identifican a la ventana
expuesta, podrán redibujar sólo la ventana afectada, lo que no es
posible con llamadas a <tt/draw()/.
Los <em/widgets/ contenedores, aunque no utilicen la diferecia
existente entre las dos funciones por sí mismos, no pueden utilizar
simplemente las funciones <tt/draw()/ que hay por defecto ya que sus
<em/widgets/ hijos puede que tengan que utilizar la diferencia. Sin
embargo, sería un derroche duplicar el código de dibujado entre las
dos funciones. Lo normal es que cada <em/widget/ tenga una función
llamada <tt/NOMBREWIDGET_paint()/ que haga el trabajo de dibujar el
<em/widget/, ésta función será a la que se llame por las funciones
<tt/draw()/ y <tt/expose()/.
En nuestro ejemplo, como el <em/widget/ Dial no es un <em/widget/
contenedor, y sólo tiene una ventana, podemos tomar el camino más
corto, utilizar la función <tt/draw()/ por defecto y sólo
implementar la función <tt/expose()/.
<!-- ----------------------------------------------------------------- -->
<sect2> Los orígenes del <em/widget/ Dial
<p>
Así como todos los animales terrestes son variaciones del primer
anfíbio que salió del barro, los <em/widgets/ Gtk tienden a nacer
como variaciones de algún otro <em/widget/ escrito previamente. Por
tanto, aunque esta sección se titule `Creando un <em/widget/ de la
nada', el <em/widget/ Dial empieza realmente con el código fuente
del <em/widget/ Range. He tomado éste como punto de arranque porque
sería bonito que nuestro dial tuviese la misma interfaz que los
<em/widgets/ Scale, que son sólo una especialización del <em/widget/
Range. Por tanto, aunque el código fuente se presente más adelante en
su forma final, no implica que fuese escrito de esta forma <em>deus ex
machina</em>. Si todavía no está familiarizado, desde el punto de
vista del escritor de aplicaciones, con la forma de funcionar de los
<em/widgets/ Scale, sería una buena idea echarles un vistazo antes de
continuar.
<!-- ----------------------------------------------------------------- -->
<sect2> Los comienzos
<p>
Nuestro <em/widget/ tiene un aspecto algo parecido al del <em/widget/
Tictactoe. Primero, tenemos un fichero de cabecera:
<tscreen><verb>
/* 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 */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* política de actualización
* (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* Botón actualmente presionado o 0 si no hay ninguno */
guint8 boton;
/* Dimensión de los componendes del dial */
gint radius;
gint pointer_width;
/* ID del temporizador de actualización, o 0 si no hay ninguno */
guint32 timer;
/* ángulo actual */
gfloat angle;
/* Viejos valores almacenados del adjustment, para que así no
* tengamos que saber cuando cambia algo */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* El objeto adjustment que almacena los datos para este dial */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
guint gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */
</verb></tscreen>
Como vamos a ir con este <em/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 <em/widget/ y lo inicializan:
<tscreen><verb>
#include <math.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
#define SCROLL_DELAY_LENGTH 300
#define DIAL_DEFAULT_SIZE 100
/* Declaraciones de funciones */
[ omitido para salvar espacio ]
/* datos locales */
static GtkWidgetClass *parent_class = NULL;
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,
};
dial_type = gtk_type_unique (gtk_widget_get_type (), &amp;dial_info);
}
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = (GtkObjectClass*) class;
widget_class = (GtkWidgetClass*) class;
parent_class = gtk_type_class (gtk_widget_get_type ());
object_class->destroy = gtk_dial_destroy;
widget_class->realize = gtk_dial_realize;
widget_class->expose_event = gtk_dial_expose;
widget_class->size_request = gtk_dial_size_request;
widget_class->size_allocate = gtk_dial_size_allocate;
widget_class->button_press_event = gtk_dial_button_press;
widget_class->button_release_event = gtk_dial_button_release;
widget_class->motion_notify_event = gtk_dial_motion_notify;
}
static 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;
dial = gtk_type_new (gtk_dial_get_type ());
if (!adjustment)
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
gtk_dial_set_adjustment (dial, adjustment);
return GTK_WIDGET (dial);
}
static void
gtk_dial_destroy (GtkObject *object)
{
GtkDial *dial;
g_return_if_fail (object != NULL);
g_return_if_fail (GTK_IS_DIAL (object));
dial = GTK_DIAL (object);
if (dial->adjustment)
gtk_object_unref (GTK_OBJECT (dial->adjustment));
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
</verb></tscreen>
Observe que ésta función <tt/init()/ hace menos cosas de las que hacía
la función <tt/init()/ que utilizamos con el <em/widget/ Tictactoe, ya
que éste no es un <em/widget/ compuesto, y la función <tt/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.
<p>
Aquí tenemos unas cuantas funciones para manipular las opciones del
<em/widget/:
<tscreen><verb>
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));
gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
(GtkSignalFunc) gtk_dial_adjustment_changed,
(gpointer) dial);
gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
(GtkSignalFunc) gtk_dial_adjustment_value_changed,
(gpointer) dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
gtk_dial_update (dial);
}
</verb></tscreen>
<sect2> <tt/gtk_dial_realize()/
<p>
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 <tt/gdk_window_new()/ que especifica que campos de la
estructura <tt/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
<tt/gtk_widget_get_events()/ para recuperar la máscara de eventos que
el usuario ha especificado para su <em/widget/ (con
<tt/gtk_widget_set_events()/), y añadir nosotros mismos los eventos
en los que estemos interesados.
<p>
Después de crear la ventana, decidiremos su estilo y su fondo, y
pondremos un puntero al <em/widget/ en el campo de datos del usuario
de la <tt/GdkWindow/. Este último paso le permite a GTK despachar los
eventos que hayan para esta ventana hacia el <em/widget/ correcto.
<tscreen><verb>
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
dial = GTK_DIAL (widget);
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
widget->window = gdk_window_new (widget->parent->window, &amp;attributes, attributes_mask);
widget->style = gtk_style_attach (widget->style, widget->window);
gdk_window_set_user_data (widget->window, widget);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
</verb></tscreen>
<sect2> Negociación del tamaño
<p>
Antes de que se muestre por primera vez la ventana conteniendo un
<em/widget/, y cuando quiera que la capa de la ventana cambie, GTK le
preguntara a cada <em/widget/ hijo por su tamaño deseado. Esta
petición se controla mediante la función
<tt/gtk_dial_size_request()/. Como nuestro <em/widget/ no es un
<em/widget/ contenedor, y no tiene ninguna limitación en su tamaño,
nos contentaremos con devolver un valor por defecto.
<tscreen><verb>
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
</verb></tscreen>
<p>
Después de que todos los <em/widgets/ hayan pedido su tamaño ideal,
se calculará la ventana y cada <em/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 <em/widget/ sea menor
que el que pidió. La notificación del tamaño se maneja mediante la
función <tt/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
<em/widgets/ de la ventana X en la nueva posición y con el nuevo
tamaño.
<tscreen><verb>
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
g_return_if_fail (allocation != NULL);
widget->allocation = *allocation;
if (GTK_WIDGET_REALIZED (widget))
{
dial = GTK_DIAL (widget);
gdk_window_move_resize (widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height);
dial->radius = MAX(allocation->width,allocation->height) * 0.45;
dial->pointer_width = dial->radius / 5;
}
}
</verb></tscreen>.
<!-- ----------------------------------------------------------------- -->
<sect2> <tt/gtk_dial_expose()/
<p>
Como se mencionó arriba, todo el dibujado de este <em/widget/ se hace
en el manejador de los eventos <tt/expose/. No hay mucho destacable
aquí, excepto la utilización de la función <tt/gtk_draw_polygon/ para
dibujar el puntero con un degradado tridimensional de acuerdo con los
colores almacenados en el estilo del <em/widget/.
<tscreen><verb>
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;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
if (event->count > 0)
return FALSE;
dial = GTK_DIAL (widget);
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/2;
/* Dibujar las rayitas */
for (i=0; i<25; i++)
{
theta = (i*M_PI/18. - M_PI/6.);
s = sin(theta);
c = cos(theta);
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* Dibujar el puntero */
s = sin(dial->angle);
c = cos(dial->angle);
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
gtk_draw_polygon (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
points, 3,
TRUE);
return FALSE;
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2> Manejo de eventos
<p>
El resto del código del <em/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 <em/widget/ con el ratón y arrastrar para mover el puntero, o el
valor del objeto Adjustement puede cambiar debido a alguna
circunstancia externa.
<p>
Cuando el usuario pulsa en el <em/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 <tt/button/ de la estructura del <em/widget/, y grabamos
todos los eventos del ratón con una llamada a <tt/gtk_grab_add()/. El
movimiento del ratón hará que se recalcule el valor del control
(mediante la función <tt/gtk_dial_update_mouse/). Dependiendo de la
política que sigamos, o bien se generarán instantáneamente los eventos
<tt/value_changed/ (<tt/GTK_UPDATE_CONTINUOUS/), o bien después de una
espera del temporizador establecido mediante <tt/gtk_timeout_add()/
(<tt/GTK_UPDATE_DELAYED/), o bien sólo cuando se levante el botón
(<tt/GTK_UPDATE_DISCONTINUOUS/).
<tscreen><verb>
static gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
/* Determinar si la pulsación del botón fue dentro de la región del
puntero - esto lo hacemos calculando la distancia x e y del punto
donde se pulsó el botón ratón de la línea que se ha pasado mediante el
puntero */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs(s*dx - c*dy);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
(dial->old_value != dial->adjustment->value))
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &amp;x, &amp;y, &amp;mods);
switch (dial->button)
{
case 1:
mask = GDK_BUTTON1_MASK;
break;
case 2:
mask = GDK_BUTTON2_MASK;
break;
case 3:
mask = GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
gtk_dial_update_mouse (dial, x,y);
}
return FALSE;
}
static 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;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
xc = GTK_WIDGET(dial)->allocation.width / 2;
yc = GTK_WIDGET(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -M_PI/2.)
dial->angle += 2*M_PI;
if (dial->angle < -M_PI/6)
dial->angle = -M_PI/6;
if (dial->angle > 7.*M_PI/6.)
dial->angle = 7.*M_PI/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == GTK_UPDATE_CONTINUOUS)
{
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
else
{
gtk_widget_draw (GTK_WIDGET(dial), NULL);
if (dial->policy == GTK_UPDATE_DELAYED)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
(GtkFunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
</verb></tscreen>
<p>
Cambios en el Adjustment por motivos externos significa que se le
comunicarán a nuestro <em/widget/ mediante las señales <tt/changed/ y
<tt/value_changed/. Los manejadores de estas funciones llaman a
<tt/gtk_dial_update()/ para comprobar los argumentos, calcular el
nuevo ángulo del puntero, y redibujar el <em/widget/ (llamando a
<tt/gtk_widget_draw()/).
<tscreen><verb>
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_draw (GTK_WIDGET(dial), NULL);
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2> Posibles mejoras
<p>
El <em/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 <em/widget/:
<itemize>
<item> Si prueba el <em/widget/, verá que el puntero cambia a
pantallazos cuando se le arrastra. Esto es debido a que todo el
<em/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 <em/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 <em/widget/ ProgressBar funciona de esta forma.)
<item> El usuario debería ser capaz de utilizar las flechas de arriba
y abajo para aumentar y decrementar el valor.
<item> Sería bonito si el <em/widget/ tuviese botones para
incrementar y decrementar el valor a saltos más o menos
grandes. Es posible utilizar <em/widgets/ botón, 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
<em/widget/ GtkRange.
<item> El <em/widget/ Dial puede utilizarse en un <em/widget/
contenedor con un simple <em/widget/ hijo colocado en la parte
inferior entre los botones antes mencionados. El usuario puede añadir
(según prefiera) una etiqueta o un <em/widget/ entry para mostrar el
valor actual del marcador.
</itemize>
<!-- ----------------------------------------------------------------- -->
<sect1> Aprendiendo más
<p>
Sólo se han descrito una pequeña parte de los muchos detalles
involucrados en la creación de <em/widgets/, la mejor fuente de
ejemplos es el código mismo de GTK. Hágase algunas preguntas acerca
del <em/widget/ que desea crear: ¿es un <em/widget/ contenedor?
¿Debe tener su propia ventana? ¿Es una modificación de un
<em/widget/ existente? En ese momento busque un <em/widget/ similar, y
comience a hacer los cambios.
¡Buena suerte!
<!-- ***************************************************************** -->
<sect>Scribble, un sencillo programa de dibujo de ejemplo
<!-- ***************************************************************** -->
<!-- ----------------------------------------------------------------- -->
<sect1> Objetivos
<p>
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
<em/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.
<!-- ----------------------------------------------------------------- -->
<sect1> Manejo de eventos
<p>
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 <em/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
<tt/GdkEventMotion/ que es (en parte) así:
<tscreen><verb>
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *ventana;
guint32 time;
gdouble x;
gdouble y;
...
guint state;
...
};
</verb></tscreen>
<tt/type/ adquirirá su valor adecuado dependiendo del tipo de evento,
en nuestro caso <tt/GDK_MOTION_NOTIFY/, <tt/ventana/ es la ventana en
la que ocurre el evento. <tt/x/ e <tt/y/ dan las coordenadas del
evento, y <tt/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:
<tscreen><verb>
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
</verb></tscreen>
<p>
Como con las otras señales, para especificar que es lo que pasa cuando
ocurre un evento, llamaremos a <tt>gtk_signal_connect()</tt>. Pero
también necesitamos decirle a GTK sobre que eventos queremos ser
informados. Para ello, llamaremos a la función:
<tscreen><verb>
void gtk_widget_set_events (GtkWidget *widget,
gint events);
</verb></tscreen>
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:
<tscreen><verb>
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
</verb></tscreen>
Hay unos cuantas sutilezas que debemos respetar cuando llamamos a
<tt/gtk_widget_set_events()/. Primero, debemos llamar a esta función
antes de que se cree la ventana X para el <em/widget/ GTK. En
términos prácticos, significa que debemos llamarla inmediatamente
después de crear el <em/widget/. Segundo, el <em/widget/ debe tener
una ventana X asociado. Por motivos de eficiencia, hay muchos
<em/widgets/ que no tienen su propia ventana, sino que dibujan en la
de su padre. Estos <em/widgets/ son:
<tscreen><verb>
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkAspectFrame
GtkFrame
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
</verb></tscreen>
Para capturar eventos para estos <em/widgets/, necesita utilizar un
<em/widget/ EventBox. Vea la sección <ref
id="sec_The_EventBox_Widget" name="El widget EventBox"> para más
detalles.
<p>
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 <tt/GDK_POINTER_MOTION_MASK/ y
<tt/GDK_BUTTON_PRESS_MASK/. También queremos saber cuando necesitamos
redibujar nuestra ventana, por lo que especificaremos el evento
<tt/GDK_EXPOSURE_MASK/. Aunque queremos estar informados mediante un
evento <tt/Configure/ cuando cambie el tamaño de nuestra ventana, no
tenemos que especificar la correspondiente <tt/GDK_STRUCTURE_MASK/,
porque ya está activada por defecto para todas las ventanas.
<p>
Tenemos un problema con lo que acabamos de hacer, y tiene que ver con
la utilización de <tt/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
<tt/GDK_POINTER_MOTION_HINT_MASK/.
<p>
Cuando especificamos <tt/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:
<tscreen><verb>
GdkWindow* gdk_window_get_pointer (GdkWindow *ventana,
gint *x,
gint *y,
GdkModifierType *mask);
</verb></tscreen>
(Hay otra función, <tt>gtk_widget_get_pointer()</tt> 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.)
<p>
El código para establecer los eventos para nuestra ventana es el
siguiente:
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
(GtkSignalFunc) expose_event, NULL);
gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
(GtkSignalFunc) configure_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
(GtkSignalFunc) motion_notify_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
(GtkSignalFunc) button_press_event, NULL);
gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK);
</verb></tscreen>
Vamos a dejar los manejadores de los eventos <tt/expose_event/ y
<tt/configure_event/ para después. Los manejadores de
<tt/motion_notify_event/ y de <tt/button_press_event/ son bastante
simples:
<tscreen><verb>
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 &amp;&amp; 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, &amp;x, &amp;y, &amp;state);
else
{
x = event->x;
y = event->y;
state = event->state;
}
if (state &amp; GDK_BUTTON1_MASK &amp;&amp; pixmap != NULL)
draw_brush (widget, x, y);
return TRUE;
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> El <em/widget/ DrawingArea, y dibujando
<p>
Vamos a pasar al proceso de dibujar en la pantalla. El <em/widget/ que
utilizaremos será el DrawingArea. Un <em/widget/ DrawingArea es
esencialmente una ventana X y nada más. Es un lienzo en blanco en
el que podemos dibujar lo que queramos. Crearemos un área de dibujo
utilizando la llamada:
<tscreen><verb>
GtkWidget* gtk_drawing_area_new (void);
</verb></tscreen>
Se puede especificar un tamaño por defecto para el <em/widget/
llamando a:
<tscreen><verb>
void gtk_drawing_area_size (GtkDrawingArea *darea,
gint width,
gint height);
</verb></tscreen>
Se puede cambiar el tamaño por defecto, como para todos los
<em/widgets/, llamando a <tt/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.
<p>
Debemos hacer notar que cuando creamos un <em/widget/ DrawingArea,
seremos <em/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.
<p>
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 <em>pixmap</em> 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.
<p>
Para crear un <em/pixmap/ intermedio, llamaremos a la función:
<tscreen><verb>
GdkPixmap* gdk_pixmap_new (GdkWindow *ventana,
gint width,
gint height,
gint depth);
</verb></tscreen>
El parámetro <tt/widget/ especifica una ventana GDK de las que este
<em/pixmap/ tomará algunas propiedades. <tt/width/ y <tt/height/
especifican el tamaño del <em/pixmap/. <tt/depth/ especifica la
<em/profundidad del color/, que es el número de bits por pixel de la
nueva ventana. Si la profundidad que se especifica es <tt/-1/, se
utilizará la misma profundidad de color que tenga la <tt/ventana/.
<p>
Creamos nuestro <em/pixmap/ en nuestro manejador del evento
<tt/configure_event/. Este evento se genera cada vez que cambia el
tamaño de la ventana, incluyendo cuando ésta se crea.
<tscreen><verb>
/* Backing pixmap for drawing area */
static GdkPixmap *pixmap = NULL;
/* Create a new backing pixmap of the appropriate size */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
if (pixmap)
gdk_pixmap_unref(pixmap);
pixmap = gdk_pixmap_new(widget->window,
widget->allocation.width,
widget->allocation.height,
-1);
gdk_draw_rectangle (pixmap,
widget->style->white_gc,
TRUE,
0, 0,
widget->allocation.width,
widget->allocation.height);
return TRUE;
}
</verb></tscreen>
La llamada a <tt/gdk_draw_rectangle()/ rellena todo el <em/pixmap/ de
blanco. Hablaremos más de todo esto en un momento.
<p>
Nuestro manejador del evento de exposición simplemente copia la
porción relevante del <em/pixmap/ en la pantalla (determinaremos la
zona a redibujar utilizando el campo <tt/event->area/ del evento de
exposición):
<tscreen><verb>
/* Redraw the screen from the backing pixmap */
static gint
expose_event (GtkWidget *widget, GdkEventExpose *event)
{
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
pixmap,
event->area.x, event->area.y,
event->area.x, event->area.y,
event->area.width, event->area.height);
return FALSE;
}
</verb></tscreen>
Ahora ya sabemos como mantener la pantalla actualizada con el
contenido de nuestro <em/pixmap/, pero ¿cómo podemos dibujar algo
interesante en nuestro <em/pixmap/? Hay un gran número de llamadas en
la biblioteca GDK para dibujar en los <em/dibujables/. Un dibujable es
simplemente algo sobre lo que se puede dibujar. Puede ser una ventana,
un <em/pixmap/, un <em/bitmap/ (una imagen en blanco y negro), etc. Ya
hemos visto arriba dos de estas llamadas,
<tt>gdk_draw_rectangle()</tt> y <tt>gdk_draw_pixmap()</tt>. La lista
completa de funciones para dibujar es:
<tscreen><verb>
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 ()
</verb></tscreen>
Ver la documentación de estas funciones o el fichero de cabecera
<tt>&lt;gdk/gdk.h&gt;</tt> 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
<em/contexto gráfico/ (GC).
<p>
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 <em/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:
<tscreen><verb>
widget->style->white_gc
widget->style->black_gc
widget->style->fg_gc[GTK_STATE_NORMAL]
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
</verb></tscreen>
Los campos <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, y
<tt>light_gc</tt> se indexan con un parámetro del tipo
<tt/GtkStateType/ que puede tomar uno de los valores:
<tscreen><verb>
GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE
</verb></tscreen>
Por ejemplo, para el <tt/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.
<p>
Nuestra función <tt/draw_brush()/, que es la que dibuja en la
pantalla, será la siguiente:
<tscreen><verb>
/* 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, &amp;update_rect);
}
</verb></tscreen>
Después de que dibujemos el rectángulo representando la brocha en el
<em/pixmap/ llamaremos a la función:
<tscreen><verb>
void gtk_widget_draw (GtkWidget *widget,
GdkRectangle *area);
</verb></tscreen>
que le informa a X de que la zona dada por el parámetro <tt/area/
necesita actualizarse. X generará un evento de exposición
(combinando posiblemente distintas zonas pasadas mediante distintas
llamadas a <tt/gtk_widget_draw()/) que hará que nuestro manejador de
eventos de exposición copie las porciones relevantes en la pantalla.
<p>
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:
<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
name="http://www.gtk.org/~otaylor/gtk/tutorial/">
<!-- ----------------------------------------------------------------- -->
<sect1> Añadiendo la capacidad de utilizar XInput
<p>
Ahora es posible comprar dispositos de entrada bastante baratos, como
tabletas digitalizadoras, que permiten dibujar de forma artística
mucho más fácilmente de cómo lo haríamos con un ratón. La forma
más sencilla de utilizar estos dispositivos es simplemente
reemplazando a los ratones, pero así perdemos muchas de las ventajas
de este tipo de dispositivos, como por ejemplo:
<itemize>
<item> Sensibilidad a la presión
<item> Información sobre la inclinación
<item> Colocación subpixel
<item> Multiples entradas (por ejemplo, un lápiz con una punta y una
goma)
</itemize>
Para información sobre la extensión XInput, ver el <htmlurl
url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
name="XInput-HOWTO">.
<p>
Si examinamos la definición completa de, por ejemplo, la estructura
<tt/GdkEventMotion/, veremos que tiene campos para almacenar la
información de los dispositivos extendidos.
<tscreen><verb>
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *ventana;
guint32 time;
gdouble x;
gdouble y;
gdouble pressure;
gdouble xtilt;
gdouble ytilt;
guint state;
gint16 is_hint;
GdkInputSource source;
guint32 deviceid;
};
</verb></tscreen>
<tt/pressure/ da la presión como un número de coma flotante entre 0
y 1. <tt/xtilt/ e <tt/ytilt/ pueden tomar valores entre -1 y 1,
correspondiendo al grado de inclinación en cada dirección. <tt/source/
y <tt/deviceid/ especifican el dispositivo para el que ocurre el
evento de dos maneras diferentes. <tt/source/ da alguna información
simple sobre el tipo de dispositivo. Puede tomar los valores de la
enumeración siguiente:
<tscreen><verb>
GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR
</verb></tscreen>
<tt/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 <tt/gdk_input_list_devices()/ (ver abajo). El
valor especial <tt/GDK_CORE_POINTER/ se utiliza para el núcleo del
dispositivo apuntador. (Normalmente el ratón.)
<sect2> Activando la información del dispositivo extendido
<p>
Para informar a GTK de nuestro interés en la información sobre los
dispositivos extendidos, sólo tenemos que añadirle una línea a
nuestro programa:
<tscreen><verb>
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
</verb></tscreen>
Dando el valor <tt/GDK_EXTENSION_EVENTS_CURSOR/ decimos que estamos
interesados en los eventos de extensión, pero sólo si no tenemos que
dibujar nuestro propio cursor. Ver la sección <ref
id="sec_Further_Sophistications" name="Sofisticaciones adicionales">
más abajo para obtener más información sobre el dibujado del
cursor. También podríamos dar los valores
<tt/GDK_EXTENSION_EVENTS_ALL/ si queremos dibujar nuestro propio
cursor, o <tt/GDK_EXTENSION_EVENTS_NONE/ para volver al estado
inicial.
<p>
Todavía no hemos llegado al final de la historia. Por defecto, no hay
ningún dispositivo extra activado. Necesitamos un mecanismo que
permita a los usuarios activar y configurar sus dispositivos
extra. GTK proporciona el <em/widget/ InputDialog para automatizar el
proceso. El siguiente procedimiento utiliza el <em/widget/
InputDialog. Crea el cuadro de diálogo si no ha sido ya creado, y lo
pone en primer plano en caso contrario.
<tscreen><verb>
void
input_dialog_destroy (GtkWidget *w, gpointer data)
{
*((GtkWidget **)data) = NULL;
}
void
create_input_dialog ()
{
static GtkWidget *inputd = NULL;
if (!inputd)
{
inputd = gtk_input_dialog_new();
gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
(GtkSignalFunc)input_dialog_destroy, &amp;inputd);
gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
"clicked",
(GtkSignalFunc)gtk_widget_hide,
GTK_OBJECT(inputd));
gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
gtk_widget_show (inputd);
}
else
{
if (!GTK_WIDGET_MAPPED(inputd))
gtk_widget_show(inputd);
else
gdk_window_raise(inputd->window);
}
}
</verb></tscreen>
(Tome nota de la manera en que hemos manejado el cuadro de
diálogo. Conectando la señal <tt/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.)
<p>
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.
<sect2> Utilizando la información de los dispositivos extras
<p>
Una vez hemos activado el dispositivo, podemos utilizar la
información que hay respecto a los dispositivos extendidos en los
campos extras de las estructuras de los eventos. De hecho, es bueno
utilizar esa información ya que esos campos tienen unos valores por
defecto razonables aún cuando no se activen los eventos extendidos.
<p>
Un cambio que tenemos que hacer es llamar a
<tt/gdk_input_window_get_pointer()/ en vez de a
<tt/gdk_window_get_pointer/. Esto es necesario porque
<tt/gdk_window_get_pointer/ no devuelve la información de los
dispositivos extra.
<tscreen><verb>
void gdk_input_window_get_pointer (GdkWindow *ventana,
guint32 deviceid,
gdouble *x,
gdouble *y,
gdouble *pressure,
gdouble *xtilt,
gdouble *ytilt,
GdkModifierType *mask);
</verb></tscreen>
Cuando llamamos a esta función, necesitamos especificar tanto el ID
del dispositivo como la ventana. Normalmente, obtendremos el ID del
dispositivo del campo <tt/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, <tt/event->deviceid/
tendrá el valor <tt/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.
<tscreen><verb>
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
print_button_press (event->deviceid);
if (event->button == 1 &amp;&amp; pixmap != NULL)
draw_brush (widget, event->source, event->x, event->y, event->pressure);
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,
&amp;x, &amp;y, &amp;pressure, NULL, NULL, &amp;state);
else
{
x = event->x;
y = event->y;
pressure = event->pressure;
state = event->state;
}
if (state &amp; GDK_BUTTON1_MASK &amp;&amp; pixmap != NULL)
draw_brush (widget, event->source, x, y, pressure);
return TRUE;
}
</verb></tscreen>
También tenemos que hacer algo con la nueva información. Nuestra
nueva función <tt/draw_brush()/ dibuja con un color diferente
dependiendo de <tt/event->source/ y cambia el tamaño de la brocha
dependiendo de la presión.
<tscreen><verb>
/* 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, &amp;update_rect);
}
</verb></tscreen>
<sect2> Obteniendo más información de un dispositivo
<p>
Como ejemplo de como podemos obtener más información de un
dispositivo, nuestro programa imprimirá el nombre del dispositivo que
genera cada pulsación de botón. Para encontrar el nombre de un
dispositivo, llamaremos a la función:
<tscreen><verb>
GList *gdk_input_list_devices (void);
</verb></tscreen>
que devuelve una GList (una lista enlazada de la biblioteca glib)
de estructuras <tt/GdkDeviceInfo/. La estructura <tt/GdkDeviceInfo/ se
define como:
<tscreen><verb>
struct _GdkDeviceInfo
{
guint32 deviceid;
gchar *name;
GdkInputSource source;
GdkInputMode mode;
gint has_cursor;
gint num_axes;
GdkAxisUse *axes;
gint num_keys;
GdkDeviceKey *keys;
};
</verb></tscreen>
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 <tt/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
<tt/has_cursor/. Si <tt/has_cursor/ es falso, tendremos que dibujar
nuestro propio cursor. Pero como hemos especificado
<tt/GDK_EXTENSION_EVENTS_CURSOR/, no tendremos que preocuparnos por
esto.
<p>
Nuestra función <tt/print_button_press()/ simplemente recorre la
lista devuelta hasta que encuentra una coincidencia, y entonces
imprime el nombre del dispositivo.
<tscreen><verb>
static void
print_button_press (guint32 deviceid)
{
GList *tmp_list;
/* gdk_input_list_devices returns an internal list, so we shouldn't
free it afterwards */
tmp_list = gdk_input_list_devices();
while (tmp_list)
{
GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
if (info->deviceid == deviceid)
{
printf("Button press on device '%s'\n", info->name);
return;
}
tmp_list = tmp_list->next;
}
}
</verb></tscreen>
Con esto hemos completado los cambios para `XInputizar' nuestro
programa. Como ocurría con la primera versión, el código completo se
encuentra disponible en el mismo sitio donde obtuvo este tutorial, o
desde:
<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
name="http://www.gtk.org/~otaylor/gtk/tutorial/">
<sect2> Sofisticaciones adicionales <label id="sec_Further_Sophistications">
<p>
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
<tt/gdk_input_list_devices()/ y escribiendo la configuración en un
fichero.
<p>
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:
<tscreen><verb>
gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()
</verb></tscreen>
(La lista devuelta por <tt/gdk_input_list_devices()/ no debería
modificarse directamente.) Podemos encontrar un ejemplo de como debe
utilizarse en el programa de dibujo <tt/gsumi/. (Disponible en
<htmlurl url="http://www.msc.cornell.edu/~otaylor/gsumi/"
name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Estaría bien
tener un procedimiento estándar para poder hacer todo esto en
cualquier aplicaciones. Probablemente se llegue a esto en una capa
superior a GTK, quizás en la biblioteca GNOME.
<p>
El programa tiene otra carencia importante que ya hemos mencionado más
arriba, y es la falta del cursor. Ninguna plataforma distinta de
XFree86 permite utilizar simultaneamente un dispositivo como puntero
núcleo y como dispositivo directamente utilizable por una
aplicación. Ver el <url
url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
name="XInput-HOWTO"> para más información sobre esto. Con esto
queremos decir que si quiere tener la máxima audiencia necesita
dibujar su propio cursor.
<p>
Una aplicación que dibuja su propio cursor necesita hacer dos cosas:
determinar si el dispositivo actual necesita que se dibuje un cursor o
no, y determinar si el dispositivo está «próximo». (Si el
dispositivo es una tableta digitalizadora, queda muy bonito que el
cursor desaparezca cuando el lápiz se separa de la tableta. Cuando el
lápiz está tocando la tableta, se dice que el dispositivo está
«próximo»). Lo primero se hace buscando la lista de dispositivos,
tal y como hicimos para encontrar el nombre del dispositivo. Lo
segundo se consigue seleccionando los eventos
<em/proximity_out/. Podemos encontrar un ejemplo de como dibujar
nuestro propio cursor en el programa `testinput' que viene con la
distribución de GTK.
<!-- ***************************************************************** -->
<sect>Trucos para escribir aplicaciones GTK
<!-- ***************************************************************** -->
<p>
Esta sección es sólo un compendio de sabiduria, de guías generales
de estilo y de consejos para crear buenas aplicaciones GTK. Y es
totalmente inútil por ahora ya que esta frase es sólo un tópico :)
¡Utilice GNU autoconf y automake! Son sus amigos :) Pretendo poner
aquí una rápida introducción a ambos.
<!-- ***************************************************************** -->
<sect>Contribuyendo <label id="sec_Contributing">
<!-- ***************************************************************** -->
<p>
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.
<p>
Si decide contribuir, por favor mande un correo-e con su texto a Tony
Gale, <tt><htmlurl url="mailto:gale@gtk.org"
name="gale@gtk.org"></tt>. 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...
<p>
Gracias.
<!-- ***************************************************************** -->
<sect>Créditos
<!-- ***************************************************************** -->
<p>
Quiero agradecer a las siguientes personas por sus contribuciones a
este texto.
<itemize>
<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
name="chamele0n@geocities.com"></tt> por el tutorial sobre los menús.
<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
name="raph@acm.org"></tt> por el «hola mundo» a la GTK, el
empaquetado de <em/widgets/, y su sabiduría general. Ha donado
generosamente un hogar para este tutorial.
<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
name="petm@xcf.berkeley.edu"></tt> por el más simple de los programas
GTK... y por la posibilidad de hacerlo :)
<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
name="werner.koch@guug.de"></tt> por convertir el texto original a
SGML, y por la jerarquia de clases de <em/widgets/.
<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
name="crichton@expert.cc.purdue.edu"></tt> por el código del menú
factory, y el tutorial sobre el empaquetamiento de las tablas.
<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
name="owt1@cornell.edu"></tt> por la sección sobre el <em/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 <em/widget/ GTK, y la aplicación de
ejemplo. ¡Muchas gracias por toda tu ayuda, Owen!
<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
name="mvboom42@calvin.edu"></tt> por su fantástico trabajo sobre los
<em/widgets/ Notebook, Progress Bar, Dialog, y selección de ficheros.
¡Muchas gracias Mark!
Has sido de una gran ayuda.
<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
name="timj@psynet.net"></tt> por su gran trabajo en el <em/widget/ List.
Gracias Tim :)
<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
name="rajat@ix.netcom.com"</tt> por el excelente trabajo con el
tutorial Pixmap.
<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
name="johnsonm@redhat.com"></tt> por la información y el código de
los menús ("popup").
<item>David Huggins-Daines <tt><htmlurl url="mailto:bn711@freenet.carleton.ca"
name="bn711@freenet.carleton.ca"></tt> por las secciones sobre los
<em/widgets/ Range y Tree.
<item>Stefan Mars <tt><htmlurl url="mailto:mars@lysator.liu.se"
name="mars@lysator.liu.se"></tt> por la sección GtkCList
</itemize>
<p>
Y a todos los que han comentado y ayudado a refinar este documento.
<p>
Gracias.
<!-- ***************************************************************** -->
<sect> Copyright del Tutorial y notas sobre los permisos
<!-- ***************************************************************** -->
<p>
Esta traducción está bajo la misma licencia bajo la que está
el documento original. A continuación se presenta la traducción
de la licencia y la licencia en versión original. En caso de haber
alguna discrepancia entre la traducción y la licencia original, se
aplicará esta última.
El Tutorial GTK tiene Copyright (C) 1997 Ian Main.
Copyright (C) 1998 Tony Gale.
<p>
Se da permiso para hacer y distribuir copias idénticas de este manual
siempre que se incluya el copyright en todas las copias.
<p>
Se da permiso para copiar y distribuir versiones modificadas de este
documento bajo las mismas condiciones que para las copias idénticas,
siempre que el copyright se incluya exactamente tal y como se
encuentra en el original, y que el trabajo completo derivado de este
documento se distribuya bajo los términos de un permiso idéntico a
éste.
<P>
Se da permiso para copiar y distribuir traducciones de este documento
en otro lenguaje, bajo las condiciones arriba mencionadas para las
versiones modificadas.
<P>
Si se propone incluir este documento en un trabajo que vaya a ser
impreso, por favor contacte con el encargado del mantenimiento, y
haremos un esfuerzo para asegurarnos de que dispone de la información
lo más actualizada posible.
<P>
No hay ninguna garantia de que este documento se mantenga activo lo
suficiente como para conseguir cumplir con su propósito. Se
proporciona como un documento libre. Como tal, los autores y
encargados del mantenimiento de la información que se da en el
documento no pueden dar ninguna garantia de que la misma esté al día.
<P>
-----------------------------
<p>
The GTK Tutorial is Copyright (C) 1997 Ian Main.
Copyright (C) 1998 Tony Gale.
<p>
Permission is granted to make and distribute verbatim copies of this
manual provided the copyright notice and this permission notice are
preserved on all copies.
<P>Permission is granted to copy and distribute modified versions of
this document under the conditions for verbatim copying, provided that
this copyright notice is included exactly as in the original,
and that the entire resulting derived work is distributed under
the terms of a permission notice identical to this one.
<P>Permission is granted to copy and distribute translations of this
document into another language, under the above conditions for modified
versions.
<P>If you are intending to incorporate this document into a published
work, please contact the maintainer, and we will make an effort
to ensure that you have the most up to date information available.
<P>There is no guarantee that this document lives up to its intended
purpose. This is simply provided as a free resource. As such,
the authors and maintainers of the information provided within can
not make any guarantee that the information is even accurate.
<sect1>Acerca de la traducción
<p>
Esta traduccion tiene copyright (C) 1999 de Joaquín Cuenca Abela
<tt><htmlurl url="mailto:e98cuenc@criens.u-psud.fr"
name="&lt;e98cuenc@criens.u-psud.fr&gt;"></tt>
y de Eduardo Anglada Varela
<tt><htmlurl url="mailto:eduardo.anglada@adi.uam.es"
name="&lt;eduardo.anglada@adi.uam.es&gt;"></tt>.
Si tiene cualquier
duda, sugerencia o corrección no dude en consultarnos.
Gracias a Manuel de Vega Barreiro <tt><htmlurl
url="mailto:barreiro@arrakis.es"
name="&lt;barreiro@arrakis.es&gt;"></tt> por haber hospedado las
versiones beta y la versión actual de este tutorial en su página
web Linux Landia <tt><url
url="http://www.croftj.net/~barreiro/spain/gnome/"
name="www.croftj.net/~barreiro/spain/gnome/"></tt>.
</sect1>
<!-- ***************************************************************** -->
<appendix>
<!-- ***************************************************************** -->
<!-- ***************************************************************** -->
<sect> Señales GTK <label id="sec_GTK_Signals">
<!-- ***************************************************************** -->
<p>
GTK+, al ser un conjunto de <em/widgets/ orientado al objeto, tiene
una jerarquía de herencias. Este mecanismo de herencia se aplica a las
señales. Por eso, debe utilizar el árbol de jerarquías de los
<em/widgets/ cuando utilice las señales que aparecen en esta sección.
<!-- ----------------------------------------------------------------- -->
<sect1>GtkObject
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkObject::destroy (GtkObject *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkWidget
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkWidget::show (GtkWidget *,
gpointer);
void GtkWidget::hide (GtkWidget *,
gpointer);
void GtkWidget::map (GtkWidget *,
gpointer);
void GtkWidget::unmap (GtkWidget *,
gpointer);
void GtkWidget::realize (GtkWidget *,
gpointer);
void GtkWidget::unrealize (GtkWidget *,
gpointer);
void GtkWidget::draw (GtkWidget *,
ggpointer,
gpointer);
void GtkWidget::draw-focus (GtkWidget *,
gpointer);
void GtkWidget::draw-default (GtkWidget *,
gpointer);
void GtkWidget::size-request (GtkWidget *,
ggpointer,
gpointer);
void GtkWidget::size-allocate (GtkWidget *,
ggpointer,
gpointer);
void GtkWidget::state-changed (GtkWidget *,
GtkStateType,
gpointer);
void GtkWidget::parent-set (GtkWidget *,
GtkObject *,
gpointer);
void GtkWidget::style-set (GtkWidget *,
GtkStyle *,
gpointer);
void GtkWidget::add-accelerator (GtkWidget *,
gguint,
GtkAccelGroup *,
gguint,
GdkModifierType,
GtkAccelFlags,
gpointer);
void GtkWidget::remove-accelerator (GtkWidget *,
GtkAccelGroup *,
gguint,
GdkModifierType,
gpointer);
gboolean GtkWidget::event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::button-press-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::button-release-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::motion-notify-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::delete-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::destroy-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::expose-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::key-press-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::key-release-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::enter-notify-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::leave-notify-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::configure-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::focus-in-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::focus-out-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::map-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::unmap-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::property-notify-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::selection-clear-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::selection-request-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::selection-notify-event (GtkWidget *,
GdkEvent *,
gpointer);
void GtkWidget::selection-get (GtkWidget *,
GtkSelectionData *,
gguint,
gpointer);
void GtkWidget::selection-received (GtkWidget *,
GtkSelectionData *,
gguint,
gpointer);
gboolean GtkWidget::proximity-in-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::proximity-out-event (GtkWidget *,
GdkEvent *,
gpointer);
void GtkWidget::drag-begin (GtkWidget *,
GdkDragContext *,
gpointer);
void GtkWidget::drag-end (GtkWidget *,
GdkDragContext *,
gpointer);
void GtkWidget::drag-data-delete (GtkWidget *,
GdkDragContext *,
gpointer);
void GtkWidget::drag-leave (GtkWidget *,
GdkDragContext *,
gguint,
gpointer);
gboolean GtkWidget::drag-motion (GtkWidget *,
GdkDragContext *,
ggint,
ggint,
gguint,
gpointer);
gboolean GtkWidget::drag-drop (GtkWidget *,
GdkDragContext *,
ggint,
ggint,
gguint,
gpointer);
void GtkWidget::drag-data-get (GtkWidget *,
GdkDragContext *,
GtkSelectionData *,
gguint,
gguint,
gpointer);
void GtkWidget::drag-data-received (GtkWidget *,
GdkDragContext *,
ggint,
ggint,
GtkSelectionData *,
gguint,
gguint,
gpointer);
gboolean GtkWidget::client-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::no-expose-event (GtkWidget *,
GdkEvent *,
gpointer);
gboolean GtkWidget::visibility-notify-event (GtkWidget *,
GdkEvent *,
gpointer);
void GtkWidget::debug-msg (GtkWidget *,
GtkString *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkData
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkData::disconnect (GtkData *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkContainer
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkContainer::add (GtkContainer *,
GtkWidget *,
gpointer);
void GtkContainer::remove (GtkContainer *,
GtkWidget *,
gpointer);
void GtkContainer::check-resize (GtkContainer *,
gpointer);
GtkDirectionType GtkContainer::focus (GtkContainer *,
GtkDirectionType,
gpointer);
void GtkContainer::set-focus-child (GtkContainer *,
GtkWidget *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkCalendar
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkCalendar::month-changed (GtkCalendar *,
gpointer);
void GtkCalendar::day-selected (GtkCalendar *,
gpointer);
void GtkCalendar::day-selected-double-click (GtkCalendar *,
gpointer);
void GtkCalendar::prev-month (GtkCalendar *,
gpointer);
void GtkCalendar::next-month (GtkCalendar *,
gpointer);
void GtkCalendar::prev-year (GtkCalendar *,
gpointer);
void GtkCalendar::next-year (GtkCalendar *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkEditable
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkEditable::changed (GtkEditable *,
gpointer);
void GtkEditable::insert-text (GtkEditable *,
GtkString *,
ggint,
ggpointer,
gpointer);
void GtkEditable::delete-text (GtkEditable *,
ggint,
ggint,
gpointer);
void GtkEditable::activate (GtkEditable *,
gpointer);
void GtkEditable::set-editable (GtkEditable *,
gboolean,
gpointer);
void GtkEditable::move-cursor (GtkEditable *,
ggint,
ggint,
gpointer);
void GtkEditable::move-word (GtkEditable *,
ggint,
gpointer);
void GtkEditable::move-page (GtkEditable *,
ggint,
ggint,
gpointer);
void GtkEditable::move-to-row (GtkEditable *,
ggint,
gpointer);
void GtkEditable::move-to-column (GtkEditable *,
ggint,
gpointer);
void GtkEditable::kill-char (GtkEditable *,
ggint,
gpointer);
void GtkEditable::kill-word (GtkEditable *,
ggint,
gpointer);
void GtkEditable::kill-line (GtkEditable *,
ggint,
gpointer);
void GtkEditable::cut-clipboard (GtkEditable *,
gpointer);
void GtkEditable::copy-clipboard (GtkEditable *,
gpointer);
void GtkEditable::paste-clipboard (GtkEditable *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkTipsQuery
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkTipsQuery::start-query (GtkTipsQuery *,
gpointer);
void GtkTipsQuery::stop-query (GtkTipsQuery *,
gpointer);
void GtkTipsQuery::widget-entered (GtkTipsQuery *,
GtkWidget *,
GtkString *,
GtkString *,
gpointer);
gboolean GtkTipsQuery::widget-selected (GtkTipsQuery *,
GtkWidget *,
GtkString *,
GtkString *,
GdkEvent *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkCList
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkCList::select-row (GtkCList *,
ggint,
ggint,
GdkEvent *,
gpointer);
void GtkCList::unselect-row (GtkCList *,
ggint,
ggint,
GdkEvent *,
gpointer);
void GtkCList::row-move (GtkCList *,
ggint,
ggint,
gpointer);
void GtkCList::click-column (GtkCList *,
ggint,
gpointer);
void GtkCList::resize-column (GtkCList *,
ggint,
ggint,
gpointer);
void GtkCList::toggle-focus-row (GtkCList *,
gpointer);
void GtkCList::select-all (GtkCList *,
gpointer);
void GtkCList::unselect-all (GtkCList *,
gpointer);
void GtkCList::undo-selection (GtkCList *,
gpointer);
void GtkCList::start-selection (GtkCList *,
gpointer);
void GtkCList::end-selection (GtkCList *,
gpointer);
void GtkCList::toggle-add-mode (GtkCList *,
gpointer);
void GtkCList::extend-selection (GtkCList *,
GtkScrollType,
ggfloat,
gboolean,
gpointer);
void GtkCList::scroll-vertical (GtkCList *,
GtkScrollType,
ggfloat,
gpointer);
void GtkCList::scroll-horizontal (GtkCList *,
GtkScrollType,
ggfloat,
gpointer);
void GtkCList::abort-column-resize (GtkCList *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkNotebook
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkNotebook::switch-page (GtkNotebook *,
ggpointer,
gguint,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkList
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkList::selection-changed (GtkList *,
gpointer);
void GtkList::select-child (GtkList *,
GtkWidget *,
gpointer);
void GtkList::unselect-child (GtkList *,
GtkWidget *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkMenuShell
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkMenuShell::deactivate (GtkMenuShell *,
gpointer);
void GtkMenuShell::selection-done (GtkMenuShell *,
gpointer);
void GtkMenuShell::move-current (GtkMenuShell *,
GtkMenuDirectionType,
gpointer);
void GtkMenuShell::activate-current (GtkMenuShell *,
gboolean,
gpointer);
void GtkMenuShell::cancel (GtkMenuShell *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkToolbar
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkToolbar::orientation-changed (GtkToolbar *,
ggint,
gpointer);
void GtkToolbar::style-changed (GtkToolbar *,
ggint,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkTree
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkTree::selection-changed (GtkTree *,
gpointer);
void GtkTree::select-child (GtkTree *,
GtkWidget *,
gpointer);
void GtkTree::unselect-child (GtkTree *,
GtkWidget *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkButton
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkButton::pressed (GtkButton *,
gpointer);
void GtkButton::released (GtkButton *,
gpointer);
void GtkButton::clicked (GtkButton *,
gpointer);
void GtkButton::enter (GtkButton *,
gpointer);
void GtkButton::leave (GtkButton *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkItem
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkItem::select (GtkItem *,
gpointer);
void GtkItem::deselect (GtkItem *,
gpointer);
void GtkItem::toggle (GtkItem *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkWindow
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkWindow::set-focus (GtkWindow *,
ggpointer,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkHandleBox
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkHandleBox::child-attached (GtkHandleBox *,
GtkWidget *,
gpointer);
void GtkHandleBox::child-detached (GtkHandleBox *,
GtkWidget *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkToggleButton
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkToggleButton::toggled (GtkToggleButton *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkMenuItem
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkMenuItem::activate (GtkMenuItem *,
gpointer);
void GtkMenuItem::activate-item (GtkMenuItem *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkListItem
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkListItem::toggle-focus-row (GtkListItem *,
gpointer);
void GtkListItem::select-all (GtkListItem *,
gpointer);
void GtkListItem::unselect-all (GtkListItem *,
gpointer);
void GtkListItem::undo-selection (GtkListItem *,
gpointer);
void GtkListItem::start-selection (GtkListItem *,
gpointer);
void GtkListItem::end-selection (GtkListItem *,
gpointer);
void GtkListItem::toggle-add-mode (GtkListItem *,
gpointer);
void GtkListItem::extend-selection (GtkListItem *,
GtkEnum,
ggfloat,
gboolean,
gpointer);
void GtkListItem::scroll-vertical (GtkListItem *,
GtkEnum,
ggfloat,
gpointer);
void GtkListItem::scroll-horizontal (GtkListItem *,
GtkEnum,
ggfloat,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkTreeItem
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkTreeItem::collapse (GtkTreeItem *,
gpointer);
void GtkTreeItem::expand (GtkTreeItem *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkCheckMenuItem
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkCheckMenuItem::toggled (GtkCheckMenuItem *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkInputDialog
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkInputDialog::enable-device (GtkInputDialog *,
ggint,
gpointer);
void GtkInputDialog::disable-device (GtkInputDialog *,
ggint,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkColorSelection
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkColorSelection::color-changed (GtkColorSelection *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkStatusBar
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkStatusbar::text-pushed (GtkStatusbar *,
gguint,
GtkString *,
gpointer);
void GtkStatusbar::text-popped (GtkStatusbar *,
gguint,
GtkString *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkCTree
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkCTree::tree-select-row (GtkCTree *,
GtkCTreeNode *,
ggint,
gpointer);
void GtkCTree::tree-unselect-row (GtkCTree *,
GtkCTreeNode *,
ggint,
gpointer);
void GtkCTree::tree-expand (GtkCTree *,
GtkCTreeNode *,
gpointer);
void GtkCTree::tree-collapse (GtkCTree *,
ggpointer,
gpointer);
void GtkCTree::tree-move (GtkCTree *,
GtkCTreeNode *,
GtkCTreeNode *,
GtkCTreeNode *,
gpointer);
void GtkCTree::change-focus-row-expansion (GtkCTree *,
GtkCTreeExpansionType,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkCurve
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkCurve::curve-type-changed (GtkCurve *,
gpointer);
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1>GtkAdjustment
<!-- ----------------------------------------------------------------- -->
<p>
<tscreen><verb>
void GtkAdjustment::changed (GtkAdjustment *,
gpointer);
void GtkAdjustment::value-changed (GtkAdjustment *,
gpointer);
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> Tipos de eventos GDK<label id="sec_GDK_Event_Types">
<!-- ***************************************************************** -->
<p>
Los siguientes tipos de datos se pasan en los manejadores de los
eventos por GTK+. Para cada tipo de dato que se muestra, se muestran
las señales que utilizan ese tipo de dato.
<itemize>
<item> GdkEvent
<itemize>
<item>drag_end_event
</itemize>
<item> GdkEventType
<item> GdkEventAny
<itemize>
<item>delete_event
<item>destroy_event
<item>map_event
<item>unmap_event
<item>no_expose_event
</itemize>
<item> GdkEventExpose
<itemize>
<item>expose_event
</itemize>
<item> GdkEventNoExpose
<item> GdkEventVisibility
<item> GdkEventMotion
<itemize>
<item>motion_notify_event
</itemize>
<item> GdkEventButton
<itemize>
<item>button_press_event
<item>button_release_event
</itemize>
<item> GdkEventKey
<itemize>
<item>key_press_event
<item>key_release_event
</itemize>
<item> GdkEventCrossing
<itemize>
<item>enter_notify_event
<item>leave_notify_event
</itemize>
<item> GdkEventFocus
<itemize>
<item>focus_in_event
<item>focus_out_event
</itemize>
<item> GdkEventConfigure
<itemize>
<item>configure_event
</itemize>
<item> GdkEventProperty
<itemize>
<item>property_notify_event
</itemize>
<item> GdkEventSelection
<itemize>
<item>selection_clear_event
<item>selection_request_event
<item>selection_notify_event
</itemize>
<item> GdkEventProximity
<itemize>
<item>proximity_in_event
<item>proximity_out_event
</itemize>
<item> GdkEventDragBegin
<itemize>
<item>drag_begin_event
</itemize>
<item> GdkEventDragRequest
<itemize>
<item>drag_request_event
</itemize>
<item> GdkEventDropEnter
<itemize>
<item>drop_enter_event
</itemize>
<item> GdkEventDropLeave
<itemize>
<item>drop_leave_event
</itemize>
<item> GdkEventDropDataAvailable
<itemize>
<item>drop_data_available_event
</itemize>
<item> GdkEventClient
<itemize>
<item>client_event
</itemize>
<item> GdkEventOther
<itemize>
<item>other_event
</itemize>
</itemize>
El tipo de dato <tt/GdkEventType/ es un tipo de dato especial que se
utiliza por todos los otros tipos de datos como un indicador del tipo
de dato que se le está pasando al manejador de señal. Como verá
más adelante, cada una de estructuras de los datos de los eventos
tienen un miembro de este tipo. Se define como la siguiente
enumeración:
<tscreen><verb>
typedef enum
{
GDK_NOTHING = -1,
GDK_DELETE = 0,
GDK_DESTROY = 1,
GDK_EXPOSE = 2,
GDK_MOTION_NOTIFY = 3,
GDK_BUTTON_PRESS = 4,
GDK_2BUTTON_PRESS = 5,
GDK_3BUTTON_PRESS = 6,
GDK_BUTTON_RELEASE = 7,
GDK_KEY_PRESS = 8,
GDK_KEY_RELEASE = 9,
GDK_ENTER_NOTIFY = 10,
GDK_LEAVE_NOTIFY = 11,
GDK_FOCUS_CHANGE = 12,
GDK_CONFIGURE = 13,
GDK_MAP = 14,
GDK_UNMAP = 15,
GDK_PROPERTY_NOTIFY = 16,
GDK_SELECTION_CLEAR = 17,
GDK_SELECTION_REQUEST = 18,
GDK_SELECTION_NOTIFY = 19,
GDK_PROXIMITY_IN = 20,
GDK_PROXIMITY_OUT = 21,
GDK_DRAG_BEGIN = 22,
GDK_DRAG_REQUEST = 23,
GDK_DROP_ENTER = 24,
GDK_DROP_LEAVE = 25,
GDK_DROP_DATA_AVAIL = 26,
GDK_CLIENT_EVENT = 27,
GDK_VISIBILITY_NOTIFY = 28,
GDK_NO_EXPOSE = 29,
GDK_OTHER_EVENT = 9999 /* Anacrónico, utilice en su lugar los
filtros */
} GdkEventType;
</verb></tscreen>
El otro tipo de evento que es diferente del resto es el mismo
<tt/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.
<!-- Just a big list for now, needs expanding upon - TRG -->
Por tanto, los tipos de los datos de los eventos se definen como
sigue:
<tscreen><verb>
struct _GdkEventAny
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
};
struct _GdkEventExpose
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
GdkRectangle area;
gint count; /* Si count no es, entonces 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;
};
/* Este tipo de evento se utiliza muy raramente. Solamente es
* importante para los programas que utilizan XInput y que dibujar su
* propio cursor */
struct _GdkEventProximity
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
guint32 time;
GdkInputSource source;
guint32 deviceid;
};
struct _GdkEventDragRequest
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
guint32 requestor;
union {
struct {
guint protocol_version:4;
guint sendreply:1;
guint willaccept:1;
guint delete_data:1; /* No borrar si se ha mandado un enlace,
sólo si se ha mandado el dato */
guint senddata:1;
guint reserved:22;
} flags;
glong allflags;
} u;
guint8 isdrop; /* Este evento gdk puede ser generado por un par de
eventos X - esto le permite a las aplicaciones
saber si ha ocurrido realmente el soltado (drop),
o si sólo hemos cambiado el valor de algunos datos
*/
GdkPoint drop_coords;
gchar *data_type;
guint32 timestamp;
};
struct _GdkEventDragBegin
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
union {
struct {
guint protocol_version:4;
guint reserved:28;
} flags;
glong allflags;
} u;
};
struct _GdkEventDropEnter
{
GdkEventType type;
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;
};
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> Código ejemplo
<!-- ***************************************************************** -->
<p>
A continuación tenemos el código ejemplo que se ha utilizado en el
texto anterior y que no se ha incluido al completo en otro lugar.
<!-- ----------------------------------------------------------------- -->
<sect1>Tictactoe
<!-- ----------------------------------------------------------------- -->
<sect2>tictactoe.h
<p>
<tscreen><verb>
/* principio del ejemplo tictactoe tictactoe.h */
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#include <gdk/gdk.h>
#include <gtk/gtkvbox.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
typedef struct _Tictactoe Tictactoe;
typedef struct _TictactoeClass TictactoeClass;
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *botones[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
void (* tictactoe) (Tictactoe *ttt);
};
guint tictactoe_get_type (void);
GtkWidget* tictactoe_new (void);
void tictactoe_clear (Tictactoe *ttt);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2>tictactoe.c
<p>
<tscreen><verb>
/* principio del ejemplo tictactoe tictactoe.c */
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gtk/gtksignal.h"
#include "gtk/gtktable.h"
#include "gtk/gtktogglebutton.h"
#include "tictactoe.h"
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
static void tictactoe_class_init (TictactoeClass *klass);
static void tictactoe_init (Tictactoe *ttt);
static void tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt);
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
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
};
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &amp;ttt_info);
}
return ttt_type;
}
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
class->tictactoe = NULL;
}
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint i,j;
table = gtk_table_new (3, 3, TRUE);
gtk_container_add (GTK_CONTAINER(ttt), table);
gtk_widget_show (table);
for (i=0;i<3; i++)
for (j=0;j<3; j++)
{
ttt->buttons[i][j] = gtk_toggle_button_new ();
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
i, i+1, j, j+1);
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
gtk_widget_show (ttt->buttons[i][j]);
}
}
GtkWidget*
tictactoe_new ()
{
return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
}
void
tictactoe_clear (Tictactoe *ttt)
{
int i,j;
for (i=0;i<3;i++)
for (j=0;j<3;j++)
{
gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
FALSE);
gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
}
}
static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 1, 2 }, { 0, 1, 2 } };
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 2, 1, 0 } };
int success, found;
for (k=0; k<8; k++)
{
success = TRUE;
found = FALSE;
for (i=0;i<3;i++)
{
success = success &amp;&amp;
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
found = found ||
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
}
if (success &amp;&amp; found)
{
gtk_signal_emit (GTK_OBJECT (ttt),
tictactoe_signals[TICTACTOE_SIGNAL]);
break;
}
}
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2>ttt_test.c
<p>
<tscreen><verb>
/* 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 *ventana;
GtkWidget *ttt;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (ventana), "Aspect Frame");
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (ventana), 10);
ttt = tictactoe_new ();
gtk_container_add (GTK_CONTAINER (ventana), ttt);
gtk_widget_show (ttt);
gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
GTK_SIGNAL_FUNC (win), NULL);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> GtkDial
<!-- ----------------------------------------------------------------- -->
<sect2> gtkdial.h
<p>
<tscreen><verb>
/* principio del ejmplo gtkdial gtkdial.h */
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#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 */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* política de actualización
* (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* Botón actualmente presionado o 0 si no hay ninguno */
guint8 boton;
/* Dimensión de los componendes del dial */
gint radius;
gint pointer_width;
/* ID del temporizador de actualización, o 0 si no hay ninguno */
guint32 timer;
/* ángulo actual */
gfloat angle;
/* Viejos valores almacenados del adjustment, para que así no
* tengamos que saber cuando cambia algo */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* El objeto adjustment que almacena los datos para este dial */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
guint gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect2> gtkdial.c
<p>
<tscreen><verb>
/* principio del ejemplo gtkdial gtkdial.c */
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <math.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
#define SCROLL_DELAY_LENGTH 300
#define DIAL_DEFAULT_SIZE 100
/* declaraciones de funciones */
static void gtk_dial_class_init (GtkDialClass *klass);
static void gtk_dial_init (GtkDial *dial);
static void gtk_dial_destroy (GtkObject *object);
static void gtk_dial_realize (GtkWidget *widget);
static void gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static void gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static gint gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event);
static gint gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event);
static gint gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event);
static gint gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event);
static gint gtk_dial_timer (GtkDial *dial);
static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y);
static void gtk_dial_update (GtkDial *dial);
static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data);
static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data);
/* datos locales */
static GtkWidgetClass *parent_class = NULL;
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,
};
dial_type = gtk_type_unique (gtk_widget_get_type (), &amp;dial_info);
}
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = (GtkObjectClass*) class;
widget_class = (GtkWidgetClass*) class;
parent_class = gtk_type_class (gtk_widget_get_type ());
object_class->destroy = gtk_dial_destroy;
widget_class->realize = gtk_dial_realize;
widget_class->expose_event = gtk_dial_expose;
widget_class->size_request = gtk_dial_size_request;
widget_class->size_allocate = gtk_dial_size_allocate;
widget_class->button_press_event = gtk_dial_button_press;
widget_class->button_release_event = gtk_dial_button_release;
widget_class->motion_notify_event = gtk_dial_motion_notify;
}
static 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;
dial = gtk_type_new (gtk_dial_get_type ());
if (!adjustment)
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
gtk_dial_set_adjustment (dial, adjustment);
return GTK_WIDGET (dial);
}
static void
gtk_dial_destroy (GtkObject *object)
{
GtkDial *dial;
g_return_if_fail (object != NULL);
g_return_if_fail (GTK_IS_DIAL (object));
dial = GTK_DIAL (object);
if (dial->adjustment)
gtk_object_unref (GTK_OBJECT (dial->adjustment));
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
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));
gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
(GtkSignalFunc) gtk_dial_adjustment_changed,
(gpointer) dial);
gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
(GtkSignalFunc) gtk_dial_adjustment_value_changed,
(gpointer) dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
gtk_dial_update (dial);
}
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
dial = GTK_DIAL (widget);
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
widget->window = gdk_window_new (widget->parent->window, &amp;attributes, attributes_mask);
widget->style = gtk_style_attach (widget->style, widget->window);
gdk_window_set_user_data (widget->window, widget);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
g_return_if_fail (allocation != NULL);
widget->allocation = *allocation;
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;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
if (event->count > 0)
return FALSE;
dial = GTK_DIAL (widget);
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/2;
/* Dibuja las rayitas */
for (i=0; i<25; i++)
{
theta = (i*M_PI/18. - M_PI/6.);
s = sin(theta);
c = cos(theta);
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* Dibuja el puntero */
s = sin(dial->angle);
c = cos(dial->angle);
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
gtk_draw_polygon (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
points, 3,
TRUE);
return FALSE;
}
static gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
/* Determinar si la pulsación del botón fue dentro de la región del
puntero - esto lo hacemos calculando la distancia x e y del punto
donde se pulsó el botón ratón de la línea que se ha pasado mediante el
puntero */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs(s*dx - c*dy);
if (!dial->button &amp;&amp;
(d_perpendicular < dial->pointer_width/2) &amp;&amp;
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &amp;&amp;
(dial->old_value != dial->adjustment->value))
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &amp;x, &amp;y, &amp;mods);
switch (dial->button)
{
case 1:
mask = GDK_BUTTON1_MASK;
break;
case 2:
mask = GDK_BUTTON2_MASK;
break;
case 3:
mask = GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods &amp; mask)
gtk_dial_update_mouse (dial, x,y);
}
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;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
xc = GTK_WIDGET(dial)->allocation.width / 2;
yc = GTK_WIDGET(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -M_PI/2.)
dial->angle += 2*M_PI;
if (dial->angle < -M_PI/6)
dial->angle = -M_PI/6;
if (dial->angle > 7.*M_PI/6.)
dial->angle = 7.*M_PI/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == GTK_UPDATE_CONTINUOUS)
{
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
else
{
gtk_widget_draw (GTK_WIDGET(dial), NULL);
if (dial->policy == GTK_UPDATE_DELAYED)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
(GtkFunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_draw (GTK_WIDGET(dial), NULL);
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> Scribble
<p>
<tscreen><verb>
/* principio del ejemplo scribble-simple scribble-simple.c */
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gtk/gtk.h>
/* Creamos un backing pixmap para la zona donde dibujamos */
static GdkPixmap *pixmap = NULL;
/* Creamos un nuevo backing pixmap del tamaño apropiado */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
if (pixmap)
gdk_pixmap_unref(pixmap);
pixmap = gdk_pixmap_new(widget->window,
widget->allocation.width,
widget->allocation.height,
-1);
gdk_draw_rectangle (pixmap,
widget->style->white_gc,
TRUE,
0, 0,
widget->allocation.width,
widget->allocation.height);
return TRUE;
}
/* Redibujamos la pantalla con el backing pixmap */
static gint
expose_event (GtkWidget *widget, GdkEventExpose *event)
{
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
pixmap,
event->area.x, event->area.y,
event->area.x, event->area.y,
event->area.width, event->area.height);
return FALSE;
}
/* Dibujamos un rectángulo en la pantalla */
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, &amp;update_rect);
}
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 &amp;&amp; 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, &amp;x, &amp;y, &amp;state);
else
{
x = event->x;
y = event->y;
state = event->state;
}
if (state &amp; GDK_BUTTON1_MASK &amp;&amp; pixmap != NULL)
draw_brush (widget, x, y);
return TRUE;
}
void
quit ()
{
gtk_exit (0);
}
int
main (int argc, char *argv[])
{
GtkWidget *ventana;
GtkWidget *drawing_area;
GtkWidget *vbox;
GtkWidget *boton;
gtk_init (&amp;argc, &amp;argv);
ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_name (ventana, "Test Input");
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (ventana), vbox);
gtk_widget_show (vbox);
gtk_signal_connect (GTK_OBJECT (ventana), "destroy",
GTK_SIGNAL_FUNC (quit), NULL);
/* Crear la zona de dibujado */
drawing_area = gtk_drawing_area_new ();
gtk_drawing_area_size (GTK_DRAWING_AREA (drawing_area), 200, 200);
gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
gtk_widget_show (drawing_area);
/* Las señales utilizadas para manejar el backing pixmap */
gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
(GtkSignalFunc) expose_event, NULL);
gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
(GtkSignalFunc) configure_event, NULL);
/* Señales evento */
gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
(GtkSignalFunc) motion_notify_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
(GtkSignalFunc) button_press_event, NULL);
gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK);
/* .. Y un botón para salir */
boton = gtk_button_new_with_label ("Quit");
gtk_box_pack_start (GTK_BOX (vbox), boton, FALSE, FALSE, 0);
gtk_signal_connect_object (GTK_OBJECT (boton), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (ventana));
gtk_widget_show (boton);
gtk_widget_show (ventana);
gtk_main ();
return 0;
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ***************************************************************** -->
<sect> El <em>widget</em> lista
<!-- ***************************************************************** -->
<p>
ATENCIÓN: El <em>widget</em> GtkList ha sido reemplazado por el
<em>widget</em> GtkCList.
El <em>widget</em> GtkList está diseñado para actuar como un
contenedor vertical de <em>widgets</em> que deben ser del tipo
GtkListItem.
Un <em>widget</em> GtkList tiene su propia ventana para recibir
eventos y su propio color de fondo, que normalmente es blanco.
Como es un objeto derivado directamente de GtkContainer puede tratarse
utilizando la macro GTK_CONTAINER(List), ver el <em>widget</em>
GtkContainer para obtener más información.
Debería familiarizarse con la utilización de un GList y con sus
funciones relacionadas <tt/g_list_*()/ para ser capaz de explotar el
<em>widget</em> GtkList hasta su límite.
Sólo hay un campo dentro de la definición de la estructura del
<em>widget</em> GtkList que nos es de interés, y es:
<tscreen><verb>
struct _GtkList
{
...
GList *selection;
guint selection_mode;
...
};
</verb></tscreen>
El campo <tt/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 <tt/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 <tt/selection_mode/ de la GtkList determina las posibilidades de
selección de una GtkList y por tanto los contenidos del campo
<tt/GTK_LIST()->selection/. El <tt/selection_mode/ puede tener uno de
los valores siguientes:
<itemize>
<item> GTK_SELECTION_SINGLE - La selección es o NULL o contiene un
puntero a un GList con un solo elemento seleccionado.
<item> GTK_SELECTION_BROWSE - La selección es NULL si la lista no
contiene <em>widgets</em> 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.
<item> 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í.
<item> GTK_SELECTION_EXTENDED - La selección siempre es NULL.
</itemize>
El valor por defecto es GTK_SELECTION_MULTIPLE.
<!-- ----------------------------------------------------------------- -->
<sect1> Señales
<p>
<tscreen><verb>
void selection_changed( GtkList *list );
</verb></tscreen>
Se invocará esta señal cuando cambie el campo <tt/selection/ de un
GtkList. Es decir, cuando un hijo de una GtkList se selecciona o
deselecciona.
<tscreen><verb>
void select_child( GtkList *list,
GtkWidget *hijo);
</verb></tscreen>
Se invoca esta señal cuando un hijo de la GtkList está siendo
seleccionado. Esto ocurre principalmente en llamadas a
<tt/gtk_list_select_item()/, a <tt/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.
<tscreen><verb>
void unselect_child( GtkList *list,
GtkWidget *hijo );
</verb></tscreen>
Se invoca esta señal cuando un hijo del GtkList está siendo
deseleccionado. Esto ocurre principalmente cuando ocurre una llamada a
<tt/gtk_list_unselect_item()/, <tt/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.
<!-- ----------------------------------------------------------------- -->
<sect1> Funciones
<p>
<tscreen><verb>
guint gtk_list_get_type( void );
</verb></tscreen>
Devuelve el identificador de tipo `GtkList'.
<tscreen><verb>
GtkWidget *gtk_list_new( void );
</verb></tscreen>
Crea un nuevo objeto GtkList. Se devuelve el nuevo <em/widget/ como un
puntero a un objeto GtkWidget. Se devuelve NULL en caso de producirse
algún fallo.
<tscreen><verb>
void gtk_list_insert_items( GtkList *list,
GList *items,
gint posicion );
</verb></tscreen>
Introduce elementos en la lista, comenzando en la posición
<tt/posicion/. <tt/items/ es una lista doblemente enlazada donde cada
puntero de datos de cada nodo se supone que apunta a una nueva
GtkListItem (recien creada). Los nodos GList de <tt/items/ son
controlados por la lista.
<tscreen><verb>
void gtk_list_append_items( GtkList *list,
GList *items);
</verb></tscreen>
Introduce elementos tal y como lo hace <tt/gtk_list_insert_items()/,
pero los mete en el final de la lista. Los nodos GList de <tt/items/
son controlados por la lista.
<tscreen><verb>
void gtk_list_prepend_items( GtkList *list,
GList *items);
</verb></tscreen>
Introduce elementos tal y como lo hace <tt/gtk_list_insert_items()/,
pero los mete al principio de la lista. Los nodos GList de <tt/items/
son controlados por la lista.
<tscreen><verb>
void gtk_list_remove_items( GtkList *list,
GList *items);
</verb></tscreen>
Elimina elementos de la lista. <tt/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
<tt/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.
<tscreen><verb>
void gtk_list_clear_items( GtkList *list,
gint start,
gint end );
</verb></tscreen>
Elimina y destruye los elementos de la lista. Esta operación afectará
a todos los <em/widgets/ que se encuentren en la lista y en el rango
especificado por <tt/start/ y <tt/end/.
<tscreen><verb>
void gtk_list_select_item( GtkList *list,
gint item );
</verb></tscreen>
Invoca la señal <tt/select_child/ para el elemento especificado
mediante su posición actual en la lista.
<tscreen><verb>
void gtk_list_unselect_item( GtkList *list,
gint item);
</verb></tscreen>
Invoca la señal <tt/unselect_child/ para un elemento especificado
mediante su posición actual en la lista.
<tscreen><verb>
void gtk_list_select_child( GtkList *list,
GtkWidget *hijo);
</verb></tscreen>
Invoca la señal <tt/select_child/ para el hijo especificado.
<tscreen><verb>
void gtk_list_unselect_child( GtkList *list,
GtkWidget *hijo);
</verb></tscreen>
Invoca la señal <tt/unselect_child/ para el hijo especificado.
<tscreen><verb>
gint gtk_list_child_position( GtkList *list,
GtkWidget *hijo);
</verb></tscreen>
Devuelve la posición de <tt/hijo/ en la lista. Se devuelve «-1» en
caso de producirse algún error.
<tscreen><verb>
void gtk_list_set_selection_mode( GtkList *list,
GtkSelectionMode mode );
</verb></tscreen>
Pone el modo de selección, que puede ser <tt/GTK_SELECTION_SINGLE/,
<tt/GTK_SELECTION_BROWSE/, <tt/GTK_SELECTION_MULTIPLE/ o
<tt/GTK_SELECTION_EXTENDED/.
<tscreen><verb>
GtkList *GTK_LIST( gpointer obj );
</verb></tscreen>
Convierte un puntero general en `GtkList *'. Para más información *Note
Standard Macros::.
<tscreen><verb>
GtkListClass *GTK_LIST_CLASS( gpointer class);
</verb></tscreen>
Convierte un puntero general en `GtkListClass *'. Para más información
*Note Standard Macros::.
<tscreen><verb>
gint GTK_IS_LIST( gpointer obj);
</verb></tscreen>
Determina si un puntero general se refiere a un objeto `GtkList'. Para
más información, *Note Standard Macros::.
<!-- ----------------------------------------------------------------- -->
<sect1> Ejemplo
<p>
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.
<tscreen><verb>
/* principio del ejemplo list list.c */
/* incluye los ficheros de cabecera de gtk+
* incluye stdio.h, que necesitamos para la función printf()
*/
#include <gtk/gtk.h>
#include <stdio.h>
/* ésta es nuestra cadena de identificación para almacenar datos en la
* lista de elementos
*/
const gchar *list_item_data_key="list_item_data";
/* prototipos para los manejadores de señal que vamos a conectar con
* el widget GtkList
*/
static void sigh_print_selection (GtkWidget *gtklist,
gpointer func_data);
static void sigh_button_event (GtkWidget *gtklist,
GdkEventButton *event,
GtkWidget *frame);
/* función principal donde se establece el interface con el usuario */
gint main (int argc, gchar *argv[])
{
GtkWidget *separator;
GtkWidget *ventana;
GtkWidget *vbox;
GtkWidget *scrolled_window;
GtkWidget *frame;
GtkWidget *gtklist;
GtkWidget *boton;
GtkWidget *list_item;
GList *dlist;
guint i;
gchar buffer[64];
/* inicializar gtk+ (y consecuentemente gdk) */
gtk_init(&amp;argc, &amp;argv);
/* crear una ventana donde meter todos los widgets y conectar
* gtk_main_quit() con el evento "destroy" de la ventana para
* poder controlar los eventos de cerrado de ventana del
* administrador de ventanas
*/
ventana=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(ventana), "GtkList Example");
gtk_signal_connect(GTK_OBJECT(ventana),
"destroy",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
/* dentro de la ventana necesitamos una caja para alinear los
* widgets verticalmente */
vbox=gtk_vbox_new(FALSE, 5);
gtk_container_border_width(GTK_CONTAINER(vbox), 5);
gtk_container_add(GTK_CONTAINER(ventana), vbox);
gtk_widget_show(vbox);
/* Ésta es la ventana con barras de desplazamiento donde meteremos
* el widget GtkList */
scrolled_window=gtk_scrolled_window_new(NULL, NULL);
gtk_widget_set_usize(scrolled_window, 250, 150);
gtk_container_add(GTK_CONTAINER(vbox), scrolled_window);
gtk_widget_show(scrolled_window);
/* crear el widget GtkList
* conectar la función manipuladora de señal
* sigh_print_selection() a la señal "selection_changed" del
* GtkList para imprimir los elementos seleccionados cada vez que
* cambie la selección */
gtklist=gtk_list_new();
gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist);
gtk_widget_show(gtklist);
gtk_signal_connect(GTK_OBJECT(gtklist),
"selection_changed",
GTK_SIGNAL_FUNC(sigh_print_selection),
NULL);
/* creamos una "Prisión" donde meteremos una lista de elementos ;)
*/
frame=gtk_frame_new("Prison");
gtk_widget_set_usize(frame, 200, 50);
gtk_container_border_width(GTK_CONTAINER(frame), 5);
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
gtk_container_add(GTK_CONTAINER(vbox), frame);
gtk_widget_show(frame);
/* conectamos el manipulador de señal sigh_button_event() al
* GtkList que manejará la lista de elementos "arrestados"
*/
gtk_signal_connect(GTK_OBJECT(gtklist),
"button_release_event",
GTK_SIGNAL_FUNC(sigh_button_event),
frame);
/* crear un separador
*/
separator=gtk_hseparator_new();
gtk_container_add(GTK_CONTAINER(vbox), separator);
gtk_widget_show(separator);
/* crear finalmente un botón y conectar su señal "clicked" con la
* destrucción de la ventana
*/
boton=gtk_button_new_with_label("Close");
gtk_container_add(GTK_CONTAINER(vbox), boton);
gtk_widget_show(boton);
gtk_signal_connect_object(GTK_OBJECT(boton),
"clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),
GTK_OBJECT(ventana));
/* ahora creamos 5 elementos de lista, teniendo cada uno su propia
* etiqueta y añadiéndolos a la GtkList mediante
* gtk_container_add() también consultaremos la cadena de texto de
* la etiqueta y la asociaremos con la list_item_data_key para
* cada elemento de la lista
*/
for (i=0; i<5; i++) {
GtkWidget *etiqueta;
gchar *string;
sprintf(buffer, "ListItemContainer with Label #%d", i);
etiqueta=gtk_label_new(buffer);
list_item=gtk_list_item_new();
gtk_container_add(GTK_CONTAINER(list_item), etiqueta);
gtk_widget_show(etiqueta);
gtk_container_add(GTK_CONTAINER(gtklist), list_item);
gtk_widget_show(list_item);
gtk_label_get(GTK_LABEL(etiqueta), &amp;string);
gtk_object_set_data(GTK_OBJECT(list_item),
list_item_data_key,
string);
}
/* aquí, estamos creando otras 5 etiquetas, esta vez utilizaremos
* gtk_list_item_new_with_label() para la creación
* no podemos consultar la cadena de texto de la etiqueta ya que
* no tenemos el puntero de etiquetas y por tanto lo único que
* haremos será asociar el list_item_data_key de cada elemento de
* la lista con la misma cadena de texto. Para añadirlo a la lista
* de elementos los pondremos en lista doblemente enlazada
* (GList), y entonces los añadimos mediante una simple llamada a
* gtk_list_append_items()
* como utilizamos g_list_prepend() para poner los elementos en la
* lista doblemente enlazada, su orden será descendente (en vez de
* ascendente como cuando utilizamos g_list_append())
*/
dlist=NULL;
for (; i<10; i++) {
sprintf(buffer, "List Item with Label %d", i);
list_item=gtk_list_item_new_with_label(buffer);
dlist=g_list_prepend(dlist, list_item);
gtk_widget_show(list_item);
gtk_object_set_data(GTK_OBJECT(list_item),
list_item_data_key,
"ListItem with integrated Label");
}
gtk_list_append_items(GTK_LIST(gtklist), dlist);
/* finalmente queremos ver la ventana, ¿verdad? ;)
*/
gtk_widget_show(ventana);
/* y nos metemos en el bucle de eventos de gtk
*/
gtk_main();
/* llegaremos aquí después de que se llame a gtk_main_quit(), lo
* que ocurre si se destruye la ventana
*/
return 0;
}
/* éste es el manejador de señal que se conectó a los eventos de
* pulsar/soltar de los botones de la GtkList
*/
void
sigh_button_event (GtkWidget *gtklist,
GdkEventButton *event,
GtkWidget *frame)
{
/* sólo hacemos algo si el tercer botón (el botón derecho) se
* levanta
*/
if (event->type==GDK_BUTTON_RELEASE &amp;&amp;
event->button==3) {
GList *dlist, *free_list;
GtkWidget *new_prisoner;
/* sacar la lista de elementos que están actualmente
* seleccionados y que serán nuestro próximos prisioneros ;)
*/
dlist=GTK_LIST(gtklist)->selection;
if (dlist)
new_prisoner=GTK_WIDGET(dlist->data);
else
new_prisoner=NULL;
/* buscar por elementos de la lista ya encarcelados, los
* volveremos a poner en la lista, recordar que hay que
* eliminar la lista doblemente enlazada que devuelve
* gtk_container_children()
*/
dlist=gtk_container_children(GTK_CONTAINER(frame));
free_list=dlist;
while (dlist) {
GtkWidget *list_item;
list_item=dlist->data;
gtk_widget_reparent(list_item, gtklist);
dlist=dlist->next;
}
g_list_free(free_list);
/* si tenemos un nuevo prisionero, lo eliminamos de la GtkList
* y lo ponemos en el marco "Prisión". Primero tenemos que
* deseleccionarlo
*/
if (new_prisoner) {
GList static_dlist;
static_dlist.data=new_prisoner;
static_dlist.next=NULL;
static_dlist.prev=NULL;
gtk_list_unselect_child(GTK_LIST(gtklist),
new_prisoner);
gtk_widget_reparent(new_prisoner, frame);
}
}
}
/* éste es el manipulador de señal que se llama si GtkList emite la
* señal "selection_changed"
*/
void
sigh_print_selection (GtkWidget *gtklist,
gpointer func_data)
{
GList *dlist;
/* sacar la lista doblemente enlazada de los elementos
* seleccionados en GtkList, ¡recuerde que hay que tratarla como
* de solo lectura!
*/
dlist=GTK_LIST(gtklist)->selection;
/* si no hay elementos seleccionados no queda nada por hacer
* excepto informar al usuario
*/
if (!dlist) {
g_print("Selection cleared\n");
return;
}
/* Bien, conseguimos una selección y la imprimimos
*/
g_print("The selection is a ");
/* obtenemos la lista de elementos de la lista doblemente enlazada
* y entonces consultamos los datos asociados con la
* list_item_data_key que acabamos de imprimir
*/
while (dlist) {
GtkObject *list_item;
gchar *item_data_string;
list_item=GTK_OBJECT(dlist->data);
item_data_string=gtk_object_get_data(list_item,
list_item_data_key);
g_print("%s ", item_data_string);
dlist=dlist->next;
}
g_print("\n");
}
/* fin del ejemplo */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
<sect1> El <em/widget/ GtkListItem
<p>
El <em/widget/ GtkListItem está diseñado para comportarse como un
contenedor que tiene un hijo, proporcionando funciones para la
selección/deselección justo como las necesitan los hijos del
<em/widget/ GtkList.
Un GtkListItem tiene su propia ventana para recibir eventos y tiene su
propio color de fondo, que normalmente es blanco.
Como está derivado directamente de un GtkItem, puede tratarse como tal
utilizando la macro GTK_ITEM(ListItem), ver el <em/widget/ GtkItem
para más detalles. Normalmente un GtkListItem sólo tiene una etiqueta
para identificar, por ejemplo, el nombre de un fichero dentro de una
GtkList -- por lo tanto se proporciona la función
<tt/gtk_list_item_new_with_label()/. Se puede conseguir el mismo
efecto creando un GtkLabel, poniendo su alineación a <tt/xalign=0/ e
<tt/yalign=0.5/ y seguido de una adición al contenedor GtkListItem.
Nadie le obliga a meter un GtkLabel en un GtkListItem, puede meter un
GtkVBox o un GtkArrow, etc...
<!-- ----------------------------------------------------------------- -->
<sect1> Señales
<p>
Un GtkListItem no crea por sí misma nuevas señales, pero hereda las
señales de un GtkItem. Para más información *Note GtkItem::.
<!-- ----------------------------------------------------------------- -->
<sect1> Funciones
<p>
<tscreen><verb>
guint gtk_list_item_get_type( void );
</verb></tscreen>
Devuelve el identificador de tipo `GtkListItem'.
<tscreen><verb>
GtkWidget *gtk_list_item_new( void );
</verb></tscreen>
Crea un nuevo objeto GtkListItem. Se devuelve el nuevo <em/widget/
como un puntero a un objeto GtkWidget. Se devuelve NULL en caso de
producirse algún error.
<tscreen><verb>
GtkWidget *gtk_list_item_new_with_label( gchar *etiqueta );
</verb></tscreen>
Crea un nuevo objeto GtkListItem, con una sola GtkLabel como único
hijo. Se devuelve el nuevo <em/widget/ como un puntero a un objeto
GtkWidget. Se devuelve NULL en caso de producirse algún error.
<tscreen><verb>
void gtk_list_item_select( GtkListItem *list_item );
</verb></tscreen>
Esta función es, básicamente, un recubrimiento de una llamada a
<tt/gtk_item_select (GTK_ITEM (list_item))/, y emitirá la señal
<tt/select/. Para más información *Note GtkItem::.
<tscreen><verb>
void gtk_list_item_deselect( GtkListItem *list_item );
</verb></tscreen>
Esta función es, básicamente, un recubrimiento de una llamada a
<tt/gtk_item_deselect (GTK_ITEM (list_item))/, y emitirá la señal
<tt/deselect/. Para más información *Note GtkItem::.
<tscreen><verb>
GtkListItem *GTK_LIST_ITEM( gpointer obj );
</verb></tscreen>
Convierte un puntero general a `GtkListItem *'. Para más información
*Note Standard Macros::.
<tscreen><verb>
GtkListItemClass *GTK_LIST_ITEM_CLASS( gpointer class );
</verb></tscreen>
Convierte un puntero general a `GtkListItemClass *'. Para más
información *Note Standard Macros::.
<tscreen><verb>
gint GTK_IS_LIST_ITEM( gpointer obj );
</verb></tscreen>
Determina si un puntero general se refiere a un puntero
`GtkListItem'. Para más información *Note Standard Macros::.
<!-- ----------------------------------------------------------------- -->
<sect1> Ejemplo
<p>
Para ver un ejemplo de todo esto, mire el de GtkList, que también
cubre la utilización un GtkListItem.
</article>