Exemple d'application : création d'un cadran d'horloge avec Cairo

Maintenant que nous avons vu les fondements du tracé avec Cairo, rassemblons le tout et créons une application simple qui fasse effectivement quelque chose. L'exemple qui suit utilise Cairo pour créer un élément graphique Clock classique. L'horloge dispose d'aiguilles pour les secondes, les minutes et les heures ; elle se met à jour toutes les secondes.

Code source

File: clock.h (For use with gtkmm 3, not gtkmm 2)

#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_draw(const Cairo::RefPtr<Cairo::Context>& cr);

  bool on_timeout();

  double m_radius;
  double m_line_width;

};

#endif // GTKMM_EXAMPLE_CLOCK_H

File: clock.cc (For use with gtkmm 3, not gtkmm 2)

#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_draw().connect(sigc::mem_fun(*this, &Clock::on_draw), false);
  #endif //GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
}

Clock::~Clock()
{
}

bool Clock::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
{
  Gtk::Allocation allocation = get_allocation();
  const int width = allocation.get_width();
  const int height = allocation.get_height();

  // 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 (For use with gtkmm 3, not gtkmm 2)

#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;
}

Comme auparavant, la plupart des choses intéressantes se passent dans le gestionnaire des événements d'exposition on_expose_event(). Avant de creuser ce gestionnaire, notez que le constructeur de l'élément graphique Clock connecte sa fonction de rappel onSecondElapsed() à un séquenceur qui se déclenche toutes les 1000 millisecondes (1 seconde). Cela signifie que onSecondElapsed() sera appelée toutes les secondes. La seule responsabilité de cette fonction consiste à invalider la fenêtre de façon à ce que gtkmm soit obligé de la retracer.

Regardons maintenant le code qui effectue en fait le tracé. La première partie de on_expose_event() doit maintenant vous être familière car il s'agit de code « standardisé » pour obtenir le Gdk::Window, créer le Cairo::Context et découper la zone que nous souhaitons retracer. À nouveau cet exemple inscrit son système de coordonnées dans un carré unité : il est ainsi plus facile de tracer le cadran en proportion de la taille de la fenêtre qui est automatiquement mise à l'échelle en cas de redimensionnement. Mieux, le système de coordonnées est gradué de façon à ce que l'origine (0, 0) soit situé au centre réel de la fenêtre.

La fonction Cairo::Context::paint() est ici utilisée pour définir la couleur d'arrière-plan de la fenêtre. Cette fonction ne prend aucun paramètre et remplit la surface actuelle (ou la partie tronquée de la surface) avec la couleur source actuellement active. Après avoir défini la couleur de fond, nous traçons un cercle pour délimiter le cadran, le remplissons en blanc et soulignons le pourtour de noir. Notez que ces actions utilisent la variante _preserve pour conserver le chemin en cours, puis ce même chemin est découpé pour s'assurer que nos prochains tracés ne sortiront pas du pourtour du cadran.

Après avoir tracé le pourtour, nous parcourons la circonférence du cadran et traçons des marques pour chaque heure, avec une marque plus importante pour midi, trois, six et neuf heures. Nous sommes maintenant prêts pour implémenter la fonctionnalité horloge de notre cadran : cela consiste à obtenir la valeur actuelle des heures, minutes et secondes et à tracer les aiguilles avec les angles adéquats.