Tutorial
|
|
|
|
Range Widgets
- Scrollbar Widgets
- Scale Widgets
- Common Range Functions
- Key and Mouse bindings
- 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 "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, 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 featureful.
There are separate types for horizontal and vertical scrollbars. There
really isn't much to say about these. You create horizontal scrollbars
with the following constructors:
HScrollbar();
explicit HScrollbar(Gtk::Adjustment& adjustment); |
and you create vertical scrollbars with the following constructors:
VScrollbar();
explicit 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 text 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.) Since they work essentially the same way, there's no
need to treat them separately here. The following constructors create
vertical and horizontal scale widgets, respectively:
VScale();
explicit VScale(Gtk::Adjustment&
adjustment);
VScale(double min, double
max, double step = 1.0); |
HScale();
explicit HScale(Gtk::Adjustment&
adjustment);
HScale(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:
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 is of type Gtk::PositionType, which can take
one of the following values:
- 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 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 <inti/gtk/range.h> 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, defined in <inti/gtk/enums.h>
as type enum Gtk::UpdateType, are:
- 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 the
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
the 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.
Example
This example 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.
The header file for range controls is rangewidgets.h:
#include<inti/main.h>
#include <inti/core.h>
#include <inti/gtk/adjustment.h>
#include <inti/gtk/scale.h>
#include <inti/gtk/checkbutton.h>
using namespace Inti;
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.h"
#include <inti/gtk/scrollbar.h>
#include <inti/gtk/label.h>
#include <inti/gtk/optionmenu.h>
#include <inti/gtk/menu.h>
#include <inti/gtk/menuitem.h>
#include <inti/gtk/separator.h>
#include <inti/bind.h>
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->sig_toggled().connect(slot(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::Menu *menu = new
Gtk::Menu;;
menu->append(*(new
MenuItem("Top")), bind(slot(this,
&RangeWidgets::on_pos_menu_select), POS_TOP));
menu->append(*(new
MenuItem("Bottom")), bind(slot(this,
&RangeWidgets::on_pos_menu_select), POS_BOTTOM));
menu->append(*(new
MenuItem("Left")), bind(slot(this,
&RangeWidgets::on_pos_menu_select), POS_LEFT));
menu->append(*(new
MenuItem("Right")), bind(slot(this,
&RangeWidgets::on_pos_menu_select), POS_RIGHT));
OptionMenu *option_menu = new
OptionMenu(*menu);
box2->pack_start(*option_menu);
option_menu->show_all();
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();
menu = new Gtk::Menu;
menu->append(*(new
MenuItem("Continuous")), bind(slot(this,
&RangeWidgets::on_update_menu_select), UPDATE_CONTINUOUS));
menu->append(*(new
MenuItem("Discontinuous")), bind(slot(this,
&RangeWidgets::on_update_menu_select), UPDATE_DISCONTINUOUS));
menu->append(*(new
MenuItem("Delayed")), bind(slot(this,
&RangeWidgets::on_update_menu_select), UPDATE_DELAYED));
option_menu = new
OptionMenu(*menu);
box2->pack_start(*option_menu);
option_menu->show_all();
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->sig_value_changed().connect(bind(slot(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->sig_value_changed().connect(bind(slot(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->sig_clicked().connect(slot(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::PositionType pos)
{
// 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::UpdateType policy)
{
// 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.sig_destroy().connect(slot(&Inti::Main::quit));
window.show();
run();
return 0;
}
|
You will notice in main() that the program does
not connect a slot 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.