In
order to write a custom widget
you need to understand 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.
Most
GTK widgets tend to start off
as variants of some other,
previously written widget. The Dial widget is no exception. It's just a
C++ version of the GtkDial widget that comes with the GTK+ source
code. GtkDial began with the source code for the GtkRange widget. This
widget was picked as a starting point so GtkDial could have the same
interface as the scale widgets, which are just specialized descendants
of the range widget. 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.
Dial is a custom widget that
derives
directly from Gtk::Widget. Creating this kind of widget is akin
to creating a new C widget in GTK+. It's 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 meets
your needs, like in the
Tictactoe
example, but
occasionally you wont. This is when you will 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.
The
X Window
An X window is not the user-visible concept of a window as represented
by Gtk::Window; rather, it's an abstraction used by the X server to
partition the screen. The background displayed by the X server is the
root window; the root window has no parent. An X window, or
Gdk::Window, gives the X server hints about the structure of the
graphics being displayed. A Gdk::Window is also the
fundamental
unit for drawing graphics - you can't draw to the screen as a whole,
you must draw on a Gdk::Window.
Most GTK+ widgets have a corresponding Gdk::Window. There are
exceptions, such as Frame and Label; these are referred to as 'no
window' widgets, and are relatively lightweight. Widgets with no
associated Gdk::Window draw onto their parent's Gdk::Window.
Sometimes you may need to access the Gdk::Window of a widget before it
gets created. For example, Gtk::Widget::show() has no immediate effect,
it merely schedules the widget to be shown. This means you don't have
to worry about showing widgets in any particular order; it also means
that you can't immediately access the widget's Gdk::Window. In those
cases you'll want to manually call Gtk::Widget::realize() to
create it. This will also realize the widget's parents, if
appropriate. It's uncommon to need Gtk::Widget::realize(); if you find
that you do, perhaps you are approaching the problem incorrectly.
Displaying
a widget on the screen
There are several steps involved in displaying a widget on the screen.
After the widget is constructed you have to override several protected
Gtk::Widget signal handlers:
virtual
void on_realize();
Realization is the process of creating GDK resources associated
with the widget; usually the GDK window if it has one and the widget's
Style. The on_realize() method should do the following:
- Set the Gtk::REALIZED
flag.
- Create the widget's
windows, especially its GDK window,
which should be a child of the widget's parent's GDK window (obtained
by calling get_parent_window()).
- Place a pointer to the
GTK+ widget in the user data field
of each window.
- For windowless
widgets, the GDK window should be set to the
parent widget's GDK window and the window's reference count should be
incremented.
- Set the widget style
using Gtk::Style::attach().
- Set the background of
each window using
Gtk::Style::set_background() if possible, and failing that using some
color from the style. A windowless widget should not do this, since its
parent already has.
The default implementation of on_realize() is only appropriate for
windowless widgets; it sets the Gtk::REALIZED flag, sets the widget's
window to the parent widget's GDK window, increases the reference count
on the parent's window, and sets the widget style. Widgets with their
own GDK Window must override the on_realize() method.
virtual
void on_map();
Mapping is responsible for making sure the widget is actually drawn on
the screen. on_map() is called after the user calls
Gtk::Widget::show(). The default implementation sets the Gtk::MAPPED
flag, and calls Gdk::Window::show() on the widget's GDK window, for
widgets that have a window. If a widget has subwindows, or needs to
take any special action when it appears on the screen, it must override
the on_map() method. Container widgets are required to override the map
method, because they must iterate over their children and map each
child widget that's been shown (has the Gtk::VISIBLE flag set).
virtual
bool on_expose_event(const
Gdk::EventExpose& event);
Expose events are received when all or part of a widget's GDK window
has just become visible on the screen and needs repainting. A widget's
on_expose_event() signal handler is responsible for performing these
redraws,
and should make the necessary calls to the drawing functions to draw
the exposed portion. For container widgets, this method must generate
expose events for its child widgets which don't have their own GDK
windows (if they have their own windows, then X will generate the
necessary expose events).
The
Realize Signal
To display the Dial widget on-screen we have to override the
on_realize() signal handler and create the widget's GDK 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, we first call the Gtk::Widget get_events()
function to
retrieve the event mask that the user has set for the widget (with
Gtk::Widget::set_events()), and then we 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. We also need to put a pointer to the GtkWidget
in the user data field of
the Gdk::Window. This allows GTK to dispatch events for this window to
the correct widget.
Size
Negotiation
Before the window containing a widget is displayed on-screen for the
first time,
and whenever the layout of the window changes, GTK asks each child
widget for its desired size. This request is handled by the
on_size_request() signal handler. Since our widget isn't a container
widget, and has no real constraints on its size, we can 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 on_size_allocate() signal handler. 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 for this widget is done in the on_expose_event() signal
handler. 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 other XFC
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
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).
Putting
it all together
The widget class has a header file which declares the object and its
members. To prevent multiple inclusions at compile time, the entire
header file is wrapped in an inclusion guard:
#ifndef
XFC_GTK_DIAL_HH
#define XFC_GTK_DIAL_HH
.
.
#endif // XFC_GTK_DIAL_HH
The header file for the Dial widget is <dial.hh>:
#ifndef
XFC_GTK_DIAL_HH
#define XFC_GTK_DIAL_HH
#ifndef XFC_GTK_WIDGET_HH
#include <xfc/gtk/widget.hh>
#endif
#ifndef XFC_GTK_WIDGET_SIGNALS_HH
#include <xfc/gtk/widgetsignals.hh>
#endif
namespace Xfc {
namespace Gtk {
class Adjustment;
class Dial : public Widget, protected WidgetSignals
{
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.
sigc::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 Xfc
#endif // XFC_GTK_DIAL_HH
Since there is quite a bit more going on in this widget than the
Tictactoe
example, there are 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
decrement it when we no longer use it) so that GTK can
keep track of when it can safely be destroyed. For convenience, Dial
uses a smart pointer to manage the adjustment's reference counting but
it could have done it manually. There are also a few methods to
manipulate the widget's options: get_adjustment(), set_adjustment() and
set_update_policy().
The source file for the Dial widget is <dial.cc>:
#include
"dial.hh"
#include <xfc/gtk/private/widgetclass.hh>
#include <xfc/gtk/adjustment.hh>
#include <xfc/gtk/style.hh>
#include <xfc/gdk/window.hh>
#include <xfc/glib/main.hh>
#include <xfc/main.hh>
#include <algorithm>
#include <cmath>
const int scroll_delay_length = 300;
const int dial_default_size = 100;
using namespace Xfc;
Gtk::Dial::Dial(Adjustment *adjustment)
: Gtk::Widget((GtkWidget*)Gtk::WidgetClass::create()),
Gtk::WidgetSignals(this),
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_->signal_changed().connect(sigc::mem_fun(this,
&Dial::on_adjustment_changed));
adjustment_->signal_value_changed().connect(sigc::mem_fun(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::WidgetSignals::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 = G::timeout_signal.connect(sigc::mem_fun(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 source file for the application that will test the Dial widget is
<dial_test.cc>
#include
<xfc/main.hh>
#include <xfc/gtk/box.hh>
#include <xfc/gtk/frame.hh>
#include <xfc/gtk/label.hh>
#include <xfc/gtk/window.hh>
#include <cstdio>
#include <cstdlib>
#include "dial.hh"
using namespace Xfc;
class DialTest : public Gtk::Window
{
Gtk::Adjustment *adjustment;
Gtk::Label *label;
protected:
void on_adjustment_value_changed();
public:
DialTest();
virtual ~DialTest();
};
DialTest::DialTest()
{
set_title("Dial");
set_border_width(10);
Gtk::VBox *vbox = new Gtk::VBox(false,
5);
add(*vbox);
vbox->show();
Gtk::Frame *frame = new Gtk::Frame;
frame->set_shadow_type(Gtk::SHADOW_IN);
vbox->add(*frame);
frame->show();
adjustment = new Gtk::Adjustment(0, 0,
100, 0.01,
0.1, 0);
Gtk::Dial *dial = new
Gtk::Dial(adjustment);
dial->set_update_policy(Gtk::UPDATE_DELAYED);
frame->add(*dial);
dial->show();
label = new Gtk::Label("0.00");
vbox->pack_end(*label, false,
false);
label->show();
adjustment->signal_value_changed().connect(sigc::mem_fun(this,
&DialTest::on_adjustment_value_changed));
show();
}
DialTest::~DialTest()
{
}
void
DialTest::on_adjustment_value_changed()
{
String buffer = String::format("%4.2f",
adjustment->get_value());
label->set_text(buffer);
}
int main (int argc, char *argv[])
{
using namespace Main;
init(&argc, &argv);
DialTest window;
window.signal_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
run();
return 0;
}
Compiling Dial
If you compiled and installed XFC yourself, you will find the source
code for Dial in the
<examples/dial> 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/dial> 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 Dial, add the following lines to a new text
file
and save it using the name "Makefile":
CC
= g++
CFLAGS = -Wall -O2
dial_test: dial.o dial_test.o
$(CC) dial_test.o dial.o -o dial_test
`pkg-config
xfcui-X.X --libs`
dial_test.o: dial_test.cc dial.hh
$(CC) -c dial_test.cc -o dial_test.o
$(CFLAGS)
`pkg-config xfcui-X.X --cflags`
dial.o: dial.cc dial.hh
$(CC) -c dial.cc -o dial.o $(CFLAGS)
`pkg-config
xfcui-X.X --cflags`
clean:
rm -f *.o dial_test
If you cut and paste these lines make sure the whitespace before $(CC)
and rm is a tab character. When you
compile and run the program you will see the following window appear:
Possible
Enhancements
The Dial widget as we've described it so far runs about 494 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. Also 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.