Xfce Foundation Classes
 « Main Page | Index

Range Widgets

Table of Contents

  1. Scrollbar Widgets
  2. Scale Widgets
  3. Common Range Functions
  4. Key and Mouse bindings
  5. Range Widget Example

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" (what is sometimes called a "thumb wheel" 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, 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.

Scrollbar Widgets

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 feature full.

There are separate types for horizontal and vertical scrollbars. There really isn't much to say about these. You create horizontal scrollbars with these constructors:

HScrollbar();

HScrollbar(Gtk::Adjustment& adjustment);

and you create vertical scrollbars with these constructors:

VScrollbar();

VScrollbar(Gtk::Adjustment& adjustment);

HScrollbar() and VScrollbar() create an anonymous Adjustment with all of its values set to 0.0. You can access this adjustment by calling the Gtk::Range's get_adjustment() method:

Gtk::Adjustment* get_adjustment() const;

That's about it (if you don't believe me, look in the header files!). The 'adjustment' argument is a reference to an existing Adjustment. If you call the first constructor an Adjustment will be created for you. This might actually be useful if you wish to pass the newly-created adjustment to the constructor of some other widget which will configure it for you, such as a TextView widget.

Scale Widgets

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 color, 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.)  The following constructors create horizontal scale widgets:

HScale();

HScale(Gtk::Adjustment& adjustment);

HScale(double min, double max, double step = 1.0);

and the following constructors create vertical scale widgets:

VScale();

VScale(Gtk::Adjustment& adjustment);

VScale(double min, double max, double step = 1.0);

VScale() and HScale() create an anonymous Adjustment 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. The last constructor takes care of creating a suitable adjustment. (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 method:

void set_draw_value(bool draw_value);

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 Adjustment. You can change this with method:

void set_digits(int digits);

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:

void set_value_pos(Gtk::PositionType pos);

The 'pos' argument can be one of the following values from the Gtk::PositionType enum:
  • POS_LEFT
  • POS_TOP
  • POS_RIGHT
  • 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.

Common Range Methods

The Gtk::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 methods and signals it defines are only really used in writing derived widgets. There are, however, a few useful methods that are defined in <xfc/gtk/range.hh> and will work on all range widgets.

Setting the Update Policy

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 defined in the Gtk::UpdateType enum in <xfc/gtk/enums.hh>:
  • UPDATE_CONTINUOUS  - This is the default. The value_changed signal is emitted continuously, i.e., whenever the slider is moved by even the tiniest amount.
  • UPDATE_DISCONTINUOUS - The value_changed signal is only emitted once the slider has stopped moving and the user has released the mouse button.
  • UPDATE_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 calling this Gtk::Range method:

void set_update_policy(Gtk::UpdateType policy);

Getting and Setting Adjustments

Getting and setting the adjustment for a range widget 'on the fly' is done, predictably, with these Gtk::Range methods:

Gtk::Adjustment* get_adjustment() const;

which returns a pointer to the adjustment to which range is connected, and:

void set_adjustment(Gtk::Adjustment *adjustment);

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

adjustment->emit_by_name ("changed");

Key and Mouse bindings

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 button-3 in the trough of a range or any button on a scrollbar's arrows will cause its adjustment's value to change by step_increment at a time.

Scrollbars are not focusable, thus have no key bindings. The key bindings for the other range widgets (which are, of course, only active when the widget has focus) are do not differentiate between horizontal and vertical range widgets.

All range widgets can be operated with the left, right, 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. This is done with the Home and End keys.

Range Widget Example

This example displays a window with three range widgets all connected to the same adjustment, and a couple of controls for adjusting some of their parameters so you can see how they affect the way these widgets work for the user.

The header file for the Range Widgets example is <rangewidgets.hh>:

#include <xfc/main.hh>
#include <xfc/gtk/adjustment.hh>
#include <xfc/gtk/checkbutton.hh>
#include <xfc/gtk/combobox.hh>
#include <xfc/gtk/scale.hh>
#include <xfc/gtk/window.hh>

using namespace Xfc;

class RangeWidgets : public Gtk::Window
{
    Gtk::Adjustment *adj1;
    Gtk::HScale *hscale;
    Gtk::VScale *vscale;
    Gtk::CheckButton *check_button;

protected:
    void on_draw_value();
    void on_pos_menu_select(Gtk::PositionType pos);
    void on_update_menu_select(Gtk::UpdateType policy);
    void on_digits_scale(Gtk::Adjustment *adj);
    void on_page_size(Gtk::Adjustment *adj);

public:
    RangeWidgets();
    ~RangeWidgets();
};

and the source file is <rangewidgets.cc>:

#include "rangewidgets.hh"
#include <xfc/gtk/box.hh>
#include <xfc/gtk/label.hh>
#include <xfc/gtk/menu.hh>
#include <xfc/gtk/menuitem.hh>
#include <xfc/gtk/scrollbar.hh>
#include <xfc/gtk/separator.hh>

RangeWidgets::RangeWidgets()
{
    using namespace Gtk;

    set_title("range controls");

    Box *box1 = new VBox;
    add(*box1);
    box1->show();

    Box *box2 = new HBox(false, 10);
    box2->set_border_width(10);
    box1->pack_start(*box2);
    box2->show();

    // Gtk::Adjustment args: 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 Adjustment(0.0, 0.0, 101.0, 0.1, 1.0, 1.0);

    vscale = new VScale(*adj1);
    vscale->set_update_policy(UPDATE_CONTINUOUS);
    vscale->set_digits(1);
    vscale->set_value_pos(POS_TOP);
    vscale->set_draw_value(true);
    box2->pack_start(*vscale);
    vscale->show();

    Box *box3 = new VBox(false, 10);
    box2->pack_start(*box3);
    box3->show();

    // Reuse the same adjustment
    hscale = new HScale(*adj1);
    hscale->set_size_request(200, -1);
    hscale->set_update_policy(UPDATE_CONTINUOUS);
    hscale->set_digits(1);
    hscale->set_value_pos(POS_TOP);
    hscale->set_draw_value(true);
    box3->pack_start(*hscale);
    hscale->show();

    // Reuse the same adjustment again
    HScrollbar *scrollbar = new HScrollbar(*adj1);

    // Notice how this causes the scales to always be updated continuously when the scrollbar is moved
    scrollbar->set_update_policy(UPDATE_CONTINUOUS);
    box3->pack_start(*scrollbar);
    scrollbar->show();

    box2 = new HBox(false, 10);
    box2->set_border_width(10);
    box1->pack_start(*box2);
    box2->show();

    // A checkbutton to control whether the value is displayed or not
    check_button = new CheckButton("Display value on scale widgets");
    check_button->set_active(true);
    check_button->signal_toggled().connect(sigc::mem_fun(this, &RangeWidgets::on_draw_value));
    box2->pack_start(*check_button);
    check_button->show();

    box2 = new HBox(false, 10);
    box2->set_border_width(10);

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

    // Gtk::ComboBoxText replaces GtkOptionMenu used in the corresponding GTK+ example.   
    Gtk::ComboBox *combobox = new Gtk::ComboBoxText;
    combobox->signal_changed().connect(sigc::bind(sigc::mem_fun(this, &RangeWidgets::on_pos_menu_select), combobox));
    combobox->append_text("Left");   
    combobox->append_text("Right");   
    combobox->append_text("Top");   
    combobox->append_text("Bottom");
    combobox->set_active(2);
    box2->pack_start(*combobox);
    combobox->show();
       
    box1->pack_start(*box2);
    box2->show();

    box2 = new HBox(false, 10);
    box2->set_border_width(10);

    // Second option menu, this time for the update policy of the scale widgets
    label = new Label("Scale Update Policy:");
    box2->pack_start(*label, false, false);
    label->show();

    // Gtk::ComboBoxText replaces GtkOptionMenu used in the corresponding GTK+ example.   
    combobox = new Gtk::ComboBoxText;
    combobox->signal_changed().connect(sigc::bind(sigc::mem_fun(this, &RangeWidgets::on_update_menu_select), combobox));
    combobox->append_text("Continuous");   
    combobox->append_text("Discontinuous");   
    combobox->append_text("Delayed");   
    combobox->set_active(0);
    box2->pack_start(*combobox);
    combobox->show();
   
    box1->pack_start(*box2);
    box2->show();
   
    box2 = new HBox(false, 10);
    box2->set_border_width(10);

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

    Adjustment *adj2 = new Adjustment(1.0, 0.0, 5.0, 1.0, 1.0, 0.0);
    adj2->signal_value_changed().connect(sigc::bind(sigc::mem_fun(this, &RangeWidgets::on_digits_scale), adj2));
    HScale *scale = new HScale(*adj2);
    scale->set_digits(0);
    box2->pack_start(*scale);
    scale->show();

    box1->pack_start(*box2);
    box2->show();

    box2 = new HBox(false, 10);
    box2->set_border_width(10);

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

    adj2 = new Adjustment(1.0, 1.0, 101.0, 1.0, 1.0, 0.0);
    adj2->signal_value_changed().connect(sigc::bind(sigc::mem_fun(this, &RangeWidgets::on_page_size), adj2));
    scale = new HScale(*adj2);
    scale->set_digits(0);
    box2->pack_start(*scale);
    scale->show();

    box1->pack_start(*box2);
    box2->show();

    HSeparator *separator = new HSeparator;
    box1->pack_start(*separator, false);
    separator->show();

    box2 = new VBox(false, 10);
    box2->set_border_width(10);
    box1->pack_start(*box2, false);
    box2->show();

    Gtk::Button *button = new Button("Quit");
    button->signal_clicked().connect(sigc::mem_fun(this, &RangeWidgets::dispose));
    box2->pack_start(*button);
    button->set_flags(Gtk::CAN_DEFAULT);
    button->grab_default();
    button->show();
}

RangeWidgets::~RangeWidgets()
{
}

void
RangeWidgets::on_draw_value()
{
    // Turn the value display on the scale widgets off or on depending on the state of the checkbutton
    hscale->set_draw_value(check_button->get_active());
    vscale->set_draw_value(check_button->get_active());
}

void
RangeWidgets::on_pos_menu_select(Gtk::ComboBox *combobox)
{
    Gtk::PositionType pos = static_cast<Gtk::PositionType>(combobox->get_active());   
   
    // Set the value position on both scale widgets
    hscale->set_value_pos(pos);
    vscale->set_value_pos(pos);
}

void
RangeWidgets::on_update_menu_select(Gtk::ComboBox *combobox)
{
    Gtk::UpdateType policy = static_cast<Gtk::UpdateType>(combobox->get_active());   
   
    // Set the update policy for both scale widgets
    hscale->set_update_policy(policy);
    vscale->set_update_policy(policy);
}

void
RangeWidgets::on_digits_scale(Gtk::Adjustment *adj)
{
    // Set the number of decimal places to which adj->value is rounded
    hscale->set_digits((int)adj->get_value());
    vscale->set_digits((int)adj->get_value());
}

void
RangeWidgets::on_page_size(Gtk::Adjustment *adj)
{
    // Set the page size and page increment size of the sample adjustment
    // to the value specified by the "Page Size" scale
    adj1->gtk_adjustment()->page_size = adj->get_value();
    adj1->gtk_adjustment()->page_increment = adj->get_value();

    // This sets the adjustment and makes it emit the "changed" signal to
    // reconfigure all the widgets that are attached to this signal.
    adj1->set_value(CLAMP(adj1->get_value(), adj1->lower(), (adj1->upper() - adj1->page_size())));
}

int main (int argc, char *argv[])
{
    using namespace Main;

    init(&argc, &argv);

    RangeWidgets window;
    window.signal_destroy().connect(sigc::ptr_fun(&XFC::Main::quit));
    window.show();

    run();
    return 0;
}

Compiling Range Widget

If you compiled and installed XFC yourself, you will find the source code for Range Widget in the <examples/rangewidgets> source directory along with a Makefile. If XFC came pre-installed, or you installed it from an RPM package, you will find the source code in the </usr/share/doc/xfcui-X.X/examples/rangewidgets> subdirectory. In this case you will have to create the Makefile yourself (replace X.X with the version number of the libXFCui library you have installed).

To create a Makefile for Range Widget, add the following lines to a new text file and save it using the name "Makefile":

CC = g++

CFLAGS = -Wall -O2

rangewidgets: rangewidgets.cc rangewidgets.hh
    $(CC) rangewidgets.cc -o rangewidgets $(CFLAGS) `pkg-config xfcui-X.X --cflags --libs`

clean:
    rm -f *.o rangewidgets


If you cut and paste these lines make sure the whitespace before $(CC) and rm is a tab character. When you compile and run this program you will see the following window appear:


 
You will notice in main() that the program does not connect a slot to the 'delete_event', only to the 'destroy' signal. This will still perform the desired function, because an unhandled delete_event will result in a destroy signal being emitted.


Copyright © 2004-2005 The XFC Development Team Top
XFC 4.4