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 "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.
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 constructors:
Gtk::HScrollbar( Gtk::Adjustment &adjustment );
Gtk::HScrollbar();
Gtk::VScrollbar( GtkAdjustment &adjustment );
Gtk::VScrollbar();
and that's about it (if you don't believe me, look in the header
files!). You can either pass a reference to an existing
adjustment
as argument, or no argument at all, in which case one
will be created for you. Not specifying an argument 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. You can fetch the object's
adjustment using the following method:
Gtk::Adjustment* GtkRange::get_adjustment()
Gtk::Scrollbar
derives from Gtk::Range
; it's defined in
<gtk--/scrollbar.h>
.
Scale widgets (or "sliders") 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. Here are the constructors for vertical and horizontal scale widgets, respectively:
Gtk::VScale(GtkAdjustment &adjustment);
Gtk::VScale();
Gtk::HScale(GtkAdjustment &adjustment);
Gtk::HScale();
As with scrollbars, you can either pass an existing adjustment
as
argument, or no argument at all, in which case an anonymous
Gtk::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, go back and read the section on
Adjustments again (you did read it
already, didn't you?) 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:
void Gtk::Scale::set_draw_value(bool draw_value);
The value displayed by a scale widget is rounded to one decimal point
by default, as is the value
field in its Gtk::Adjustment
. You can
change this with:
void Gtk::Scale::set_digits(gint 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.
Finally, the value can be drawn in different positions relative to the trough:
void Gtk::Scale::set_value_pos(GtkPositionType pos);
The argument pos
can be one of the following:
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.
The Gtk::Scale
class is defined in <gtk--/scale.h>
.
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 <gtk--/range.h>
and 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
Gtk::Adjustment
and emit the value_changed
signal on this
Gtk::Adjustment
. The update policies, defined in
<gtk/gtkenums.h>
as type enum 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 using this method:
void Gtk::Range::set_update_policy(GtkUpdateType policy);
Getting and setting the adjustment for a range widget "on the fly" is done, predictably, with:
Gtk::Adjustment* Gtk::Range::get_adjustment();
void Gtk::Range::set_adjustment(Gtk::Adjustment *adjustment);
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
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 (or you!) will find this too confusing, you can
always disable this by unsetting the GTK_CAN_FOCUS
flag on the
scrollbar, like this:
scrollbar.unset_flag(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 Gtk::VScale
widget, this is
done with the Home
and End
keys, whereas with the
Gtk::VScrollbar
widget, it's done by typing 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 Gtk::HScale
widget, moving the slider by page_increment
is
accomplished with Control-Left
and Control-Right
,
while for Gtk::HScrollbar
, it's done with Control-Home
and
Control-End
.
This example displays a window with three range widgets all connected to the same adjustment, along with 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.
Notice the use of subclassing in the example. We've made a
"mini-widget" called LabeledOptionMenu
which simplifies the
creation of the two labelled option menus. Although, in this small
example, we don't really gain anything in terms of code size, for
large programs the technique can quickly prove its worth.
Source location: examples/rangewidgets/rangewidgets.cc
#include <gtk--/main.h>
#include <gtk--/adjustment.h>
#include <gtk--/scale.h>
#include <gtk--/box.h>
#include <gtk--/label.h>
#include <gtk--/window.h>
#include <gtk--/separator.h>
#include <gtk--/scrollbar.h>
#include <gtk--/checkbutton.h>
#include <string>
#include <gtk--/menu.h>
#include <gtk--/optionmenu.h>
// Gtk-- version of the range widgets example from the gtk+ tutorial
class LabeledOptionMenu : public Gtk::HBox
{
Gtk::Label m_label;
public:
LabeledOptionMenu(const Gtk::string &menutitle, Gtk::Menu *menu,
bool homogeneous=false,
gint spacing=10);
Gtk::Menu *m_menu;
};
LabeledOptionMenu::LabeledOptionMenu(const Gtk::string &menutitle,
Gtk::Menu *menu,
bool homogeneous,
gint spacing) :
Gtk::HBox(homogeneous, spacing),
m_label(menutitle),
m_menu(menu)
{
pack_start(m_label, false, false, 0);
Gtk::OptionMenu *om=manage(new Gtk::OptionMenu);
om->set_menu(m_menu);
pack_start(*om);
}
class RangeControls : public Gtk::Window
{
Gtk::VBox m_vbox1, m_vbox2, m_vbox3;
Gtk::HBox m_hbox1, m_hbox2;
Gtk::Adjustment m_adj1;
Gtk::VScale m_vscale;
Gtk::HScale m_hscale;
Gtk::HSeparator m_separator;
Gtk::Button m_buttonQuit;
Gtk::CheckButton m_checkbutton;
Gtk::HScrollbar m_scrollbar;
public:
RangeControls();
// callbacks
void draw_value(Gtk::CheckButton *button);
void menu_pos_select_cb(GtkPositionType type);
void menu_update_select_cb(GtkUpdateType type);
void digits_cb(Gtk::Adjustment *adj);
void psize_cb(Gtk::Adjustment *adj);
gint delete_event_impl(GdkEventAny*) {
Gtk::Main::quit(); return 0;
}
};
RangeControls::RangeControls() :
m_vbox1(false, 0),
m_vbox2(false, 20),
m_vbox3(false, 10),
m_hbox1(false, 10),
m_hbox2(false, 10),
// 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).
m_adj1(0.0, 0.0, 101.0, 0.1, 1.0, 1.0),
m_vscale(m_adj1),
m_hscale(m_adj1),
m_buttonQuit("Quit"),
// a checkbutton to control whether the value is displayed or not
m_checkbutton("Display value on scale widgets",0),
// reuse the same adjustment again
m_scrollbar(m_adj1)
// notice how this causes the scales to always be update
// continuously when the scrollbar is moved
{
set_title("range controls");
m_vscale.set_update_policy(GTK_UPDATE_CONTINUOUS);
m_vscale.set_digits(1);
m_vscale.set_value_pos(GTK_POS_TOP);
m_vscale.set_draw_value(true);
m_hscale.set_update_policy(GTK_UPDATE_CONTINUOUS);
m_hscale.set_digits(1);
m_hscale.set_value_pos(GTK_POS_TOP);
m_hscale.set_draw_value(true);
add(m_vbox1);
m_vbox1.pack_start(m_vbox2);
m_vbox2.set_border_width(10);
m_vbox2.pack_start(m_hbox1);
m_hbox1.pack_start(m_vscale);
m_hbox1.pack_start(m_vbox3);
m_hscale.set_usize(200, 30);
m_vbox3.pack_start(m_hscale);
m_scrollbar.set_update_policy(GTK_UPDATE_CONTINUOUS);
m_vbox3.pack_start(m_scrollbar);
m_checkbutton.set_active(true);
m_checkbutton.toggled.connect(
bind(slot(this, &RangeControls::draw_value), &m_checkbutton));
m_vbox2.pack_start(m_checkbutton);
{
using namespace Gtk::Menu_Helpers;
Gtk::Menu *menu_vpos=manage(new Gtk::Menu);
MenuList& list_vpos=menu_vpos->items();
list_vpos.push_back(
MenuElem("Top",bind(
slot(this,&RangeControls::menu_pos_select_cb),GTK_POS_TOP)));
list_vpos.push_back(
MenuElem("Bottom",bind(
slot(this,&RangeControls::menu_pos_select_cb),GTK_POS_BOTTOM)));
list_vpos.push_back(
MenuElem("Left",bind(
slot(this,&RangeControls::menu_pos_select_cb),GTK_POS_LEFT)));
list_vpos.push_back(
MenuElem("Right",bind(
slot(this,&RangeControls::menu_pos_select_cb),GTK_POS_RIGHT)));
Gtk::Menu *menu_upd=manage(new Gtk::Menu);
MenuList& list_upd=menu_upd->items();
list_upd.push_back(
MenuElem("Continuous",bind(
slot(this,&RangeControls::menu_update_select_cb),GTK_UPDATE_CONTINUOUS)));
list_upd.push_back(
MenuElem("Discontinuous",bind(
slot(this,&RangeControls::menu_update_select_cb),GTK_UPDATE_DISCONTINUOUS)));
list_upd.push_back(
MenuElem("Delayed",bind(
slot(this,&RangeControls::menu_update_select_cb),GTK_UPDATE_DELAYED)));
m_vbox2.pack_start(
*manage(new LabeledOptionMenu("Scale Value Position:",menu_vpos)));
m_vbox2.pack_start(
*manage(new LabeledOptionMenu("Scale Update Policy:",menu_upd)));
}
Gtk::HBox *lsbox1=manage(new Gtk::HBox(false,10));
lsbox1->pack_start(*manage(new Gtk::Label("Scale Digits:",0)),false,false);
Gtk::Adjustment *adj1=manage(new Gtk::Adjustment(1.0, 0.0, 5.0));
Gtk::HScale *digits=manage(new Gtk::HScale(*adj1));
digits->set_digits(0);
adj1->value_changed.connect(bind(slot(this,&RangeControls::digits_cb),adj1));
lsbox1->pack_start(*digits,true,true);
Gtk::HBox *lsbox2=manage(new Gtk::HBox(false,10));
lsbox2->pack_start(*manage(new Gtk::Label("Scrollbar Page Size:",0)),false,false);
Gtk::Adjustment *adj2=manage(new Gtk::Adjustment(1.0, 1.0, 101.0));
Gtk::HScale *pgsize=manage(new Gtk::HScale(*adj2));
pgsize->set_digits(0);
adj2->value_changed.connect(bind(slot(this,&RangeControls::psize_cb),adj2));
lsbox2->pack_start(*pgsize,true,true);
m_vbox2.pack_start(*lsbox1);
m_vbox2.pack_start(*lsbox2);
m_vbox1.pack_start(m_separator, false, true, 0);
m_vbox1.pack_start(m_buttonQuit,false,false);
m_buttonQuit.set_flags(GTK_CAN_DEFAULT);
m_buttonQuit.grab_default();
m_buttonQuit.clicked.connect(Gtk::Main::quit.slot());
m_buttonQuit.set_border_width(10);
show_all();
}
void RangeControls::draw_value(Gtk::CheckButton *button)
{
m_vscale.set_draw_value(button->get_active());
m_hscale.set_draw_value(button->get_active());
}
void RangeControls::menu_pos_select_cb(GtkPositionType postype)
{
m_vscale.set_value_pos(postype);
m_hscale.set_value_pos(postype);
}
void RangeControls::menu_update_select_cb(GtkUpdateType type)
{
m_vscale.set_update_policy(type);
m_hscale.set_update_policy(type);
}
void RangeControls::digits_cb(Gtk::Adjustment *adj)
{
gfloat val=adj->get_value();
m_vscale.set_digits(val);
m_hscale.set_digits(val);
}
void RangeControls::psize_cb(Gtk::Adjustment *adj)
{
Gtk::Adjustment *swadj=m_scrollbar.get_adjustment();
gfloat val=adj->get_value();
swadj->set_page_size(val);
swadj->set_page_increment(val);
// note that we don't have to emit the "changed" signal;
// GTK-- does this for us
}
int main(int argc, char *argv[])
{
Gtk::Main myapp(&argc, &argv);
RangeControls rangecontrols;
myapp.run();
return 0;
}