Chapter 17. Range Widgets

Table of Contents
17.1. Common Range Functions
17.2. Key and Mouse Bindings
17.3. Range Widget Example

Inheritance Hierarchy

Object
   +--- Widget
         +--- Range
         

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 and a slider (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 the chapter on adjustments , 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.

17.1. Common Range Functions

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 on 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 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 are:

The update policy of a range widget can be set with this function:

$range->set_update_policy( $policy );

Getting and setting the adjustment for a range widget "on the fly" is done, predictably, with:

$range->get_adjustment();

$range->set_adjustment( $adjustment );

get_adjustment() returns the adjustment to which $range is connected, and set_adjustment() does absolutely nothing if you pass it the adjustment that the 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 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:

$adjustment->signal_emit( "changed" );

17.2. Key and Mouse Bindings

All of the GTK range widgets react to mouse clicks in more or less the same way. Clicking button1 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 button2 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 at a 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 'focus' flag on the scrollbar, like this:

$scrollbar->unset_flags( '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 typing Control-Page Up and Control-Page Down.

The left and right arrow keys work as you might expect in horizontal range 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 and Control-Right, while for HScrollbar, it's done with Control-Home and Control-End.

17.3. Range Widget Example

This example 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.

Range Widget Source

      
#!/usr/bin/perl -w

use Gtk;
use strict;

set_locale Gtk;
init Gtk;

my $false = 0;
my $true = 1;

my $hscale;
my $vscale;
my $window;
my $box1;
my $box2;
my $box3;
my $button;
my $scrollbar;
my $separator;
my $opt;
my $menu;
my $item;
my $label;
my $scale;
my $adj1;
my $adj2;

# Standard window-creating stuff
$window = new Gtk::Window( "toplevel" );
$window->set_title( "range controls" );

$window->signal_connect( "destroy", sub { Gtk->exit( 0 ); } );

$box1 = new Gtk::VBox( $false, 0 );
$window->add( $box1 );
$box1->show();

$box2 = new Gtk::HBox( $true, 10 );
$box2->border_width( 10 );
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();

# 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 = new Gtk::Adjustment( 0.0, 0.0, 101.0, 0.1, 1.0, 1.0 );

$vscale = new Gtk::VScale( $adj1 );
scale_set_default_values( $vscale );
$box2->pack_start( $vscale, $true, $true, 0 );
$vscale->show();

$box3 = new Gtk::VBox( $false, 10 );
$box2->pack_start( $box3, $true, $true, 0 );
$box3->show();

# Reuse the same adjustment
$hscale = new Gtk::HScale( $adj1 );
$hscale->set_usize( 200, 30 );
scale_set_default_values( $hscale );
$box3->pack_start( $hscale, $true, $true, 0 );
$hscale->show();

# Reuse the same adjustment again
$scrollbar = new Gtk::HScrollbar( $adj1 );
# Notice how this causes the scales to always be updated
# continuously when the scrollbar is moved
$scrollbar->set_update_policy( 'continuous' );
$box3->pack_start( $scrollbar, $true, $true, 0 );
$scrollbar->show();

$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();

# A checkbutton to control whether the value is displayed or not
$button = new Gtk::CheckButton( "Display value on scale widgets" );
$button->set_active( $true );
$button->signal_connect( "toggled", \&cb_draw_value );
$box2->pack_start( $button, $true, $true, 0 );
$button->show();

$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );

# An option menu to change the position of the value
$label = new Gtk::Label( "Scale Value Position:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();

$opt = new Gtk::OptionMenu();
$menu = new Gtk::Menu();

$item = make_menu_item( "Top", \&cb_pos_menu_select, 'top' );
$menu->append( $item );

$item = make_menu_item( "Bottom", \&cb_pos_menu_select, 'bottom' );
$menu->append( $item );

$item = make_menu_item( "Left", \&cb_pos_menu_select, 'left' );
$menu->append( $item );

$item = make_menu_item( "Right", \&cb_pos_menu_select, 'right' );
$menu->append( $item );

$opt->set_menu( $menu );
$box2->pack_start( $opt, $true, $true, 0 );
$opt->show();

$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();

$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );

# Yet another option menu, this time for the update policy of the
# scale widgets
$label = new Gtk::Label( "Scale Update Policy:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();

$opt = new Gtk::OptionMenu();
$menu = new Gtk::Menu();

$item = make_menu_item ("Continuous",
			\&cb_update_menu_select,
			'continuous' );
$menu->append( $item );

$item = make_menu_item ("Discontinuous",
			\&cb_update_menu_select,
			'discontinuous' );
$menu->append( $item );

$item = make_menu_item ("Delayed",
			\&cb_update_menu_select,
			'delayed' );
$menu->append( $item );

$opt->set_menu( $menu );
$box2->pack_start( $opt, $true, $true, 0 );
$opt->show();

$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();

$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );

# An HScale widget for adjusting the number of digits on the
# sample scales.
$label = new Gtk::Label( "Scale Digits:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();

$adj2 = new Gtk::Adjustment( 1.0, 0.0, 5.0, 1.0, 1.0, 0.0 );
$adj2->signal_connect( "value_changed", \&cb_digits_scale );
$scale = new Gtk::HScale( $adj2 );
$scale->set_digits( 0 );
$box2->pack_start( $scale, $true, $true, 0 );
$scale->show();

$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();

$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );

# And, one last HScale widget for adjusting the page size of the scrollbar
$label = new Gtk::Label( "Scrollbar Page Size:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();

$adj2 = new Gtk::Adjustment( 1.0, 1.0, 101.0, 1.0, 1.0, 0.0 );
$adj2->signal_connect( "value_changed", \&cb_page_size, $adj1 );
$scale = new Gtk::HScale( $adj2 );
$scale->set_digits( 0 );
$box2->pack_start( $scale, $true, $true, 0 );
$scale->show();

$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();

$separator = new Gtk::HSeparator();
$box1->pack_start( $separator, $false, $true, 0 );
$separator->show();

$box2 = new Gtk::VBox( $false, 10 );
$box2->border_width( 10 );
$box1->pack_start( $box2, $false, $true, 0 );
$box2->show();

$button = new Gtk::Button( "Quit" );
$button->signal_connect( "clicked", sub { Gtk->exit( 0 ); } );
$box2->pack_start( $button, $true, $true, 0 );
$button->can_default( $true );
$button->grab_default();
$button->show();

$window->show();

main Gtk;
exit( 0 );



### Subroutines


# Callback that changes to position of the scale text labels.

sub cb_pos_menu_select
{
   my ( $item, $pos ) = @_;

   # Set the value position on both scale widgets
   $hscale->set_value_pos( $pos );
   $vscale->set_value_pos( $pos );
}


# Callback that changes the update policy of the scale widgets.

sub cb_update_menu_select
{
   my ( $item, $policy ) = @_;

   # Set the update policy for both scale widgets
   $hscale->set_update_policy( $policy );
   $vscale->set_update_policy( $policy );
}


# Callback that sets the number of digits after the decimal point of
# scale widgets.

sub cb_digits_scale
{
   my ( $adj ) = @_;

   # Set the number of decimal places to which adj->value is rounded
   $hscale->set_digits( $adj->value );
   $vscale->set_digits( $adj->value );
}


# Callback to set the page size of the scale widgets.

sub cb_page_size
{
   my ( $get, $set ) = @_;

   # 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
   $set->signal_emit_by_name( "changed" );
}


# Callback to toggle whether text labels are drawn with the scale
# widgets.

sub cb_draw_value
{
   my ( $button ) = @_;

   # Turn the value display on the scale widgets off or on depending
   # on the state of the checkbutton
   $hscale->set_draw_value( $button->active );
   $vscale->set_draw_value( $button->active );
}


# Create a menu item with a given name, callback, and optional data.
# Once created, the menu item is returned.

sub make_menu_item
{
   my ( $name, $callback, $data ) = @_;
   my $item;

   $item = new Gtk::MenuItem( $name );
   $item->signal_connect( "activate", $callback, $data );
   $item->show();

   return $item;
}


# Given a scale widget, set its default values.

sub scale_set_default_values
{
   my ( $scale ) = @_;

   $scale->set_update_policy( 'continuous' );
   $scale->set_digits( 1 );
   $scale->set_value_pos( 'top' );
   $scale->set_draw_value( $true );
}


# END EXAMPLE PROGRAM
      
   

Range Widget Example Screenshot

You will notice that the program does not call 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.

See the chapter on scrollbars and scales for more information on how to use those widgets.