Página siguiente Página anterior Índice general

9. Widgets varios

9.1 Etiquetas

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

Para crear una nueva etiqueta se usa:

GtkWidget *gtk_label_new( char *str );

El único argumento es la cadena de texto que se quiere mostrar.

Para cambiarla después de que haya sido creada se usa:

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

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

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

void gtk_label_get( GtkLabel  *etiqueta,
                    char     **str );
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:

void gtk_label_set_justify( GtkLabel         *etiqueta,
                            GtkJustification  jtype );

Los valores posibles para jtype son:

El 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:

void gtk_label_set_line_wrap (GtkLabel *etiqueta,
                              gboolean  wrap);

El argumento wrap toma el valor TRUE o FALSE.

Si quiere que su etiqueta salga subrayada, puede especificar un motivo para el subrayado con:

void       gtk_label_set_pattern   (GtkLabel          *etiqueta,
                                    const gchar       *pattern);

El argumento 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 "__ __" 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 widget marco (frame) para hacer una mejor demostración de los estilos de la etiqueta. Por ahora puede ignorarlo, ya que el widget Frame se explicará más tarde.

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

9.2 Flechas

En widget flecha (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 widget flecha:

GtkWidget *gtk_arrow_new( GtkArrowType   arrow_type,
                          GtkShadowType  shadow_type );

void gtk_arrow_set( GtkArrow      *arrow,
                    GtkArrowType   arrow_type,
                    GtkShadowType  shadow_type );

La primera crea un nuevo widget flecha del tipo y apariencia indicados. La segunda permite alterar posteriormente estos valores. El argumento arrow_type puede tomar uno de los valores siguientes:

Naturalmente, estos valores indican la dirección a la que debe apuntar la flecha. El argumento shadow_type puede tomar uno de los valores siguientes:

Aquí tenemos un pequeño ejemplo para ilustrar la utilización de la flecha.

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

9.3 El widget de información rápida (tooltip)

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

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

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

GtkTooltips *gtk_tooltips_new( void );

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

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

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

Veamos un ejemplo:

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

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

void gtk_tooltips_enable( GtkTooltips *tooltips );

Permite que funcionen un conjunto de tooltips

void gtk_tooltips_disable( GtkTooltips *tooltips );

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

void gtk_tooltips_set_delay( GtkTooltips *tooltips,
                             gint         delay );

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

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

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

9.4 Barras de progreso

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.

GtkWidget *gtk_progress_bar_new( void );

GtkWidget *gtk_progress_bar_new_with_adjustment( GtkAdjustment *adjustment );

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:

void gtk_progress_set_adjustment( GtkProgress   *progress,
                                  GtkAdjustment *adjustment );

Ahora que hemos creado la barra de progreso ya podemos utilizarla.

void gtk_progress_bar_update( GtkProgressBar *pbar,
                              gfloat          percentage );

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

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

void gtk_progress_bar_set_orientation( GtkProgressBar *pbar,
                                       GtkProgressBarOrientation orientation );

Donde el argumento 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:

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:

void gtk_progress_bar_set_bar_style( GtkProgressBar      *pbar,
                                     GtkProgressBarStyle  style );

El parámetro style puede tomar uno de los dos valores siguientes:

El número de bloques se puede establecer utilizando

void gtk_progress_bar_set_discrete_blocks( GtkProgressBar *pbar,
                                           guint           blocks );

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:

void gtk_progress_set_activity_mode( GtkProgress *progress,
                                     guint        activity_mode );

El tamaño del paso del indicador de actividad, y el número de bloques se indican usando las siguientes funciones:

void gtk_progress_bar_set_activity_step( GtkProgressBar *pbar,
                                         guint           step );

void gtk_progress_bar_set_activity_blocks( GtkProgressBar *pbar,
                                           guint           blocks );

Cuando estamos en modo continuo, la barra de progreso puede mostrar un texto configurable dentro la barra misma, utilizando la función siguiente:

void gtk_progress_set_format_string( GtkProgress *progress,
                                     gchar       *format);

El argumento format es parecido al que se utiliza en una orden printf de C. Se pueden utilizar las siguientes opciones para el formateado de la cadena:

Puede activar o desactivar el texto utilizando:

void gtk_progress_set_show_text( GtkProgress *progress,
                                 gint         show_text );

El argumento show_text es un valor booleano TRUE/FALSE. La apariencia del texto puede modificarse utilizando:

void gtk_progress_set_text_alignment( GtkProgress   *progress,
                                      gfloat         x_align,
                                      gfloat         y_align );

Los argumentos x_align y 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.

gchar *gtk_progress_get_current_text( GtkProgress   *progress );

gchar *gtk_progress_get_text_from_value( GtkProgress *progress,
                                         gfloat       value );

Hay otra forma de cambiar el rango y el valor de un objeto barra de progreso utilizando la función:

void gtk_progress_configure( GtkProgress  *progress,
                             gfloat        value,
                             gfloat        min,
                             gfloat        max );

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.

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

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 (timeouts), sección Tiempos de espera, E/S (I/O) y funciones ociosas (idle)) para crear la ilusión de la multitarea. Todas usan la función gtk_progress_bar_update de la misma manera.

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

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

#include <gtk/gtk.h>

#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 */

9.5 Cuadros de diálogo

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

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

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

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

GtkWidget *gtk_dialog_new( void );

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

GtkWidget *ventana;
ventana = gtk_dialog_new ();

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

boton = ...
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (ventana)->action_area), boton,
                    TRUE, TRUE, 0);
gtk_widget_show (boton);

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

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

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

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

9.6 Pixmaps

Los pixmaps son estructuras de datos que contienen dibujos. Estos pueden ser usados en diferentes lugares, pero los iconos y los cursores son los más comunes.

Un bitmap es un pixmap que sólo tiene dos colores, y hay unas cuantas rutinas especiales para controlar este caso particular.

Para comprender los 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 «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 pixmaps por ejemplo, se almacenan en la memoria del servidor X. Esto significa que una vez que se establecen los valores del 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 pixmap número XYZ aquí». Incluso si no está utilizando X-windows con GTK, al utilizar construcciones como los pixmaps conseguirá que sus programas funciones de forma aceptable bajo X-windows.

Para usar un pixmap en GTK primero tiene que construir una estructura del tipo GdkPixmap usando rutinas de GDK. Los pixmaps se pueden crear usando datos que se encuentren en la memoria o en un archivo. Veremos con detalle cada una de las dos posibilidades.

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

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

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

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

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

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

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

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

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

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

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

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

Las otras funciones del widget pixmap son:

guint gtk_pixmap_get_type( void );

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

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

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

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

/* comienzo del ejemplo pixmap.c */

#include <gtk/gtk.h>


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

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

    /* cargar un pixmap desde un fichero */
    pixmap = gdk_pixmap_create_from_xpm( 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 );

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

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

/* comienzo del ejemplo carretilla wheelbarrow.c */

#include <gtk/gtk.h>

/* XPM */
static char * WheelbarrowFull_xpm[] = {
"48 48 64 1",
"       c None",
".      c #DF7DCF3CC71B",
"X      c #965875D669A6",
"o      c #71C671C671C6",
"O      c #A699A289A699",
"+      c #965892489658",
"@      c #8E38410330C2",
"#      c #D75C7DF769A6",
"$      c #F7DECF3CC71B",
"%      c #96588A288E38",
"&      c #A69992489E79",
"*      c #8E3886178E38",
"=      c #104008200820",
"-      c #596510401040",
";      c #C71B30C230C2",
":      c #C71B9A699658",
">      c #618561856185",
",      c #20811C712081",
"<      c #104000000000",
"1      c #861720812081",
"2      c #DF7D4D344103",
"3      c #79E769A671C6",
"4      c #861782078617",
"5      c #41033CF34103",
"6      c #000000000000",
"7      c #49241C711040",
"8      c #492445144924",
"9      c #082008200820",
"0      c #69A618611861",
"q      c #B6DA71C65144",
"w      c #410330C238E3",
"e      c #CF3CBAEAB6DA",
"r      c #71C6451430C2",
"t      c #EFBEDB6CD75C",
"y      c #28A208200820",
"u      c #186110401040",
"i      c #596528A21861",
"p      c #71C661855965",
"a      c #A69996589658",
"s      c #30C228A230C2",
"d      c #BEFBA289AEBA",
"f      c #596545145144",
"g      c #30C230C230C2",
"h      c #8E3882078617",
"j      c #208118612081",
"k      c #38E30C300820",
"l      c #30C2208128A2",
"z      c #38E328A238E3",
"x      c #514438E34924",
"c      c #618555555965",
"v      c #30C2208130C2",
"b      c #38E328A230C2",
"n      c #28A228A228A2",
"m      c #41032CB228A2",
"M      c #104010401040",
"N      c #492438E34103",
"B      c #28A2208128A2",
"V      c #A699596538E3",
"C      c #30C21C711040",
"Z      c #30C218611040",
"A      c #965865955965",
"S      c #618534D32081",
"D      c #38E31C711040",
"F      c #082000000820",
"                                                ",
"          .XoO                                  ",
"         +@#$%o&                                ",
"         *=-;#::o+                              ",
"           >,<12#:34                            ",
"             45671#:X3                          ",
"               +89<02qwo                        ",
"e*                >,67;ro                       ",
"ty>                 459@>+&&                    ",
"$2u+                  ><ipas8*                  ",
"%$;=*                *3:.Xa.dfg>                ",
"Oh$;ya             *3d.a8j,Xe.d3g8+             ",
" Oh$;ka          *3d$a8lz,,xxc:.e3g54           ",
"  Oh$;kO       *pd$%svbzz,sxxxxfX..&wn>         ",
"   Oh$@mO    *3dthwlsslszjzxxxxxxx3:td8M4       ",
"    Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B*     ",
"     Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5&   ",
"      Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM*  ",
"       OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ",
"        2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ",
"        :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo",
"         +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g",
"          *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en",
"           p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>",
"           OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ",
"            3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ",
"             @26MvzxNzvlbwfpdettttttttttt.c,n&  ",
"             *;16=lsNwwNwgsvslbwwvccc3pcfu<o    ",
"              p;<69BvwwsszslllbBlllllllu<5+     ",
"              OS0y6FBlvvvzvzss,u=Blllj=54       ",
"               c1-699Blvlllllu7k96MMMg4         ",
"               *10y8n6FjvllllB<166668           ",
"                S-kg+>666<M<996-y6n<8*          ",
"                p71=4 m69996kD8Z-66698&&        ",
"                &i0ycm6n4 ogk17,0<6666g         ",
"                 N-k-<>     >=01-kuu666>        ",
"                 ,6ky&      &46-10ul,66,        ",
"                 Ou0<>       o66y<ulw<66&       ",
"                  *kk5       >66By7=xu664       ",
"                   <<M4      466lj<Mxu66o       ",
"                   *>>       +66uv,zN666*       ",
"                              566,xxj669        ",
"                              4666FF666>        ",
"                               >966666M         ",
"                                oM6668+         ",
"                                  *4            ",
"                                                ",
"                                                "};



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

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

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

9.7 Reglas

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

Las reglas (horizontales y verticales) se crean usando:

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

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

void gtk_ruler_set_metric( GtkRuler      *ruler,
                           GtkMetricType  metric );

El valor por defecto es GTK_PIXELS.

gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS );

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

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

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

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

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

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

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

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

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

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

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

/* comienzo del ejemplo rulers.c */

#include <gtk/gtk.h>

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

#define XSIZE  600
#define YSIZE  400

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

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

9.8 Barras de estado

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

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

Las barras de estado se crean con una llamada a:

GtkWidget *gtk_statusbar_new( void );

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

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

Hay tres funciones que pueden manipular las barras de estado:

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

void gtk_statusbar_pop( GtkStatusbar *statusbar)
                        guint         context_id );

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

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

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

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

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

#include <gtk/gtk.h>
#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 */

9.9 Entrada de texto

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

Hay dos funciones para crear un widget Entry:

GtkWidget *gtk_entry_new( void );

GtkWidget *gtk_entry_new_with_max_length( guint16 max );

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

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

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

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

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

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

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

void gtk_entry_set_position( GtkEntry *entry,
                             gint      posicion );

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

gchar *gtk_entry_get_text( GtkEntry *entry );

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

void gtk_entry_set_editable( GtkEntry *entry,
                             gboolean  editable );

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

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

void gtk_entry_set_visibility( GtkEntry *entry,
                               gboolean  visible );

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

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

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

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

9.10 Botones spin

El widget botón 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 widget botón 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 spin utiliza un objeto Ajuste para conservar la información referente al rango de valores que puede tomar el botón spin. Esto hace que el widget botón spin sea muy poderoso.

Recuerde que un widget ajuste puede crearse con la siguiente función, que ilustra la información que se guarda:

GtkObject *gtk_adjustment_new( gfloat valor,
                               gfloat inferior,
                               gfloat superior,
                               gfloat paso,
                               gfloat incremento_pagina,
                               gfloat tamano_pagina );

Estos atributos de un ajuste se utilizan en un botón spin de la forma siguiente:

Además, se puede utilizar el botón 3 para saltar directamente a los valores superior o inferior cuando se pulsa en una de las flechas. Veamos como crear un botón spin:

GtkWidget *gtk_spin_button_new( GtkAdjustment *ajuste,
                                gfloat         aceleracion,
                                guint          digitos );

El argumento aceleracion toma un valor entre 0.0 y 1.0 e indica la aceleración que tendrá el botón spin. El argumento digitos especifica el número de cifras decimales con que se mostrará el valor.

Se puede reconfigurar un botón spin después de su creación utilizando la función:

void gtk_spin_button_configure( GtkSpinButton *boton_spin,
                                GtkAdjustment *ajuste,
                                gfloat         aceleracion,
                                guint          digitos );

El argumento boton_spin especifica el botón 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:

void gtk_spin_button_set_adjustment( GtkSpinButton  *boton_spin,
                                     GtkAdjustment  *ajuste );

GtkAdjustment *gtk_spin_button_get_adjustment( GtkSpinButton *boton_spin );

El número de cifras decimales también puede alterarse utilizando:

void gtk_spin_button_set_digits( GtkSpinButton *boton_spin,
                                 guint          digitos) ;

El valor que un botón spin está mostrando actualmente puede cambiarse utilizando las siguientes funciones:

void gtk_spin_button_set_value( GtkSpinButton *boton_spin,
                                gfloat         valor );

El valor actual de un botón spin puede obtenerse como un entero o como un flotante con las funciones siguientes:

gfloat gtk_spin_button_get_value_as_float( GtkSpinButton *boton_spin );

gint gtk_spin_button_get_value_as_int( GtkSpinButton *boton_spin );

Si quiere alterar el valor de un spin de forma relativa a su valor actual, puede utilizar la siguiente función:

void gtk_spin_button_spin( GtkSpinButton *boton_spin,
                           GtkSpinType    direccion,
                           gfloat         incremento );

El parámetro direccion puede tomar uno de los valores siguientes:

Trataré de explicar todas las posibilidades que ofrece esta función. Algunos de los valores que puede utilizar direccion hacen que se utilicen valores que están almacenados en el objeto Ajuste que está asociado con el botón spin.

GTK_SPIN_STEP_FORWARD y GTK_SPIN_STEP_BACKWARD aumentan o disminuyen (respectivamente) el valor del botón spin por la cantidad especificada por incremento, a menos que incremento sea igual a 0, en cuyo caso el valor se aumentará o disminuirá por el valor especificado en paso dentro del Ajuste.

GTK_SPIN_PAGE_FORWARD y GTK_SPIN_PAGE_BACKWARD sencillamente alteran el valor del botón spin por la cantidad incremento.

GTK_SPIN_HOME hace que el botón spin tenga el mismo valor que el valor inferior del rango Ajuste.

GTK_SPIN_END hace que el botón spin tenga el mismo valor que el valor superior del rango Ajuste.

GTK_SPIN_USER_DEFINED cambia el valor del botón 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 spin, y vamos a pasar a las funciones que afectan a la apariencia y al comportamiento del widget botón spin en sí mismo.

La primera de estas funciones se utiliza para restringir el contenido de la caja de texto de un botón spin a un valor numérico. Esto evita que un usuario introduzca cualquier valor no númerico.

void gtk_spin_button_set_numeric( GtkSpinButton *boton_spin,
                                  gboolean       numerico );

Puede indicar si un botón spin pasará del límite superior al inferior utilizando la siguiente función:

void gtk_spin_button_set_wrap( GtkSpinButton *boton_spin,
                               gboolean       wrap );

Puede hacer que un botón spin redondee su valor al paso más cercano, que se indica cuando creamos el Ajuste que se utiliza con el botón spin. Para hacer que redondee tenemos que utilizar la función siguiente:

void gtk_spin_button_set_snap_to_ticks( GtkSpinButton  *boton_spin,
                                        gboolean        redondear );

Para política de actualización de un botón spin puede cambiarse con la siguiente función:

void gtk_spin_button_set_update_policy( GtkSpinButton  *boton_spin,
                                    GtkSpinButtonUpdatePolicy politica );

Los valores posibles de politica son o GTK_UPDATE_ALWAYS o GTK_UPDATE_IF_VALID.

Estas políticas afectan al comportamiento de un botón 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 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 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 spin pueden cambiarse utilizando las siguientes funciones:

void gtk_spin_button_set_shadow_type( GtkSpinButton *boton_spin,
                                      GtkShadowType  tipo_sombra );

Como siempre, el tipo_sombra puede ser uno de los siguientes:

Finalmente, puede pedir de forma explícita que un botón spin se actualice a sí mismo:

void gtk_spin_button_update( GtkSpinButton  *boton_spin );

Es hora de un nuevo ejemplo.

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

9.11 Caja combinada (Combo Box)

La caja combinada o combo box es otro sencillo widget exclusivamente compuesto por otros widgets. Desde el punto de vista del usuario, el 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:

struct _GtkCombo { 
        GtkHBox hbox; 
        GtkWidget *entry; 
        GtkWidget *boton;
        GtkWidget *popup; 
        GtkWidget *popwin; 
        GtkWidget *list;
        ...  };

Como puede ver, el Combo Box tiene dos partes principales que tiene que conocer: un widget entry y un widget list (lista).

Lo primero, para crear un combo box, utilice:

GtkWidget *gtk_combo_new( void );

Ahora, si quiere indicar la cadena que debe aparecer en la sección entry del combo box, podrá hacerlo manipulando directamente el widget entry:

    gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), "Mi cadena.");

Para introducir valores en la lista desplegable, puede utilizar la función:

void gtk_combo_set_popdown_strings( GtkCombo *combo,
                                    GList    *cadenas );

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

GList *g_list_append( GList *glist, 
                      gpointer datos );

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:

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

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:

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

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 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 gtk_combo_set_use_arrows().

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.

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 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 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 activate, que indica que el usuario ha presionado la tecla «Intro», y leer el texto. Lo primero podemos hacerlo utilizando algo así:

    gtk_signal_connect(GTK_OBJECT(GTK_COMB(combo)->entry), "activate",
                       GTK_SIGNAL_FUNC (mi_funcion_respuesta), mis_datos);

Para conseguir el texto que hay en la caja en cualquier momento sólo tenemos que utilizar la función siguiente:

gchar *gtk_entry_get_text(GtkEntry *entry);

De esta forma:

    char *cadena;

    cadena = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(combo)->entry));

Esto es todo lo interesante. Existe otra función,

void gtk_combo_disable_activate(GtkCombo *combo);

que permite desactivar la señal activate en el widget entry dentro del combo box. Personalmente no se me ocurre ningún motivo para utilizarla, pero existir existe.

9.12 Selección de Color

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

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

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

GtkWidget *gtk_color_selection_new( void );

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

 
GtkWidget *gtk_color_selection_dialog_new( const gchar *title );

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

void gtk_color_selection_set_update_policy( GtkColorSelection *colorsel, 
                                            GtkUpdateType      policy );

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

void gtk_color_selection_set_opacity( GtkColorSelection *colorsel,
                                      gint               use_opacity );

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

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

Puede poner el color actual explicitamente haciendo uso de esta función con un puntero a un vector de colores (de tipo gdouble). La longitud del vector depende de si está activada la transparencia. La posición 0 contiene la componente roja del color, la 1 contiene la verde, la 2 la azul y la transparencia está en la posición 3 (solamente si está activada la transparencia, ver gtk_color_selection_set_opacity()). Todos los valores se encuentran entre 0.0 y 1.0.

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

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

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

/* principio del ejemplo colorsel colorsel.c */

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

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

/* Manejador del cambio de color */

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

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

  colormap = gdk_window_get_colormap (drawingarea->window);

  /* Obtener el color actual */

  gtk_color_selection_get_color (colorsel,color);

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

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

  /* Pedir memoria para el color */

  gdk_color_alloc (colormap, &gdk_color);

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

  gdk_window_set_background (drawingarea->window, &gdk_color);

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

9.13 Selección de ficheros

El widget de selección de ficheros nos proporciona una forma rápida y sencilla de mostrar un cuadro de diálogo para la selección de un fichero. 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:

GtkWidget *gtk_file_selection_new( gchar *title );

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

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

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

gchar *gtk_file_selection_get_filename( GtkFileSelection *filesel );

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

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

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

/* principio del ejemplo filesel filesel.c */

#include <gtk/gtk.h>

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

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

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


Página siguiente Página anterior Índice general