Differences between revisions 3 and 4
Revision 3 as of 2009-08-30 18:25:55
Size: 2428
Comment: Añadir tareas pendientes
Revision 4 as of 2011-06-15 10:09:52
Size: 3108
Comment: Añadidos nuevos Include()
Deletions are marked like this. Additions are marked like this.
Line 64: Line 64:
<<Include(Documentacion/Desarrollo/Gtk/Ventanas,"Ventanas",2,from="== ==", to="^----")>>
<<Include(Documentacion/Desarrollo/Gtk/Visualizacion,"Visualización y Entrada de información",2,from="== ==", to="^----")>>
<<Include(Documentacion/Desarrollo/Gtk/Botones,"Botones",2,from="== ==", to="^----")>>
<<Include(Documentacion/Desarrollo/Gtk/MenusYBarrasDeHerramientas,"Menús y barras de herramientas",2,from="== ==", to="^----")>>
<<Include(Documentacion/Desarrollo/Gtk/VentanasDeSeleccion,"Ventanas de selección",2,from="== ==", to="^----")>>
<<Include(Documentacion/Desarrollo/Gtk/WidgetsDeDesplazamiento ,"Widgets de desplazamiento",2,from="== ==", to="^----")>>

ARTÍCULO EN DESARROLLO

Si quieres colaborar con nosotros completando este apartado o cualquier otra parte del Curso, puedes informarte en el enlace siguiente.

>[Documentación para desarrolladores]

CONTENIDO
Los contenidos y tareas pendientes se indican a continuación:

/0820 Ventanas
/0821 +Ventanas (GtkWindow)
/0822 +Diálogos (GtkDialog)
/0823 +Ventanas de mensaje (GtkMessageDialog)
/0824 +Ventanas invisibles (GtkInvisible)
/0825 +Ventanas embebidas (GtkPlug)
/0826 +Grupos de ventanas (GtkWindowGroup)
/0827 Visualización y Entrada de información
/0828 +Etiquetas
/0829 +Entrada de datos
/0830 +Ajustes
/0831 +Widgets de selección de rango
/0832 +Imágenes (GtkImage)
/0833 +Barras de progreso (GtkProgressBar)
/0834 +Barras de estado (GtkStatusBar)
/0835 Botones
/0836 +GtkButton
/0837 +GtkToggleButton
/0838 +GtkCheckButton
/0839 +GtkRadioButton
/0840 Menús y barras de herramientas
/0841 +Menús
/0842 +GtkToolbar
/0843 +GtkCombo
/0844 Ventanas de selección
/0845 +GtkFileSelection
/0846 +GtkFileChooser
/0847 +GtkColorSelection
/0848 +GtkColorSelectionDialog
/0849 +GtkFontSelection
/0850 +GtkFontSelectionDialog
/0851 Widgets de desplazamiento
/0852 +GtkHScrollbar/GtkVSrollbar
/0853 +GtkScrolledWindow 

Todos estos apartados están pendientes de copiar desde http://webs.ono.com/gonav/librognome-html/x5241.html (apartados 7.5 en adelante)

GTK+

GTK+ es la librería gráfica (toolkit) sobre la que se sustenta todo el interfaz gráfico de GNOME. Es una librería que contiene todo lo necesario para el desarrollo de interfaces gráficas, permitiendo la posibilidad de crear todo tipo de widgets, desde los más básicos, como botones, etiquetas, cajas de texto, hasta cosas más complejas como selectores de ficheros, colores, fuentes, cajas de texto multilínea con soporte para todo tipo de efectos sobre el texto, etc.

Qué es un widget

En términos de ingeniería del software, un widget es un componente software visible y personalizable. Visible porque está pensado para ser usado en los interfaces gráficos de los programas, y personalizable porque el programador puede cambiar muchas de sus propiedades. De esta forma se logra una gran reutilización del software, un objetivo prioritario en ingeniería del software. Los widgtes se combinan para construir los interfaces gráficos de usuario. El programador los adapta según sus necesidades sin tener que escribir más código que el necesario para definir los nuevos valores de las propiedades de los widgets.

La librería GTK+ sigue el modelo de programación orientado a objetos. La jerarquía de objetos comienza en GObject de la librería Glib del que hereda GtkObject. Todos los widgets heredan de la clase de objetos GtkWidget, que a su vez hereda directamente de GtkObject. La clase GtkWidget contiene las propiedades comunes a todos los widgets; cada widget particular le añade sus propias propiedades.

Los widgets se definen mediante punteros a una estructura GtkWidget. En GTK+, los widgets presentan una relación padre/hijo entre sí. Las aplicaciones suelen tener un widget "ventana" de nivel superior que no tiene padre, pero aparte de él, todos los widgets que se usen en una aplicación deberán tener un widget padre. Al widget padre se le denomina contenedor. El proceso de creacción de un widget consta de dos pasos: el de creación propiamente dicho y el de visualización. La función de creación de un widget tiene un nombre que sigue el esquema gtk_nombre_new donde "nombre" debe sustituirse por el nombre del widget que se desea crear. La función gtk_widget_show hará visible el widget creado.

La función de creación de un widget gtk_nombre_new devuelve un puntero a un objeto de tipo GtkWidget y no un puntero a un widget del tipo creado. Por ejemplo, la función gtk_button_new devuelve un puntero a un objeto de GtkWidget y no una referencia a un botón. Esta referencia puede convertirse a una referencia a un objeto GtkButton mediante la macro GTK_BUTTON, si se desea utilizar en lugares donde se requieran objetos botones. Aunque sería posible pasar en esos casos la referencia genérica, el compilador se quejará si se hace así posibilitando un control de tipos de objetos. Todo widget tiene una macro de conversión de una referencia genérica a una referencia al tipo propio. Eso sí, la macro únicamente funcionará correctamente si el widget referenciado fue creado con la función de creación del tipo apropiado, lo que incluye el propio tipo del widget o un descendiente.

El interfaz gráfico de una aplicación se construye combinando diferentes widgets (ventanas, cuadros combinados, cuadros de texto, botones, ...) y se establecen diversas retrollamadas (callbacks) para estos widgets, de esta forma se obtiene el procesamiento requerido por el programa a medida que se producen ciertas señales que a su vez provocan las retrollamadas. Las señales se producen por diversos sucesos como oprimir el boton de un ratón que se encuentra sobre un widget botón, pasar el cursor por encima de un widget u oprimir una tecla.

GTK+ utiliza GDK para visualizar los widgets. GDK es una interfaz de programación (API) de aplicaciones que se situa por encima de la API gráfica nativa como Xlib o Win32. De esta forma portando GDK pueden utilizarse las aplicaciones construidas con GTK+ en otras plataformas.

Widgets básicos

El conjunto de "widgets" básicos incluye todos los que nos podríamos esperar de una librería como GTK, es decir, botones, etiquetas, marcos, barras de tareas y de menús, ventanas, cajas de diálogo, etc. Todos y cada uno de ellos, implementado como una clase (es decir, un objeto derivado de GObject, aunque indirectamente, pues todos los "widgets" están basados en la clase GtkWidget), permiten una personalización bastante amplia, lo cual nos permite desarrollar nuestros interfaces de usuario al más mínimo detalle.

Cada clase en GTK ofrece una función _new que nos permite crear una instancia de esa clase. Así, por ejemplo, tenemos las siguientes funciones:

   GtkWidget *gtk_list_new (void);
   GtkWidget *gtk_toolbar_new (GtkOrientation orientation, GtkToolbarStyle style);
   GtkWidget *gtk_button_new (void);
   GtkWidget *gtk_button_new_with_label (const gchar *label);
   ...

Una vez que hemos creado una instancia de una clase GTK, podemos alterar su aspecto/comportamiento mediante las funciones disponibles para esa clase, así como las funciones disponibles en las clases de las que, directa o indirectamente, hereda la primera clase.

Esto último, tratar a una clase como otra, se consigue mediante macros de conversión. Así, por ejemplo, la clase GtkCheckButton, que deriva de GtkToggleButton puede ser tratada de la siguiente forma:

   1       GtkWidget *check_button;
   2       check_button = gtk_check_button_new_with_label("un check button");
   3       ...
   4       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_button))) {
   5       /* el "check button" está activado */
   6       ...
   7       }

Como se ve en este ejemplo, primero creamos una instancia de la clase GtkCheckButton mediante la función gtk_check_button_new_with_label, para luego comprobar si está activo mediante la función gtk_toggle_button_get_active. Esto se debe a que, como coméntabamos anteriormente, la clase GtkCheckButton hereda de la clase GtkToggleButton y, por tanto, podemos usar todas las funciones de esta última clase con cualquier objeto de la clase GtkCheckButton.

Este mecanismo de herencia es el equivalente a la herencia en los lenguajes orientados a objetos, en los que no es necesaria la conversión de los objetos, sino que es posible acceder a los métodos/propiedades del objeto directamente. Así, para entenderlo mejor, veamos el equivalente, en C++, del ejemplo anterior.

   1    Gtk::CheckButton check_button = new Gtk::CheckButton("un check button");
   2    ...
   3    if (check_button->get_active()) {
   4         ...
   5    }

Bucle de ejecución y eventos

Como es habitual en cualquier librería de desarrollo de interfaces gráficos, en GTK+ se usa el modelo de programación por eventos. Este modelo consiste en la asociación de determinadas acciones ("eventos") a determinadas operaciones ("funciones"). Dichas acciones, o eventos, marcan un cambio de estado de determinado objeto, de forma que la aplicación pueda ser informada de ello.

Los eventos en GTK se refieren, en terminología GObject, a señales especificadas en la definición de las clases de los distintos widgets. Así, por ejemplo, el widget GtkButton tiene definida una señal llamada "clicked" que es emitida (emitir una señal es la terminología usada en GTK) cuando el botón en cuestión es pulsado por el usuario, de forma que la aplicación no tiene más que "conectarse" a dicha señal para ser informada por GTK+ cada vez que dicho botón es pulsado.

Para que éste sistema de señales funcione correctamente, se usa lo que se denomina el bucle de eventos, que no es más que un bucle interno de GTK+, en el que se van, una y otra vez, comprobando los estados de cada uno de los elementos de la aplicación, e informando de dichos cambios a los elementos que se hayan registrado para ser informados. Este concepto es exactamente idéntico al del bucle de ejecución contenido en la librería GLib, y, de hecho, no es más que una ampliación de dicho concepto para hacerlo funcionar en aplicaciones con interfaz gráfica. Este bucle de eventos GTK+ se traduce básicamente en dos funciones, que son:

        void       gtk_main                (void);
        void       gtk_main_quit           (void);

Estas dos funciones, análogas a g_main_run y g_main_quit representan el interfaz que tienen las aplicaciones para usar el bucle de eventos. Como en el caso de su equivalente en GLib, gtk_main ejecuta el bucle de eventos. Esto significa que, una vez que se haya realizado la llamada a gtk_main, se cede todo el control de la aplicación a GTK. Es decir, gtk_main no retorna hasta que, dentro de algun manejador (instalado ANTES de llamar a gtk_main) se haga una llamada a gtk_main_quit, que, como su nombre indica, termina el bucle de eventos de GTK+.

Un ejemplo de una típica aplicación GTK+ sería:

   1         int main (int argc, char *argv[])
   2         {
   3             gtk_init (&argc, &argv);
   4             /* creación del interfaz principal */
   5             /* conexión a las distintas señales */
   6 
   7             gtk_main ();
   8 
   9             return 0;
  10         }

Como puede comprobarse, el programa inicializa GTK+, crea el interfaz básico, conecta funciones a las distintas señales en las que esté interesado (llamadas a g_signal_connect), para seguidamente llamar a gtk_main para que se ejecute la aplicación. Cuando en algun manejador de señal realicemos una llamada a gtk_main_quit, gtk_main retornará, tras lo cual la aplicación termina.

Señales

Las señales en GTK+ no son más que una muestra de uso extensivo del sistema de objetos de GLib. Las señales son el medio mediante el cual GTK+ informa a las aplicaciones de los eventos producidos en el interfaz gráfico. Estos eventos o señales normalmente estan relacionados con un cambio de estado en determinado widget, como el ejemplo de la clase GtkButton que se mencionaba en el apartado anterior, que tiene definida una señal llamada "clicked" para informar de la pulsación, por parte del usuario, del botón en cuestión.

Ejemplo básico

Antes de seguir adelante, lo mejor es ver un ejemplo completo de un programa (básico) en GTK, de forma que puedan apreciarse claramente los pasos básicos.

   1 #include <gtk/gtk.h>
   2 
   3 /* This is a callback function. The data arguments are ignored
   4  * in this example. More on callbacks below. */
   5 void hello( GtkWidget *widget, gpointer   data )
   6 {
   7         g_print ("Hello World\n");
   8 }
   9 
  10 gint delete_event( GtkWidget *widget,
  11                    GdkEvent  *event,
  12                    gpointer   data )
  13 {
  14         /* If you return FALSE in the "delete_event" signal handler,
  15          * GTK will emit the "destroy" signal. Returning TRUE means
  16          * you don't want the window to be destroyed.
  17          * This is useful for popping up 'are you sure you want to quit?'
  18          * type dialogs. */
  19 
  20         g_print ("delete event occurred\n");
  21 
  22         /* Change TRUE to FALSE and the main window will be destroyed with
  23          * a "delete_event". */
  24 
  25         return TRUE;
  26 }
  27 
  28 /* Another callback */
  29 void destroy( GtkWidget *widget, gpointer   data )
  30 {
  31         gtk_main_quit ();
  32 }
  33 
  34 int main( int   argc, char *argv[] )
  35 {
  36         /* GtkWidget is the storage type for widgets */
  37         GtkWidget *window;
  38         GtkWidget *button;
  39 
  40         /* This is called in all GTK applications. Arguments are parsed
  41          * from the command line and are returned to the application. */
  42         gtk_init (&argc, &argv);
  43 
  44         /* create a new window */
  45         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  46 
  47         /* When the window is given the "delete_event" signal (this is given
  48          * by the window manager, usually by the "close" option, or on the
  49          * titlebar), we ask it to call the delete_event () function
  50          * as defined above. The data passed to the callback
  51          * function is NULL and is ignored in the callback function. */
  52         g_signal_connect (G_OBJECT (window), "delete_event",
  53                           G_CALLBACK (delete_event), NULL);
  54 
  55         /* Here we connect the "destroy" event to a signal handler.
  56          * This event occurs when we call gtk_widget_destroy() on the window,
  57          * or if we return FALSE in the "delete_event" callback. */
  58         g_signal_connect (G_OBJECT (window), "destroy",
  59                           G_CALLBACK (destroy), NULL);
  60 
  61         /* Sets the border width of the window. */
  62         gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  63 
  64         /* Creates a new button with the label "Hello World". */
  65         button = gtk_button_new_with_label ("Hello World");
  66 
  67         /* When the button receives the "clicked" signal, it will call the
  68          * function hello() passing it NULL as its argument.  The hello()
  69          * function is defined above. */
  70         g_signal_connect (G_OBJECT (button), "clicked",
  71                           G_CALLBACK (hello), NULL);
  72 
  73         /* This will cause the window to be destroyed by calling
  74          * gtk_widget_destroy(window) when "clicked".  Again, the destroy
  75          * signal could come from here, or the window manager. */
  76         g_signal_connect_swapped (G_OBJECT (button), "clicked",
  77                                   G_CALLBACK (gtk_widget_destroy),
  78                                   G_OBJECT (window));
  79 
  80         /* This packs the button into the window (a gtk container). */
  81         gtk_container_add (GTK_CONTAINER (window), button);
  82 
  83         /* The final step is to display this newly created widget. */
  84         gtk_widget_show (button);
  85 
  86         /* and the window */
  87         gtk_widget_show (window);
  88 
  89         /* All GTK applications must have a gtk_main(). Control ends here
  90          * and waits for an event to occur (like a key press or
  91          * mouse event). */
  92         gtk_main ();
  93 
  94         return 0;
  95 }

El ejemplo paso a paso

Ahora que ya conocemos la teoría básica, explicaremos paso a paso el ejemplo mostrado en el punto anterior.

Aquí tenemos la función que será llamada cuando se haga "click" sobre el botón. En este ejemplo, el cuerpo de la función ignorará tanto el widget como los datos que recoge como parámetros, pero no sería difícil saber como utilizarlos. El siguiente ejemplo usará el argumento data para controlar qué botón se pulsó.

   1      static void hello( GtkWidget *widget,
   2                     gpointer   data )
   3      {
   4         g_print ("Hello World\n");
   5      }

La siguiente función de conexión es un poco especial. El gestor de ventanas emite el evento "delete_event" a la aplicación y en ese momento se llamará a la función que hemos definido con el nombre "delete_event" . Ahora podemos reaccionar de varias formas dentro de la función: podemos ignorar el evento, procesarlo o símplemente cerrar la aplicación.

El valor que devuelva esta función de conexión le permite a GTK conocer qué acción debe llevar a cabo. Si devolvemos TRUE, estamos indicando que no queremos que se emita la señal "destroy", lo que permitirá que nuestra aplicación siga ejecutándose. Si devolvemos FALSE, indicaremos que se emita la señal "destroy", evento que será recogido por nuestra función de conexión "destroy".

   1 static gboolean delete_event( GtkWidget *widget,
   2                               GdkEvent  *event,
   3                               gpointer   data )
   4 {
   5     g_print ("delete event occurred\n");
   6 
   7     return TRUE;
   8 }

Aquí tenemos otra función callback que provoca que el programa termine mediante la llamada a la función gtk_main_quit(). Esta función indica a GTK que debe salir de gtk_main cuando se le devuelva el control desde la función.

   1 static void destroy( GtkWidget *widget,
   2                      gpointer   data )
   3 {
   4     gtk_main_quit ();
   5 }

Asumimos que el lector conoce la función main()... sí, como sucede con otras aplicaciones, todas las aplicaciones GTK también hacen uso de main.

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

El siguiente trozo de programa declara punteros a estructuras de tipo GtkWidget. Serán usadas más abajo para crear una ventana y un botón

   1     GtkWidget *window;
   2     GtkWidget *button;

Aquí tenemos de nuevo a gtk_init(). Como antes, esto inicializa el toolkit, y parsea los argumentos que encuentre en la línea de comandos. Cualquier argumento que reconozca en la línea de comandos será eliminado de la lista y de argc y argv, permitiendo a tu aplicación parsear el resto de argumentos.

   1     gtk_init (&argc, &argv);

Crear una nueva ventana es un proceso bastante directo. Se asigna memoria para la estructura GtkWidget *window de tal forma que apunte a una estructura válida. Se inicializa una nueva ventana, pero no se muestra hasta que se llame a

   1 gtk_widget_show(window)

cerca del final del program.

   1     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

Aquí tenemos dos ejemplos sobre cómo conectar un manejador de señal a un objeto, en este caso, una ventana. Aquí, las señales "delete_event" y "destroy" son capturadas y tratadas. La primera es emitida cuando usamos el gestor de ventanas para cerrar la ventan, o cuando usamos la llamada gtk_widget_destroy() pasando el widget de la ventana como el objeto a destruir. La segunda es emitida cuando, en el manejador "delete_event", devolvemos el valor FALSE. G_OBJECT y G_CALLBACK son macros que permiten realizar la conversión y comprobación de tipos de forma sencilla, además de facilitar la legibilidad del código.

   1     g_signal_connect (G_OBJECT (window), "delete_event",
   2                       G_CALLBACK (delete_event), NULL);
   3     g_signal_connect (G_OBJECT (window), "destroy",
   4                       G_CALLBACK (destroy), NULL);

La siguiente función se usa para asignar un atributo a un objeto contenedor. Lo que hacemos es ajustar la ventana para que tenga un área blanca alrededor de su perímetro de 10 pixels de ancho, donde no podrá ir ningún otro widget. Existen otras funciones similares que estudiaremos más adelante.

De nuevo, GTK_CONTAINER es una macro que permite la conversión de tipos.

   1     gtk_container_set_border_width (GTK_CONTAINER (window), 10);

La siguiente llamada crea un nuevo botón. Asigna espacio para una nueva estructura GtkWidget en memoria, la inicializa, y apunta el puntero button hacia él. Tendrá la etiqueta "Hello World" cuando sea mostrado.

   1     button = gtk_button_new_with_label ("Hello World");

Aquí haremos que el botón tenga alguna utilidad. Le asignaremos un manejador de señal de tal forma que cuando emita el evento "clicked", se llame a nuestra función hello(). El parámetro data se ignora, por lo que pasamos simplemente un NULL en esa posición a la función de conexión hello(). Obviamente, la señal "clicked" se emitirá cuando se haga click sobre el botón con el ratón.

   1     g_signal_connect (G_OBJECT (button), "clicked",
   2                       G_CALLBACK (hello), NULL);

También usaremos este botón para salir de nuestro programa. Esto ilustrará lo indicado antes sobre la señal "destroy": puede venir tanto del gestor de ventanas como de nuestro propio programa. Cuando se pulse el botón, al igual que antes, se llama primero a la función hello() y posteriormente a la función gtk_widget_destroy() en el mismo orden en el que se hayan definido las funciones callback. Puedes tener tantas funciones callback como desees, y todas será ejecutadas en el orden en el que fueron conectadas. Dado que la funcion gtk_widget_destroy() acepta únicamente un GtkWidget *widget como argumento, usaremos la función g_signal_connect_swapped() en lugar de g_signal_connect().

   1     g_signal_connect_swapped (G_OBJECT (button), "clicked",
   2                               G_CALLBACK (gtk_widget_destroy),
   3                               G_OBJECT (window));

La siguiente es una llamada a una función de empaquetamiento, que serán explicadas en el siguiente capítulo. Pero es bastante sencillo de entender. Símplemente indica a GTK que el botón debe situarse en la ventana en la que será mostrado. Obsérvese que un contenedor GTK únicamente puede contener un widget. Existen otros widgets, descritos más adelante, diseñados para albergar múltiples widgets en distintas posiciones.

   1     gtk_container_add (GTK_CONTAINER (window), button);

Ya tenemos todo dispuesto de la forma que queríamos. Con todos los manejadores de señal inicializados y el botón insertado en la ventana correctamente, sólo nos queda indicar a GTK que muestre los widgets en pantalla. El widget de la ventana se muestra el último, de tal forma que toda la ventana con todo su contenido salga al final "a escena", para evitar mostrar primero la ventana y después ver que el botón se coloca encima. Aunque con un ejemplo tan sencillo, nunca se notaría ningún efecto "raro".

   1     gtk_widget_show (button);
   2 
   3     gtk_widget_show (window);

Por supuesto, queda finalmente llamar a gtk_main() que se ejecurará en bucle, esperando eventos que vengan del servidor X y llamando a los widgets para que emitan señales cuando lleguen estos eventos.

   1     gtk_main ();

Para terminar el programa, se debe devolver 0 si todo ha ido correctamente, cuando se devuelva el control tras una llamada gtk_quit().

   1     return 0;

Ahora, cuando hagamos click con el botón del ratón en un botón GTK, el widget emitirá una señal "clicked". Para poder usar esta información, nuestro programa ha inicializado un manejador de señal para tratar este evento, que ejecutará la función que hayamos especificado. En nuestro ejemplo, cuando el botón creado sea pulsado, se llamará a a la función hello() con NULL como argumento; posteriormente, se ejecutará el siguiente manejador para esta señal. Esto llamará a gtk_widget_destroy(), pasándole el widget de la ventana como argumento, destruyéndolo. Esto provoca que la ventana emita la señal "destroy", que será interceptada, y llamará a nuestra función callback destroy(), que símplemente saldrá de GTK.

Otro posible camino de ejecución podría ser el usar el gestor de ventanas para cerrar la ventana, lo que provocará que se emita la señal "delete_event". Ésta será interceptada por nuestro manejador "delete_event". Si aquí devolvemos TRUE, la ventana seguirá como si nada hubiera ocurrido. Devolver FALSE provocará que GTK emita la señal "destroy" que por supuesto, llamará al callback "destroy", terminando la aplicación GTK.

Cómo compilar el ejemplo

Para compilar los ejemplos del capítulo sobre GTK 2.0, podemos usar la siguiente orden:

     gcc `pkg-config --cflags --libs gtk+-2.0` hello-world.c -o hello-world

Contenedores

GTK utiliza los contenedores para colocar los widgets de una forma determinada. Cuando se desarrolla una aplicación, normalmente se necesita colocar más de un widget dentro de una ventana. En el ejemplo anterior de helloworld se usa sólo un contenedor (gtk_container_add (GTK_CONTAINER (window), button);), para colocar el botón dentro de la ventana donde será mostrado. Pero, ¿qué pasa si se quiere usar más de un widget dentro de una ventana?, ¿cómo se puede controlar la posición de los widgets?

En otros sistemas gráficos (como por ejemplo MS Windows), la colocación de los widgets dentro de las ventanas se hace por medio de coordenadas relativas. Esto hace necesario un nivel de detalle a la hora de diseñar las ventanas de las aplicaciones que lo hacen indeseable. En GTK+, al igual que en todos los toolkits gráficos provinientes del mundo UNIX (Motif, QT, GTK, AWT de Java), está basado en el modelo de contenedores, donde no es necesario el uso de coordenadas. Simplemente se crean distintos tipos de contenedores (cada uno de los cuales coloca los widgets dentro de si mismo de una forma determinada) para cada caso concreto, y simplemente se colocan widgets dentro de dichos contenedores. La forma en que se metan los widgets dentro del contenedor define cómo se comportarán dichos widgets cuando la ventana contenedora cambie de tamaño.

Cajas

Existen varios tipos de contenedores que se irán explicando a lo largo de este capítulo, pero los widgets GtkHBox y GtkVBox son los más usados. GtkHBox y GtkVBox son cajas invisibles que sirven para empaquetar los widgets que se vayan a colocar dentro de una ventana u otro contenedor. Cuando se empaquetan widgets en una caja horizontal (GtkHBox) se insertan horizontalmente de izquierda a derecha o de derecha a izquierda, dependiendo de la función que se utilice después. En una caja vertical (GtkVBox) se insertan de arriba a abajo o vice versa. También se puede usar una combinación de cajas dentro o al lado de otras cajas para crear el efecto deseado.

Para crear una caja horizontal se hace una llamada a la función gtk_hbox_new(), y para cajas verticales, gtk_vbox_new(). Las funciones gtk_box_pack_start() y gtk_box_pack_end se usan para colocar los widgets dentro de las cajas creadas. La función gtk_box_pack_start coloca los widgets de arriba a abajo en una caja vertical y de izquierda a derecha en una horizontal, mientras que gtk_box_pack_end() hace lo contrario, que es colocar los widgets de abajo a arriba en una caja vertical y de derecha a izquierda en una horizontal. Usando estas funciones será posible alinear los widgets a la derecha o izquierda de la caja según se desee.

Usando estas funciones, GTK sabe en qué posición colocar los widgets y así poder cambiar el tamaño de los mismos automáticamente, cuando se cambia el tamaño del contenedor. Además cuentan con varias opciones para poder cambiar el estilo de colocación de los widgets. En la siguiente figura se muestran los 5 estilos diferentes de colocación.

Figura 1. Cinco estilos diferentes de colocación

packbox1.png

Cada línea contiene una caja horizontal (hbox) con varios botones. La llamada a la función gtk_box_pack es para colocar los botones en la caja horizontal.

Las dos funciones para añadir widgets a las cajas tienen la siguiente forma:

   1 void gtk_box_pack_start (       box,     
   2         child,   
   3         expand,          
   4         fill,    
   5         padding);        
   6 GtkBox *        box;
   7 GtkWidget *     child;
   8 gboolean        expand;
   9 gboolean        fill;
  10 guint   padding;
  11 void gtk_box_pack_end ( box,     
  12         child,   
  13         expand,          
  14         fill,    
  15         padding);        
  16 GtkBox *        box;
  17 GtkWidget *     child;
  18 gboolean        expand;
  19 gboolean        fill;
  20 guint   padding;

El primer argumento se refiere a la caja en la que se va a colocar el objeto, el segundo argumento es el objeto. Los objetos en este ejemplo son los botones, así que se colocarán los botones dentro de las cajas, pero puede ser cualquier otro widget.

El argumento expand controla que los botones se extiendan hasta rellenar todo el espacio dentro de la caja (TRUE), o que la caja se ajuste al tamaño de los botones (FALSE). Con expand con una valor de FALSE se pueden alinear los botones a la izquierda o a la derecha.

El argumento fill de las funciones gtk_box_pack controla si el espacio extra se coloca en los botones (TRUE), o como espacio extra entre cada botón (FALSE). Esto sólo tiene validez si el argumento expand está a TRUE.

El argumento padding controla el espacio añadido a cada lado del botón. En la siguiente figura se puede ver mejor el resultado.

Figura 2. Diferencias entre padding y spacing

packbox2.png

Para crear una caja, las funciones tienen el siguiente formato:

   1 GtkWidget * gtk_hbox_new (      homogenous,      
   2         spacing);        
   3 gboolean        homogenous;
   4 gint    spacing;
   5 GtkWidget * gtk_vbox_new (      homogenous,      
   6         spacing);        
   7 gboolean        homogenous;
   8 gint    spacing;

El argumento homogeneous controla si cada botón dentro de la caja tiene el mismo tamaño (la misma anchura en una hbox y la misma altura en una vbox). Si este argumento está a TRUE, las funciones de gtk_box_pack() funcionan como si el argumento expand estuviera siempre a TRUE.

El argumento spacing controla el espacio añadido entre los botones. La figura anterior muestra el resultado y la diferencia con el argumento padding de las funciones gtk_box_pack().

Ejemplo de uso de cajas

El código que se muestra a continuación es el usado para crear las figuras mostradas anteriormente.

   1 #include <stdio.h>
   2 #include <stdlib.h>
   3 #include "gtk/gtk.h"
   4 
   5 gint delete_event( GtkWidget *widget,
   6                    GdkEvent  *event,
   7                    gpointer   data )
   8 {
   9         gtk_main_quit ();
  10         return FALSE;
  11 }
  12 
  13 /* Crea una hbox con botones con etiquetas.
  14  * Se muestran todos los widgets (gtk_widget_show()) a excepción de la caja. */
  15 GtkWidget *make_box( gboolean homogeneous,
  16                      gint     spacing,
  17                      gboolean expand,
  18                      gboolean fill,
  19                      guint    padding )
  20 {
  21         GtkWidget *box;
  22         GtkWidget *button;
  23         char padstr[80];
  24 
  25         /* Crea una hbox con los parámetros establecidos
  26          * para homogeneous y spacing */
  27         box = gtk_hbox_new (homogeneous, spacing);
  28 
  29         /* Crea una serie de botones con etiqueta */
  30         button = gtk_button_new_with_label ("gtk_box_pack");
  31         gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  32         gtk_widget_show (button);
  33 
  34         button = gtk_button_new_with_label ("(box,");
  35         gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  36         gtk_widget_show (button);
  37 
  38         button = gtk_button_new_with_label ("button,");
  39         gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  40         gtk_widget_show (button);
  41 
  42         /* Crea un botón con etiqueta dependiendo del valor del parámetro
  43          * expand. */
  44         if (expand == TRUE)
  45                 button = gtk_button_new_with_label ("TRUE,");
  46         else
  47                 button = gtk_button_new_with_label ("FALSE,");
  48 
  49         gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  50         gtk_widget_show (button);
  51 
  52         /* Lo mismo con el botón de "expand"
  53          * pero esta vez de la forma abreviada. */
  54         button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
  55         gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  56         gtk_widget_show (button);
  57 
  58         sprintf (padstr, "%d);", padding);
  59 
  60         button = gtk_button_new_with_label (padstr);
  61         gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  62         gtk_widget_show (button);
  63 
  64         return box;
  65 }
  66 
  67 int main( int   argc,
  68           char *argv[])
  69 {
  70         GtkWidget *window;
  71         GtkWidget *button;
  72         GtkWidget *box1;
  73         GtkWidget *box2;
  74         GtkWidget *separator;
  75         GtkWidget *label;
  76         GtkWidget *quitbox;
  77         int which;
  78 
  79 
  80         gtk_init (&argc, &argv);
  81 
  82         if (argc != 2) {
  83                 fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n");
  84                 /* Esto finaliza GTK y sale con un estatus de 1. */
  85                 exit (1);
  86         }
  87 
  88         which = atoi (argv[1]);
  89 
  90         /* Crea una ventana */
  91         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  92 
  93         /* Simpre se debe conectar la señal delete_event a la ventana
  94          * principal, para así poder cerrarla.  */
  95         g_signal_connect (G_OBJECT (window), "delete_event",
  96                           G_CALLBACK (delete_event), NULL);
  97         gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  98 
  99         /* Crea una caja vertical (vbox) para colocar las cajas horizontales dentro.
 100          * Esto permite colocar las cajas horizontales llenas de botones una
 101          * encima de la otra dentro de la caja vertical (vbox). */
 102         box1 = gtk_vbox_new (FALSE, 0);
 103 
 104         /* Muestra una de las Figuras de arriba. */
 105         switch (which) {
 106         case 1:
 107                 /* crea una etiqueta. */
 108                 label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
 109 
 110                 /* Alínea la etiqueta a la izquierda.  Se hablará más adelante de esta
 111                  * función. */
 112                 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
 113 
 114                 /* Coloca la etiqueta dentro de la caja vertical (vbox box1).  Los
 115                  * widgets que se añaden a una vbox se colocan, por orden, uno
 116                  * encima del otro. */
 117                 gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
 118 
 119                 /* Muestra la etiqueta */
 120                 gtk_widget_show (label);
 121 
 122                 /* Llamada a la función make box, para crear un hbox con
 123                  * una serie de botones - homogeneous = FALSE, spacing = 0,
 124                  * expand = FALSE, fill = FALSE, padding = 0 */
 125                 box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
 126                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 127                 gtk_widget_show (box2);
 128 
 129                 /* Llamada a la función make box, para crear otra hbox con
 130                  * una serie de botones - homogeneous = FALSE, spacing = 0,
 131                  * expand = TRUE, fill = FALSE, padding = 0 */
 132                 box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
 133                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 134                 gtk_widget_show (box2);
 135 
 136                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 137                 box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
 138                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 139                 gtk_widget_show (box2);
 140 
 141                 /* Crea un separador, se verán más adelante,
 142                  * aunque son bastante sencillos. */
 143                 separator = gtk_hseparator_new ();
 144 
 145                 /* Coloca el separador en una vbox. Todos estos
 146                  * widgets han sido colocados en una vbox, así que estarán
 147                  * ordenados verticalmente. */
 148                 gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
 149                 gtk_widget_show (separator);
 150 
 151                 /* Crea otra etiqueta y la muestra. */
 152                 label = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
 153                 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
 154                 gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
 155                 gtk_widget_show (label);
 156 
 157                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 158                 box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
 159                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 160                 gtk_widget_show (box2);
 161 
 162                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 163                 box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
 164                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 165                 gtk_widget_show (box2);
 166 
 167                 /* Otro separador. */
 168                 separator = gtk_hseparator_new ();
 169                 /* Los tres últimos argumentos de gtk_box_pack_start son:
 170                  * expand, fill, padding. */
 171                 gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
 172                 gtk_widget_show (separator);
 173 
 174                 break;
 175 
 176         case 2:
 177 
 178                 /* Crea una etiqueta, box1 es una
 179                  * vbox que ya ha sido creada    */
 180                 label = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
 181                 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
 182                 gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
 183                 gtk_widget_show (label);
 184 
 185                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 186                 box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
 187                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 188                 gtk_widget_show (box2);
 189 
 190                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 191                 box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
 192                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 193                 gtk_widget_show (box2);
 194 
 195                 separator = gtk_hseparator_new ();
 196                 /* Los tres últimos argumentos de gtk_box_pack_start son:
 197                  * expand, fill, padding. */
 198                 gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
 199                 gtk_widget_show (separator);
 200 
 201                 label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
 202                 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
 203                 gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
 204                 gtk_widget_show (label);
 205 
 206                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 207                 box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
 208                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 209                 gtk_widget_show (box2);
 210 
 211                 /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
 212                 box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
 213                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 214                 gtk_widget_show (box2);
 215 
 216                 separator = gtk_hseparator_new ();
 217                 /* Los tres últimos argumentos de gtk_box_pack_start son: expand, fill, padding. */
 218                 gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
 219                 gtk_widget_show (separator);
 220                 break;
 221 
 222         case 3:
 223 
 224                 /* Esto demuestra la capacidad de gtk_box_pack_end() para
 225                  * alinear a la derecha los widgets. Primero, se crea una caja. */
 226                 box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
 227 
 228                 /* Crea una etiqueta colocada al final. */
 229                 label = gtk_label_new ("end");
 230                 /* Coloca la etiqueta usando gtk_box_pack_end(), así que se situa a la
 231                  * derecha de la hbox creada con la llamada a make_box(). */
 232                 gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
 233                 /* Show the label. */
 234                 gtk_widget_show (label);
 235 
 236                 /* Coloca box2 dentro de box1 (vbox) */
 237                 gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
 238                 gtk_widget_show (box2);
 239 
 240                 /* Crea un separador. */
 241                 separator = gtk_hseparator_new ();
 242                 /* Establece las medidas del separador, una anchura de 400 pixels por 5 pixels
 243                  * de altura. La hbox que se creó anteriormente también tendrá 400 pixels de
 244                  * anchura,y la etiqueta "end" se separa de las otras etiquetas en la
 245                  * hbox. */
 246                 gtk_widget_set_size_request (separator, 400, 5);
 247                 /* coloca el separador en la vbox (box1) */
 248                 gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
 249                 gtk_widget_show (separator);
 250         }
 251 
 252         /* Crea otra hbox.. (se pueden crear tantas cajas como se necesiten) */
 253         quitbox = gtk_hbox_new (FALSE, 0);
 254 
 255         /* Crea el botón para salir del programa. */
 256         button = gtk_button_new_with_label ("Quit");
 257 
 258         /* Establece la señal para terminar el programa cundo se pulsa el botón ("quit") */
 259         g_signal_connect_swapped (G_OBJECT (button), "clicked",
 260                                   G_CALLBACK (gtk_main_quit),
 261                                   G_OBJECT (window));
 262         /* Coloca el botón en la hbox (quitbox).
 263          * Los 3 últimos argumentos de gtk_box_pack_start son:
 264          * expand, fill, padding. */
 265         gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
 266         /* Coloca la hbox (quitbox) en la vbox (box1) */
 267         gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
 268 
 269         /* Coloca la vbox (box1), la cual contiene ahora todos los widgets, en la
 270          * ventana principal. */
 271         gtk_container_add (GTK_CONTAINER (window), box1);
 272 
 273         /* Se muestra todo lo restante */
 274         gtk_widget_show (button);
 275         gtk_widget_show (quitbox);
 276 
 277         gtk_widget_show (box1);
 278         /* Se muestra la ventana en último lugar, así todo se muestra a la vez. */
 279         gtk_widget_show (window);
 280 
 281         /* La función principal. */
 282         gtk_main ();
 283 
 284         /* El control vuelve aquí cuando se llama a gtk_main_quit(), pero no cuando
 285          * se usa exit(). */
 286 
 287         return 0;
 288 }

Cajas de botones

Uno de los usos más comunes que se le dan a las cajas citadas en el apartado anterior es para agrupar botones. Al ser esta una tarea muy común, GTK incluye las cajas de botones (Gtk?ButtonBox).

Las cajas de botones son una utilidad que permite crear grupos de botones de una forma rápida y sencilla. Estas cajas de botones pueden ser horizontales o verticales.

Las siguientes funciones son para crear una caja de botones horizontal y vertical.

   1           GtkWidget *gtk_hbutton_box_new( void );
   2 
   3           GtkWidget *gtk_vbutton_box_new( void );

Para añadir los botones a la caja de botones, se usa la siguiente función:

   1           gtk_container_add (GTK_CONTAINER (button_box), child_widget);

El siguiente ejemplo muestra las diferentes layout settings de las cajas de botones.

Figura 3. Diferentes layout settings de las cajas de botones

cajadebotones.png

   1 #include <gtk/gtk.h>
   2 
   3 /* Crea una Caja de Botones con los parámetros específicos */
   4 GtkWidget *create_bbox( gint  horizontal,
   5                         char *title,
   6                         gint  espacio,
   7                         gint  child_w,
   8                         gint  child_h,
   9                         gint  layout )
  10 {
  11   GtkWidget *frame;
  12   GtkWidget *bbox;
  13   GtkWidget *button;
  14 
  15   frame = gtk_frame_new (title);
  16 
  17   if (horizontal)
  18     bbox = gtk_hbutton_box_new ();
  19   else
  20     bbox = gtk_vbutton_box_new ();
  21 
  22   gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
  23   gtk_container_add (GTK_CONTAINER (frame), bbox);
  24 
  25   /* Establece la apariencia de la Caja de Botones */
  26   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), layout);
  27   gtk_box_set_spacing (GTK_BOX (bbox), espacio);
  28   /*gtk_button_box_set_child_size (GTK_BUTTON_BOX (bbox), child_w, child_h);*/
  29 
  30   button = gtk_button_new_from_stock (GTK_STOCK_OK);
  31   gtk_container_add (GTK_CONTAINER (bbox), button);
  32 
  33   button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
  34   gtk_container_add (GTK_CONTAINER (bbox), button);
  35 
  36   button = gtk_button_new_from_stock (GTK_STOCK_HELP);
  37   gtk_container_add (GTK_CONTAINER (bbox), button);
  38 
  39   return frame;
  40 }
  41 
  42 int main( int   argc,
  43           char *argv[] )
  44 {
  45   static GtkWidget* window = NULL;
  46   GtkWidget *main_vbox;
  47   GtkWidget *vbox;
  48   GtkWidget *hbox;
  49   GtkWidget *frame_horz;
  50   GtkWidget *frame_vert;
  51 
  52   /* Inicializa GTK */
  53   gtk_init (&argc, &argv);
  54 
  55   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  56   gtk_window_set_title (GTK_WINDOW (window), "Cajas de Botones");
  57 
  58   g_signal_connect (G_OBJECT (window), "destroy",
  59                     G_CALLBACK (gtk_main_quit),
  60                     NULL);
  61 
  62   gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  63 
  64   main_vbox = gtk_vbox_new (FALSE, 0);
  65   gtk_container_add (GTK_CONTAINER (window), main_vbox);
  66 
  67   frame_horz = gtk_frame_new ("Cajas de Botones Horizontales");
  68   gtk_box_pack_start (GTK_BOX (main_vbox), frame_horz, TRUE, TRUE, 10);
  69 
  70   vbox = gtk_vbox_new (FALSE, 0);
  71   gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
  72   gtk_container_add (GTK_CONTAINER (frame_horz), vbox);
  73 
  74   gtk_box_pack_start (GTK_BOX (vbox),
  75            create_bbox (TRUE, "Spread (espacio 40)", 40, 85, 20, GTK_BUTTONBOX_SPREAD),
  76                       TRUE, TRUE, 0);
  77 
  78   gtk_box_pack_start (GTK_BOX (vbox),
  79            create_bbox (TRUE, "Edge (espacio 30)", 30, 85, 20, GTK_BUTTONBOX_EDGE),
  80                       TRUE, TRUE, 5);
  81 
  82   gtk_box_pack_start (GTK_BOX (vbox),
  83            create_bbox (TRUE, "Start (espacio 20)", 20, 85, 20, GTK_BUTTONBOX_START),
  84                       TRUE, TRUE, 5);
  85 
  86   gtk_box_pack_start (GTK_BOX (vbox),
  87            create_bbox (TRUE, "End (espacio 10)", 10, 85, 20, GTK_BUTTONBOX_END),
  88                       TRUE, TRUE, 5);
  89 
  90   frame_vert = gtk_frame_new ("Cajas de Botones Verticales");
  91   gtk_box_pack_start (GTK_BOX (main_vbox), frame_vert, TRUE, TRUE, 10);
  92 
  93   hbox = gtk_hbox_new (FALSE, 0);
  94   gtk_container_set_border_width (GTK_CONTAINER (hbox), 10);
  95   gtk_container_add (GTK_CONTAINER (frame_vert), hbox);
  96 
  97   gtk_box_pack_start (GTK_BOX (hbox),
  98            create_bbox (FALSE, "Spread (espacio 5)", 5, 85, 20, GTK_BUTTONBOX_SPREAD),
  99                       TRUE, TRUE, 0);
 100 
 101   gtk_box_pack_start (GTK_BOX (hbox),
 102            create_bbox (FALSE, "Edge (espacio 30)", 30, 85, 20, GTK_BUTTONBOX_EDGE),
 103                       TRUE, TRUE, 5);
 104 
 105   gtk_box_pack_start (GTK_BOX (hbox),
 106            create_bbox (FALSE, "Start (espacio 20)", 20, 85, 20, GTK_BUTTONBOX_START),
 107                       TRUE, TRUE, 5);
 108 
 109   gtk_box_pack_start (GTK_BOX (hbox),
 110            create_bbox (FALSE, "End (espacio 20)", 20, 85, 20, GTK_BUTTONBOX_END),
 111                       TRUE, TRUE, 5);
 112 
 113   gtk_widget_show_all (window);
 114 
 115 
 116   gtk_main ();
 117 
 118   return 0;
 119 }

Tablas

Otro tipo de contenedor son las tablas, muy útiles en algunas ocasiones.

Usando tablas, se crea una rejilla en la que se pueden colocar widgets. Los widgets pueden ocupar los espacios que se especifiquen (1 o más celdas).

La función para crear tablas es gtk_table_new, y tiene la siguiente forma:

   1 GtkWidget * gtk_table_new (     rows,    
   2         columns,         
   3         homogeneous);    
   4 guint   rows;
   5 guint   columns;
   6 gboolean        homogeneous;

El primer argumento es el número de filas de la tabla, el segundo, obviamente, el número de columnas. El argumento homogeneous especifica las medidas de las cajas de la tabla. Si su valor es TRUE, las cajas de la tabla se ajustan al tamaño del widget más largo que esté en la tabla. Por contra, si vale FALSE, las cajas de la tabla se ajustan al tamaño del widget más alto de la fila y el más ancho de la columna.

Las filas y columnas empiezan de 0 a n, siendo “n” el número especificado en la llamada a gtk_table_new(). Si se especifica por ejemplo, 2 filas (rows = 2) y 2 columnas (columns = 2), la estructura quedaría como se ve en la siguiente imagen:

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

Hay que tener en cuenta que el sistema de coordenadas comienza en la esquina superior izquierda (0,0).

Para colocar widgets dentro de la tabla, se usa la función gtk_table_attach, que tiene la siguiente forma:

   1 void gtk_table_attach ( table,   
   2         child,   
   3         left_attach,     
   4         right_attach,    
   5         top_attach,      
   6         bottom_attach,   
   7         xoptions,        
   8         yoptions,        
   9         xpadding,        
  10         ypadding);       
  11 GtkTable *      table;
  12 GtkWidget *     child;
  13 guint   left_attach;
  14 guint   right_attach;
  15 guint   top_attach;
  16 guint   bottom_attach;
  17 GtkAttachOptions        xoptions;
  18 GtkAttachOptions        yoptions;
  19 guint   xpadding;
  20 guint   ypadding;

El primer argumento es la tabla que se ha creado y el segundo el widget que se va a colocar en la tabla. Los argumentos left_attach y right_attach especifican dónde colocar el widget, y cuántas celdas usar. Si, por ejemplo, se quiere posicionar un botón en la parte inferior derecha de la tabla anterior de 2x2 y rellenar únicamente esa entrada, los valores de los argumentos serían los siguientes: left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2.

Ahora bien, si se quiere posicionar un widget que ocupe la fila superior entera de la tabla de 2x2, los valores de los argumentos serían los siguientes: left_attach = 0, right_attach = 2, top_attach = 0, bottom_attach = 1.

Los argumentos xoptions y yoptions especifican las opciones de colocación en forma de máscara de bits, donde los valores existentes para el tipo GtkAttachOptions pueden ser agrupados mediante el operador binario OR (|). Estas opciones son:

    • GTK_FILL: Si la caja de la tabla es más larga que el widget, y se ha especificado GTK_FILL, el widget se extenderá hasta ocupar todo el sitio que ocupa la caja.
    • GTK_SHRINK: Cuando se reduce el tamaño de la tabla, los widgets, normalmente, no cambian su tamaño junto con el de la tabla, de forma que las partes inferior y derecha de dichos widgets desaparezca a medida que se reduce el tamaño de la tabla. Usando GTK_SHRINK los widgets reducirán su tamaño junto con el de la tabla.
    • GTK_EXPAND: La tabla se extiende hasta ocupar todo el espacio de la ventana.

El argumento padding funciona de la misma forma que con cajas, es decir, especifica el espacio, en pixels, alrededor del widget.

Al tener tantas opciones la función gtk_table_attach, GTK ofrece una función extra que permite añadir widgets a la tabla usando los valores por defecto de colocación. Dicha función es gtk_table_attach_defaults, cuya sintaxis es la siguiente:

   1 void gtk_table_attach_defaults (        table,   
   2         child,   
   3         left_attach,     
   4         right_attach,    
   5         top_attach,      
   6         bottom_attach);  
   7 GtkTable *      table;
   8 GtkWidget *     child;
   9 guint   left_attach;
  10 guint   right_attach;
  11 guint   top_attach;
  12 guint   bottom_attach;

Como puede apreciarse, los parámetros para especificar las opciones de colocación y de espaciado alrededor del widget han sido omitidos en esta función con respecto a gtk_table_attach. Eso se debe a que gtk_table_attach_defaults toma unos valores por defecto para esos parámetros, que son GTK_FILL | GTK_EXPAND en el caso de xoptions y yoptions, y 0 en el caso de xpadding e ypadding. El resto de argumentos son idénticos a los comentados para la función gtk_table_attach.

Las funciones gtk_table_set_row_spacing() y gtk_table_set_col_spacing(), añaden espacio entre las filas y las columnas.

   1 void gtk_table_set_row_spacing( table,   
   2         row,     
   3         spacing);        
   4 GtkTable *      table;
   5 guint   row;
   6 guint   spacing;
   7 
   8 void gtk_table_set_col_spacing( table,   
   9         column,          
  10         spacing);        
  11 GtkTable *      table;
  12 guint   column;
  13 guint   spacing;

Hay que tener en cuenta que el espacio se sitúa a la derecha de la columna, y en las filas, se sitúa debajo de la fila.

También es posible establecer un espacio para las filas y columnas a la vez, con las siguientes funciones:

   1 void gtk_table_set_row_spacings(        table,   
   2         spacing);        
   3 GtkTable *      table;
   4 guint   spacing;
   5 
   6 void gtk_table_set_col_spacings(        table,   
   7         spacing);        
   8 GtkTable *      table;
   9 guint   spacing;

Con estas funciones, no se deja espacio en la última fila y la última columna.

Ejemplo de uso de tablas

A continuación se muestra el código para crear una ventana con tres botones en una tabla de 2x2. Los primeros dos botones se colocan en la fila superior de la tabla. El tercero, el botón "quit", se coloca en la fila inferior, ocupando las dos columnas.

Figura 4. Tabla con 3 botones

tablepacking.png

   1 #include <gtk/gtk.h>
   2 
   3 /* Los datos pasados a esta función se imprimen en la salida estándar (stdout) */
   4 void callback( GtkWidget *widget,
   5                gpointer   data )
   6 {
   7     g_print ("Hello again - %s was pressed\n", (char *) data);
   8 }
   9 
  10 /* Función para terminar el programa */
  11 gint delete_event( GtkWidget *widget,
  12                    GdkEvent  *event,
  13                    gpointer   data )
  14 {
  15     gtk_main_quit ();
  16     return FALSE;
  17 }
  18 
  19 int main( int   argc,
  20           char *argv[] )
  21 {
  22     GtkWidget *window;
  23     GtkWidget *button;
  24     GtkWidget *table;
  25 
  26     gtk_init (&argc, &argv);
  27 
  28     /* Crea una ventana */
  29     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  30 
  31     /* Establece el título de la ventana */
  32     gtk_window_set_title (GTK_WINDOW (window), "Table");
  33 
  34     /* Establece un manejador para "delete_event" que inmediatamente
  35      * cerrará GTK. */
  36     g_signal_connect (G_OBJECT (window), "delete_event",
  37                       G_CALLBACK (delete_event), NULL);
  38 
  39     /* Establece el tamaño del borde de la ventana. */
  40     gtk_container_set_border_width (GTK_CONTAINER (window), 20);
  41 
  42     /* Crea una tabla de 2x2 */
  43     table = gtk_table_new (2, 2, TRUE);
  44 
  45     /* Coloca la tabla en la ventana principal */
  46     gtk_container_add (GTK_CONTAINER (window), table);
  47 
  48     /* Crea el primer botón */
  49     button = gtk_button_new_with_label ("button 1");
  50 
  51     /* cuando se pulsa el botón, se llama a la función que se ha creado anteriormente
  52      * "callback", con un puntero a "button 1" como argumento */
  53     g_signal_connect (G_OBJECT (button), "clicked",
  54                       G_CALLBACK (callback), (gpointer) "button 1");
  55 
  56 
  57     /* Coloca el primer botón (button 1) en el cuadrante superior izquierdo de la tabla */
  58     gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 1, 0, 1);
  59 
  60     gtk_widget_show (button);
  61 
  62     /* Crea el segundo botón */
  63 
  64     button = gtk_button_new_with_label ("button 2");
  65 
  66     /* cuando se pulsa el botón, se llama a la función que se ha creado anteriormente
  67      * "callback", con un puntero a "button 2" como argumento */
  68     g_signal_connect (G_OBJECT (button), "clicked",
  69                       G_CALLBACK (callback), (gpointer) "button 2");
  70     /* Coloca el segundo botón (button 2) en el cuadrante superior derecho de la tabla. */
  71     gtk_table_attach_defaults (GTK_TABLE (table), button, 1, 2, 0, 1);
  72 
  73     gtk_widget_show (button);
  74 
  75     /* Crea el botón de "Quit" */
  76     button = gtk_button_new_with_label ("Quit");
  77 
  78     /* Cuando se pulsa el botón, se hace una llamada a función "delete_event" y el
  79      * programa termina. */
  80     g_signal_connect (G_OBJECT (button), "clicked",
  81                       G_CALLBACK (delete_event), NULL);
  82 
  83     /* Coloca el botón "quit" ocupando los dos cuadrantes inferiores de la tabla.*/
  84     gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 2, 1, 2);
  85 
  86     gtk_widget_show (button);
  87 
  88     gtk_widget_show (table);
  89     gtk_widget_show (window);
  90 
  91     gtk_main ();
  92 
  93     return 0;
  94 }

GtkNotebook

El widget GtkNotebook es una colección de "páginas" que se solapan entre ellas. Cada página, sólo visible una cada vez, contiene una información (widgets) determinada. Este widget ha sido cada vez más común en la programación de interfaces gráficos, ya que es una buena forma de mostrar bloques de similar información uno a uno.

La función que se necesita para la creación de un widget notebook es la siguiente:

   1 GtkWidget * gtk_notebook_new();
   2 void;

GTK también tiene varias funciones para establecer el formato del widget notebook. La primera de estas funciones sirve para posicionar los indicadores de página o pestañas, los cuales pueden ser colocados arriba, abajo, a derecha o izquierda.

   1 void gtk_notebook_set_tab_pos(  notebook,        
   2         pos);    
   3 GtkNotebook *   notebook;
   4 GtkPositionType         pos;

Los diferentes tipos de la variable de tipo GtkPositionType son los siguientes:

    • GTK_POS_LEFT (posición a la izquierda)
    • GTK_POS_RIGHT (posición a la derecha)
    • GTK_POS_TOP (posición arriba)
    • GTK_POS_BOTTOM (posición abajo)
    • GTK_POS_TOP es la opción por defecto.

El siguiente paso sería añadir páginas al notebook. Existes tres formas de añadir páginas. Las dos primeras que se muestran a continuación, y son muy similares.

   1 void gtk_notebook_append_page(  notebook,        
   2         child,   
   3         tab_label);      
   4 GtkNotebook *   notebook;
   5 GtkWidget *     child;
   6 GtkWidget *     tab_label;
   7 
   8 void gtk_notebook_prepend_page( notebook,        
   9         child,   
  10         tab_label);      
  11 GtkNotebook *   notebook;
  12 GtkWidget *     child;
  13 GtkWidget *     tab_label;

La primera función (gtk_notebook_append_page), añade las páginas al final del notebook y la segunda (gtk_botebook_prepend_page), las añade al principio. El argumento child es el widget que será colocado dentro de la página del notebook, y tab_label es la etiqueta de la página que será añadida. El widget child deberá ser creado por separado y normalmente suele ser un contenedor que contiene otros widgets, aunque, por supuesto, puede ser cualquier widget (cualquier GtkWidget).

La última función para añadir páginas al notebook contiene todas las propiedades de las dos anteriores, pero además permite especificar en qué posición se quiere colocar la pagína dentro del notebook. Es decir, permite insertar una página antes de otra ya existente en el notebook.

   1 void gtk_notebook_insert_page(  notebook,        
   2         child,   
   3         tab_label,       
   4         position);       
   5 GtkNotebook *   notebook;
   6 GtkWidget *     child;
   7 GtkWidget *     tab_label;
   8 gint    position;

El parámetro extra (position) se usa para especificar la posición en la que se desea insertar la nueva página. Hay que tener en cuenta que 0 representa la primera página.

Para borrar una página del notebook, se utiliza la siguiente función:

   1 void gtk_notebook_remove_page(  notebook,        
   2         page_num);       
   3 GtkNotebook *   notebook;
   4 gint    page_num;

Esta función borra del notebook, la página que se haya especificado en el parámetro page_num.

Para saber en que página se encuentra el notebook se utiliza la siguiente función:

   1 gint gtk_notebook_get_current_page(     notebook);       
   2 GtkNotebook *   notebook;

Esta función devuelve la posición de la página actual dentro del notebook, siendo 0 la primera página.

Las suguientes funciones sirven para mover las hojas del notebook hacia adelante o hacia atrás. Si el notebook está en la última página y se llama a la función gtk_notebook_next_page éste volverá a la primera página. Igualmente si se llama a la función gtk_notebook_prev_page y notebook está en la primera página, éste volverá a la última página.

   1 void gtk_notebook_next_page(    notebook);       
   2 GtkNoteBook *   notebook;
   3 
   4 void gtk_notebook_prev_page(    notebook);       
   5 GtkNoteBook *   notebook;

La siguiente función sirve para activar una página determinada del notebook. Si por ejemplo se necesita activar la página 5 del notebook, se usaría esta función. Si no se usa esta función, el notebook se sitúa siempre en la primera página al ser creada ésta y las siguientes que se vayan añadiendo.

   1 void gtk_notebook_set_current_page(     notebook,        
   2         page_num);       
   3 GtkNotebook *   notebook;
   4 gint    page_num;

Las siguientes funciones sirven para mostrar u ocultar los indicadores o pestañas y los bordes del notebook respectivamente.

   1 void gtk_notebook_set_show_tabs(        notebook,        
   2         show_tabs);      
   3 GtkNotebook *   notebook;
   4 gboolean        show_tabs;
   5 
   6 void gtk_notebook_set_show_border(      notebook,        
   7         show_border);    
   8 GtkNotebook *   notebook;
   9 gboolean        show_border;

Ambas funciones reciben un valor de tipo boolean que especifica si se debe (TRUE) o no (FALSE) mostrar los indicadores o los bordes.

La siguiente función es muy útil cuando se tiene un gran número de páginas y las pestañas no caben en la página. La función crea dos botones con flechas, para así permitir al usuario el ir recorriendo las múltiples pestañas.

   1 void gtk_notebook_set_scrollable(       notebook,        
   2         scrollable);     
   3 GtkNotebook *   notebook;
   4 gboolean        scrollable;

El siguiente ejemplo se ha sacado del fichero testgtk.c que viene junto con la distribución de GTK. Este pequeño programa, crea una ventana con un notebook que contiene seis botones. El notebook contiene 11 páginas, que son añadidas de tres maneras diferentes (append, insert y prepend). Los botones permiten cambiar la posición de la pestaña, borrar o añadir los bordes y pestañas del notebook y cambiar a la anterior o siguiente página.

Figura 5. GtkNotebook en acción

notebook.png

   1 #include <stdio.h>
   2 #include <gtk/gtk.h>
   3 
   4 /* Esta función va cambiando la posición de las pestañas
   5 This function rotates the position of the tabs */
   6 void rotate_book( GtkButton   *button,
   7                   GtkNotebook *notebook )
   8 {
   9     gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos + 1) % 4);
  10 }
  11 
  12 /* Añade o borra las pestañas y los bordes del notebook */
  13 void tabsborder_book( GtkButton   *button,
  14                       GtkNotebook *notebook )
  15 {
  16     gint tval = FALSE;
  17     gint bval = FALSE;
  18     if (notebook->show_tabs == 0)
  19             tval = TRUE;
  20     if (notebook->show_border == 0)
  21             bval = TRUE;
  22 
  23     gtk_notebook_set_show_tabs (notebook, tval);
  24     gtk_notebook_set_show_border (notebook, bval);
  25 }
  26 
  27 /* Borra una página del notebook */
  28 void remove_book( GtkButton   *button,
  29                   GtkNotebook *notebook )
  30 {
  31     gint page;
  32 
  33     page = gtk_notebook_get_current_page (notebook);
  34     gtk_notebook_remove_page (notebook, page);
  35     /* Con la siguiente función se vuelve a dibujar el notebook
  36      * para que no se vea la página que ha sido borrada. */
  37     gtk_widget_queue_draw (GTK_WIDGET (notebook));
  38 }
  39 
  40 gint delete( GtkWidget *widget,
  41              GtkWidget *event,
  42              gpointer   data )
  43 {
  44     gtk_main_quit ();
  45     return FALSE;
  46 }
  47 
  48 int main( int argc,
  49           char *argv[] )
  50 {
  51     GtkWidget *window;
  52     GtkWidget *button;
  53     GtkWidget *table;
  54     GtkWidget *notebook;
  55     GtkWidget *frame;
  56     GtkWidget *label;
  57     GtkWidget *checkbutton;
  58     int i;
  59     char bufferf[32];
  60     char bufferl[32];
  61 
  62     gtk_init (&argc, &argv);
  63 
  64     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  65 
  66     g_signal_connect (G_OBJECT (window), "delete_event",
  67                       G_CALLBACK (delete), NULL);
  68 
  69     gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  70 
  71     table = gtk_table_new (3, 6, FALSE);
  72     gtk_container_add (GTK_CONTAINER (window), table);
  73 
  74     /* Crea un notebook, y especifica la posición de las pestañas */
  75     notebook = gtk_notebook_new ();
  76     gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  77     gtk_table_attach_defaults (GTK_TABLE (table), notebook, 0, 6, 0, 1);
  78     gtk_widget_show (notebook);
  79 
  80     /* El siguiente bucle, añade 5 páginas al notebook (append)*/
  81     for (i = 0; i < 5; i++) {
  82         sprintf(bufferf, "Append Frame %d", i + 1);
  83         sprintf(bufferl, "Page %d", i + 1);
  84 
  85         frame = gtk_frame_new (bufferf);
  86         gtk_container_set_border_width (GTK_CONTAINER (frame), 10);
  87         gtk_widget_set_size_request (frame, 100, 75);
  88         gtk_widget_show (frame);
  89 
  90         label = gtk_label_new (bufferf);
  91         gtk_container_add (GTK_CONTAINER (frame), label);
  92         gtk_widget_show (label);
  93 
  94         label = gtk_label_new (bufferl);
  95         gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label);
  96     }
  97 
  98     /* Se añade un página para Now let's add a page to a specific spot */
  99     checkbutton = gtk_check_button_new_with_label ("Check me please!");
 100     gtk_widget_set_size_request (checkbutton, 100, 75);
 101     gtk_widget_show (checkbutton);
 102 
 103     label = gtk_label_new ("Add page");
 104     gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2);
 105 
 106     /* El siguiente bucle, añade 5 páginas al notebook (prepend)*/
 107     for (i = 0; i < 5; i++) {
 108         sprintf (bufferf, "Prepend Frame %d", i + 1);
 109         sprintf (bufferl, "PPage %d", i + 1);
 110 
 111         frame = gtk_frame_new (bufferf);
 112         gtk_container_set_border_width (GTK_CONTAINER (frame), 10);
 113         gtk_widget_set_size_request (frame, 100, 75);
 114         gtk_widget_show (frame);
 115 
 116         label = gtk_label_new (bufferf);
 117         gtk_container_add (GTK_CONTAINER (frame), label);
 118         gtk_widget_show (label);
 119 
 120         label = gtk_label_new (bufferl);
 121         gtk_notebook_prepend_page (GTK_NOTEBOOK (notebook), frame, label);
 122     }
 123 
 124     /* Con esta función, se mostrará una página específica "page 4" */
 125     gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 3);
 126 
 127     /* Crea los botones */
 128     button = gtk_button_new_with_label ("close");
 129     g_signal_connect_swapped (G_OBJECT (button), "clicked",
 130                               G_CALLBACK (delete), NULL);
 131     gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 1, 1, 2);
 132     gtk_widget_show (button);
 133 
 134     button = gtk_button_new_with_label ("next page");
 135     g_signal_connect_swapped (G_OBJECT (button), "clicked",
 136                               G_CALLBACK (gtk_notebook_next_page),
 137                               G_OBJECT (notebook));
 138     gtk_table_attach_defaults (GTK_TABLE (table), button, 1, 2, 1, 2);
 139     gtk_widget_show (button);
 140 
 141     button = gtk_button_new_with_label ("prev page");
 142     g_signal_connect_swapped (G_OBJECT (button), "clicked",
 143                               G_CALLBACK (gtk_notebook_prev_page),
 144                               G_OBJECT (notebook));
 145     gtk_table_attach_defaults (GTK_TABLE (table), button, 2, 3, 1, 2);
 146     gtk_widget_show (button);
 147 
 148     button = gtk_button_new_with_label ("tab position");
 149     g_signal_connect (G_OBJECT (button), "clicked",
 150                       G_CALLBACK (rotate_book),
 151                       (gpointer) notebook);
 152     gtk_table_attach_defaults (GTK_TABLE (table), button, 3, 4, 1, 2);
 153     gtk_widget_show (button);
 154 
 155     button = gtk_button_new_with_label ("tabs/border on/off");
 156     g_signal_connect (G_OBJECT (button), "clicked",
 157                       G_CALLBACK (tabsborder_book),
 158                       (gpointer) notebook);
 159     gtk_table_attach_defaults (GTK_TABLE (table), button, 4, 5, 1, 2);
 160     gtk_widget_show (button);
 161 
 162     button = gtk_button_new_with_label ("remove page");
 163     g_signal_connect (G_OBJECT (button), "clicked",
 164                       G_CALLBACK (remove_book),
 165                       (gpointer) notebook);
 166     gtk_table_attach_defaults (GTK_TABLE (table), button, 5, 6, 1, 2);
 167     gtk_widget_show (button);
 168 
 169     gtk_widget_show (table);
 170     gtk_widget_show (window);
 171 
 172     gtk_main ();
 173 
 174     return 0;
 175 }

GtkAlignment

El widget GtkAlignment permite colocar widgets en una posición y tamaño relativos al tamaño de sí mismo. Esto puede ser muy útil, por ejemplo, a la hora de centrar un widget en una ventana.

Sólo hay dos funciones asociadas a este widget, que son:

   1 GtkWidget * gtk_alignment_new(  xalign,          
   2         yalign,          
   3         xscale,          
   4         yscale);         
   5 gfloat          xalign;
   6 gfloat          yalign;
   7 gfloat          xscale;
   8 gfloat          yscale;
   9 
  10 void gtk_alignment_set( alignment,       
  11         xalign,          
  12         yalign,          
  13         xscale,          
  14         yscale);         
  15 GtkAlignment *          alignment;
  16 gfloat          xalign;
  17 gfloat          yalign;
  18 gfloat          xscale;
  19 gfloat          yscale;

La primera función crea un nuevo widget GtkAlignment con los parámetros especificados. La segunda función permite cambiar los parámetros de un widget GtkAlignment ya existente.

Los cuatro parámetros de estas funciones son de tipo gfloat con un valor que puede ir de 0.0 a 1.0. Los argumentos xalign e yalign determinan la posición del widget dentro del GtkAlignment. Los argumentos xscale e yscale determinan la cantidad de espacio asignado al widget.

También se puede añadir un widget al GtkAlignment usando la siguiente función (perteneciente a la clase GtkContainer, de la que deriva GtkAlignment):

   1           gtk_container_add (GTK_CONTAINER (alignment), child_widget);

Se puede ver un ejemplo del uso del widget GtkAlignment en el programa de ejemplo del uso del widget de barra de progreso (GtkProgressBar).

GtkHPaned/GtkVPaned

Los widgets "paned" se utilizan cuando se quiere dividir un área en dos partes, con el tamaño de las dos partes controlado por el usuario. Un separador, con un manejador que el usuario puede arrastrar y cambiar el ratio, separa las dos partes. La división puede ser horizontal (GtkHPaned) o vertical (GtkVPaned).

Para crear una ventana "paned", horizontal o vertical, se necesita una de las siguientes funciones:

   1           GtkWidget *gtk_hpaned_new (void);
   2 
   3           GtkWidget *gtk_vpaned_new (void);

Después de crear una ventana "paned", se necesita añadir widgets a las dos mitades. Para ello GTK cuenta con las siguientes funciones:

   1           void gtk_paned_add1 (GtkPaned *paned, GtkWidget *child);
   2 
   3           void gtk_paned_add2 (GtkPaned *paned, GtkWidget *child);

La función gtk_paned_add1 añade el widget hijo a la izquierda o arriba de una de las partes de la ventana "paned", mientras que la función gtk_paned_add2 añade el widget hijo a la derecha o abajo de una de las partes de la ventana "paned".

El siguiente ejemplo crea parte de un interfaz de usuario de un programa de correo imaginario. La ventana se divide en dos partes verticales. La parte de arriba muestra una lista de los mensajes recibidos y la parte de abajo el texto de los mensajes. Hay que tener dos cosas en cuenta: no se puede añadir texto a un widget de texto hasta no indicarlo explícitamente a GTK ("realize"). Ésto puede hacerse llamando a la gtk_widget_realize(), pero como demostración de una técnica alternativa, conectaremos un manejador a la señal "realize" para añadir texto al widget. Además, necesitaremos añadir la opción GTK_SHRINK a algunos elementos de la tabla que contiene la ventana de texto y sus barras de scroll, de tal forma que cuando la parte inferior se haga pequeña, se encoja también la porción adecuada en lugar de salir por debajo de la ventana.

Figura 6. Ejemplo de Paned Window

panedwindow.png

GtkLayout

El contenedor GtkLayout es similar al contenedor GtkFixed, salvo que el primero implementa un área de scroll muy amplia (hasta 2^32). El sistema X window tiene una limitación ya que las ventanas sólo pueden ser de 32767 pixels tanto de ancho como de largo. El contenedor GtkLayout salva esta limitación pudiendo tener un amplio área de scroll, incluso cuando se tienen muchos widgets hijos dentro de este área.

Para crear un contenedor Layout se utiliza la siguiente función:

   1           GtkWidget *gtk_layout_new( GtkAdjustment *hadjustment,
   2                                      GtkAdjustment *vadjustment );

Como puede verse, se puede, al crear un GtkLayout, especificar los GtkAdjustment, tanto horizontal como vertical, que serán usados para controlar el desplazamiento del contenido del contenedor. Dichos parámetros son opcionales, por lo que en la mayor parte de los casos se usará esta función especificando NULL para ambos.

Las siguientes funciones sirven para añadir y mover widgets en el contenedor GtkLayout:

   1           void gtk_layout_put( GtkLayout *layout,
   2                                GtkWidget *widget,
   3                                gint       x,
   4                                gint       y );
   5 
   6           void gtk_layout_move( GtkLayout *layout,
   7                                 GtkWidget *widget,
   8                                 gint       x,
   9                                 gint       y );

Para establecer el tamaño de un GtkLayout se usa la siguiente función:

   1           void gtk_layout_set_size( GtkLayout *layout,
   2                                     guint      width,
   3                                     guint      height );

Y para terminar con el GtkLayout, las cuatro funciones siguientes se usan para manipular el alineamiento horizontal y vertical de los widgets.

   1           GtkAdjustment* gtk_layout_get_hadjustment( GtkLayout *layout );
   2 
   3           GtkAdjustment* gtk_layout_get_vadjustment( GtkLayout *layout );
   4 
   5           void gtk_layout_set_hadjustment( GtkLayout     *layout,
   6                                            GtkAdjustment *adjustment );
   7 
   8           void gtk_layout_set_vadjustment( GtkLayout     *layout,
   9                                            GtkAdjustment *adjustment);

Colocación por coordenadas

Una de las cosas que hacen especial a GTK+ (al igual que a Java Swing y otros toolkits), es que la colocación de los widgets en las ventanas se hace por medio de contenedores, como se comenta en esta sección, que controlan la forma en que son colocados los distintos widgets controlados por el contenedor.

Sin embargo, hay ocasiones en las que puede ser necesario el usar una colocación de los distintos widgets en coordenadas exactas. Para dicho menester, GTK incluye el widget GtkFixed, que permite colocar los widgets en una posición fija dentro de la ventana, relativa a la esquina superior izquierda de la misma. La posición de los widgets se puede cambiar dinámicamente.

Las funciones asociadas a este widget son las siguientes:

   1           GtkWidget* gtk_fixed_new( void );
   2 
   3           void gtk_fixed_put( GtkFixed  *fixed,
   4                               GtkWidget *widget,
   5                               gint       x,
   6                               gint       y );
   7 
   8           void gtk_fixed_move( GtkFixed  *fixed,
   9                                GtkWidget *widget,
  10                                gint       x,
  11                                gint       y );

La primera función, gtk_fixed_new permite crear un nuevo widget GtkFixed

La función gtk_fixed_put coloca el widget en el contenedor GtkFixed en la posición especificada por los parámetros x e y.

gtk_fixed_move permite mover el widget especificado en el parámetro widget a una nueva posición.

   1         void gtk_fixed_set_has_window( GtkFixed  *fixed,
   2                                        gboolean   has_window );
   3 
   4         gboolean gtk_fixed_get_has_window( GtkFixed *fixed );

En las últimas versiones de GTK, los widgets GtkFixed no tenían su propia ventana X, pero ahora la función gtk_fixed_set_has_window permite crearlos con su propia ventana. No obstante, esta función tendrá que ser llamada antes de que se visualice el widget.

El siguiente ejemplo muestra cómo usar el contenedor GtkFixed.

Figura 7. Ejemplo de GtkFixed

fixed.png

   1 #include <gtk/gtk.h>
   2 
   3 /* Para agilizar el trabajo, se usarán algunas variables globales para
   4  * guardar la posición del widget dentro del contenedor fixed */
   5 gint x = 50;
   6 gint y = 50;
   7 
   8 /* Esta función mueve el botón a una nueva posición
   9  * del contenedor Fixed. */
  10 void move_button( GtkWidget *widget,
  11                   GtkWidget *fixed )
  12 {
  13   x = (x + 30) % 300;
  14   y = (y + 50) % 300;
  15   gtk_fixed_move (GTK_FIXED (fixed), widget, x, y);
  16 }
  17 
  18 int main( int   argc,
  19           char *argv[] )
  20 {
  21   /* GtkWidget es el tipo utilizado para widgets */
  22   GtkWidget *window;
  23   GtkWidget *fixed;
  24   GtkWidget *button;
  25   gint i;
  26 
  27   /* Inicializa GTK */
  28   gtk_init (&argc, &argv);
  29 
  30   /* Crea una ventana */
  31   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  32   gtk_window_set_title (GTK_WINDOW (window), "Fixed Container");
  33 
  34   /* Conecta el evento "destroy" a un manejador de señales */
  35   g_signal_connect (G_OBJECT (window), "destroy",
  36                     G_CALLBACK (gtk_main_quit), NULL);
  37 
  38   /* Establece la anchura del borde de la ventana. */
  39   gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  40 
  41   /* Crea un Contenedor Fixed */
  42   fixed = gtk_fixed_new ();
  43   gtk_container_add (GTK_CONTAINER (window), fixed);
  44   gtk_widget_show (fixed);
  45 
  46   for (i = 1 ; i <= 3 ; i++) {
  47     /* Crea un botón con la etiqueta "Press me" */
  48     button = gtk_button_new_with_label ("Press me");
  49 
  50     /* Cuando el botón recive la señal "clicked", se llama a la función
  51      * move_button() pasando el Contenedor Fixed como argumento */
  52     g_signal_connect (G_OBJECT (button), "clicked",
  53                       G_CALLBACK (move_button), (gpointer) fixed);
  54 
  55     /* Coloca el botón dentro de la ventana de los contenedores fixed. */
  56     gtk_fixed_put (GTK_FIXED (fixed), button, i*50, i*50);
  57 
  58     /* El último paso es mostrar el nuevo widget que se ha creado. */
  59     gtk_widget_show (button);
  60   }
  61 
  62   /* Muestra la ventana */
  63   gtk_widget_show (window);
  64 
  65   /* Entra en el bucle de eventos */
  66   gtk_main ();
  67 
  68   return 0;
  69 }

Marcos

GtkAspectFrame

GtkAspectFrame es igual que el widget GtkFrame, salvo que aquél además especifica el ratio entre la anchura y la altura del widget hijo, para así poder tener un valor determinado y añadir espacio extra si fuera necesario. Esto es muy útil cuando se quiere visualizar una imagen y ampliarla o reducirla segun cambie el tamaño de la ventana contenedora. El tamaño de la imagen debe cambiar cuando el usuario maximice o minimice la ventana, pero el ratio siempre deberá coincidir con la imagen original.

La siguiente función crea un GtkAspectFrame nuevo.

   1 GtkWidget * gtk_aspect_frame_new(       label,   
   2         xalign,          
   3         yalign,          
   4         ratio,   
   5         obey_child);     
   6 const gchar *   label;
   7 gfloat          xalign;
   8 gfloat          yalign;
   9 gfloat          ratio;
  10 gboolean        obey_child;

Los argumentos xalign e yalign establecen la posición al igual que se comentó con el widget definido en “GtkAlignment”. Si el valor del argumento obey_child es TRUE, el ratio del widget hijo coincidirá con el ratio del tamaño necesario. En caso contrario, el valor del ratio vendrá dado por el valor del argumento del mismo nombre.

La siguiente función cambia las opciones de un GtkAspectFrame

   1 void gtk_aspect_frame_set(      aspect_frame,    
   2         xalign,          
   3         yalign,          
   4         ratio,   
   5         obey_child);     
   6 GtkAspectFrame *        aspect_frame;
   7 gfloat          xalign;
   8 gfloat          yalign;
   9 gfloat          ratio;
  10 gboolean        obey_child;

El siguiente programa usa un GtkAspectFrame para presentar un área de dibujo cuyo ratio será siempre 2:1, independientemente que el usuario cambie el tamaño de la ventana principal.

Figura 8. GtkAspectFrame: al redimensionar la ventana, el ratio se mantiene

aspectframe.png

   1 #include <gtk/gtk.h>
   2 
   3 int main( int argc,
   4           char *argv[] )
   5 {
   6     GtkWidget *window;
   7     GtkWidget *aspect_frame;
   8     GtkWidget *drawing_area;
   9     gtk_init (&argc, &argv);
  10 
  11     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  12     gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
  13     g_signal_connect (G_OBJECT (window), "destroy",
  14                       G_CALLBACK (gtk_main_quit), NULL);
  15     gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  16 
  17     /* Crea un widget aspect_frame y lo añade a la ventana principal */
  18 
  19     aspect_frame = gtk_aspect_frame_new ("2x1", /* etiqueta */
  20                                          0.5, /* centra x */
  21                                          0.5, /* centra y */
  22                                          2, /* el ratio: xsize/ysize = 2 */
  23                                          FALSE /* este argumento se ignora */);
  24 
  25     gtk_container_add (GTK_CONTAINER (window), aspect_frame);
  26     gtk_widget_show (aspect_frame);
  27 
  28     /* Se añade un widget child al widget aspect frame */
  29 
  30     drawing_area = gtk_drawing_area_new ();
  31 
  32     /* el tamaño de la ventana se establece a 200x200, aunque el widget AspectFrame
  33      * establece el tamaño a 200x100 ya que el ratio es de 2x1 */
  34     gtk_widget_set_size_request (drawing_area, 200, 200);
  35     gtk_container_add (GTK_CONTAINER (aspect_frame), drawing_area);
  36     gtk_widget_show (drawing_area);
  37 
  38     gtk_widget_show (window);
  39     gtk_main ();
  40     return 0;
  41 }

GtkViewport

El widget GtkViewport se suele usar muy poco directamente. Normalmente se usan las ventanas con scroll, las cuales usan el widget Viewport.

GtkViewport permite la colocación de un widget mayor dentro de él, de tal forma que es posible visualizar todo el widget por partes. Se usa la alineación para definir el área que se va a ver.

La siguiente función crea un widget Viewport:

   1           GtkWidget *gtk_viewport_new( GtkAdjustment *hadjustment,
   2                                        GtkAdjustment *vadjustment );

Como se puede ver, se puede especificar la alineación horizontal o vertical que va a usar el widget. Si el valor de los argumentos es NULL, creará la alineación internamente.

Con las siguientes funciones se puede obtener y cambiar la alineación de un widget que ya ha sido creado anteriormente.

   1           GtkAdjustment *gtk_viewport_get_hadjustment (GtkViewport *viewport );
   2 
   3           GtkAdjustment *gtk_viewport_get_vadjustment (GtkViewport *viewport );
   4 
   5           void gtk_viewport_set_hadjustment( GtkViewport   *viewport,
   6                                              GtkAdjustment *adjustment );
   7 
   8           void gtk_viewport_set_vadjustment( GtkViewport   *viewport,
   9                                              GtkAdjustment *adjustment );

Por último, se usará la siguiente función para cambiar la apariencia del widget viewport.

   1           void gtk_viewport_set_shadow_type( GtkViewport   *viewport,
   2                                              GtkShadowType  type );

Los posibles valores para el parámetro type son:

  • GTK_SHADOW_NONE
  • GTK_SHADOW_IN
  • GTK_SHADOW_OUT
  • GTK_SHADOW_ETCHED_IN
  • GTK_SHADOW_ETCHED_OUT

Documentacion/Desarrollo/Gtk (last edited 2011-06-15 10:09:52 by DomingoGonzalez)