GTK Tutorial v1.2 <author>Tony Gale <tt><htmlurl url="mailto:gale@gtk.org" name="<gale@gtk.org>"></tt>, Ian Main <tt><htmlurl url="mailto:imain@gtk.org" name="<imain@gtk.org>"></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 (&argc, &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 (&argc, &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 (&argc, &argv); /* creamos una ventana nueva */ ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Cuando la ventana recibe la señal "delete_event" (emitida * por el gestor de ventanas, normalmente mediante la opción * 'close', o en la barra del título) hacemos que llame a la * función delete_event() tal y como ya hemos visto. Los datos * pasados a la función de respuesta son NULL, y serán ignorados. */ gtk_signal_connect (GTK_OBJECT (ventana), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL); /* Aquí conectamos el evento "destroy" con el administrador de * señales. El evento se produce cuando llamamos a * gtk_widget_destroy() desde la ventana o si devolvemos 'FALSE' * en la respuesta "delete_event". */ gtk_signal_connect (GTK_OBJECT (ventana), "destroy", GTK_SIGNAL_FUNC (destroy), NULL); /* establecemos el ancho del borde de la ventana. */ gtk_container_border_width (GTK_CONTAINER (ventana), 10); /* creamos un botón nuevo con la etiqueta "Hola mundo" */ boton = gtk_button_new_with_label ("Hola mundo"); /* Cuando el botón recibe la señal "clicked" llama a la * función hello() pasándole NULL como argumento. (La * función ya ha sido definida arriba). */ gtk_signal_connect (GTK_OBJECT (boton), "clicked", GTK_SIGNAL_FUNC (hello), NULL); /* Esto hará que la ventana sea destruida llamando a * gtk_widget_destroy(ventana) cuando se produzca "clicked". Una * vez mas la señal de destrucción puede provenir del gestor * de ventanas o de aquí. */ gtk_signal_connect_object (GTK_OBJECT (boton), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (ventana)); /* Ahora empaquetamos el botón en la ventana (usamos un gtk * container ). */ gtk_container_add (GTK_CONTAINER (ventana), boton); /* El último paso es representar el nuevo widget... */ gtk_widget_show (boton); /* y la ventana */ gtk_widget_show (ventana); /* Todas las aplicaciones basadas en GTK deben tener una llamada * gtk_main() ya que el control termina justo aquí y debe * esperar a que suceda algún evento */ gtk_main (); return 0; } /* final del ejemplo*/ </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 (&argc, &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 (&argc, &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 (&argc, &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 (&argc, &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, &mask, &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 (&argc, &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(&argc,&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><gtk/gtkhscrollbar.h></tt> y <tt><gtk/gtkvscrollbar.h></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><gtk/gtkvscale.h></tt> y <tt><gtk/gtkhscale.h></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><gtk/gtkscale.h></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><gtk/gtkscale.h></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><gtk/gtkrange.h></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><gtk/gtkenums.h></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(&argc, &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(&argc, &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 (&argc, &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 (&argc, &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( &argc, &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, &mask, &style->bg[GTK_STATE_NORMAL], (gchar **)xpm_data ); /* Un pixmap widget que contendrá al pixmap */ pixmapwid = gtk_pixmap_new( pixmap, mask ); gtk_widget_show( pixmapwid ); /* Un botón para contener al pixmap */ 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, &mask, &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", "& c #A69992489E79", "* c #8E3886178E38", "= c #104008200820", "- c #596510401040", "; c #C71B30C230C2", ": c #C71B9A699658", "> c #618561856185", ", c #20811C712081", "< c #104000000000", "1 c #861720812081", "2 c #DF7D4D344103", "3 c #79E769A671C6", "4 c #861782078617", "5 c #41033CF34103", "6 c #000000000000", "7 c #49241C711040", "8 c #492445144924", "9 c #082008200820", "0 c #69A618611861", "q c #B6DA71C65144", "w c #410330C238E3", "e c #CF3CBAEAB6DA", "r c #71C6451430C2", "t c #EFBEDB6CD75C", "y c #28A208200820", "u c #186110401040", "i c #596528A21861", "p c #71C661855965", "a c #A69996589658", "s c #30C228A230C2", "d c #BEFBA289AEBA", "f c #596545145144", "g c #30C230C230C2", "h c #8E3882078617", "j c #208118612081", "k c #38E30C300820", "l c #30C2208128A2", "z c #38E328A238E3", "x c #514438E34924", "c c #618555555965", "v c #30C2208130C2", "b c #38E328A230C2", "n c #28A228A228A2", "m c #41032CB228A2", "M c #104010401040", "N c #492438E34103", "B c #28A2208128A2", "V c #A699596538E3", "C c #30C21C711040", "Z c #30C218611040", "A c #965865955965", "S c #618534D32081", "D c #38E31C711040", "F c #082000000820", " ", " .XoO ", " +@#$%o& ", " *=-;#::o+ ", " >,<12#:34 ", " 45671#:X3 ", " +89<02qwo ", "e* >,67;ro ", "ty> 459@>+&& ", "$2u+ ><ipas8* ", "%$;=* *3:.Xa.dfg> ", "Oh$;ya *3d.a8j,Xe.d3g8+ ", " Oh$;ka *3d$a8lz,,xxc:.e3g54 ", " Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ", " Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ", " Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ", " Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ", " Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ", " OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ", " 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ", " :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo", " +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g", " *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en", " p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>", " OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ", " 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ", " @26MvzxNzvlbwfpdettttttttttt.c,n& ", " *;16=lsNwwNwgsvslbwwvccc3pcfu<o ", " p;<69BvwwsszslllbBlllllllu<5+ ", " OS0y6FBlvvvzvzss,u=Blllj=54 ", " c1-699Blvlllllu7k96MMMg4 ", " *10y8n6FjvllllB<166668 ", " S-kg+>666<M<996-y6n<8* ", " p71=4 m69996kD8Z-66698&& ", " &i0ycm6n4 ogk17,0<6666g ", " N-k-<> >=01-kuu666> ", " ,6ky& &46-10ul,66, ", " Ou0<> o66y<ulw<66& ", " *kk5 >66By7=xu664 ", " <<M4 466lj<Mxu66o ", " *>> +66uv,zN666* ", " 566,xxj669 ", " 4666FF666> ", " >966666M ", " oM6668+ ", " *4 ", " ", " "}; 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 (&argc, &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, &mask, &style->bg[GTK_STATE_NORMAL], WheelbarrowFull_xpm ); pixmap = gtk_pixmap_new( gdk_pixmap, mask ); gtk_widget_show( pixmap ); fixed = gtk_fixed_new(); gtk_widget_set_usize( fixed, 200, 200 ); gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); gtk_container_add( GTK_CONTAINER(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( &argc, &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 (&argc, &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), &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), &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 (&argc, &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(&argc, &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, &gdk_color); /* Poner el color de fondo de la ventana */ gdk_window_set_background (drawingarea->window, &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 && 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 (&argc,&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 (&argc, &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, &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 (&argc, &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(&argc, &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(&argc, &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 (&argc, &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, &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 (&argc, &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 (&argc, &argv); /* Crea un nuevo cuadro de diálogo para que la ventana con barras de * desplazamiento se meta dentro. Un cuadro de diálogo es como una * ventana normal excepto que tiene dentro una vbox y un separador * horizontal. Es sólo un atajo para crear cuadros de diálogo */ 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( &argc, &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 (&argc, &argv); /* crear una ventana nueva con un título y el tamaño adecuado */ dialog = gtk_dialog_new (); gtk_window_set_title ( GTK_WINDOW ( dialog ) , "GTKToolbar Tutorial"); gtk_widget_set_usize( GTK_WIDGET ( dialog ) , 600 , 300 ); GTK_WINDOW ( dialog ) ->allow_shrink = TRUE; /* salimos si alguien intenta cerrarnos */ gtk_signal_connect ( GTK_OBJECT ( dialog ), "delete_event", GTK_SIGNAL_FUNC ( delete_event ), NULL); /* tenemos que mandar la señalo realize porque utilizamos pixmaps * para los elementos que hay en la barra de herramientas */ gtk_widget_realize ( dialog ); /* para hacerlo más bonito ponemos la barra de herramientas en la * caja flotante, para que así se pueda desatar de la ventana * principal */ handlebox = gtk_handle_box_new (); gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG(dialog)->vbox ), handlebox, FALSE, FALSE, 5 ); </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, &mask, &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 (&argc, &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, &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(&argc, &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, &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, &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, &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 (&argc, &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 (&argc, &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 (&argc, &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, &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 (&argc, &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, &colour)) { g_error("couldn't allocate colour"); } /* Cargar un fuente de tamaño fijo */ fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*"); /* Al enviar la señal relize a un widget se crea una ventana para el * mismo, y nos permite insertar texto */ gtk_widget_realize (text); /* Congela el widget text, lo que nos permite hacer varias * actualizaciones */ gtk_text_freeze (GTK_TEXT (text)); /* Insertamos algún texto coloreado */ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL, "Supports ", -1); gtk_text_insert (GTK_TEXT (text), NULL, &colour, NULL, "colored ", -1); gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL, "text and different ", -1); gtk_text_insert (GTK_TEXT (text), fixed_font, &text->style->black, NULL, "fonts\n\n", -1); /* Cargamos el fichero text.c en la ventana de texto */ infile = fopen("text.c", "r"); 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, &x1, &y1, &x2, &y2); width = x2-x1; height = y2-y1; /* If there's a SELECTION, we got its bounds!) if (width != drawable->width && height != drawable->height) NoSelectionMade=FALSE; /* Become aware of whether the user has made an active selection */ /* This will become important later, when creating a reduced mask. */ /* If we want to preview the entire image, overrule the above! */ /* Of course, if no selection has been made, this does nothing! */ if (Selection==ENTIRE_IMAGE) { x1=0; x2=drawable->width; y1=0; y2=drawable->height; } /* If we want to preview a selection with some surrounding area we */ /* have to expand it a little bit. Consider it a bit of a riddle. */ if (Selection==SELECTION_IN_CONTEXT) { x1=MAX(0, x1-width/2.0); x2=MIN(drawable->width, x2+width/2.0); y1=MAX(0, y1-height/2.0); y2=MIN(drawable->height, y2+height/2.0); } /* How we can determine the width and the height of the area being */ /* reduced. */ width = x2-x1; height = y2-y1; /* The lines below determine which dimension is to be the longer */ /* side. The idea borrowed from the supernova plug-in. I suspect I */ /* could've thought of it myself, but the truth must be told. */ /* Plagiarism stinks! */ if (width>height) { RW=LongerSize; RH=(float) height * (float) LongerSize/ (float) width; } else { RH=LongerSize; RW=(float)width * (float) LongerSize/ (float) height; } /* The entire image is stretched into a string! */ tempRGB = (guchar *) malloc(RW*RH*bytes); tempmask = (guchar *) malloc(RW*RH); gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE); /* Grab enough to save a row of image and a row of mask. */ src_row = (guchar *) malloc (width*bytes); src_mask_row = (guchar *) malloc (width); for (i=0; i < RH; i++) { whichrow=(float)i*(float)height/(float)RH; gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width); for (j=0; j < RW; j++) { whichcol=(float)j*(float)width/(float)RW; /* No selection made = each point is completely selected! */ if (NoSelectionMade) tempmask[i*RW+j]=255; else tempmask[i*RW+j]=src_mask_row[whichcol]; /* Add the row to the one long string which now contains the image! */ tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2]; /* Hold on to the alpha as well */ if (bytes==4) tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; } } temp->bpp=bytes; temp->width=RW; temp->height=RH; temp->rgb=tempRGB; temp->mask=tempmask; return temp; } The following is a preview function which used the same ReducedImage type! Note that it uses fakes transparency (if one is present by means 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 (&argc, &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(&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 (&argc, &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), &have_selection); gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", GTK_SIGNAL_FUNC (selection_clear), &have_selection); gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, selection_handle, NULL); gtk_widget_show (selection_button); gtk_widget_show (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 (), &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 && GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; found = found || ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; } if (success && found) { gtk_signal_emit (GTK_OBJECT (ttt), tictactoe_signals[TICTACTOE_SIGNAL]); break; } } } </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 (&argc, &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 (), &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, &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, &x, &y, &mods); switch (dial->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; } if (mods & mask) gtk_dial_update_mouse (dial, x,y); } return FALSE; } static 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 && pixmap != NULL) draw_brush (widget, event->x, event->y); return TRUE; } static gint motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { int x, y; GdkModifierType state; if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } if (state & GDK_BUTTON1_MASK && 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><gdk/gdk.h></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, &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, &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 && 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, &x, &y, &pressure, NULL, NULL, &state); else { x = event->x; y = event->y; pressure = event->pressure; state = event->state; } if (state & GDK_BUTTON1_MASK && pixmap != NULL) draw_brush (widget, event->source, x, y, pressure); return TRUE; } </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, &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@gtk.org" name="timj@gtk.org"></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="<e98cuenc@criens.u-psud.fr>"></tt> y de Eduardo Anglada Varela <tt><htmlurl url="mailto:eduardo.anglada@adi.uam.es" name="<eduardo.anglada@adi.uam.es>"></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="<barreiro@arrakis.es>"></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 (), &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 && GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; found = found || ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; } if (success && found) { gtk_signal_emit (GTK_OBJECT (ttt), tictactoe_signals[TICTACTOE_SIGNAL]); break; } } } /* fin del ejemplo */ </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 (&argc, &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 (), &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, &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 && (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, &x, &y, &mods); switch (dial->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; } if (mods & mask) gtk_dial_update_mouse (dial, x,y); } return FALSE; } static 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, &update_rect); } static gint button_press_event (GtkWidget *widget, GdkEventButton *event) { if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->x, event->y); return TRUE; } static gint motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { int x, y; GdkModifierType state; if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } if (state & GDK_BUTTON1_MASK && 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 (&argc, &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(&argc, &argv); /* crear una ventana donde meter todos los widgets y conectar * gtk_main_quit() con el evento "destroy" de la ventana para * poder controlar los eventos de cerrado de ventana del * administrador de ventanas */ 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), &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 && 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>