Página siguiente Página anterior Índice general

8. Widgets de selección de rango

Este tipo de widgets incluye a las scroollbar (barras de deslizamiento) y la menos conocida scale (escala). Ambos pueden ser usados para muchas cosas, pero como sus funciones y su implementación son muy parecidas los describimos al mismo tiempo. Principalmente se utilizan para permitirle al usuario escoger un valor dentro de un rango ya prefijado.

Todos los widgets de selección comparten elementos gráficos, cada uno de los cuales tiene su propia ventana X window y recibe eventos. Todos contienen una guía y un rectángulo para determinar la posición dentro de la guía (en una procesador de textos con entorno gráfico se encuentra situado a la derecha del texto y sirve para situarnos en las diferentes partes del texto). Con el ratón podemos subir o bajar el rectángulo, mientras que si hacemos `click' dentro de la guía, pero no sobre el rectángulo, este se mueve hacia donde hemos hecho el click. Dependiendo del botón pulsado el rectángulo se moverá hasta la posición del click o una cantidad prefijada de ante mano.

Tal y como se mencionó en Ajustes todos los widgets usados para seleccionar un rango estan asociados con un objeto de ajuste, a partir del cual calculan la longitud de la barra y su posición. Cuando el usuario manipula la barra de desplazamiento el widget cambiará el valor del ajuste.

8.1 Widgets de escala

Los widgets de escala se usan para determinar el valor de una cantidad que se puede interpretar visualmente. El usuario probablemente fijará el valor a ojo. Por ejemplo el widget GtkColorSelection contiene widgets de escala que controlan las componentes del color a seleccionar. Normalmente el valor preciso es menos importante que el efecto visual, por lo que el color se selecciona con el ratón y no mediante un número concreto.

Creación de un widget de escala

Existen dos tipos de widgets de escala: GtkHScale (que es horizontal) y GtkVscale (vertical). Como funcionan de la misma manera los vamos a describir a la vez. Las funciones definidas en <gtk/gtkvscale.h> y <gtk/gtkhscale.h>, crean widgets de escala verticales y horizontales respectivamente.

GtkWidget* gtk_vscale_new( GtkAdjustment *adjustment );

GtkWidget* gtk_hscale_new( GtkAdjustment *adjustment );

El ajuste (adjustment) puede ser tanto un ajuste creado mediante gtk_adjustment_new() como NULL. En este último caso se crea un GtkAdjustment anónimo con todos sus valores iguales a 0.0. Si no ha quedado claro el uso de esta función consulte la sección Ajustes para una discusión más detallada.

Funciones y señales

Los widgets de escala pueden indicar su valor actual como un número. Su comportamiento por defecto es mostrar este valor, pero se puede modificar usando:

void gtk_scale_set_draw_value( GtkScale *scale,
                               gint      draw_value );

Los valores posibles de draw_value son son TRUE o FALSE. Con el primero se muestra el valor y con el segundo no.

El valor mostrado por un widget de escala por defecto se redondea a un valor decimal (igual que con value en un GtkAdjustment). Se puede cambiar con:

void gtk_scale_set_digits( GtkScale *scale,
                            gint     digits );

donde digits es el número de posiciones decimales que se quiera. En la práctica sólo se mostrarán 13 como máximo.

Por último, el valor se puede dibujar en diferentes posiciones con respecto a la posición del rectangulo que hay dentro de la guía:

void gtk_scale_set_value_pos( GtkScale        *scale,
                              GtkPositionType  pos );

Si ha leido la sección acerca del widget libro de notas entonces ya conoce cuales son los valores posibles de pos. Estan definidos en <gtk/gtkscale.h> como enum GtkPositionType y son auto explicatorios. Si se escoge un lateral de la guía, entonces seguirá al rectángulo a lo largo de la guía.

Todas las funcioenes precedentes se encuentran definidas en: <gtk/gtkscale.h>.

8.2 Funciones comunes

La descripción interna de la clase GtkRange es bastante complicada, pero al igual que con el resto de las ``clases base'' sólo es interesante si se quiere ``hackear''. Casi todas las señales y funciones sólo son útiles para desarrollar derivados. Para un usuario normal las funciones interesantes son aquellas definidas en: <gtk/gtkrange.h> y funcionan igual en todos los widgets de rango.

Estableciendo cada cúanto se actualizan

La política de actualización de un widget define en que puntos de la interacción con el usuario debe cambiar el valor value en su GtkAdjustment y emitir la señal ``value_changed''. Las actualizaciones definidas en <gtk/gtkenums.h> como enum GtkUpdateType, son:

Para establecer la política de actualización se usa la conversión definida en la macro

void gtk_range_set_update_policy( GtkRange      *range,
                                  GtkUpdateType  policy) ;

Obteniendo y estableciendo Ajustes

Para obtener o establecer el ajuste de un widget de rango se usa:

GtkAdjustment* gtk_range_get_adjustment( GtkRange *range );

void gtk_range_set_adjustment( GtkRange      *range,
                               GtkAdjustment *adjustment );

La función gtk_range_get_adjustment() devuelve un puntero al ajuste al que range esté conectado.

La función gtk_range_set_adjustment() no hace nada si se le pasa como argumento el valor range del ajuste que esta siendo usado (aunque se haya modificado algún valor). En el caso de que sea un ajuste nuevo (GtkAdjustment) dejará de usar el antiguo (probablemente lo destruirá) y conectará las señales apropiadas al nuevo. A continuación llamará a la función gtk_range_adjustment_changed() que en teoría recalculará el tamaño y/o la posición de la barra, redibujándola en caso de que sea necesario. Tal y como se mencionó en la sección de los ajustes si se quiere reusar el mismo GtkAdjustment cuando se modifican sus valores se debe emitir la señal ``changed''. Por ejemplo:

gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");

8.3 Enlaces con el teclado y el ratón (Key and Mouse bindings)

Todos los widgets de rango reaccionan más o menos de la misma manera a las pulsaciones del ratón. Al pulsar el botón 1 sobre el rectángulo de la barra el value del ajuste aumentará o disminuirá según page_increment. Con el botón 2 la barra se desplazará al punto en el que el botón fue pulsado. Con cada pulsación de cualquier botón sobre las flechas el valor del ajuste se modifica una cantidad igual a step_increment.

Acostumbrarse a que tanto las barras deslizantes como los widgets de escala puedan tomar la atención del teclado puede ser un proceso largo. Si que se cree que los usuarios no lo van a entender se puede anular mediante la función GTK_WIDGET_UNSET_FLAGS y con GTK_CAN_FOCUS como argumento:

GTK_WIDGET_UNSET_FLAGS (scrollbar, GTK_CAN_FOCUS);

Los enlaces entre teclas (que sólo estan activos cuando el widget tiene la atención (focus)) se comportan de manera diferente para los widgets de rango horizontales que para los verticales. También son diferentes para los widgets de escala y para las barras deslizantes. (Simplemente para evitar confusiones entre las teclas de las barras deslizantes horizontales y verticales, ya que ambas actúan sobre la misma área)

Widgets de rango vertical

Todos los widgets de rango pueden ser manipulados con las teclas arriba, abajo, Re Pág, Av Pág. Las flechas mueven las barras la cantidad fijada mediante step_increment, mientras que Re Pág y Av Pag lo hacen según page_increment.

El usuario también puede mover la barra de un extremo al otro de la guía mediante el teclado. Con el widget GtkVScale podemos ir a los extremos utilizando las teclas Inicio y Final mientras que con el widget GtkVScrollbar habrá que utilizar Control-Re Pág y Control-Av Pág.

Widgets de rango horizontal

Las teclas izquierda y derecha funcionan tal y como espera que funcionen en estos widgets: mueven la barra una cantidad dada por step_increment. A su vez Inicio y Final sirven para pasar de un extremo al otro de la guía. Para el widget GtkHScale el mover la barra una cantidad dada por page_increment se consigue mediante Control-Izquierda y Control-derecha, mientras que para el widget GtkHScrollbar se consigue con Control-Inicio y Control-Final.

8.4 Ejemplo

Este ejemplo es una versión modificada del test ``range controls'' que a su vez forma parte de testgtk.c. Simplemente dibuja una ventana con tres widgets de rango conectados al mismo ajuste, y un conjunto de controles para ajustar algunos de los parámetros ya mencionados. Así se consigue ver como funcionan estos widgtes al ser manipulados por el usuario.

/* principio del ejemplo widgets de selección de rango rangewidgets.c */

#include <gtk/gtk.h>

GtkWidget *hscale, *vscale;

void cb_pos_menu_select( GtkWidget       *item,
                         GtkPositionType  pos )
{
    /* Establece el valor position en los widgets de escala */
    gtk_scale_set_value_pos (GTK_SCALE (hscale), pos);
    gtk_scale_set_value_pos (GTK_SCALE (vscale), pos);
}

void cb_update_menu_select( GtkWidget     *item,
                            GtkUpdateType  policy )
{
    /* Establece la política de actualización para los widgets
     * de escala */
    gtk_range_set_update_policy (GTK_RANGE (hscale), policy);
    gtk_range_set_update_policy (GTK_RANGE (vscale), policy);
}

void cb_digits_scale( GtkAdjustment *adj )
{
    /* Establece el número de cifras decimales a las que se
     * redondeará adj->value */
    gtk_scale_set_digits (GTK_SCALE (hscale), (gint) adj->value);
    gtk_scale_set_digits (GTK_SCALE (vscale), (gint) adj->value);
}

void cb_page_size( GtkAdjustment *get,
                   GtkAdjustment *set )
{
    /* Establece el tamaño de la página y el incremento del
     * ajuste al valor especificado en la escala "Page Size" */
    set->page_size = get->value;
    set->page_increment = get->value;
    /* Ahora emite la señal "changed" para reconfigurar todos los
     * widgets que están enlazados a este ajuste */
    gtk_signal_emit_by_name (GTK_OBJECT (set), "changed");
}

void cb_draw_value( GtkToggleButton *button )
{
    /* Activa o desactiva el valor display en los widgets de escala
     * dependiendo del estado del botón de comprobación */
    gtk_scale_set_draw_value (GTK_SCALE (hscale), button->active);
    gtk_scale_set_draw_value (GTK_SCALE (vscale), button->active);  
}

/* Funciones varias */

GtkWidget *make_menu_item( gchar         *name,
                           GtkSignalFunc  callback,
                           gpointer       data )
{
    GtkWidget *item;
  
    item = gtk_menu_item_new_with_label (name);
    gtk_signal_connect (GTK_OBJECT (item), "activate",
                        callback, data);
    gtk_widget_show (item);

    return(item);
}

void scale_set_default_values( GtkScale *scale )
{
    gtk_range_set_update_policy (GTK_RANGE (scale),
                                 GTK_UPDATE_CONTINUOUS);
    gtk_scale_set_digits (scale, 1);
    gtk_scale_set_value_pos (scale, GTK_POS_TOP);
    gtk_scale_set_draw_value (scale, TRUE);
}

/* crea la ventana principal */

void create_range_controls( void )
{
    GtkWidget *window;
    GtkWidget *box1, *box2, *box3;
    GtkWidget *button;
    GtkWidget *scrollbar;
    GtkWidget *separator;
    GtkWidget *opt, *menu, *item;
    GtkWidget *label;
    GtkWidget *scale;
    GtkObject *adj1, *adj2;

    /* creación estándar de una ventana */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_signal_connect (GTK_OBJECT (window), "destroy",
                        GTK_SIGNAL_FUNC(gtk_main_quit),
                        NULL);
    gtk_window_set_title (GTK_WINDOW (window), "range controls");

    box1 = gtk_vbox_new (FALSE, 0);
    gtk_container_add (GTK_CONTAINER (window), box1);
    gtk_widget_show (box1);

    box2 = gtk_hbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);
    gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
    gtk_widget_show (box2);

    /* value, lower, upper, step_increment, page_increment, page_size */
    /* Observe que el valor de page_size solo sirve para los widgets
     * barras de desplazamiento (scrollbar), y que el valor más
     * alto que obtendrá será (upper - page_size). */
    adj1 = gtk_adjustment_new (0.0, 0.0, 101.0, 0.1, 1.0, 1.0);
  
    vscale = gtk_vscale_new (GTK_ADJUSTMENT (adj1));
    scale_set_default_values (GTK_SCALE (vscale));
    gtk_box_pack_start (GTK_BOX (box2), vscale, TRUE, TRUE, 0);
    gtk_widget_show (vscale);

    box3 = gtk_vbox_new (FALSE, 10);
    gtk_box_pack_start (GTK_BOX (box2), box3, TRUE, TRUE, 0);
    gtk_widget_show (box3);

    /* Reutilizamos el mismo ajuste */
    hscale = gtk_hscale_new (GTK_ADJUSTMENT (adj1));
    gtk_widget_set_usize (GTK_WIDGET (hscale), 200, 30);
    scale_set_default_values (GTK_SCALE (hscale));
    gtk_box_pack_start (GTK_BOX (box3), hscale, TRUE, TRUE, 0);
    gtk_widget_show (hscale);

    /* Reutilizamos de nuevo el mismo ajuste */
    scrollbar = gtk_hscrollbar_new (GTK_ADJUSTMENT (adj1));
    /* Observe que con esto conseguimos que la escala siempre se
     * actualice de una forma continua cuando se mueva la barra de
     * desplazamiento */
    gtk_range_set_update_policy (GTK_RANGE (scrollbar), 
                                 GTK_UPDATE_CONTINUOUS);
    gtk_box_pack_start (GTK_BOX (box3), scrollbar, TRUE, TRUE, 0);
    gtk_widget_show (scrollbar);

    box2 = gtk_hbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);
    gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
    gtk_widget_show (box2);

    /* Un botón para comprobar si el valor se muestra o no*/
    button = gtk_check_button_new_with_label("Display value on scale widgets");
    gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), TRUE);
    gtk_signal_connect (GTK_OBJECT (button), "toggled",
                        GTK_SIGNAL_FUNC(cb_draw_value), NULL);
    gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
    gtk_widget_show (button);
  
    box2 = gtk_hbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);

    /* Una opción en el menú para cambiar la posición del
     * valor */
    label = gtk_label_new ("Scale Value Position:");
    gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
    gtk_widget_show (label);
  
    opt = gtk_option_menu_new();
    menu = gtk_menu_new();

    item = make_menu_item ("Top",
                           GTK_SIGNAL_FUNC(cb_pos_menu_select),
                           GINT_TO_POINTER (GTK_POS_TOP));
    gtk_menu_append (GTK_MENU (menu), item);
  
    item = make_menu_item ("Bottom", GTK_SIGNAL_FUNC (cb_pos_menu_select), 
                           GINT_TO_POINTER (GTK_POS_BOTTOM));
    gtk_menu_append (GTK_MENU (menu), item);
  
    item = make_menu_item ("Left", GTK_SIGNAL_FUNC (cb_pos_menu_select),
                           GINT_TO_POINTER (GTK_POS_LEFT));
    gtk_menu_append (GTK_MENU (menu), item);
  
    item = make_menu_item ("Right", GTK_SIGNAL_FUNC (cb_pos_menu_select),
                            GINT_TO_POINTER (GTK_POS_RIGHT));
    gtk_menu_append (GTK_MENU (menu), item);
  
    gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
    gtk_box_pack_start (GTK_BOX (box2), opt, TRUE, TRUE, 0);
    gtk_widget_show (opt);

    gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
    gtk_widget_show (box2);

    box2 = gtk_hbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);

    /* Sí, otra opción de menú, esta vez para la política
     * de actualización de los widgets */
    label = gtk_label_new ("Scale Update Policy:");
    gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
    gtk_widget_show (label);
  
    opt = gtk_option_menu_new();
    menu = gtk_menu_new();
  
    item = make_menu_item ("Continuous",
                           GTK_SIGNAL_FUNC (cb_update_menu_select),
                           GINT_TO_POINTER (GTK_UPDATE_CONTINUOUS));
    gtk_menu_append (GTK_MENU (menu), item);
  
    item = make_menu_item ("Discontinuous",
                            GTK_SIGNAL_FUNC (cb_update_menu_select),
                            GINT_TO_POINTER (GTK_UPDATE_DISCONTINUOUS));
    gtk_menu_append (GTK_MENU (menu), item);
  
    item = make_menu_item ("Delayed",
                           GTK_SIGNAL_FUNC (cb_update_menu_select),
                           GINT_TO_POINTER (GTK_UPDATE_DELAYED));
    gtk_menu_append (GTK_MENU (menu), item);
  
    gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
    gtk_box_pack_start (GTK_BOX (box2), opt, TRUE, TRUE, 0);
    gtk_widget_show (opt);
  
    gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
    gtk_widget_show (box2);

    box2 = gtk_hbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);
  
    /* Un widget GtkHScale para ajustar el número de dígitos en
     * la escala. */
    label = gtk_label_new ("Scale Digits:");
    gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
    gtk_widget_show (label);

    adj2 = gtk_adjustment_new (1.0, 0.0, 5.0, 1.0, 1.0, 0.0);
    gtk_signal_connect (GTK_OBJECT (adj2), "value_changed",
                        GTK_SIGNAL_FUNC (cb_digits_scale), NULL);
    scale = gtk_hscale_new (GTK_ADJUSTMENT (adj2));
    gtk_scale_set_digits (GTK_SCALE (scale), 0);
    gtk_box_pack_start (GTK_BOX (box2), scale, TRUE, TRUE, 0);
    gtk_widget_show (scale);

    gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
    gtk_widget_show (box2);
  
    box2 = gtk_hbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);
  
    /* Y un último widget GtkHScale para ajustar el tamaño de la
     * página de la barra de desplazamiento. */
    label = gtk_label_new ("Scrollbar Page Size:");
    gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
    gtk_widget_show (label);

    adj2 = gtk_adjustment_new (1.0, 1.0, 101.0, 1.0, 1.0, 0.0);
    gtk_signal_connect (GTK_OBJECT (adj2), "value_changed",
                        GTK_SIGNAL_FUNC (cb_page_size), adj1);
    scale = gtk_hscale_new (GTK_ADJUSTMENT (adj2));
    gtk_scale_set_digits (GTK_SCALE (scale), 0);
    gtk_box_pack_start (GTK_BOX (box2), scale, TRUE, TRUE, 0);
    gtk_widget_show (scale);

    gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
    gtk_widget_show (box2);

    separator = gtk_hseparator_new ();
    gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0);
    gtk_widget_show (separator);

    box2 = gtk_vbox_new (FALSE, 10);
    gtk_container_border_width (GTK_CONTAINER (box2), 10);
    gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0);
    gtk_widget_show (box2);

    button = gtk_button_new_with_label ("Quit");
    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                               GTK_SIGNAL_FUNC(gtk_main_quit),
                               NULL);
    gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
    GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
    gtk_widget_grab_default (button);
    gtk_widget_show (button);

    gtk_widget_show (window);
}

int main( int   argc,
          char *argv[] )
{
    gtk_init(&argc, &argv);

    create_range_controls();

    gtk_main();

    return(0);
}

/* fin del ejemplo */

Página siguiente Página anterior Índice general