Ma petite horloge transparente [1]

Dans un premier temps on écrit un programme gtk minimal:

#include <gtk/gtk.h> GtkWidget *window; int main(int argc, char **argv) { gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_widget_show_all (window); gtk_main (); return 0; }

La ligne de commande pour compiler est:

gcc `pkg-config --cflags --libs gtk+-2.0 cairo` -o clock clock.c

Ce code est très simple, il initialise la librairie GTK, crée une fenêtre puis lance la boucle d'évènements. Maintenant passons aux choses sérieuses et ajoutons de quoi dessiner notre horloge.

// nécessaire pour accéder à l'heure système #include <time.h> // déclaration d'un nouveau gestionnaire d'évènements gboolean expose(GtkWidget *widget, GdkEventExpose *event); ... // on branche le gestionaire sur le bon évènement de la bonne fenêtre // dans main() g_signal_connect (window, "expose_event", G_CALLBACK (expose), NULL); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); ... gboolean expose(GtkWidget *widget, GdkEventExpose *event) { cairo_t *cr; double x, y, w, h; double radius; double angle; x = widget->allocation.x; y = widget->allocation.y; w = widget->allocation.width; h = widget->allocation.height; // obtenir un contexte cairo pour dessiner cr = gdk_cairo_create (widget->window); // nettoyer la fenetre // je génère un chemin rectangulaire // qui entoure la surface que je veux dessiner cairo_rectangle (cr, x, y, w, h); // je veux effacer alors j'utilise un opérateur pour effacer // voir [2] et [3] cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); // je remplis la surface cairo_fill(cr); // on veut dessiner au dessus de la surface cairo_set_operator(cr, CAIRO_OPERATOR_OVER); // on dessine en noir x = widget->allocation.x + widget->allocation.width / 2; y = widget->allocation.y + widget->allocation.height / 2; radius = MIN (widget->allocation.width / 2, widget->allocation.height / 2) - 5; cairo_arc (cr, x, y, radius, 0, 2 * M_PI); // le cercle est remplit de blanc // et le contour est préservé dans le contexte cairo_set_source_rgb (cr, 1, 1, 1); cairo_fill_preserve (cr); // le contour préservé est réutilisé pour // cercler ce rond de noir cairo_set_source_rgb (cr, 0, 0, 0); cairo_stroke (cr); // l'heure locale est récupèré struct tm* now_hms; time_t now = time(NULL); // heure GMT now_hms = localtime(&now); // conversion vers heure locale // dessiner l'aiguille des heures // on calcule l'angle de l'aiguille // l'unité est en radian et l'angle 0 est à 3 heures angle = now_hms->tm_hour/12.0*M_PI-M_PI/2; // le contexte dispose d'une mémoire // ou l'on peut sauvegarder l'état courant // des outils de dessin // ici on veut changer la largeur du pinceau // sans perturber le reste des dessins cairo_save (cr); cairo_set_line_width (cr, 1.5 * cairo_get_line_width (cr)); // on se positionne au center, pinceau levé cairo_move_to(cr, x, y); cairo_line_to(cr, x + radius * 0.9 * cos(angle-M_PI/100), y + radius * 0.9 * sin(angle-M_PI/100)); // on peind une ligne cairo_stroke (cr); cairo_restore (cr); return TRUE; }

Bon c'est bien beau j'ai l'heure courante mais ca ne bouge pas ton truc ?

... gboolean expose(GtkWidget *widget, GdkEventExpose *event); // on déclare un nouveau gestionnaire d'évènement // mais cette fois branché sur les battements d'horloge gboolean tick(gpointer data); ... // on connecte le gestionnaire à l'évènement 'battement d'horloge // toutes les 1000 millisecondes' (= 1s) g_timeout_add(1000, tick, NULL); gtk_main (); ... // les gestionnaire demande à ce qu'on redessine la fenêtre gboolean tick(gpointer data) { gtk_widget_queue_draw(window); return TRUE; } ...
La transparence

Pour obtenir une fenêtre transparente vous aurez besoin d'un serveur X comprenant l'extension Composite et d'un gestionaire de composition, comme xcompmgr.

Aprés cela il suffit dire à GTK que notre fenêtre doit supporter les informations de transparence.

... // dans main() // on attache un alpha channel à la fenêtre // i.e une image contenant les valeurs // de transparence des pixels de la fenêtre. gtk_widget_set_colormap(window, gdk_screen_get_rgba_colormap (gtk_widget_get_screen(window))); // on supprime les décorations tant qu'à faire gtk_window_set_decorated(GTK_WINDOW(window), FALSE); g_signal_connect (window, "expose_event", G_CALLBACK (expose), NULL); ...
Annexe

Le code pour afficher les autres aiguilles et les repères des heures.

... // les repères cairo_stroke (cr); int i; for (i = 0; i < 12; i++) { int inset; inset = 0.1 * radius; cairo_move_to (cr, x + (radius - inset) * cos (i * M_PI / 6), y + (radius - inset) * sin (i * M_PI / 6)); cairo_line_to (cr, x + radius * cos (i * M_PI / 6), y + radius * sin (i * M_PI / 6)); cairo_stroke (cr); } for (i = 0; i < 12; i++) { int inset; cairo_save (cr); /* save pen size to stack */ if (i % 3 == 0) inset = 0.2 * radius; else { inset = 0.1 * radius; cairo_set_line_width (cr, 0.5 * cairo_get_line_width (cr)); } cairo_move_to (cr, x + (radius - inset) * cos (i * M_PI / 6), y + (radius - inset) * sin (i * M_PI / 6)); cairo_line_to (cr, x + radius * cos (i * M_PI / 6), y + radius * sin (i * M_PI / 6)); cairo_stroke (cr); cairo_restore (cr); /* recover pen size from stack */ } ... // dessiner les minutes angle = now_hms->tm_min/60.0*M_PI-M_PI/2; cairo_move_to(cr, x, y); cairo_line_to(cr, x + radius * 0.95 * cos(angle), y + radius * 0.95 * sin(angle); cairo_stroke (cr); // dessiner la trotteuse angle = now_hms->tm_sec/60.0*M_PI-M_PI/2; cairo_save (cr); /* save pen size to stack */ cairo_set_line_width (cr, 0.7 * cairo_get_line_width (cr)); cairo_move_to(cr, x, y); cairo_line_to(cr, x + radius * 0.95 * cos(angle), y + radius * 0.95 * sin(angle); cairo_stroke (cr); cairo_restore (cr); /* recover pen size from stack */ ...
Références
  1. Article, dans le gnome journal, dont est tiré ce tutorial
  2. Référence de la librairie cairo sur le type cairo_operator_t
  3. un texte traitant des opérateurs de Porter-Duff
  4. Code source complet
Remerciements

Benjamin Dauvergne <benjamin.dauvergne@gmail.com> Cet article et son code sont dans le domaine public