[Previous] [Contents] [Next]
8. Range Widgets
The category of range widgets includes the ubiquitous scrollbar widget and the less common scale widget.
Though these two types of widgets are generally used for different purposes, they are quite similar in function and
implementation. All range widgets share a set of common graphic elements, each of which has its own X window and
receives events. They all contain a trough (if English isn't your mother-tongue you may need to know that this
is pronounced troff) and a slider (what is sometimes called a thumbwheel in other GUI environments).
Dragging the slider with the pointer moves it back and forth within the trough, while clicking in the trough advances the
slider towards the location of the click, either completely, or by a designated amount, depending on which mouse button
is used.
As mentioned in Adjustments above, all range widgets are associated with an adjustment object,
from which they calculate the length of the slider and its position within the trough. When the user manipulates the
slider, the range widget will change the value of the adjustment.
[Previous] [Contents] [Next]
These are your standard, run-of-the-mill scrollbars. These should be used only for scrolling some other widget, such
as a list, a text box, or a viewport (and it's generally easier to use the scrolled window widget in most cases). For
other purposes, you should use scale widgets, as they are friendlier and more featureful.
There are separate types for horizontal and vertical scrollbars. There really isn't much to say about these. You create
them with the following functions:
FUNCTION gtk_hscrollbar_new( adjustment :
pGtkAdjustment ) : pGtkWidget;
FUNCTION gtk_vscrollbar_new( adjustment :
pGtkAdjustment ) : pGtkWidget;
and that's about it. The adjustment argument can either be a pointer to an existing Adjustment, or
NIL, in which case one will be created for you. Specifying NIL
might actually be useful in this case, if you wish to pass the newly-created adjustment to the constructor function of
some other widget which will configure it for you, such as a text widget.
[Previous] [Contents] [Next]
Scale widgets are used to allow the user to visually select and manipulate a value within a specific range. You might
want to use a scale widget, for example, to adjust the magnification level on a zoomed preview of a picture, or to
control the brightness of a colour, or to specify the number of minutes of inactivity before a screensaver takes over
the screen.
As with scrollbars, there are separate widget types for horizontal and vertical scale widgets. (Most programmers seem
to favour horizontal scale widgets.) Since they work essentially the same way, there's no need to treat them separately
here. The following functions create vertical and horizontal scale widgets, respectively:
FUNCTION gtk_vscale_new( adjustment :
pGtkAdjustment ) : pGtkWidget;
FUNCTION gtk_hscale_new( adjustment :
pGtkAdjustment ) : pGtkWidget;
The adjustment argument can either be an adjustment which has already been created with
gtk_adjustment_new(), or NIL, in which case, an anonymous
Adjustment is created with all of its values set to 0.0 (which isn't very useful in this case). In order to avoid
confusing yourself, you probably want to create your adjustment with a page_size of 0.0 so that its upper value
actually corresponds to the highest value the user can select. (If you're already thoroughly confused, read the section
on Adjustments again for an explanation of what exactly adjustments do and how to
create and manipulate them.)
Scale widgets can display their current value as a number beside the trough. The default behaviour is to show the
value, but you can change this with this function:
PROCEDURE gtk_scale_set_draw_value( scale :
pGtkScale ;
draw_value : boolean );
As you might have guessed, draw_value is either TRUE or
FALSE, with predictable consequences for either one.
The value displayed by a scale widget is rounded to one decimal point by default, as is the value field in its
GtkAdjustment. You can change this with:
PROCEDURE gtk_scale_set_digits( scale :
pGtkScale ; digits : gint );
where digits is the number of decimal places you want. You can set digits to anything you like, but no more than 13
decimal places will actually be drawn on screen.
Finally, the value can be drawn in different positions relative to the trough:
PROCEDURE gtk_scale_set_value_pos( scale :
pGtkScale;
pos : LONGINT );
The argument pos can take one of the following values:
| GTK_POS_LEFT | GTK_POS_RIGHT | GTK_POS_TOP | GTK_POS_BOTTOM |
If you position the value on the side of the trough (e.g., on the top or bottom of a horizontal scale widget),
then it will follow the slider up and down the trough.
[Previous] [Contents] [Next]
The Range widget class is fairly complicated internally, but, like all the base class widgets, most of its
complexity is only interesting if you want to hack it. Also, almost all of the functions and signals it defines are only
really used in writing derived widgets. There are, however, a few useful functions that are
defined that will work on all range widgets.
The update policy" of a range widget defines at what points during user interaction it will change the value
field of its Adjustment and emit the "value_changed" signal on this Adjustment.
The update policies, defined as GtkUpdateType, are:
| GTK_UPDATE_POLICY_CONTINUOUS |
This is the default. The value_changed signal is emitted continuously, i.e., whenever the slider is
moved by even the tiniest amount. |
| GTK_UPDATE_POLICY_DISCONTINUOUS |
The value_changed signal is only emitted once the slider has stopped moving and the user has
released the mouse button. |
| GTK_UPDATE_POLICY_DELAYED |
The value_changed signal is emitted when the user releases the mouse
button, or if the slider stops moving for a short period of time. |
The update policy of a range widget can be set by casting it using the GTK_RANGE(widget) macro and passing it to this
procedure:
PROCEDURE gtk_range_set_update_policy( range :
pGtkRange;
policy : LONGINT );
Getting and setting the adjustment for a range widget "on the fly" is done, predictably, with:
FUNCTION gtk_range_get_adjustment( range :
pGtkRange ) : pGtkAdjustment;
PROCEDURE gtk_range_set_adjustment( range :
pGtkRange ;
adjustment : pGtkAdjustment );
gtk_range_get_adjustment() returns a pointer to the adjustment to which range is connected.
gtk_range_set_adjustment() does absolutely nothing if you pass it the adjustment that range
is already using, regardless of whether you changed any of its fields or not. If you pass it a new Adjustment, it will
unreference the old one if it exists (possibly destroying it), connect the appropriate signals to the new one, and call
the private function gtk_range_adjustment_changed(), which will (or at least, is supposed
to...) recalculate the size and/or position of the slider and redraw if necessary. As mentioned in the section on
adjustments, if you wish to reuse the same Adjustment, when you modify its values directly, you should emit the
changed signal on it, like this:
gtk_signal_emit_by_name(GTK_OBJECT(adjustment), 'changed');
[Previous] [Contents] [Next]
All of the GTK range widgets react to mouse clicks in more or less the same way. Clicking button-1 in the trough
will cause its adjustment's page_increment to be added or subtracted from its value, and the slider to be moved
accordingly. Clicking mouse button-2 in the trough will jump the slider to the point at which the button was clicked.
Clicking any button on a scrollbar's arrows will cause its adjustment's value to change step_increment each time.
It may take a little while to get used to, but by default, scrollbars as well as scale widgets can take the keyboard
focus in GTK. If you think your users will find this too confusing, you can always disable this by unsetting the
GTK_CAN_FOCUS flag on the scrollbar, like this:
GTK_WIDGET_UNSET_FLAGS(scrollbar, GTK_CAN_FOCUS);
The key bindings (which are, of course, only active when the widget has focus) are slightly different between horizontal
and vertical range widgets, for obvious reasons. They are also not quite the same for scale widgets as they are for
scrollbars, for somewhat less obvious reasons (possibly to avoid confusion between the keys for horizontal and vertical
scrollbars in scrolled windows, where both operate on the same area).
All vertical range widgets can be operated with the up and down arrow keys, as well as with the Page Up and
Page Down keys. The arrows move the slider up and down by step_increment, while Page Up and Page Down
move it by page_increment.
The user can also move the slider all the way to one end or the other of the trough using the keyboard. With the
VScale widget, this is done with the Home and End keys, whereas with the VScrollbar widget, this is done
by pressing Control + Page Up and Control + Page Down.
The left and right arrow keys work as you might expect in these widgets, moving the slider back and forth by
step_increment. The Home and End keys move the slider to the ends of the trough. For the HScale
widget, moving the slider by page_increment is accomplished with Control + Left Arrow and
Control + Right Arrow, while for HScrollbar, it's done with Control + Home and Control + End.
[Previous] [Contents] [Next]
This example is a somewhat modified version of the range controls test from testgtk.c. It basically puts up
a window with three range widgets all connected to the same adjustment, and a couple of controls for adjusting some of the
parameters mentioned above and in the section on adjustments, so you can see how they affect the way these widgets work
for the user. This is a long example, but is well worth going through.
{ Converted from C to Pascal by Thomas E. Payne }
PROGRAM range_ex;
{$mode objfpc}
USES glib, gdk, gtk, sysutils;
{ ------------------------------Global Variables:-------------------------------- }
VAR
hscale, vscale : pGtkWidget;
{ -----------------------------cb_pos_menu_select-------------------------------- }
PROCEDURE cb_pos_menu_select( item : pGtkWidget;
pos : tGtkPositionType ); cdecl;
BEGIN
{ Set the value position on both scale widgets }
gtk_scale_set_value_pos(GTK_SCALE(hscale), pos);
gtk_scale_set_value_pos(GTK_SCALE(vscale), pos);
END;
{ --------------------------------cb_pos_menu_select-------------------------------- }
{ ---------------------------------cb_update_menu_select---------------------------- }
PROCEDURE cb_update_menu_select( item : pGtkWidget;
policy : TGtkUpdateType ); cdecl;
BEGIN
{ Set the update policy for both scale widgets }
gtk_range_set_update_policy (GTK_RANGE (hscale), policy);
gtk_range_set_update_policy (GTK_RANGE (vscale), policy);
END;
{ ---------------------------------cb_update_menu_select---------------------------- }
{ ---------------------------------cb_digits_scale---------------------------- }
PROCEDURE cb_digits_scale( adj :pGtkAdjustment );
cdecl;
BEGIN
{ Set the number of decimal places to which adj^.value is rounded }
gtk_scale_set_digits (GTK_SCALE (hscale), round(adj^.value));
gtk_scale_set_digits (GTK_SCALE (vscale), round(adj^.value));
END;
{ ------------------------------------cb_digits_scale------------------------------ }
{ ------------------------------------cb_page_size------------------------------ }
PROCEDURE cb_page_size( get : pGtkAdjustment; set_ :
pGtkAdjustment ); cdecl;
BEGIN
{ Set the page size and page increment size of the sample adjustment to the
value specified by the --Page Size-- scale }
set_^.page_size := get^.value;
set_^.page_increment := get^.value;
{ Now emit the --changed-- signal to reconfigure all the widgets that
are attached to this adjustment }
gtk_signal_emit_by_name(GTK_OBJECT(set_), 'changed');
END;
{ ------------------------------------cb_page_size------------------------------ }
{ ------------------------------------cb_draw_value------------------------------ }
PROCEDURE cb_draw_value( button : pGtkToggleButton );
cdecl;
BEGIN
{ Turn the value display on the scale widgets off or on depending
on the state of the checkbutton }
gtk_scale_set_draw_value(GTK_SCALE(hscale), active(button^) <> 0);
gtk_scale_set_draw_value(GTK_SCALE(vscale), active(button^) <> 0);
END;
{ ------------------------------------cb_draw_value------------------------------ }
{ -----///////////////---Convenience functions---///////////////----- }
{ ------------------------------------make_menu_item------------------------------ }
FUNCTION make_menu_item( name : pgchar ; callback :
tGtkSignalFunc ;
data : gint ) :
pGtkWidget;
VAR
item : pGtkWidget;
BEGIN
item := gtk_menu_item_new_with_label(name);
gtk_signal_connect(GTK_OBJECT(item), 'activate',
callback, gpointer(data));
gtk_widget_show(item);
make_menu_item := item;
END;
{ ------------------------------------make_menu_item------------------------------ }
{ ------------------------------------GINT_TO_POINTER------------------------------ }
FUNCTION GINT_TO_POINTER( t : gint ):
gpointer;
VAR
temp : gint;
BEGIN
temp := t;
GINT_TO_POINTER := @temp;
END;
{ ------------------------------------GINT_TO_POINTER------------------------------ }
{ ------------------------------------scale_set_default_values------------------------------ }
PROCEDURE scale_set_default_values( scale : pGtkScale );
BEGIN
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);
END;
{ ------------------------------------scale_set_default_values------------------------------ }
{ ------------------------------------create_range_controls------------------------------ }
PROCEDURE create_range_controls;
{ makes the sample window }
VAR
window, box1, box2, box3, button, scrollbar : pGtkWidget;
separator, opt, menu, item, label_, scale : pGtkWidget;
adj1, adj2 : pGtkObject;
BEGIN
{ Standard window-creating stuff }
window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_signal_connect(GTK_OBJECT(window), 'destroy',
GTK_SIGNAL_FUNC(@gtk_main_quit),
NIL);
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_set_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. Note that the
--page_size-- value only makes a
difference for scrollbar widgets, and the highest value you'll
get is actually (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);
{ Reuse the same adjustment }
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);
{ Reuse the same adjustment again }
scrollbar := gtk_hscrollbar_new(GTK_ADJUSTMENT(adj1));
{ Notice how this causes the scales to always be updated continuously when the scrollbar
is moved }
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_set_border_width(GTK_CONTAINER(box2), 10);
gtk_box_pack_start(GTK_BOX(box1), box2, TRUE,
TRUE, 0);
gtk_widget_show(box2);
{ A checkbutton to control whether the value is displayed or not }
button := gtk_check_button_new_with_label('Display
value on scale widgets');
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
TRUE);
gtk_signal_connect(GTK_OBJECT(button), 'toggled',
GTK_SIGNAL_FUNC(@cb_draw_value), NIL);
gtk_box_pack_start(GTK_BOX(box2), button, TRUE,
TRUE, 0);
gtk_widget_show(button);
box2 := gtk_hbox_new(FALSE, 10);
gtk_container_set_border_width(GTK_CONTAINER(box2), 10);
{ An option menu to change the position of the value }
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),
GTK_POS_TOP);
gtk_menu_append(GTK_MENU(menu), item);
item := make_menu_item('Bottom', GTK_SIGNAL_FUNC(@cb_pos_menu_select),
GTK_POS_BOTTOM);
gtk_menu_append(GTK_MENU(menu), item);
item := make_menu_item('Left', GTK_SIGNAL_FUNC(@cb_pos_menu_select),
GTK_POS_LEFT);
gtk_menu_append(GTK_MENU(menu), item);
item := make_menu_item('Right', GTK_SIGNAL_FUNC(@cb_pos_menu_select),
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_set_border_width(GTK_CONTAINER(box2), 10);
{ Yet another option menu, this time for the update policy of the scale 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),
GTK_UPDATE_CONTINUOUS);
gtk_menu_append(GTK_MENU(menu), item);
item := make_menu_item('Discontinuous', GTK_SIGNAL_FUNC(@cb_update_menu_select),
GTK_UPDATE_DISCONTINUOUS);
gtk_menu_append(GTK_MENU(menu), item);
item := make_menu_item('Delayed', GTK_SIGNAL_FUNC(@cb_update_menu_select),
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_set_border_width(GTK_CONTAINER(box2), 10);
{ A GtkHScale widget for adjusting the number of digits on the sample scales. }
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), NIL);
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_set_border_width(GTK_CONTAINER(box2), 10);
{ And, one last GtkHScale widget for adjusting the page size of the scrollbar. }
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_set_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),
NIL);
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);
END;
{ ---------------------------------create_range_controls-------------------------------- }
{ ---------------------------------------Main Program----------------------------------- }
BEGIN
gtk_init(@argc, @argv);
create_range_controls();
gtk_main();
END.
{ ---------------------------------------Main Program-------------------------------------- }
You will notice that the program does not call gtk_signal_connect() for the delete_event,
but only for the destroy signal. This will still perform the desired function, because an unhandled
delete_event will result in a destroy signal being given to the window.
[Previous] [Contents] [Next]