Tutorial
|
|
|
|
Creating a widget from scratch
- Displaying a widget on the
screen
- The origins of the Dial
Widget
- The Basics
- The Realize Signal
- Size Negotiation
- The Expose Event
- Event Handling
- Possible Enhancements
In this section, we'll learn more
about how widgets display themselves on the screen and interact with
events. As an example of this, we'll create a analog dial widget
with a pointer that the user can drag to set the value.
Dial is a custom widget that derives
directly from Gtk::Widget. Creating this kind of kind of widget is akin
to creating a new C widget in GTK+. It's a little more involved than
simply deriving a new widget from an existing widget and making a few
changes. Most of the time you can find a predefined widget that will
meets your needs, like Tictactoe in the previous section, but
occasionally you wont. This is when you need to create a new widget from
scratch. The steps involved are the same as those used to create a GTK+
widget, except in C++ there are fewer of them. Lets take a look. You
will find the source code for Dial in the <examples/dial>
directory.
Displaying
a widget on the screen
There are several steps that are involved in displaying a widget on the
screen. After the widget is constructed you will have to override
several protected Gtk::Widget signal handlers:
virtual void on_realize();
|
on_realize() is responsible for creating an X window for the widget if
it has one.
on_map() is invoked after the user
calls Gtk::Widget::show(). It is responsible for making sure the widget
is actually drawn on the screen (mapped). For a container class, it must
also make calls to map() functions of any child widgets.
virtual bool on_expose_event(const Gdk::EventExpose&
event); |
on_expose_event() is a handler for
expose events for the widget. It makes the necessary calls to the
drawing functions to draw the exposed portion on the screen. For
container widgets, this function must generate expose events for its
child widgets which don't have their own windows. (If they have their
own windows, then X will generate the necessary expose events.)
The origins of the Dial Widget
Just as all land animals are just
variants on the first amphibian that crawled up out of the mud, GTK
widgets tend to start off as variants of some other, previously written
widget. Thus, although this section is entitled "Creating a Widget from
Scratch", the Dial widget really began with the source code for the
Range widget. This was picked as a starting point because it would be
nice if our Dial had the same interface as the Scale widgets which are
just specialized descendants of the Range widget. So, though the source
code is presented below in finished form, it should not be implied that
it was written, ab initio in this fashion. Also, if you aren't yet
familiar with how scale widgets work from the application writer's point
of view, it would be a good idea to look them over before continuing.
The Basics
The header file for the Dial class is dial.h
/* Inti: Integrated Foundation
Classes
* Copyright (C)
2003 The Inti Development Team.
*
* dial.h - Dial
is a custom widget. It's a C++ version of the GtkDial example.
*
* This program
is free software; you can redistribute it and/or modify
* it under the
terms of the GNU General Public License as published by
* the Free
Software Foundation; either version 2 of the License, or
* (at your
option) any later version.
*
* This program
is distributed in the hope that it will be useful,
* but WITHOUT
ANY WARRANTY; without even the implied warranty of
*
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library
General Public License for more details.
*
* You should
have received a copy of the GNU Library General Public License
* along with
this program; if not, write to the Free Software
* Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INTI_GTK_DIAL_H
#define INTI_GTK_DIAL_H
#ifndef INTI_GTK_WIDGET_H
#include <inti/gtk/widget.h>
#endif
namespace Inti {
namespace Gtk {
class Adjustment;
class Dial : public Widget
{
UpdateType policy_;
// Either
UPDATE_CONTINUOUS, UPDATE DISCONTINUOUS or UPDATE_DELAYED.
unsigned int
button;
// The
mouse button currently pressed or 0 if none.
int
radius;
int
pointer_width;
//
Dimensions of the dial components.
Connection timer;
// Signal
connection of the update timer.
float
angle;
float
last_angle;
//
Current angle.
float
old_value;
float
old_lower;
float
old_upper;
// Old values
from adjustment stored so we know when something changes.
Pointer<Adjustment> adjustment_;
// The
adjustment object that stores the data for this dial. A smart
//
pointer is used to handle the reference counting.
void
update();
void
update_mouse(int x, int y);
bool
on_timeout();
protected:
// Signal Handlers
virtual void on_realize();
virtual bool
on_expose_event(const
Gdk::EventExpose& event);
virtual void
on_size_request(Requisition *requisition);
virtual void
on_size_allocate(const
Allocation& allocation);
virtual bool
on_button_press_event(const
Gdk::EventButton& event);
virtual bool
on_button_release_event(const
Gdk::EventButton& event);
virtual bool
on_motion_notify_event(const
Gdk::EventMotion& event);
void
on_adjustment_changed();
void
on_adjustment_value_changed();
public:
// Constructors
Dial(Adjustment *adjustment = 0);
virtual
~Dial();
// Accessors
Adjustment* get_adjustment() const;
// Methods
void
set_adjustment(Adjustment *adjustment);
void
set_update_policy(UpdateType policy);
};
} // namespace Gtk
} // namespace Inti
#endif // INTI_GTK_DIAL_H
|
Since there is quite a bit more going
on in this widget than the last one, we have more class data members and
signal handlers, but otherwise things are pretty similar. Note that when
we store a pointer to the Adjustment object, we need to increment its
reference count, (and correspondingly decrement it when we no longer
use it) so that GTK can keep track of when it can be safely destroyed.
For convenience, Dial uses a smart pointer to manage the adjustment's
reference counting but it could have done it manually.
Also, there are a few functions to manipulate the widget's options:
get_adjustment(), set_adjustment() and set_update_policy().
The source file for the Dial class is dial.cc:
#include "dial.h"
#include <inti/gtk/private/widget_p.h>
#include <inti/gtk/adjustment.h>
#include <inti/gtk/style.h>
#include <inti/gdk/window.h>
#include <inti/main.h>
#include <algorithm>
#include <math.h>
const int
scroll_delay_length = 300;
const int
dial_default_size = 100;
using namespace Inti;
Gtk::Dial::Dial(Adjustment *adjustment)
: Gtk::Widget((GtkWidget*)Gtk::WidgetClass::create()), adjustment_(0)
{
button = 0;
policy_ = UPDATE_CONTINUOUS;
radius = 0;
pointer_width = 0;
angle = 0.0;
old_value = 0.0;
old_lower = 0.0;
old_upper = 0.0;
if
(!adjustment)
adjustment = new Gtk::Adjustment;
set_adjustment(adjustment);
}
Gtk::Dial::~Dial()
{
}
Gtk::Adjustment*
Gtk::Dial::get_adjustment() const
{
return
adjustment_;
}
void
Gtk::Dial::set_adjustment(Adjustment *adjustment)
{
if
(adjustment_)
{
adjustment_->disconnect_by_name("changed");
adjustment_->disconnect_by_name("value_changed");
}
adjustment_ = adjustment;
if
(adjustment_)
{
adjustment_->sig_changed().connect(slot(this,
&Dial::on_adjustment_changed));
adjustment_->sig_value_changed().connect(slot(this,
&Dial::on_adjustment_value_changed));
}
old_value = adjustment_->get_value();
old_lower = adjustment_->lower();
old_upper = adjustment_->upper();
update();
}
void
Gtk::Dial::on_realize()
{
set_flags(REALIZED);
Gdk::EventMaskField mask = get_events() |
Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK;
mask |= (Gdk::POINTER_MOTION_MASK |
Gdk::POINTER_MOTION_HINT_MASK);
Gdk::WindowAttr attributes(get_allocation(),
Gdk::WINDOW_CHILD, mask);
attributes.set_visual(*get_visual());
attributes.set_colormap(*get_colormap());
Gdk::Window *window = new
Gdk::Window(*get_parent_window(), attributes);
set_window(*window);
get_style()->attach(*window);
window->set_user_data(gtk_widget());
get_style()->set_background(*window,
STATE_ACTIVE);
}
void
Gtk::Dial::on_size_request(Requisition *requisition)
{
requisition->set(dial_default_size,
dial_default_size);
}
void
Gtk::Dial::on_size_allocate(const Allocation& allocation)
{
set_allocation(allocation);
if
(is_realized())
{
get_window()->move_resize(allocation);
}
radius = (int)(std::min(allocation.width(),
allocation.height()) * 0.45);
pointer_width = radius / 5;
}
bool
Gtk::Dial::on_expose_event(const
Gdk::EventExpose& event)
{
if
(event.count() > 0)
return false;
Allocation allocation = get_allocation();
int xc =
allocation.width() / 2;
int yc =
allocation.height() / 2;
int
upper = (int)adjustment_->upper();
int
lower = (int)adjustment_->lower();
double s
= sin(last_angle);
double c
= cos(last_angle);
last_angle = angle;
Gdk::Point points[6];
points[0].set(int(xc
+ s * pointer_width / 2), int(yc
+ c * pointer_width / 2));
points[1].set(int(xc
+ c * radius), int(yc - s
* radius));
points[2].set(int(xc
- s * pointer_width / 2), int(yc
- c * pointer_width / 2));
points[3].set(int(xc
- c * radius / 10), int(yc
+ s * radius / 10));
points[4].set(points[0].x(), points[0].y());
Gdk::Window *window = get_window();
window->invalidate(allocation, false);
Gtk::Style *style = get_style();
style->draw_polygon(*window, STATE_NORMAL,
SHADOW_OUT, points, 5, false,
0, this);
// Draw
ticks
int inc
= upper - lower;
if (inc
== 0)
return false;
double
increment = (100 * G_PI) / ( radius * radius);
while
(inc < 100) inc *= 10;
while
(inc >= 1000) inc /= 10;
double
last = -1;
for (int i = 0; i <= inc; i++)
{
double theta = ((float)i * G_PI / (18 * inc /
24.) - G_PI / 6.);
if ((theta - last) <
(increment))
continue;
last = theta;
s = sin(theta);
c = cos(theta);
int tick_length = (i % (inc / 10)
== 0) ? pointer_width : pointer_width / 2;
window->draw_line(*style->fg_gc(get_state()), int(xc + c * (radius -
tick_length)),
int(yc - s * (radius -
tick_length)), int(xc + c *
radius), int(yc - s *
radius));
}
// Draw
pointer
s = sin(angle);
c = cos(angle);
last_angle = angle;
points[0].set(int(xc
+ s * pointer_width / 2), int(yc
+ c * pointer_width / 2));
points[1].set(int(xc
+ c * radius), int(yc - s * radius));
points[2].set(int(xc
- s * pointer_width / 2), int(yc
- c * pointer_width / 2));
points[3].set(int(xc
- c * radius / 10), int(yc
+ s * radius / 10));
points[4].set(points[0].x(), points[0].y());
style->draw_polygon(*window, STATE_NORMAL,
SHADOW_OUT, points, 5, true,
0, this);
return false;
}
bool
Gtk::Dial::on_button_press_event(const
Gdk::EventButton& event)
{
//
Determine if button press was within pointer region - we do this by
//
computing the parallel and perpendicular distance of the point where
// the
mouse was pressed from the line passing through the pointer.
int dx = int(event.x()) -
get_allocation().width() / 2;
int dy =
get_allocation().height() / 2 - int(event.y());
double s
= sin(angle);
double c
= cos(angle);
double
d_parallel = s * dy + c * dx;
double
d_perpendicular = fabs(s * dx - c * dy);
if
(!button && (d_perpendicular < pointer_width/2) &&
(d_parallel > - pointer_width))
{
Main::grab_add(*this);
button = event.button();
update_mouse(int(event.x()), int(event.y()));
}
return false;
}
bool
Gtk::Dial::on_button_release_event(const
Gdk::EventButton& event)
{
if
(button == event.button())
{
Main::grab_remove(*this);
button = 0;
if (policy_ == UPDATE_DELAYED)
timer.disconnect();
if ((policy_ !=
UPDATE_CONTINUOUS) && (old_value != adjustment_->get_value()))
adjustment_->value_changed();
}
return false;
}
bool
Gtk::Dial::on_motion_notify_event(const
Gdk::EventMotion& event)
{
Gtk::Widget::on_motion_notify_event(event);
if
(button != 0)
{
int x = int(event.x());
int y = int(event.y());
Gdk::ModifierTypeField mods;
Gdk::Window *window =
get_window();
if (event.is_hint() ||
(event.window() != window))
window->get_pointer(&x, &y, &mods);
int mask;
switch (button)
{
case 1:
mask =
GDK_BUTTON1_MASK;
break;
case 2:
mask =
GDK_BUTTON2_MASK;
break;
case 3:
mask =
GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
update_mouse(x, y);
}
return false;
}
void
Gtk::Dial::set_update_policy(UpdateType policy)
{
policy_ = policy;
}
void
Gtk::Dial::update()
{
double
value = adjustment_->get_value();
double
lower = adjustment_->lower();
double
upper = adjustment_->upper();
double
new_value = std::max(lower, value);
new_value = std::min(new_value, upper);
if
(new_value != value)
{
adjustment_->set_value(new_value);
adjustment_->value_changed();
}
angle = 7.*G_PI/6. - (new_value - lower) *
4.*G_PI/3. / (upper - lower);
queue_draw();
}
void
Gtk::Dial::update_mouse(int
x, int y)
{
int xc =
get_allocation().width() / 2;
int yc =
get_allocation().height() / 2;
float
old_value = adjustment_->get_value();
angle = atan2(yc - y, x - xc);
if
(angle < -G_PI/2.)
angle += 2*G_PI;
if
(angle < -G_PI/6)
angle = -G_PI/6;
if
(angle > 7.*G_PI/6.)
angle = 7.*G_PI/6.;
double
lower = adjustment_->lower();
adjustment_->set_value(lower + (7.*G_PI/6 -
angle) * (adjustment_->upper() - lower) / (4.*G_PI/3.));
if
(adjustment_->get_value() == old_value)
{
if (policy_ == UPDATE_CONTINUOUS)
{
adjustment_->value_changed();
}
else
{
queue_draw();
if (policy_ == UPDATE_DELAYED)
{
timer = Main::timeout_signal.connect(slot(this, &Dial::on_timeout),
scroll_delay_length);
}
}
}
}
bool
Gtk::Dial::on_timeout()
{
if
(policy_ == UPDATE_DELAYED)
adjustment_->value_changed();
return false;
}
void
Gtk::Dial::on_adjustment_changed()
{
double
value = adjustment_->get_value();
double
lower = adjustment_->lower();
double
upper = adjustment_->upper();
if
((old_value != value) || (old_lower != lower) || (old_upper != upper))
{
update();
old_value = value;
old_lower = lower;
old_upper = upper;
}
}
void
Gtk::Dial::on_adjustment_value_changed()
{
double
value = adjustment_->get_value();
if
(old_value != value)
{
update();
old_value = value;
}
}
|
The Realize Signal
Now we come to some new types of functions - signal handlers. First, we
have a signal handler that does the work of creating the X window.
void
Gtk::Dial::on_realize()
{
set_flags(REALIZED);
Gdk::EventMaskField mask = get_events() |
Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK;
mask |= (Gdk::POINTER_MOTION_MASK |
Gdk::POINTER_MOTION_HINT_MASK);
Gdk::WindowAttr attributes(get_allocation(),
Gdk::WINDOW_CHILD, mask);
attributes.set_visual(*get_visual());
attributes.set_colormap(*get_colormap());
Gdk::Window *window = new
Gdk::Window(*get_parent_window(), attributes);
set_window(*window);
get_style()->attach(*window);
window->set_user_data(gtk_widget());
get_style()->set_background(*window,
STATE_ACTIVE);
} |
First, on_realize() sets the Gtk::REALIZE
flag and then sets up a mask specifying the events the Dial widget
should receive. When setting the event mask you first call
Gtk::Widget::get_events() to retrieve the event mask that the user has
set for the widget (with Gtk::Widget::set_events()), and add then add
the events we're interested in ourselves.
Gdk::WindowAttr specifies the creation parameters for the new window
and is later passed to the Gdk::Window constructor.
Gdk::WindowAttr is a wrapper class for the GdkWindowAttr structure. It
manages the GdkWindowAttributesType mask internally so you don't need to
set this mask manually. Next, the visual and colormap the widget will
use is set. After creating the new window the widget Style and
background are set. Note the Gtk::Style::attach() method. This is the
only time you should use this method, and there is no corresponding
detach() method because GtkWidget will detach the Style for you when the
widget is destroyed.
Also, you need to put a pointer to the widget in the user data field of
the Gdk::Window. This last step allows GTK to dispatch events for this
window to the correct widget.
Size
Negotiation
Before the first time that the window
containing a widget is displayed, and whenever the layout of the window
changes, GTK asks each child widget for its desired size. This request
is handled by the function Gtk::Dial::on_size_request(). Since our
widget isn't a container widget, and has no real constraints on its
size, we just return a reasonable default value.
void
Gtk::Dial::on_size_request(Requisition *requisition)
{
requisition->set(dial_default_size,
dial_default_size);
} |
After all the widgets have requested
an ideal size, the layout of the window is computed and each child
widget is notified of its actual size. Usually, this will be at least as
large as the requested size, but if for instance the user has resized
the window, it may occasionally be smaller than the requested size. The
size notification is handled by the function
Gtk::Dial::on_size_allocate(). Notice that as well as computing the
sizes of some component pieces for future use, this routine also does
the grunt work of moving the widget's X window into the new position and
size.
void
Gtk::Dial::on_size_allocate(const Allocation& allocation)
{
set_allocation(allocation);
if
(is_realized())
{
get_window()->move_resize(allocation);
}
radius = (int)(std::min(allocation.width(),
allocation.height()) * 0.45);
pointer_width = radius / 5;
} |
The Expose
Event
All the drawing of this widget is
done in the handler for expose events. There's not much to remark on
here except the use of the function Gdk::Drawable::draw_polygon to draw
the pointer with three dimensional shading according to the colors
stored in the widget's style.
bool
Gtk::Dial::on_expose_event(const
Gdk::EventExpose& event)
{
if
(event.count() > 0)
return false;
Allocation allocation = get_allocation();
int xc =
allocation.width() / 2;
int yc =
allocation.height() / 2;
int
upper = (int)adjustment_->upper();
int
lower = (int)adjustment_->lower();
double s
= sin(last_angle);
double c
= cos(last_angle);
last_angle = angle;
Gdk::Point points[6];
points[0].set(int(xc
+ s * pointer_width / 2), int(yc
+ c * pointer_width / 2));
points[1].set(int(xc
+ c * radius), int(yc - s
* radius));
points[2].set(int(xc
- s * pointer_width / 2), int(yc
- c * pointer_width / 2));
points[3].set(int(xc
- c * radius / 10), int(yc
+ s * radius / 10));
points[4].set(points[0].x(), points[0].y());
Gdk::Window *window = get_window();
window->invalidate(allocation, false);
Gtk::Style *style = get_style();
style->draw_polygon(*window, STATE_NORMAL,
SHADOW_OUT, points, 5, false,
0, this);
// Draw
ticks
int inc
= upper - lower;
if (inc
== 0)
return false;
double
increment = (100 * G_PI) / ( radius * radius);
while
(inc < 100) inc *= 10;
while
(inc >= 1000) inc /= 10;
double
last = -1;
for (int i = 0; i <= inc; i++)
{
double theta = ((float)i * G_PI / (18 * inc /
24.) - G_PI / 6.);
if ((theta - last) <
(increment))
continue;
last = theta;
s = sin(theta);
c = cos(theta);
int tick_length = (i % (inc / 10)
== 0) ? pointer_width : pointer_width / 2;
window->draw_line(*style->fg_gc(get_state()), int(xc + c * (radius -
tick_length)),
int(yc - s * (radius -
tick_length)), int(xc + c *
radius), int(yc - s *
radius));
}
// Draw
pointer
s = sin(angle);
c = cos(angle);
last_angle = angle;
points[0].set(int(xc
+ s * pointer_width / 2), int(yc
+ c * pointer_width / 2));
points[1].set(int(xc
+ c * radius), int(yc - s * radius));
points[2].set(int(xc
- s * pointer_width / 2), int(yc
- c * pointer_width / 2));
points[3].set(int(xc
- c * radius / 10), int(yc
+ s * radius / 10));
points[4].set(points[0].x(), points[0].y());
style->draw_polygon(*window, STATE_NORMAL,
SHADOW_OUT, points, 5, true,
0, this);
return false;
} |
Event
Handling
The rest of the widget's code handles
various types of events, and isn't too different from what would be
found in many Inti applications. Two types of events can occur - either
the user can click on the widget with the mouse and drag to move the
pointer, or the value of the Adjustment object can change due to some
external circumstance.
When the user clicks on the widget, we check to see if the click was
appropriately near the pointer, and if so, store the button that the
user clicked with in the button field of the widget, and grab all mouse
events with a call to Main::grab_add(). Subsequent motion of the mouse
causes the value of the control to be recomputed (by the function
Gtk::Dial::update_mouse). Depending on the policy that has been set,
"value_changed" events are either generated instantly
(Gtk::UPDATE_CONTINUOUS), after a delay in a timer added with the timeout_signal(Gtk::UPDATE_DELAYED),
or only when the button is released (Gtk::UPDATE_DISCONTINUOUS).
bool
Gtk::Dial::on_button_press_event(const
Gdk::EventButton& event)
{
//
Determine if button press was within pointer region - we do this by
//
computing the parallel and perpendicular distance of the point where
// the
mouse was pressed from the line passing through the pointer.
int dx = int(event.x()) -
get_allocation().width() / 2;
int dy =
get_allocation().height() / 2 - int(event.y());
double s
= sin(angle);
double c
= cos(angle);
double
d_parallel = s * dy + c * dx;
double
d_perpendicular = fabs(s * dx - c * dy);
if
(!button && (d_perpendicular < pointer_width/2) &&
(d_parallel > - pointer_width))
{
Main::grab_add(*this);
button = event.button();
update_mouse(int(event.x()), int(event.y()));
}
return false;
}
bool
Gtk::Dial::on_button_release_event(const
Gdk::EventButton& event)
{
if
(button == event.button())
{
Main::grab_remove(*this);
button = 0;
if (policy_ == UPDATE_DELAYED)
timer.disconnect();
if ((policy_ !=
UPDATE_CONTINUOUS) && (old_value != adjustment_->get_value()))
adjustment_->value_changed();
}
return false;
}
bool
Gtk::Dial::on_motion_notify_event(const
Gdk::EventMotion& event)
{
Gtk::Widget::on_motion_notify_event(event);
if
(button != 0)
{
int x = int(event.x());
int y = int(event.y());
Gdk::ModifierTypeField mods;
Gdk::Window *window =
get_window();
if (event.is_hint() ||
(event.window() != window))
window->get_pointer(&x, &y, &mods);
int mask;
switch (button)
{
case 1:
mask =
GDK_BUTTON1_MASK;
break;
case 2:
mask =
GDK_BUTTON2_MASK;
break;
case 3:
mask =
GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
update_mouse(x, y);
}
return false;
}
void
Gtk::Dial::update()
{
double
value = adjustment_->get_value();
double
lower = adjustment_->lower();
double
upper = adjustment_->upper();
double
new_value = std::max(lower, value);
new_value = std::min(new_value, upper);
if
(new_value != value)
{
adjustment_->set_value(new_value);
adjustment_->value_changed();
}
angle = 7.*G_PI/6. - (new_value - lower) *
4.*G_PI/3. / (upper - lower);
queue_draw();
}
void
Gtk::Dial::update_mouse(int
x, int y)
{
int xc =
get_allocation().width() / 2;
int yc =
get_allocation().height() / 2;
float
old_value = adjustment_->get_value();
angle = atan2(yc - y, x - xc);
if
(angle < -G_PI/2.)
angle += 2*G_PI;
if
(angle < -G_PI/6)
angle = -G_PI/6;
if
(angle > 7.*G_PI/6.)
angle = 7.*G_PI/6.;
double
lower = adjustment_->lower();
adjustment_->set_value(lower + (7.*G_PI/6 -
angle) * (adjustment_->upper() - lower) / (4.*G_PI/3.));
if
(adjustment_->get_value() == old_value)
{
if (policy_ == UPDATE_CONTINUOUS)
{
adjustment_->value_changed();
}
else
{
queue_draw();
if (policy_ == UPDATE_DELAYED)
{
timer = Main::timeout_signal.connect(slot(this, &Dial::on_timeout),
scroll_delay_length);
}
}
}
}
bool
Gtk::Dial::on_timeout()
{
if
(policy_ == UPDATE_DELAYED)
adjustment_->value_changed();
return false;
}
void
Gtk::Dial::on_adjustment_changed()
{
double
value = adjustment_->get_value();
double
lower = adjustment_->lower();
double
upper = adjustment_->upper();
if
((old_value != value) || (old_lower != lower) || (old_upper != upper))
{
update();
old_value = value;
old_lower = lower;
old_upper = upper;
}
}
void
Gtk::Dial::on_adjustment_value_changed()
{
double
value = adjustment_->get_value();
if
(old_value != value)
{
update();
old_value = value;
}
} |
Possible
Enhancements
The Dial widget as we've described it
so far runs about 374 lines of code. Although that might sound like a
fair bit, we've really accomplished quite a bit with that much code,
especially since much of that length is headers and boilerplate.
However, there are quite a few more enhancements that could be made to
this widget:
If you try this widget out, you'll find that there is some flashing as
the pointer is dragged around. This is because the entire widget is
erased every time the pointer is moved before being redrawn. Often, the
best way to handle this problem is to draw to an offscreen pixmap, then
copy the final results onto the screen in one step. (The ProgressBar
widget draws itself in this fashion.)
The user should be able to use the up and down arrow keys to increase
and decrease the value.
It would be nice if the widget had buttons to increase and decrease the
value in small or large steps. Although it would be possible to use
embedded Button widgets for this, we would also like the buttons to
auto-repeat when held down, as the arrows on a scrollbar do. Most of the
code to implement this type of behavior can be found in the GtkRange
widget.
The Dial widget could be made into a container widget with a single
child widget positioned at the bottom between the buttons mentioned
above. The user could then add their choice of a label or entry widget
to display the current value of the dial.