OpenScheme : Primitives graphiques
Par :
Guilhem de Wailly (gdw@erian-concept.com)

et :
Fernand Boéri (boeri@unice.fr)

Résumé
Le mois dernier, nous avons présenté la programmation orientée objets dans OpenScheme. Le moteur orienté objet permet de construire de puissante bibliothèque de classes utilisant les dernières avances technologiques en matière de programmation orientée objet.
Ce mois-ci, nous présentons la bibliothèque de fonctions graphiques d'OpenScheme. Cette bibliothèque rassemble des fonctions primitives que nous utiliserons pour programmer en Scheme un petit ensemble de classes graphiques dans un prochain numéro.
L'environnement OpenScheme, version 1.3.2 illimitée pour Linux est maintenant disponible sur le CDROM de Linux Magazine. Il est aussi disponible sur www.open-scheme.com.
Introduction
La bibliothèque graphique d'OpenScheme est un ensemble de fonctions primitives gérant l'affichage graphique.
Ces fonctions s'appuient sur le système graphique existant, que se soit X11 pour les systèmes Unix, Windows, BeOS ou Machintoch (le portage est prévu pour le premier semestre 2000).
Contrairement aux autres environnements Scheme, Universitaires ou commerciaux, OpenScheme repose donc sur sa propre bibliothèque de primitives. Les autres environnements ont choisi TK (couche graphique de TCL/TK), wxWindow ou s'interfacent directement avec le système d'exploitation.
La bibliothèque de base présentée ici est conçue entièrement par Erian Concept, comme la totalité de l'environnement. Elle est écrite en C dont une partie du code est dépendante du système et l'autre non. Elle se présente sous la forme d'un module (code chargeable séparément et dynamiquement) de manière à n'utiliser la bibliothèque que lorsque cela est nécessaire.
Ce module est appelé OpenScheme toolKit ou OK. Les fonctionnalités offertes sont primitives : par exemple, OK ne permet pas de dessiner un bouton graphique. Il ne connaît que la fenêtre dans sa plus simple expression, c'est à dire un rectangle que l'on peut manipuler et auquel il est possible d'attacher des gestionnaires d'évènements. 
Bien que pripitive, OK n'est pas en reste au niveau des performances. Il permet par exemple de définir un angle pour les fonctions de dessin. On peut ainsi écrire du texte à 45°. Un moteur 3D est en cours de développement : il permettra de manipuler des formes en trois dimensions et de les animer, sans vouloir concurrencer les moteurs existants comme OpenGL. Ce moteur est déjà utilisable dans la dernière version d'OpenScheme sur le site www.open-scheme.com. Le support des polices vectorielles est en cours et sera utilisable dans une prochaine version. La particularité des polices de caractères est quelles utiliseront une syntaxe Scheme !
La couche haute de la bibliothèque graphique est un autre module appelé OOK pour Object Oriented OpenScheme ToolKit. Comme son nom l'indique, c'est une bibliothèque de classes offrant les éléments graphiques usuels, comme les boutons, les menu, les zones d'édition. La programmation de cette bibliothèque repose entièrement sur la programmation orientée objet en Scheme qui se prête particulièrement bien à ce genre d'exercice. Dans un prochain article, nous concevrons un mini système d'objets graphiques en utilisant OpenScheme et OK. Le lecteur se rendra compte à quel point la programmation en Scheme est efficace, tant au niveau de la clarté du code que des performances.
L'un des avantages de la solution retenue dans OpenScheme est de permettre une programmation entièrement objet de la bibliothèque graphique de haut niveau, en utilisant des fonctionnalités initiales primitives.
De plus, seule une partie du module OK est dépendante du système d'exploitation et nécessite un portage. Le reste du code ainsi que le module OOK sont indépendants. Nous prévoyons ainsi de créer un plugin pour les navigateurs NetScape et InternetExplorer : notre effort de programmation ne va porter que sur l'interfaçage de OK avec le navigateur, car le reste du code reste complètement indépendant. Cette souplesse se retrouve dans tout l'environnement OpenScheme, conçu dès le départ pour être portable et modulaire.
Nous allons commencer la description de OK par ce qui est visible, c'est à dire la fenêtre affichable à l'écran.
Fenêtre
Pour le module OK, une fenêtre est une zone rectangulaire possédant une couleur de fond, éventuellement, une fenêtre parent et des gestionnaires d'évènements.
Un gestionnaire d'événement est une fonction qui est appelée par le système graphique (X11, Windows, …) lorsqu'un événement se produit, comme par exemple, l'appui sur une touche ou l'entrée du curseur de la souris dans la fenêtre.
Une fenêtre qui n'a pas de parent est décorée par le gestionnaire de fenêtre qui lui ajoute un cadre et des boutons. Une fenêtre qui possède un parent n'est pas décorée et ne s'affiche que dans le cadre de son parent. Il est possible de créer des fenêtres sans parent et sans décoration ; elles sont utilisées par exemple pour afficher les menus. Il existe une fenêtre particulière qui est la fenêtre racine (le bureau) que l'on peut manipuler quasiment sans distinction.
Création
La fonction de création des fenêtres est :
(ok:create-window parent
                 fond
                 x y
                 width height)
Les arguments sont les suivants :
·	Parent est la fenêtre parent ou #f si la fenêtre n'a pas de parent.
·	Fond est la couleur de fond de la fenêtre. Cette couleur peut être une constante :
Ok:transparent 	Ok:green 	
Ok:white 	Ok:blue 	
Ok:black 	Ok:yellow 	
Ok:red 	Ok:gray 	
Ok:light-gray 	Ok:dark-gray 	

La couleur peut aussi être créée avec la fonction (ok:make-color r g b) ou r, g et b sont respectivement les composantes rouge, verte et bleue de la couleur, sous la forme d'un entier de 0 à 65535.
La fonction (ok:color nom) retourne la couleur dont le nom est donné, sous la forme d'une chaîne de caractères. Les noms sont les noms standard du système X11.
·	X et y représente les coordonnées en pixels du coin en haut à gauche de la fenêtre par rapport à la fenêtre parent, s'il y en a une, ou par rapport à l'écran. 
L'origine des coordonnées se situe dans le coin en haut à gauche est l'axe des y descend vers le bas. 
·	Width et height représentent la largeur et la hauteur de la fenêtre, en pixels.
Racine
La fenêtre ok:root est la fenêtre racine, c'est à dire le bureau. Il est possible de dessiner directement dans le bureau, sans limitation.
Cette fenêtre est crée et initialisée au démarrage de OK.
Destruction
La programmation en Scheme utilise de manière transparente un mécanisme de récupération de la mémoire. Si une fenêtre est créée sans parent, et quelle n'est pas stockée dans une variable ou une structure de données, elle sera détruite tôt ou tard.
C'est ce qui arrivera à la fenêtre créée ci-dessous :
Osm> (ok:create-window #t 
                      ok:black
                      1 1
                      100 50)
  => ok:window
Les fenêtre filles sont protégées tant que leur parent l'est. Pour éviter la destruction automatique des fenêtres sans parent, il est nécessaire de les placer dans une variable globale ou toute structure de données accessible au mécanisme ramasse miettes (garbage collector).
Il est possible de détruire explicitement une fenêtre en utilisant la fonction (ok:destroy-window fenêtre) :
Osm> (define f (ok:create-window #t 
                                ok:black
                                1 1
                                100 50))
  => #unspecified
Osm> f
  => ok:window
Osm> (ok:desroy-window f)
  => #unspecified
Osm> f
  => ok:destroyed-window
Dans ce cas, l'objet Scheme associé à la fenêtre change de nature pour devenir explicitement une fenêtre détruite.
Affichage
Par défaut, les fenêtre créées sont cachées. Pour les afficher, on utilise la fonction suivante :
(ok:show fenêtre mode)
Les arguments sont les suivants :
·	Fenêtre est la fenêtre pour laquelle on veut changer l'affichage.
·	Mode détermine les nouveaux paramètres d'affichage parmi :
Valeur	Description	
ok:show:iconic	Transforme la fenêtre en icône	
ok:show:plain 	La fenêtre est affichée en plein écran	
ok:show:normal	La taille de la fenêtre est restaurée et la fenêtre et affichée.	
ok:show:hide 	Cache la fenêtre	
ok:show:top 	La fenêtre est affichée au-dessus des autres	
ok:show:bottom	La fenêtre est affichée au-dessous des autres	

Pour afficher une fenêtre nouvellement créée, on utilisera :
Osm> (define f (ok:create-window #t 
                                ok:black
                                1 1
                                100 50))
  => #unspecified
Osm> (ok:show f ok:normal)
  => #unspecified
Focus clavier
Dans un environnement graphique, les entrées du clavier sont envoyées à une seule fenêtre. En général, le titre de la fenêtre qui reçoit les entrées clavier est dessiné avec une couleur différente des autres. On dit que la fenêtre reliée au clavier `` a le focus ''. 
OK étend la gestion du focus aux fenêtres filles. Pour savoir si une fenêtre a le focus, on utilise la fonction (ok:focus? fenêtre) qui retourne #t si la fenêtre a le focus et #f sinon.
Pour donner le focus à une fenêtre, on utilise (ok:focus! fenêtre).
Quant à la fonction (ok:focus), elle retourne la fenêtre OK qui a le focus, ou #f, si aucune fenêtre ne l'a. Il se peut que la fenêtre ayant le focus n'appartiennent pas au module OK ; dans ce cas, #f est retourné.
Parent/enfant
La fonction (ok:parent fenêtre) retourne la fenêtre parent ou #f si na fenêtre n'a pas de parent.
La fonction (ok:parent? fenêtre parent) retourne #t si parent est un ancêtre direct ou indirect de fenêtre, et #f sinon. 
La fonction (ok:parent! fenêtre parent) permet de changer le parent d'une fenêtre. Cette fonction est très puissante ; elle est très utilisée dans les gestionnaires de fenêtres pour ajouter des décorations autour des fenêtres principales. Cette technique est  connue sous le nom du reparenting.
La fonction (ok:children fenêtre) retourne la liste des fenêtres filles de fenêtre ou la liste vide, le cas échéant.
Gestionnaire d'évènement
Le gestionnaire d'évènement est une fonction basée sur une boucle qui traite les évènements associés aux fenêtres d'une même application.
Le rôle du gestionnaire d'événement est de lire les évènements dans la liste des évènements de l'application et de les envoyer aux fenêtres concernées.
A chaque fenêtre, on associe des fonctions appelées fonctions de rappel ou callback destiné à traiter un évènement particulier. 
Ces fonctions sont appelées callback car elles sont invoquées par le gestionnaire de fenêtre du système, contrairement aux fonctions classiques invoquées par l'application elle-même.
Ce type de programmation est né avec les interfaces graphiques et il est appelé programmation par évènements ou programmation évènementielle.
Dans OK, le gestionnaire d'événement est activé en appelant la fonction (ok:exec). L'appel à cette fonction active le traitement des évènements ; les fenêtres créées et rendues visibles apparaissent sitôt la fonction appelée. L'appel à cette fonction ne se termine que si toutes les fenêtres de l'application sont détruites ou si l'un des gestionnaires d'évènements de fenêtre retourne #f ou si la fonction (ok:stop) est invoquée.
On active donc l'affichage des fenêtres avec :
Osm> (ok:exec)
     ; l'appel ne se termine pas 
    ; immédiatement
 => #unspecified
Evènements
Attacher une fonction de rappel
Pour attacher une nouvelle procédure de rappel à une fenêtre, on utilise la fonction :
Osm> (ok:nom-callback! fenêtre procédure)
Où nom est le nom de l'évènement. Ainsi, on attache une nouvelle fonction de rappel procédure à la fenêtre. Le nombre et le type des paramètres de la procédure dépendent de l'événement, résumés dans le tableau ci-dessous :

Nom	Paramètres	Description	
Display	Mode 	Le mode d'affichage de la fenêtre a changé.
Le nouveau mode est passé en argument	
Move 	X y 	La fenêtre est déplacée par le gestionnaire de fenêtre.
Les nouvelles coordonnées du coin en haut à gauche sont passées en arguments.	
Resize 	W h 	Les dimensions de la fenêtre sont modifiées.
Les nouvelles dimensions sont passées en arguments, la largeur en premier, la hauteur en second.	
Mouse 	X y 	La souris est déplacée sur la fenêtre.
La position de la souris relativement au coin en haut à gauche de la fenêtre sont passés en arguments.	
Press 	Key 	Une touche est pressée dans fenêtre.
Le code de la touche est donné en argument. Les différents codes sont donnés plus bas.	
Release 	Key 	Une touche est relachée dans la fenêtre.
Le code de la touche est donné en argument.	
Paint 	#(x y w h)	La fenêtre doit être repeinte par l'application.
Le rectangle à dessiner est donné en argument sous la forme d'un vecteur contenant ses coordonnées. 	
Refresh 		La fenêtre doit être entièrement redessinée.
Cet événement est émis pour un ou plusieurs événement paint. 	
In 		La fenêtre reçoit le focus clavier.	
Out 		La fenêtre perd le focus clavier.	
Delete 		La fenêtre est détruite via le gestionnaire de fenêtre.
En général, cela se produit en cliquant sur un bouton dans la barre de titre de la fenêtre.	
Enter 		La souris entre dans la fenêtre.	
Leave 		La souris quitte la fenêtre.	
Add 	child 	Une fenêtre fille est crée dans la fenêtre. 
La fenêtre fille est passée en arguments.	
Del 	Child 	Une fenêtre fille est détruite.
La fenêtre fille détruite est passée en argument.	

Par exemple, si nous voulons afficher un message à l'écran chaque fois que la souris entre dans la fenêtre, nous écrivons :
Osm> (ok:enter-callback! 
     fenêtre 
     (lambda ()
       (format #t
               "entrée dans ~a\n"
               fenêtre)))
Obtenir une fonction de rappel
Pour obtenir la fonction de rappel attachée à une fenêtre, on utilise la fonction :
(ok:nom-callback fenêtre)
Par défaut, lorsqu'une fenêtre est créée, une procédure sans effet est attachée au évènements.
Pour chaîner les procédures, on peut utiliser le code :
Osm> (ok:enter-callback! 
     fenêtre
     (let ([ancienne (ok:enter-callback
                      fenêtre)]) 
      (lambda ()
       (ancienne)
       (format #t
               "entrée dans ~a\n"
               fenêtre))))
L'utilisation de Scheme rend la programmation particulièrement aisée du fait de la gestion puissante de variables et des procédures. 
Codage des touches
Les procédures de rappel liées aux événements press et release reçoivent comme argument un code de touche sous la forme d'un entier. 
Les fonctions suivantes permettent d'extraire l'information contenue dans le code de touche.
·	(ok:function? key) : retourne #t si le code concerne une touche de fonction, #f sinon.
·	(ok:control? key) : retourne #t si la touche [CONTROL] est pressée, #f sinon.
·	(ok:alt? key) : retourne #t si la touche [ALT] est pressée, #f sinon.
·	(ok:shift? key) : retourne #t si la touche [SHIFT] est pressée, #f sinon.
·	(ok:keypad? key) : retourne #t si la touche provient du pavé numérique, #f sinon.
·	(ok:alpha? key) : retourne #t si la touche contient un code ASCII, #f sinon.
Les fonctions suivantes permettent de construire un code de touche, en général pour effectuer des comparaisons :
·	(ok:control key) : retourne le code de touche key, entier ou caractère, associé au code de la touche [CONTROL].
·	(ok:alt key) : retourne le code de touche key, entier ou caractère,  associé au code de la touche [ALT].
·	(ok:shift key) : retourne le code de touche key, entier ou caractère,  associé au code de la touche [SHIFT].
·	(ok:alpha key) : extrait le code ASCII du code key.
Nous pourrions avoir comme gestionnaire d'événement :
Osm> (ok:press-callback! 
     fenêtre
     (lambda (key)
      (let ([ascii (ok:alpha key)])
       (cond [(and (ok:conrol? key)
                  (eq? ascii #\a))
             (display "CONTROL+A\n")]
             [(not (or (ok:conrol? key)
                       (ok:shift?  key)
                       (ok:alt?    key))
              (if (char-alphabetic? ascii)
               (format #t 
                       "ASCII:~a\n" 
                       ascii))])))
La prochaine fois
Nous savons maintenant créer des fenêtres, les afficher et les animer. Nous avons vu comment attacher des procédures aux évènements associés aux fenêtres. Nous savons comment obtenir la liste des fenêtres enfants d'une fenêtre donnée, et comment lui donner le focus des entrées clavier.
La prochaine fois, nous examinerons comment dessiner dans les fenêtres des formes simples, comment changer l'angle et l'origine de dessins, et manipuler des bitmaps et des images.
Les auteurs

Dr Guilhem de Wailly, directeur de la société Erian Concept : support, formations, configurations, administration, développements Linux. Environnement OpenScheme.
http://www.erian-concept.com

Pr Fernand Boéri, Professeur à l'Université de Nice - Sophia-Antipolis, membre de l'unité SPORTS du laboratoire I3S, spécialiste en modélisation, évaluation de performances et architectures parallèles.
http://www.i3s.unice.fr
Références
Programmation orientée objet
¨	OMT: 1-Modélisation et conception orientées objet
James Bumbaugh et al.
Masson / Prentice Hall
Scheme
¨	Structure et Interprétation des programmes informatiques
H. Abelson, GJ. Sussman
InterEdition
¨	The Scheme Programming Languages - Ansi Scheme 
R. Kent Dybvig
Prentice Hall
¨	Les langages Lisps -  Christian Queinnec
InterEdition
¨	Programmer avec Scheme - J. Chazarin
Thomson Publishing
¨	Revised4 Report on the Algorithmic Language Scheme
W. Clinger, J. Rees
ftp://ftp.nj.nec.com/pub/kelsey
Environnements Scheme
¨	Scm - A. Jaffer
http://www-swiss.ai.mit.edu/~jaffer
La référence des interprètes Scheme. Très petit, rapide, pour beaucoup de plates-formes, extensible.
¨	PCScheme - Texas Instrument
ftp://cui.unige.ch/public/pcs/pcscheme.exe
Un très bon environnement de programmation Scheme pour DOS, avec éditeur intégré.
¨	DrScheme - Rice University
http://www.cs.rice.edu/CS/PLT/
Environnement Scheme libre très avancé.
¨	ChezScheme - Cadence, Inc
http://www.scheme.com/
Environnement Scheme très performant.
¨	Inlab Scheme - Inlab Software GmbH
http://www.munich.net/inlab/scheme/
Environnement commercial
¨	EdScheme, 3Dscheme - Scheme, Inc
http://www.schemers.com/
Environnement de programmation Scheme pour Windows.
¨	OpenScheme - Erian Concept
http://www.open-scheme.com
Environnement professionnel de programmation Scheme comprenant un interprète, un compilateur et un débogueur symbolique. 
Existe en version libre et commerciale, pour Linux, FreeBSD, Solaris, Windows et BeOS, sur systèmes Intel.