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.
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.
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.
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>
.
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.
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) ;
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");
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)
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
.
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
.
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 */