Par Edouard Thiel

Dans cette note, je vous présente une petite boîte à outil graphique qui s'appelle EZ-Draw et qui vient de sortir en version 1.0 ; c'est un logiciel libre sous licence LGPL.

J'ai eu l'idée de développer ce logiciel en discutant avec des collègues qui enseignent le langage C en première année de la Licence Informatique à Luminy ; ils souhaitaient faire un miniprojet de fin d'année avec une interface graphique, par exemple un jeu de dame ou de sudoku.

J'ai moi-même enseigné pendant plus de dix ans en première année de Deug MIAS et MASS, avant le passage au LMD (le nouveau système de diplômes européen Licence-Master-Doctorat) ; à l'époque le premier langage était le Pascal, et grâce à Turbo Pascal ou à Delphi, on pouvait facilement faire du graphisme, et ce genre de mini-projet graphique de fin d'année avait le don de motiver beaucoup les étudiants.

Actuellement, parmi d'autres choses, j'enseigne la programmation d'interfaces graphiques en GTK en fin de deuxième année. GTK est la librairie graphique qui anime le bureau GNOME ; c'est joli, pro, complet, écrit en C, mais nécessite d'écrire des montagnes de code pour créer une fenêtre, faire vivre un bouton, faire un dessin, etc : bref, ce n'est absolument pas du niveau débutant.

Il existe bien d'autres toolkits graphiques utilisables en C, mais leur emploi n'est guère plus simple et donc on en revient au même : nécessité de quelque chose de simple pour grands débutants en langage C.

D'où cette idée d'écrire EZ-Draw : j'ai gardé le strict essentiel, en conservant les 3 ou 4 notions de base, et en supprimant tout ce qui est secondaire (on veut juste des fenêtres, pouvoir dessiner dedans et gérer des évènements simples).

Pendant que j'y étais, j'ai fait en sorte que EZ-Draw fonctionne sur tous les systèmes, que ce soit Linux (toutes les salles de TP sont sous Debian), Mac OS (un certain nombre d'étudiants ont un Mac), et bien sûr Windows (la plupart des étudiants on accès à un PC sous Windows chez eux, avant que d'investir dans un portable, puis installer Ubuntu dans l'immense majorité des cas).

Pour installer le logiciel, rien de plus simple, il suffit de suivre les instructions dans le fichier README, il n'y a (presque) pas de configuration, ça marche tout de suite. Le coeur de EZ-Draw tient dans seulement 2 fichiers (ez-draw.c et ez-draw.h), le reste est constitué de démos pour illustrer le tutorial pas à pas, et de quelques jeux avec du code plus conséquent pour tester le tout : ça m'a permis de voir s'il ne manquait rien et si c'était réellement pratique à utiliser. Par exemple, un jeu de labyrinthe en 3D "fil de fer", il y a des captures d'écran ici.

Pour avoir une idée précise de EZ-Draw, je vous invite naturellement à consulter le tutorial et le manuel de référence, mais auparavant voici un exemple simple d'utilisation : nous allons ouvrir une fenêtre et afficher une croix ; chaque fois que l'utilisateur cliquera dans la fenêtre, la croix sera redessinée à la position de la souris ; en tapant sur la lettre 'q', l'utilisateur pourra quitter le programme.

On va avoir besoin d'écrire 3 fonctions. La première est la fonction main, qui est le programme principal.

#include "ez-draw.h"

int main ()
{
    Window win1;

    if (ez_init() < 0) exit(1);

    win1 = ez_window_create (400, 300, "Essai 1", win1_event);

    ez_main_loop ();
    exit(0);
}

Il suffit d'inclure ez-draw.h pour bénéficier de toutes les définitions de EZ-Draw.

On commence par déclarer une variable win1 de type Window ; cette variable contiendra le numéro de la fenêtre que l'on va créer.

Ensuite on initialise EZ-Draw avec ez_init ; si cette initialisation échoue (résultat négatif), alors on abandonne le programme avec exit(1) (le 1 pour dire que la programme a échoué).

Pour créer une fenêtre, une seule ligne suffit : on appelle ez_window_create avec 4 paramètres : la largeur et hauteur de la fenêtre en pixels, son titre, et un 4e paramètre win1_event sur lequel je vais revenir dans un instant.

Enfin on appelle ez_main_loop : cette fonction s'appelle la "boucle d'évènements" ; il s'agit d'une boucle sans fin, qui attend les événements (clic souris, touche clavier, etc) et fait les opérations nécessaires. Cette fonction fait apparaitre la ou les fenêtres créées, puis se termine lorsque toutes les fenêtres ont été fermées ou lorsque on appelle la fonction ez_quit.

La seconde fonction est la fonction win1_event ; c'est elle qu'on a passé en 4e paramètre à ez_window_create. De quoi s'agit-il ? Eh bien c'est la fonction que ez_main_loop va appeler chaque fois que il y aura un évènement sur la fenêtre win1. On appelle ça la callback de la fenêtre.

void win1_event (Ez_event *ev)         /* Appelée a chaque événement sur win1 */
{
    switch (ev->type) {

        case Expose :                              /* Il faut tout redessiner */
            win1_redessiner (ev->win);
            break;

        case ButtonPress :            /* Un bouton de la souris a été enfoncé */
            clic_x = ev->mx;
            clic_y = ev->my;
            ez_send_expose (ev->win);
            break;

        case KeyPress :                           /* Une touche a été pressée */
            switch (ev->key_sym) {
                case XK_q : ez_quit (); break;
            }
            break;
    }
}

Cette fonction est appelée avec un évènement ev en paramètre (pour les amateurs de Pascal, l'écriture EZ_event *ev signifie var ev : Ez_event, ce dernier étant un type record).

Dans cet évènement il y a un champ ev->type qui décrit le type (codé par un entier) de l'évènement. On fait donc un branchement avec le switch selon ce type d'évènement. Je vous présente ici 3 évènements.

  1. L'évènement Expose signifie que l'on doit entièrement redessiner l'intérieur de la fenêtre. C'est le seul moment où l'on peut dessiner.
  2. L'évènement ButtonPress signifie que l'un des boutons de la souris a été enfoncé. On récupère ici les coordonnées de la souris ev->mx et ev->my dans des variables globales clic_x et clic_y, puis on demande à ce que la fenêtre soit redessinée avec ez_send_expose, qui comme son nom l'indique va envoyer un évènement Expose à la fenêtre.
  3. L'évènement KeyPress signifie que une touche du clavier a été enfoncée. Chaque touche a un numéro, représenté par une constante préfixée par XK_. Par exemple XK_q désigne la touche 'q', XK_space représente la barre d'espace, etc. Comme vous le voyez ici, presser la touche 'q' provoquera la fin du programme.

Venons-en à la 3e et dernière fonction win1_redessiner, chargée de dessiner l'intérieur de la fenêtre. Souvenez-vous que le seul endroit où vous avez le droit d'appeler cette fonction est lors de l'évènement Expose.

int clic_x = 200, clic_y = 150;

void win1_redessiner (Window win)
{
    ez_set_color (ez_magenta);
    ez_set_thick (5);
    ez_draw_line (win, clic_x - 20, clic_y, clic_x + 20, clic_y);
    ez_draw_line (win, clic_x, clic_y - 20, clic_x, clic_y + 20);
}

La fonction reçoit en paramètre la fenêtre win dans laquelle elle va dessiner. Elle sélectionne la couleur et l'épaisseur des prochains dessins, puis trace deux traits en forme de croix, centrée autour de la position clic_x, clic_y, qui avait été mémorisée lors du précédent clic souris. Pour simplifier on a mémorisé cette position dans des variables globales clic_x, clic_y, mais il existe aussi un moyen d'éviter les variables globales (voir ici).

Après avoir recopié les trois fonctions dans le fichier essai1.c, il reste à le compiler pour produire un exécutable. Sous Unix (Linux, Mac OS, Solaris, etc) taper :

gcc -Wall essai1.c ez-draw.c -o essai1 -L/usr/X11R6/lib -lX11 -lXext

puis ./essai1 pour l'exécuter.

Sous Window (toutes versions, de Seven à Windows 98 !)

gcc -Wall essai1.c ez-draw.c -o essai1.exe -lgdi32

puis essai1 dans un terminal pour l'exécuter, ou encore double-cliquez sur l'icone de essai1.

Et voila c'est fini. Si EZ-Draw vous plait, n'hésitez pas à partager votre retour d'expérience.