Example Application: Creating a Clock with Cairo
| Chapter 16. The Drawing Area Widget |

## Example Application: Creating a Clock with Cairo

Now that we've covered the basics of drawing with Cairo, let's try to put it all together and create a simple application that actually does something. The following example uses Cairo to create a custom `Clock` widget. The clock has a second hand, a minute hand, and an hour hand, and updates itself every second.

Source Code

File: `clock.h`

    #ifndef GTKMM_EXAMPLE_CLOCK_H
    #define GTKMM_EXAMPLE_CLOCK_H

    #include <gtkmm/drawingarea.h>

    class Clock : public Gtk::DrawingArea
    {
    public:
      Clock();
      virtual ~Clock();

    protected:
      //Override default signal handler:
      virtual bool on_expose_event(GdkEventExpose* event);

      bool on_timeout();

      double m_radius;
      double m_line_width;

    };

    #endif // GTKMM_EXAMPLE_CLOCK_H

File: `clock.cc`

    #include <ctime>
    #include <cmath>
    #include <cairomm/context.h>
    #include "clock.h"

    Clock::Clock()
    : m_radius(0.42), m_line_width(0.05)
    {
      Glib::signal_timeout().connect( sigc::mem_fun(*this, &Clock::on_timeout), 1000 );

      #ifndef GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
      //Connect the signal handler if it isn't already a virtual method override:
      signal_expose_event().connect(sigc::mem_fun(*this, &Clock::on_expose_event), false);
      #endif //GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
    }

    Clock::~Clock()
    {
    }

    bool Clock::on_expose_event(GdkEventExpose* event)
    {
      // This is where we draw on the window
      Glib::RefPtr<Gdk::Window> window = get_window();
      if(window)
      {
        Gtk::Allocation allocation = get_allocation();
        const int width = allocation.get_width();
        const int height = allocation.get_height();

        Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();

        if(event)
        {
            // clip to the area indicated by the expose event so that we only
            // redraw the portion of the window that needs to be redrawn
            cr->rectangle(event->area.x, event->area.y,
                    event->area.width, event->area.height);
            cr->clip();
        }

        // scale to unit square and translate (0, 0) to be (0.5, 0.5), i.e.
        // the center of the window
        cr->scale(width, height);
        cr->translate(0.5, 0.5);
        cr->set_line_width(m_line_width);

        cr->save();
        cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
        cr->paint();
        cr->restore();
        cr->arc(0, 0, m_radius, 0, 2 * M_PI);
        cr->save();
        cr->set_source_rgba(1.0, 1.0, 1.0, 0.8);
        cr->fill_preserve();
        cr->restore();
        cr->stroke_preserve();
        cr->clip();

        //clock ticks
        for (int i = 0; i < 12; i++)
        {
            double inset = 0.05;

            cr->save();
            cr->set_line_cap(Cairo::LINE_CAP_ROUND);

            if(i % 3 != 0)
            {
                inset *= 0.8;
                cr->set_line_width(0.03);
            }

            cr->move_to(
                    (m_radius - inset) * cos (i * M_PI / 6),
                    (m_radius - inset) * sin (i * M_PI / 6));
            cr->line_to (
                    m_radius * cos (i * M_PI / 6),
                    m_radius * sin (i * M_PI / 6));
            cr->stroke();
            cr->restore(); /* stack-pen-size */
        }

        // store the current time
        time_t rawtime;
        time(&rawtime);
        struct tm * timeinfo = localtime (&rawtime);

        // compute the angles of the indicators of our clock
        double minutes = timeinfo->tm_min * M_PI / 30;
        double hours = timeinfo->tm_hour * M_PI / 6;
        double seconds= timeinfo->tm_sec * M_PI / 30;

        cr->save();
        cr->set_line_cap(Cairo::LINE_CAP_ROUND);

        // draw the seconds hand
        cr->save();
        cr->set_line_width(m_line_width / 3);
        cr->set_source_rgba(0.7, 0.7, 0.7, 0.8); // gray
        cr->move_to(0, 0);
        cr->line_to(sin(seconds) * (m_radius * 0.9),
                -cos(seconds) * (m_radius * 0.9));
        cr->stroke();
        cr->restore();

        // draw the minutes hand
        cr->set_source_rgba(0.117, 0.337, 0.612, 0.9);   // blue
        cr->move_to(0, 0);
        cr->line_to(sin(minutes + seconds / 60) * (m_radius * 0.8),
                -cos(minutes + seconds / 60) * (m_radius * 0.8));
        cr->stroke();

        // draw the hours hand
        cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
        cr->move_to(0, 0);
        cr->line_to(sin(hours + minutes / 12.0) * (m_radius * 0.5),
                -cos(hours + minutes / 12.0) * (m_radius * 0.5));
        cr->stroke();
        cr->restore();

        // draw a little dot in the middle
        cr->arc(0, 0, m_line_width / 3.0, 0, 2 * M_PI);
        cr->fill();

      }

      return true;
    }

    bool Clock::on_timeout()
    {
        // force our program to redraw the entire clock.
        Glib::RefPtr<Gdk::Window> win = get_window();
        if (win)
        {
            Gdk::Rectangle r(0, 0, get_allocation().get_width(),
                    get_allocation().get_height());
            win->invalidate_rect(r, false);
        }
        return true;
    }

File: `main.cc`

    #include "clock.h"
    #include <gtkmm/main.h>
    #include <gtkmm/window.h>

    int main(int argc, char** argv)
    {
       Gtk::Main kit(argc, argv);

       Gtk::Window win;
       win.set_title("Cairomm Clock");

       Clock c;
       win.add(c);
       c.show();

       Gtk::Main::run(win);

       return 0;
    }

As before, almost all of the interesting stuff is done in the expose event handler `on_expose_event()`. Before we dig into the expose event handler, notice that the constructor for the `Clock` widget connects a handler function `onSecondElapsed()` to a timer with a timeout period of 1000 milliseconds (1 second). This means that `onSecondElapsed()` will get called once per second. The sole responsibility of this function is to invalidate the window so that gtkmm will be forced to redraw it.

Now let's take a look at the code that performs the actual drawing. The first section of `on_expose_event()` should be pretty familiar by now as it's mostly 'boilerplate' code for getting the `Gdk::Window`, creating a `Cairo::Context`, and clipping to the area that we want to re-draw. This example again scales the coordinate system to be a unit square so that it's easier to draw the clock as a percentage of window size so that it will automatically scale when the window size is adjusted. Furthermore, the coordinate system is scaled over and down so that the (0, 0) coordinate is in the very center of the window.

The function `Cairo::Context::paint()` is used here to set the background color of the window. This function takes no arguments and fills the current surface (or the clipped portion of the surface) with the source color currently active. After setting the background color of the window, we draw a circle for the clock outline, fill it with white, and then stroke the outline in black. Notice that both of these actions use the `_preserve` variant to preserve the current path, and then this same path is clipped to make sure than our next lines don't go outside the outline of the clock.

After drawing the outline, we go around the clock and draw ticks for every hour, with a larger tick at 12, 3, 6, and 9. Now we're finally ready to implement the time-keeping functionality of the clock, which simply involves getting the current values for hours, minutes and seconds, and drawing the hands at the correct angles.

Drawing Images |  | Chapter 17. Drag and Drop