Tutorial

Inti Logo

« Drag and Drop                Writing Your Own Widgets »

Scribble, A Simple Example Drawing Program

  1. Event Handling
  2. The DrawingArea Widget, and Drawing
  3. The Complete Scribble Source Code

In this section, we will build the simple drawing program Scribble. In the process, we will examine how to handle mouse events, how to draw in a window, and how to do the drawing using a backing pixmap. This is a port of an older GTK+ example still in the current release. Since GTK+ 2.0 widgets are double buffered by default. "Double buffered" simply means that all drawing to a widget's Gdk::Window is diverted to an offscreen buffer, and after the last "expose_event" the buffer is drawn to the screen. The result is that users see the window update in one smooth step, and don't see individual graphics primitives being rendered.

In very simple terms, double buffered widgets don't flicker, so you would only turn off double buffering if you had special needs and really knew what you were doing. This is the reason that the Scribble example is still in the tutorial. If you ever need to turn double buffering off, and draw to an offscreen buffer yourself, this example shows you how.


Scribble

Event Handling

The GTK signals we have already discussed are for high-level actions, such as a menu item being selected. However, sometimes it is useful to learn about lower-level occurrences, such as the mouse being moved, or a key being pressed. There are also GTK signals corresponding to these low-level events. The handlers for these signals have a parameter which is a reference to a class containing information about the event. For instance, motion event handlers are passed a reference to a Gdk::EventMotion class which looks like:

class EventMotion : public EventAny
{
public:
// Accessors
    GdkEventMotion* gdk_event_motion() const { return (GdkEventMotion*)this; }

    unsigned int time() const;

    double x() const;

    double y() const;

    double* axes() const;

    unsigned int state() const;

    bool is_hint() const;

    Device* device() const;

    double x_root() const;

    double y_root() const;
};

All events inherit the following methods:

Gdk::EventType type() const;

Gdk::Window* window() const;

type() returns the type of event, in this case Gdk::MOTION_NOTIFY. window() returns the Gdk::Window in which the event occurred. x() and y() return the coordinates of the event and state() specifies the modifier state when the event occurred (that is, it specifies which modifier keys and mouse buttons were pressed). It is the bitwise OR of one or more of the following values in the Gdk::ModifierType enumeration:
As for other signals, to determine what happens when an event occurs we can either explicitly connect a signal handler to a signal or we can derive a new widget class and override the virtual signal handlers for the events we are interested in. We also need let GTK know which events we want to be notified about. To do this, we call the Gtk::Widget method:

void set_events(Gdk::EventMaskField events);

where events is the bitwise OR of one or more of the following values in the Gdk::EventMask enumeration that specify the event types:
There are a few subtle points that have to be observed when calling Gtk::Widget::set_events(). First, it must be called before the X window for a widget is created. In practical terms, this means you should call it immediately after creating the widget. Second, the widget must have an associated X window. For efficiency, many widget types do not have their own window, but draw in their parent's window. These widgets are:
To capture events for these widgets, you need to use an EventBox widget. See the section on the EventBox widget for details.

For our drawing program, we want to know when the mouse button is pressed and when the mouse is moved, so we specify Gdk::POINTER_MOTION_MASK and Gdk::BUTTON_PRESS_MASK. We also want to know when we need to redraw our window, so we specify Gdk::EXPOSURE_MASK. Although we want to be notified via a Configure event when our window size changes, we don't have to specify the corresponding Gdk::STRUCTURE_MASK flag, because it is automatically specified for all windows.

It turns out, however, that there is a problem with just specifying Gdk::POINTER_MOTION_MASK. This will cause the server to add a new motion event to the event queue every time the user moves the mouse. Imagine that it takes us 0.1 seconds to handle a motion event, but the X server queues a new motion event every 0.05 seconds. We will soon get way behind with the users drawing. If the user draws for 5 seconds, it will take us another 5 seconds to catch up after they release the mouse button! What we would like is to only get one motion event for each event we process. The way to do this is to specify Gdk::POINTER_MOTION_HINT_MASK.

When we specify Gdk::POINTER_MOTION_HINT_MASK, the server sends us a motion event the first time the pointer moves after entering our window, or after a button press or release event. Subsequent motion events will be suppressed until we explicitly ask for the position of the pointer using the Gdk::Window method:

Gdk::Window* get_pointer(int *x, int *y, Gdk::ModifierTypeField *mask) const;

(There is another function, Gtk::Widget::get_pointer() which has a simpler interface, but turns out not to be very useful, since it only retrieves the position of the mouse, not whether the buttons are pressed.)

In scribble, we derive a new DrawingArea from Gtk::DrawingArea and override the following virtual signal handlers:

virtual bool on_expose_event(const Gdk::EventExpose& event);

virtual bool on_configure_event(const Gdk::EventConfigure& event);

virtual bool on_button_press_event(const Gdk::EventButton& event);

virtual bool on_motion_notify_event(const Gdk::EventMotion& event);

We still have to let GTK know which events to notify us about. The code that does this is:

Gdk::EventMaskField flags = get_events();
flags |= (Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK);
set_events(flags);

We'll save the expose_event and configure_event handlers for later. The motion_notify_event and button_press_event handlers are pretty simple:

bool
DrawingArea::on_button_press_event(const Gdk::EventButton& event)
{
    if (event.button() == 1 && pixmap)
        draw_brush(event.x(), event.y());

    return true;
}

bool
DrawingArea::on_motion_notify_event(const Gdk::EventMotion& event)
{
    int x, y;
    Gdk::ModifierTypeField state;

    if (event.is_hint())
         event.window()->get_pointer(&x, &y, &state);
    else
    {
        x = (int)event.x();
        y = (int)event.y();
        state = event.state();
    }

    if (state & Gdk::BUTTON1_MASK && pixmap)
        draw_brush(x, y);

    return true;
}


The DrawingArea Widget, and Drawing

We now turn to the process of drawing on the screen. The widget we use for this is the DrawingArea widget. A drawing area widget is essentially an X window and nothing more. It is a blank canvas in which we can draw whatever we like. A drawing area is created using one of the following constructors:

DrawingArea();

DrawingArea(int width, int height);

The width and height are the default size for the widget. If you use the first constructor you can set the default size later by calling:

void size(int width, int height);

This default size can be overridden, as is true for all widgets, by calling Gtk::Widget::set_size_request(), and that, in turn, can be overridden if the user manually resizes the the window containing the drawing area.

In this example we are going draw to our own offscreen pixmap so we can turn off double buffering with the following method:

void set_double_buffered(bool double_buffered);

It should be noted that when we create a DrawingArea widget, we are completely responsible for drawing the contents. If our window is obscured then uncovered, we get an exposure event and must redraw what was previously hidden.

Having to remember everything that was drawn on the screen so we can properly redraw it can, to say the least, be a nuisance. In addition, it can be visually distracting if portions of the window are cleared, then redrawn step by step. The solution to this problem is to use an offscreen backing pixmap. Instead of drawing directly to the screen, we draw to an image stored in server memory but not displayed, then when the image changes or new portions of the image are displayed, we copy the relevant portions onto the screen.

To create an offscreen pixmap, we call the constructor:

Gdk::Pixmap(const Gdk::Window& window, int width, int height);

The window parameter specifies the Gdk::Window that this pixmap takes some of its properties from. width and height specify the size of the pixmap.

We create the pixmap in our configure_event handler. This event is generated whenever the window changes size, including when it is originally created.

bool
DrawingArea::on_configure_event(const Gdk::EventConfigure& event)
{
    // Create a new backing pixmap of the appropriate size
    if (pixmap)
        pixmap->unref();

    pixmap = new Gdk::Pixmap(*get_window(), get_allocation().width(), get_allocation().height());

    pixmap->draw_rectangle(*(get_style()->white_gc()), 0, 0, get_allocation().width(), get_allocation().height());
    return true;
}

The call to Gdk::Drawable::draw_rectangle() clears the pixmap initially to white. We'll say more about that in a moment.

Our exposure event handler then simply copies the relevant portion of the pixmap onto the screen (we determine the area we need to redraw by using the event.area() field of the exposure event):

bool
DrawingArea::on_expose_event(const Gdk::EventExpose& event)
{
    // Redraw the screen from the backing pixmap
    get_window()->draw_drawable(*(get_style()->fg_gc(get_state())), *pixmap, event.area().x(), event.area().y(), event.area());
    return false;
}

We've now seen how to keep the screen up to date with our pixmap, but how do we actually draw interesting stuff on our pixmap? There are a large number of calls in Inti's Gdk library for drawing on drawables. A drawable is simply something that can be drawn upon. It can be a window, a pixmap, or a bitmap (a black and white image). We've already seen two such calls above, Gdk::Drawable::draw_rectangle() and Gdk::Drawable:draw_drawable(). The complete list of Gdk::Drawable methods is:
See the the header file <inti/gdk/drawable.h> for further details on these methods. These methods all share the same first argument - a reference to a graphics context (GC).

A graphics context encapsulates information about things such as foreground and background color and line width. Gdk::GC has a full set of methods for creating and modifying graphics contexts, but to keep things simple we'll just use predefined graphics contexts. Each widget has an associated style. (Which can be modified in a gtkrc file.) This, among other things, stores a number of graphics contexts. Some examples of accessing these graphics contexts are:

widget->get_style()->white_gc()
widget->get_style()->black_gc()
widget->get_style()->fg_gc(Gtk::STATE_NORMAL)
widget->get_style()->bg_gc(widget->get_state())


The methods fg_gc(), bg_gc(), dark_gc(), and light_gc() take a single parameter, the Gtk::StateType, which can be one of the values from the Gtk::StateType enumeration:
For instance, for Gtk::STATE_SELECTED the default foreground color is white and the default background color, dark blue.

Our draw_brush() method, which does the actual drawing on the screen, is then:

void
DrawingArea::draw_brush(double x, double y)
{
    // Draw a rectangle on the screen
    Gdk::Rectangle update_rect((int)x - 5, (int)y - 5, 10, 10);
    pixmap->draw_rectangle(*(get_style()->black_gc()), update_rect);
    queue_draw_area(update_rect);
}

After we draw the rectangle representing the brush onto the pixmap, we call one of the following Gtk::Widget methods:

void queue_draw_area(int x, int y, int width, int height);

void queue_draw_area(const Gdk::Rectangle& rectangle);

which notifies X that the area given needs to be updated. X will eventually generate an expose event which will cause our expose event handler to copy the relevant portions to the screen.

We have now covered the entire drawing program except for a few mundane details like creating the main window.


The Complete Scribble Source Code

The header file for the scribble example is scribble.h:

#include<inti/main.h>
#include <inti/core.h>
#include <inti/gtk/drawingarea.h>


using namespace Inti;

class DrawingArea : public Gtk::DrawingArea
{
    Gdk::Pixmap *pixmap;

    void draw_brush(double x, double y);

protected:
    virtual bool on_expose_event(const Gdk::EventExpose& event);
    virtual bool on_configure_event(const Gdk::EventConfigure& event);
    virtual bool on_button_press_event(const Gdk::EventButton& event);
    virtual bool on_motion_notify_event(const Gdk::EventMotion& event);

public:
    DrawingArea();
    virtual ~DrawingArea();
};

class ScribbleWindow : public Gtk::Window
{
public:
    ScribbleWindow();
    virtual ~ScribbleWindow();
};

and the source file is scribble.cc:

#include"scribble.h"
#include <inti/gtk/style.h>
#include <inti/gtk/button.h>
#include <inti/gdk/color.h>
#include <inti/gdk/gc.h>
#include <inti/gdk/pixmap.h>
#include <inti/gdk/window.h>


// DrawingArea

DrawingArea::DrawingArea()
: pixmap(0)
{
    set_size_request(200, 200);
    set_double_buffered(false);
    Gdk::EventMaskField flags = get_events();
    flags |= (Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK);
    set_events(flags);
}

DrawingArea::~DrawingArea()
{
    if (pixmap)
        pixmap->unref();
}

bool
DrawingArea::on_expose_event(const Gdk::EventExpose& event)
{
    // Redraw the screen from the backing pixmap
    get_window()->draw_drawable(*(get_style()->fg_gc(get_state())), *pixmap, event.area().x(), event.area().y(), event.area());
    return false;
}

bool
DrawingArea::on_configure_event(const Gdk::EventConfigure& event)
{
    // Create a new backing pixmap of the appropriate size
    if (pixmap)
        pixmap->unref();

    pixmap = new Gdk::Pixmap(*get_window(), get_allocation().width(), get_allocation().height());

    pixmap->draw_rectangle(*(get_style()->white_gc()), 0, 0, get_allocation().width(), get_allocation().height());
    return true;
}

bool
DrawingArea::on_button_press_event(const Gdk::EventButton& event)
{
    if (event.button() == 1 && pixmap)
        draw_brush(event.x(), event.y());

    return true;
}

bool
DrawingArea::on_motion_notify_event(const Gdk::EventMotion& event)
{
    int x, y;
    Gdk::ModifierTypeField state;

    if (event.is_hint())
         event.window()->get_pointer(&x, &y, &state);
    else
    {
        x = (int)event.x();
        y = (int)event.y();
        state = event.state();
    }

    if (state & Gdk::BUTTON1_MASK && pixmap)
        draw_brush(x, y);

    return true;
}

void
DrawingArea::draw_brush(double x, double y)
{
    // Draw a rectangle on the screen
    Gdk::Rectangle update_rect((int)x - 5, (int)y - 5, 10, 10);
    pixmap->draw_rectangle(*(get_style()->black_gc()), update_rect);
    queue_draw_area(update_rect);
}

// ScribbleWindow

ScribbleWindow::ScribbleWindow()
{
    set_name("Test Input");

    Gtk::VBox *vbox = new Gtk::VBox;
    add(*vbox);
    vbox->show();

    // Create the drawing area
    DrawingArea *drawing_area = new DrawingArea;
    vbox->pack_start(*drawing_area);
    drawing_area->show();

    // And a quit button
    Gtk::Button *button = new Gtk::Button("Quit");
    vbox->pack_start(*button, false,false);
    button->sig_clicked().connect(slot(this, &ScribbleWindow::dispose));
    button->show();
}

ScribbleWindow::~ScribbleWindow()
{
}

int main (int argc, char *argv[])
{
    using namespace Main;

    init(&argc, &argv);

    ScribbleWindow window;
    window.sig_destroy().connect(slot(&Inti::Main::quit));
    window.show();

    run();
    return 0;
}




« Drag and Drop Index
Top
Writing Your Own Widgets »