Xfce Foundation Classes
 « Main Page

Writing a Custom Widget

Table of Contents

  1. The X Window
  2. Displaying a widget on the screen
  3. The Realize Signal
  4. Size Negotiation
  5. The Expose Event
  6. Event Handling
  7. Putting it all together
  8. Possible Enhancements

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.


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