II. Les objets de GVLOGO▲
Cette partie décrit les objets fondamentaux manipulés par GVLOGO. Après un survol des fichiers communs seront successivement abordés le traitement des erreurs, les mots, les listes, les listes de propriétés et la tortue graphique.
II-A. Les fichiers communs▲
II-A-1. La centralisation des données▲
Les avantages d'une centralisation de certaines données sont de deux types :
- il est possible modifier un élément à partir d'un seul point et donc inutile de parcourir tous les fichiers sources éparpillés dans plusieurs unités : on imagine le gain de temps dans la situation où l'on devrait changer un contenu utilisé plusieurs dizaines de fois ;
- le risque d'erreurs difficiles à déceler est moindre puisqu'il n'y a pas à recopier un élément pour l'utiliser ailleurs : coder en dur une chaîne de caractères, par exemple, expose à une copie erronée qui donnera des résultats inattendus lors de comparaisons.
La centralisation des données s'organise autour de quatre fichiers : GVDefines.inc, GVConsts, GVErrConsts et GVPrimConsts. Le projet final GVLOGO utilise par ailleurs un fichier qui lui est propre et sur lequel nous reviendrons en temps utile : GVLOGOConsts.
II-A-2. GVDefines.inc▲
Le fichier GVDefines.inc contient les options de compilation. Il permettra par exemple d'optimiser la compilation lors d'une transposition du logiciel vers Delphi. Son contenu s'organise par conséquent autour de directives.
Actuellement, il ne prend en compte que la compilation avec Lazarus, provoquant une erreur si l'on tente d'activer Delphi :
// Lazarus (défini par défaut)
{$DEFINE CompLazarus }
// Delphi prévu... (non défini par défaut)
{.$DEFINE CompDelphi}
// Lazarus : options de compilation
{$IFDEF CompLazarus}
{$UNDEF CompDelphi}
{$MODE objfpc}{$H+}
{$ENDIF}
// Delphi : options de compilation
{$IFDEF CompDelphi}
Implémentation prévue...
{$ENDIF}
// option de débogage (non défini par défaut)
{.$DEFINE Debug}
II-A-3. GVConsts▲
L'unité GVConsts servira pour toutes les unités du projet. Elle contient en effet les constantes, types et chaînes de caractères partagés par l'ensemble du projet.
Dans un premier temps, on notera les constantes les plus fréquemment utilisées dans les différentes unités :
const
// *** listes ***
CBlank = ' '
; // espace
CTab = #9
; // tabulation
CSlash = '/'
; // slash
CBeginList = '['
; // début de liste
CEndList = ']'
; // fin de liste
CBeginPar = '('
; // début d'expression
CEndPar = ')'
; // fin d'expression
CEmptyList = CBeginList + CEndList; // liste vide
// *** séparateurs ***
CBlanks = [CTab, CBlank];
CBraces = [CBeginList, CEndList, CBeginPar, CEndPar];
CSeparators = CBlanks + CBraces;
// *** caractères ***
CLink = '$'
; // caractère de lien
CUnderline = '_'
; // soulignement
CDot = '.'
; // point
CAsk = '?'
; // point d'interrogation
CQuote = '"'
; // guillemets
CColon = ':'
; // deux points (début de variable)
CComma = ','
; // virgule
CExclamation = '!'
;
CPlus = '+'
; // addition
CMinus = '-'
; // soustraction
CMul = '*'
; // multiplication
CDiv = '/'
; // division
CPower = '^'
; // puisssance
CGreater = '>'
; // plus grand
CLower = '<'
; // plus petit
CEqual = '='
; // égal
CNotEqual = '<>'
; // différent
CNotEqual2 = '!='
; // différent (2)
CNot = '!'
; // négation
COrB = '|'
; // ou binaire
CAndB = '&'
; // et binaire
CGreaterOrEqual = '>='
; // plus grand ou égal
CLowerOrEqual = '<='
; // plus petit ou égal
// *** caractères spéciaux ***
CSpecialChar = [CTab, CBlank, CEqual, CPlus, CMinus, CMul, CDiv, CBeginPar,
CEndPar, CPower, CGreater, CLower];
CNonPrintable = [#0
..#32
];
// *** ensembles de caractères courants ***
CLowAlpha = ['a'
.. 'z'
]; // caractères alphabétiques en minuscules
CHighAlpha = ['A'
.. 'Z'
]; // caractères alphabétiques en majuscules
CAlpha = CLowAlpha + CHighAlpha; // caractères alphabétiques
// caractères alphabétiques autorisés pour un identificateur
CAlphaPlus = CAlpha + [CUnderline, CColon, CDot];
CDigit = ['0'
.. '9'
]; // chiffres
CDigitPlus = CDigit + [CPlus, CMinus]; // chiffres et signes
CDigitPlusDot = CDigitPlus + [CDot]; // point en plus
CAlphaNum = CAlpha + CDigit; // caractères alphanumériques
// caractères autorisés pour un identificateur
CAlphaNumPlus = CAlphaPlus + CDigit;
// caractères autorisés hors chaîne de caractères
CAllValidChars = CAlphaNumPlus + CSpecialChar;
// *** listes de propriétés ***
CExtPl = '.GPL'
; // extension pour les fichiers de listes de propriétés
// entête de fichier de listes de propriétés
CHeader = '[GPL100 (c) GV 2014]'
;
CSep = '|'
; // séparateur de liste de propriétés
// *** valeurs remarquables ***
CRLower = -1
; // résultats de comparaison (plus petit)
CREqual = 0
; // résultats de comparaison (égal)
CRGreater = 1
; // résultats de comparaison (plus grand)
CRTrue = -1
; // vrai
CStTrue = '-1'
;
CRFalse = 0
; // faux
CStFalse = '0'
;
// *** tortue graphique ***
// couleurs de base
CColors: array
[0
..19
] of
TColor = (clBlack, clMaroon, clGreen, clOlive,
clNavy, clPurple, clTeal, clGray, clSilver, clRed, clLime, clYellow,
clBlue, clFuchsia, clAqua, clWhite, clMoneyGreen, clSkyBlue, clCream,
clMedGray);
DgToRad = Pi / 180
; // pour les conversions en radians
RadToDg = 180
/ Pi; // pour les conversions en degrés
CDefaultScale = 100
; // échelle par défaut
CDefaultHeading = 90
; // cap par défaut
CDefaultXY = 600
; // taille écran tortue par défaut
CDefaultSize = 8
; // taille d'une tortue par défaut
CMaxSize = 20
; // taille maximale de la tortue
CMaxSpeed = 100
; // vitesse maximum de la tortue
CDefaultPenColor = clWhite; // couleur de fond par défaut
CDefaultBackColor = clBlack; // couleur du crayon par défaut
CDefaultPenWidth = 1
; // largeur du crayon par défaut
// *** piles ***
CMinStack = 8
; // minimum d'espace pour une pile
// *** noyau ***
CVr = CDot + 'VAR'
; // variable
CBurried = CDot + 'BUR'
; // enterré
CInPackage = CDot + 'INP'
; // dans un paquet
CPackage = CDot + 'PKG'
; // un paquet
CProc = CDot + 'PRC'
; // une procédure
CExtLP = CDot + 'GVE'
; // extension d'un espace de travail
// *** interpréteur ***
CDisabledState = 1
; // désactivé
CTrueState = -1
; // vrai
CFalseState = 0
; // faux
CBreak = '#'
; // marque de fin
Les autres éléments de cette unité seront étudiés lors de la description des unités qui y font appel.
II-A-4. GVErrConsts▲
L'unité GVErrConsts abrite les éléments de base utiles pour la gestion des erreurs. Elle comprend les chaînes des messages, un type énumération pour les erreurs possibles et un tableau reprenant ces messages. Un enregistrement décrit une erreur :
// *** enregistrement d'une erreur ***
TGVErrorRec = record
Code: TGVError; // code de l'erreur
ErrItem: string
; // élément fautif dans la ligne de travail
ErrPos: Integer
; // position (absolue ou relative) de l'élément fautif
end
;
II-A-5. GVPrimConsts▲
L'unité GVPrimConsts fait de même, mais pour ce qui concerne les primitives du langage GVLOGO, c'est-à -dire les mots qui renvoient à une action/commande comprise d'origine par le langage.
L'enregistrement d'une primitive comprend son nom et le nombre de paramètres qu'elle attend :
type
// *** enregistrement d'une primitive ***
TGVPrimRec = record
Name: string
; // nom
NbParams: Integer
; // nombre de paramètres
end
;
L'unité contient alors la liste des noms possibles et un tableau des enregistrements des primitives.
On remarquera que la partie implémentation de ces unités est vide, le rôle de ces dernières étant seulement de déclarer des types, des chaînes et des constantes.
L'utilisation de resourcestring facilitera la traduction du logiciel en une autre langue. Toutefois, l'utilisation de tableaux fait que les chaînes traitées selon ce mode ne seront pas traduites automatiquement(4).
II-B. Le traitement des erreurs▲
Il peut paraître surprenant de commencer par la gestion des erreurs, mais la réalisation d'une unité utilisée par tous les modules nécessaires au projet permettra de simplifier leur écriture. L'objectif est de fournir un outil simple de centralisation des erreurs : à partir d'un minimum de déclarations, tout module gèrera de manière homogène une éventuelle erreur.
II-B-1. L'unité GVErrors▲
L'unité GVErrors concentre les outils nécessaires à cette gestion des erreurs. L'élément fondamental de communication est le gestionnaire d'événements relatifs aux erreurs :
type
// *** notification d'une erreur ***
TGVOnErrorEvent = procedure
(Sender: TObject; ErrorRec: TGVErrorRec) of
object
;
Comme tout gestionnaire d'événements, il propose un modèle de procédure (ou, plus rarement, de fonction) avec une signature particulière (ici, un Sender de type TObject et un enregistrement d'erreur ErrorRec de type TGVErrorRec tel que défini plus haut dans l'unité GVErrConsts).
Voici l'interface de la classe TGVErrors définie dans GVErrors :
TGVErrors = class
(TObject)
strict
private
fOnError: TGVOnErrorEvent; // notification de changement
fErrorRec: TGVErrorRec; // enregistrement en cours
fErrorMessage: string
; // message d'erreur en cours
fOKFlag: Boolean
; // drapeau d'erreur
function
GetOKFlag: Boolean
; // récupération du drapeau d'erreur
procedure
SetOKFlag(AValue: Boolean
); // mise à jour du drapeau d'erreur
protected
// changement notifié
procedure
NotifyError;
public
constructor
Create; // constructeur
destructor
Destroy; override
; // destructeur
procedure
Clear; // nettoyage des erreurs
// procédure concernant la collecte des informations
procedure
SetError(const
Code: TGVError; ErrItem: string
; ErrPos:
Integer
= CE_NoErr); virtual
;
// centralisation des informations
procedure
GetError(Sender: TObject; ErrorRec: TGVErrorRec);
// message associé à une erreur
property
ErrorMessage: string
read
fErrorMessage;
// notification de changement
property
OnError: TGVOnErrorEvent read
fOnError write
fOnError;
// drapeau d'erreur
property
Ok: Boolean
read
GetOKFlag write
SetOKFlag default
True
;
// enregistrement d'erreur
property
Error: TGVErrorRec read
fErrorRec write
fErrorRec;
end
;
ErrorMessage transforme le numéro d'une erreur en un message lisible. La propriété Error permet d'accéder à l'enregistrement de l'erreur. La propriété OK permet de tester si une erreur a été rencontrée ou non.
Plus intéressant est le gestionnaire OnError qui sera activé à chaque erreur rencontrée. C'est par son intermédiaire que les erreurs seront traitées par l'unité appelante. SetError et GetError permettent de gérer les données associées aux erreurs. La méthode GetError a une signature compatible avec le type TGVOnErrorEvent si bien qu'elle peut récupérer les erreurs d'une unité qui gérerait elle-même un objet de type TGVErrors !
Voici par exemple comment l'interpréteur récupère et centralise les erreurs détectées dans les objets qu'il manipule :
constructor
TGVAutomat.Create;
// *** création ***
begin
// piles
fWkStack := TGVStringStack.Create; // travail
fParamsStack := TGVIntegerStack.Create; // paramètres
fDatasStack := TGVStringStack.Create; // données
fCommandsStack := TGVStringStack.Create; // commandes
fExeStack := TGVStringStack.Create; // exécution
// drapeaux
fStop := False
; // pas d'arrêt
fFollow := False
; // pas de suivi
fTurtleOutPut:= False
; // écriture sur l'écran normal
fState := asWaiting; // en attente
// modules
Error := TGVErrors.Create; // traitement des erreurs
fKernel:= TGVLogoKernel.Create; // noyau
fKernel.Error.OnError := @Error.GetError; // gestionnaire centralisé d'erreurs
fLocVars := TGVLocVars.Create; // variables locales
fLocVars.Error.OnError := @Error.GetError; // gestionnaire centralisé d'erreurs
fEval := TGVEval.Create; // évaluateur
fEval.Kernel := fKernel; // noyau et évaluateur liés
fEval.LocVars := fLocVars; // idem pour les variables locales
fEval.Error.OnError := @Error.GetError; // gestionnaire centralisé d'erreurs
fMessage := TGVAutomatMessage.Create; // messages
end
;
Une fois assuré le traitement local des erreurs par la création de l'objet Error (instance de la classe TGVErrors), les objets fKernel, fLocVars et fEval voient leurs gestionnaires d'erreurs reliés à celui de leur hôte. À présent, toute erreur déclenchée dans leur sein sera répercutée dans Error de l'interpréteur.
II-B-2. L'utilisation de l'unité▲
Le processus de déclaration est toujours le même. Voici, par exemple, la classe TGVNumber qui traite les nombres :
// *** classe des nombres ***
TGVNumber = class
(TObject)
strict
private
fError: TGVErrors; // traitement des erreurs
fNum: Double
; // nombre de travail
fSt: string
; // chaîne brute d'entrée
fValid: Boolean
; // drapeau de validité
function
GetInt: Integer
; // renvoie un entier
function
GetDouble: Double
; // renvoie un réel
function
GetStr: string
; // acquiert une chaîne à convertir en nombre
procedure
SetStr(const
St: string
); // forme une chaîne à partir d'un nombre
public
constructor
Create; // constructeur
destructor
Destroy; override
; // destructeur
procedure
Clear; // remise à zéro
function
IsValid: Boolean
; // est-ce un nombre ?
function
IsInt: Boolean
; // est-ce un entier ?
function
IsZero: Boolean
; // nombre 0 ?
function
IsNegate: Boolean
; // nombre négatif ?
property
Text: string
read
GetStr write
SetStr; // une chaîne de nombre
property
AsDouble: Double
read
GetDouble; // un réel
property
AsInt: Integer
read
GetInt; // un entier
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
end
;
On crée une propriété qui renvoie à une variable privée. Dans le constructeur Create, on crée l'instance de classe tandis qu'on la libère dans le destructeur Destroy. Il suffit à présent de générer quand nécessaire une erreur pour qu'elle puisse être exploitée : on utilisera la méthode SetError qui requiert un numéro d'erreur, le nom de l'élément ayant déclenché l'erreur et facultativement un emplacement de l'erreur dans l'élément.
Dans le programme appelant, on crée une méthode d'affichage des erreurs :
procedure
GetError(Sender: TObject; ErrorRec: TGVErrorRec);
Voici le listing de cette méthode qui affiche les données associées à l'erreur :
procedure
TMainForm.GetError(Sender: TObject; ErrorRec: TGVErrorRec);
// gestionnaire d'erreurs
begin
// message en toutes lettres
mmoMain.Lines.Add('// >>> '
+ (Sender as
TGVErrors).ErrorMessage);
with
mmoMain.Lines, ErrorRec do
begin
Add('// Code: '
+ IntToStr(Ord(Code))); // code de l'erreur
Add('// Elément : '
+ ErrItem); // élément fautif dans la ligne de travail
if
ErrPos <> CE_NoErr then
// position pertinente ?
Add('// Position : '
+ IntToStr(ErrPos)); // position de l'erreur
Add(''
);
end
;
end
;
Lorsqu'on crée la fiche de travail, on relie cette méthode à l'unité de gestion des erreurs :
procedure
TMainForm.FormCreate(Sender: TObject);
// création de la fiche
begin
Automat := TGVAutomat.Create; // automate créé
Automat.OnStateChange := @GetStateChange; // état changé
Automat.Error.OnError := @GetError; // gestionnaire d'erreurs
Automat.Message
.OnMessageChange := @GetMessage; // gestionnaire de messages
Automat.Follow := False
; // pas de trace par défaut
fDeepTrace := False
;
// on crée la tortue
GVTurtle := TGVTurtle.Create(imgTurtle.Width, imgTurtle.Height);
Automat.Turtle := GVTurtle; // tortue liée à l'automate
GVTurtle.OnChange := @TurtleState; // gestionnaire de changement
GVTurtle.OnBeforeChange := @TurtleBeforePaint; // idem avant de dessiner
GVTurtle.Error.OnError := @GetError; // gestionnaire d'erreurs
GVTurtle.ReInit; // initialisation
GVTurtle.Kind := tkPng; // tortue image
end
;
On remarquera que le même gestionnaire est ici partagé par deux unités : c'est tout l'intérêt de notre centralisation des erreurs que de n'avoir qu'une seule méthode de traitement à coder.
À présent, tout ce qu'il reste à faire, c'est de vérifier l'état d'erreur de l'unité. Si une erreur a été levée, elle a été traitée.
Il n'est par conséquent pas besoin de tester quoi que ce soit d'autre :
procedure
TGVMainForm.btnFirstClick(Sender: TObject);
// test de FIRST
var
LS: string
;
begin
fWord.Text := LabEdtFirst.Text; // on affecte
LS := fWord.First; // on effectue l'opération
if
fWord.Error.OK then
// pas d'erreur ?
lblResult.Caption := LS; // on affiche
end
;
Dans la méthode donnée en exemple, on récupère le contenu d'un composant TLabelEdit qu'on transforme en mot formaté. Le second mot issu lui aussi d'un TLabelEdit est comparé au premier. S'il n'y a pas eu d'erreur, on affiche le résultat, à savoir le mot qui précède l'autre par ordre alphabétique. En cas d'erreur, un message aura automatiquement été affiché.
II-B-3. Le programme d'exemple▲
Les versions proposées pour le programme d'exemple sont au nombre de deux :
- une version Lazarus Win32Â ;
- une version Lazarus Linux.
On trouvera les fichiers sources et les exécutables dans le sous-dossier « 00 - testgverrors » du dossier « tests ».
Toutes les unités produites seront ainsi testées par un programme autonome. Cette démarche permet d'isoler les éventuels problèmes : il sera bien plus facile de repérer une erreur dans un module que dans le programme final qui comportera des milliers de lignes !
En dehors de la gestion des erreurs déjà étudiée, le programme est tout à fait trivial. Un composant TMemo affiche les messages d'erreurs. Chacune d'entre elles est générée de manière aléatoire.
II-C. Les mots▲
II-C-1. Définitions▲
GVLOGO travaille essentiellement à partir de mots et de listes. Ce chapitre se propose d'étudier les mots.
Un mot est une suite quelconque de caractères.
Un mot est délimité par un espace, les signes [, ], (, ). Ces cinq caractères particuliers sont appelés des délimiteurs : ils indiquent à GVLOGO comment séparer les éléments du langage.
Afin d'inclure un délimiteur dans un mot, on le fait précéder d'un autre caractère appelé caractère d'échappement : c'est le $. Si ce caractère d'échappement doit apparaître en tant que tel dans un mot, il faut le faire précéder de lui-même !
Il existe un mot particulier qui ne comprend aucun caractère : on l'appelle le mot vide. On le représente par un guillemet anglais suivi d'un espace.
Les mots "VRAI et "FAUX, qui peuvent s'écrire simplement VRAI et FAUX, sont réservés au renvoi de valeurs booléennes. Pour GVLOGO, la valeur « vrai » est représentée par le nombre -1 alors que la valeur « faux » l'est par le nombre 0.
II-C-2. Exemples de mots▲
II-C-2-a. Mots simples▲
Voici des mots simples qui ne poseront pas de problèmes particuliers :
- libellule
- porte-avions
- entendre
- EnTenDRe
Les nombres ne sont que des mots particuliers qui seront traités comme des mots ordinaires ou comme des nombres, suivant l'opération en cours :
- 1245
- 1E2(5)
- 13456,58
Les mots accentués et les caractères exotiques sont utilisables eux aussi, à condition de ne pas être choisis en tant qu'identificateur :
- éléphant
- %&/ç\
Les caractères particuliers comme ceux qui sont accentués ne sont pas toujours bien gérés par les langages de programmation et les systèmes d'exploitation. Les normes se sont multipliées et des hiéroglyphes étranges peuvent parfois apparaître. Le travail de développement de Lazarus est largement déterminé par ce problème. Nous y reviendrons lorsque nous aborderons l'implémentation des mots. Disons que, pour le moins, il s'agit d'un point délicat susceptible de troubler le programmeur.
II-C-2-b. Mots avec caractère d'échappement▲
Un caractère pourtant interdit dans un mot GVLOGO peut toujours être introduit s'il est précédé du caractère d'échappement :
- Victor$ Hugo
- GVLOGO$ est$ un$ langage$ facile$ Ã $ apprendre
- $[encore$ un$ mot$]
- $(un$ autre$)
- 15$ $$
Lors de l'écriture des mots ainsi construits, le caractère d'échappement, sauf contre-ordre, n'apparaîtra pas à l'écran :
- ECRIS $[encore$ un$ mot$] donnera [encore un mot]
- ECRIS 15$ $$ donnera 15 $
Dès qu'une suite de mots est en jeu, l'emploi des listes est préconisé. Le caractère d'échappement est une commodité, sans plus. Il est plus facile d'écrire et de comprendre ECRIS [15 $] que ECRIS 15$ $$.
II-C-2-c. Autres caractères particuliers▲
En plus des délimiteurs, certains caractères sont utilisés par le langage lui-même et déterminent son comportement selon le contexte :
- le " (guillemet anglais) indique que le mot qui suit doit être pris tel quel, sans chercher à l'interpréter (par exemple, ECRIS "Bonjour.) ;
- les : (deux-points) indiquent que le mot qui suit est une variable dont la valeur est à rechercher (par exemple, ECRIS :salutation affichera, si elle existe, la valeur qui correspond à la variable « salutation ») ;
- le . (point) en début de mot indique une primitive dont l'emploi est risqué, car elle touche au cœur de GVLOGO (par exemple .EFFACE.TOUT détruit tous les objets en réinitialisant le noyau) ;
- le ? (point d'interrogation) est utilisé à la fin des primitives qui renvoient une valeur booléenne (VRAI/FAUX), mais, comme le point, son utilisation est une question d'habitude et n'a pas de caractère obligatoire.
II-C-3. Opérations sur les mots▲
Ci-après, le lecteur trouvera les primitives connues de GVLOGO qui concernent les mots. Pour chaque primitive, avant sa définition, il est indiqué le nombre de paramètres attendus, leurs types et le type de valeur qu'elle renvoie. Au moins un exemple illustre l'emploi de la primitive décrite.
Les primitives énumérées peuvent évidemment se combiner pour obtenir le résultat escompté : c'est même ainsi qu'elles prennent tout leur intérêt ! On y reviendra dans la partie qui traitera de la réalisation de programmes en GVLOGO.
II-C-3-a. Fabriquer des mots▲
METS.PREMIER (raccourci : MP) : attend deux mots en entrée - renvoie un mot - le mot rendu est composé du premier paramètre placé avant le second.
Exemple :
- ECRIS METS.PREMIER "tourne "dos → tournedos
METS.DERNIER (raccourci : MD) : attend deux mots en entrée - renvoie un mot - le mot rendu est composé du premier paramètre placé après le second.
Exemple :
- ECRIS METS.DERNIER "tourne "dé → détourne
MOT : attend deux mots en entrée - renvoie un mot - le mot rendu est composé du second paramètre placé après le premier.
Exemple :
- ECRIS MOT "dé "tourne → détourne
Il s'agit donc d'un synonyme de METS.DERNIER. Cependant, cette dernière primitive fonctionne aussi avec les listes alors que MOT déclenchera une erreur si une liste est fournie en entrée.
II-C-3-b. Modifier des mots▲
INSERE : attend un entier suivi deux mots en entrée - renvoie un mot - Le mot rendu est composé du deuxième paramètre inséré dans le dernier paramètre à la position précisée par l'entier.
Exemple :
- ECRIS INSERE 4 "en "atttion → attention
INVERSE : attend un mot en entrée - renvoie un mot - le mot rendu est celui d'entrée dont les lettres ont été inversées.
Exemple :
- ECRIS INVERSE "billet → tellib
MAJUSCULES : attend un mot en entrée - renvoie un mot - le mot rendu est en majuscules.
Exemple :
- ECRIS MAJUCULES "éléphant → ĒLĒPHANT
MELANGE : attend un mot en entrée - renvoie un mot - le mot rendu a les mêmes lettres que celui d'origine, mais dans un ordre aléatoire.
Exemple :
- ECRIS MELANGE "alouette → eoetault
MINUSCULES : attend un mot en entrée - renvoie un mot - le mot rendu est en minuscules.
Exemple :
- ECRIS MINUSCULES "ESSAI → essai
REMPLACE : attend un entier suivi de deux mots en entrée - renvoie un mot - le mot rendu est composé de toutes les lettres du second mot en entrée, sauf celle visée par l'entier qui a été remplacée par le premier mot.
Exemple :
- ECRIS REMPLACE 3 "é "éliment → élément
TRIE : attend un mot en entrée - renvoie un mot - les lettres du mot sont triées par ordre alphabétique(6).
Exemple :
- ECRIS TRIE "important → aimnoprtt
ROTATION : attend un mot en entrée - renvoie un mot - la primitive renvoie le mot dont le premier caractère est placé à la fin.
Exemples :
- ECRIS ROTATION "essai → ssaie
II-C-3-c. Extraire depuis des mots▲
ELEMENT : attend un entier suivi d'un mot en entrée - renvoie un mot - le mot rendu est composé de la lettre du mot en entrée indiquée par l'entier fourni.
Exemple :
- ECRIS ELEMENT 4 "Zorro → r
DERNIER (raccourci : DER) : attend un mot en entrée - renvoie un mot - le mot renvoyé est composé de la dernière lettre du mot en entrée.
Exemple :
- ECRIS DERNIER "important → t
HASARD : attend un mot en entrée - renvoie un mot - le mot renvoyé est composé d'une lettre du mot en entrée, tirée au hasard. Si le mot en entrée est un nombre, le mot renvoyé est un nombre compris entre 1 et ce nombre.
Exemples :
- ECRIS HASARD "destin → i
- ECRIS HASARD "destin → d
- ECRIS HASARD "10 → 8
PREMIER (raccourci : PREM) : attend un mot en entrée - renvoie un mot - le mot renvoyé est composé de la première lettre du mot en entrée.
Exemple :
- ECRIS PREMIER "important → i
SAUF.DERNIER (raccourci : SD) : attend un mot en entrée - renvoie un mot - le mot renvoyé est le mot en entrée amputé de sa dernière lettre.
Exemple :
- ECRIS SAUF.DERNIER "important → importan
SAUF.PREMIER (raccourci : SP) : attend un mot en entrée - renvoie un mot - le mot renvoyé est le mot en entrée amputé de sa première lettre.
Exemple :
- ECRIS SAUF.PREMIER "important → mportant
II-C-3-d. Tester des mots▲
PLG? : attend deux mots en entrée - renvoie un booléen - la primitive renvoie "VRAI si le premier mot vient strictement après le second selon l'ordre alphabétique, "FAUX sinon.
Exemples :
- ECRIS PLG? "petit "grand → -1
- ECRIS PLG? 1245 138 → -1
Comme EGAL? et PLP?, PLG? se comporte différemment si les deux mots sont des nombres : ce sont leurs valeurs qui sont alors comparées.
PLP? : attend deux mots en entrée - renvoie un booléen - la primitive renvoie "VRAI si le premier mot vient strictement avant le second selon l'ordre alphabétique, "FAUX sinon.
Exemple :
- ECRIS PLP? "petit "grand → 0
COMPTE : attend un mot en entrée - renvoie un entier - la primitive renvoie le nombre de caractères du mot en entrée.
Exemple :
- ECRIS COMPTE "urticaire → 9
EGAL? : attend deux mots en entrée - renvoie un booléen - la primitive renvoie "VRAI si le premier mot est égal au second selon l'ordre alphabétique, "FAUX sinon.
Exemple :
- ECRIS EGAL? "100 "1E2 → -1
IDENTIFICATEUR? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot en entrée est un identificateur correct, "FAUX sinon.
Exemples :
- ECRIS IDENTIFICATEUR? "MaFonction12 → -1
- ECRIS IDENTIFICATEUR? "12MaFonction → 0
Un identificateur est correct quand il ne comprend que des lettres non accentuées, des chiffres non placés en première position, le signe _ (souligné) et le signe . (point) en début de mot, ou le ? (point d'interrogation) à la fin.
MEMBRE? : attend deux mots en entrée - renvoie un booléen - la primitive renvoie "VRAI si le premier mot est compris dans le second, "FAUX sinon.
Exemple :
- ECRIS MEMBRE? "an "étonnant → -1
MOT? : attend un objet en entrée - renvoie un booléen - la primitive renvoie "VRAI si l'objet est un mot, "FAUX sinon.
Exemple :
- ECRIS MOT? [coucou] → 0
NOMBRE? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot est un nombre, "FAUX sinon.
Exemple :
- ECRIS NOMBRE? "12458 → -1
Le nombre est ici précédé d'un guillemet anglais, comme le serait un mot ordinaire. Pour les nombres, cette écriture est facultative.
PRECEDENT : attend deux mots en entrée - renvoie un mot - la primitive renvoie le mot qui vient le premier selon l'ordre alphabétique.
Exemple :
- ECRIS PRECEDENT "un "deux → deux
SUIVANT : attend deux mots en entrée - renvoie un mot - la primitive renvoie le mot qui vient le dernier selon l'ordre alphabétique.
Exemple :
- ECRIS SUIVANT "un "deux → un
VIDE? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot en entrée est le mot vide, "FAUX sinon.
Exemple :
- ECRIS VIDE? " → -1
II-C-4. Implémentation des mots▲
L'implémentation des mots passe par l'unité GVConsts qui contient les constantes centralisées du projet et une unité baptisée GVWords qui abrite les classes nécessaires à la gestion proprement dite des mots.
II-C-4-a. L'objet TGVString▲
La difficulté essentielle dans le traitement des mots en GVLOGO réside dans la présence d'un caractère d'échappement dont il faut tenir compte pour toutes les opérations portant sur eux. L'objet TGVString accomplit le travail de normaliser tout mot qui lui est proposé. Il est capable de restituer le mot d'origine ainsi que celui transformé.
Voici sa partie interface :
type
// *** objet chaîne ***
TGVString = object
strict
private
fRawStr: string
; // chaîne brute entrée
fIsValid: Boolean
; // validité de la chaîne d'entrée
fStr: string
; // chaîne brute interne
fFmtStr: string
; // chaîne formatée interne
function
GetFmtStr: string
; // renvoie la chaîne formatée
function
GetStr: string
; // renvoie la chaîne brute
procedure
SetStr(const
St: string
); // établit une nouvelle chaîne brute
function
WithEsc(const
St: string
): string
; // avec échappement
function
WithoutEsc(const
St: string
): string
; // sans échappement
public
procedure
Clear; // remise à zéro
property
IsValid: Boolean
read
fIsValid; // validité de la chaîne d'entrée
property
Str: string
read
GetStr write
SetStr; // chaîne brute d'entrée
property
FmtStr: string
read
GetFmtStr; // chaîne formatée
property
RawStr: string
read
fRawStr; // chaîne brute
end
;
L'interface est bâtie de manière « classique » en proposant des propriétés dont l'implémentation est protégée par des méthodes privées. La propriété Str lit une chaîne afin de la transformer en chaîne normalisée et de la restituer ainsi. La propriété en lecture seule RawStr permet de retrouver la chaîne brute, telle qu'elle a été introduite.
La méthode SetStr est chargée de transformer la chaîne fournie en entrée. Elle invoque successivement WithoutEsc qui retire un éventuel formatage et WithEsc qui normalise la chaîne. Cette façon de procéder évite de formater une chaîne qui le serait déjà  : en revanche, elle exige deux analyses de la chaîne et ne repère pas les mots normalisés par hasard !
Un objet a été préféré à une classe dans le cas bien précis où aucun appel à un constructeur et/ou à un destructeur n'était nécessaire. En effet, l'objet n'utilise pas de méthodes virtuelles et n'utilise aucune classe particulière.
Encore un avantage à déléguer un travail particulier à un objet ou à une classe : un meilleur algorithme pourra être implémenté dans cette classe sans avoir à modifier la moindre ligne des autres fichiers. Autrement dit, une fois l'interface bien définie, il n'importe en rien aux classes qui s'en servent de savoir comment le problème est traité : seul le résultat compte. Il est même très judicieux d'imposer qu'un objet ou une classe n'ait aucun présupposé à faire avant d'utiliser une autre classe.
L'algorithme utilisé par WithEsc est trivial. On remarquera cependant le couple for…in qui balaie la chaîne en lieu et place d'une boucle traditionnelle for…next moins lisible :
function
TGVString.WithEsc(const
St: string
): string
;
// *** normalise un mot en tenant compte du caractère d'échappement ***
var
Lch: Char
;
begin
Result := EmptyStr; // chaîne vide par défaut
for
Lch in
St do
// balaie la chaîne de départ
begin
if
Lch in
CSeparators + [CLink] then
// si séparateur ou échappement suit, on insère un échappement
Result := Result + CLink;
Result := Result + Lch; // caractère en cours ajouté
end
;
end
;
Les utilisateurs de Delphi devront remplacer in CSeparators par la fonction CharInSet s'ils ne veulent pas obtenir un message d'avertissement.
La méthode WithoutEsc n'intéressera que pour l'utilisation d'un drapeau (variable LFlag) qui mémorise le fait d'avoir rencontré précédemment un caractère d'échappement. Ce drapeau est remis à zéro lorsqu'on a traité son repérage :
function
TGVString.WithoutEsc(const
St: string
): string
;
// *** sans caractère d'échappement ***
var
LCh: Char
;
LFlag: Boolean
;
begin
Result := EmptyStr;
// chaîne vide par défaut
LFlag := False
; // on n'a pas eu affaire à un caractère d'échappement
for
LCh in
St do
// on balaie la chaîne
if
(LCh <> CLink) or
LFlag then
// caractère d'échappement initial ?
begin
Result := Result + LCh; // non : on ajoute simplement
LFlag := False
; // on indique ce cas
end
else
LFlag := True
; // échappement initial qu'on ignore
end
;
Les utilisateurs de Delphi pourront envisager de transformer cet objet en enregistrement. En effet, cet EDI accepte des enregistrements comprenant des méthodes.
II-C-4-b. La classe TGVNumber▲
La classe TGVNumber traite les chaînes susceptibles de représenter des nombres.
Voici sa partie interface :
TGVNumber = class
(TObject)
strict
private
fError: TGVErrors; // traitement des erreurs
fNum: Double
; // nombre de travail
fSt: string
; // chaîne brute d'entrée
fValid: Boolean
; // drapeau de validité
function
GetInt: Integer
; // renvoie un entier
function
GetDouble: Double
; // renvoie un réel
function
GetStr: string
; // acquiert une chaîne à convertir en nombre
procedure
SetStr(const
St: string
); // forme une chaîne à partir d'un nombre
public
constructor
Create; // constructeur
destructor
Destroy; override
; // destructeur
procedure
Clear; // remise à zéro
function
IsValid: Boolean
; // est-ce un nombre ?
function
IsInt: Boolean
; // est-ce un entier ?
function
IsZero: Boolean
; // nombre 0 ?
function
IsNegate: Boolean
; // nombre négatif ?
property
Text: string
read
GetStr write
SetStr; // une chaîne de nombre
property
AsDouble: Double
read
GetDouble; // un réel
property
AsInt: Integer
read
GetInt; // un entier
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
end
;
La partie publique comprend, outre le constructeur et le destructeur, une procédure Clear qui remet à zéro le nombre interne, quatre fonctions utiles et quatre propriétés.
L'entrée dans la classe se fait par la propriété AsString. Si possible, la chaîne est de manière interne transformée en nombre. En cas d'échec, la fonction IsValid retournera False comme résultat. L'accès en lecture d'un nombre erroné déclenchera une erreur.
La lecture peut être effectuée de trois manières : sous forme de chaîne (AsString), d'entier (AsInt) ou de nombre réel (AsDouble). Il est par ailleurs possible de tester le nombre interne pour savoir s'il s'agit d'un entier grâce à la fonction IsInt. La fonction IsNegate permet de savoir si le nombre est négatif. Enfin, la fonction IsZero compare de manière correcte des nombres réels qui seraient jugés inégaux pour des raisons de représentation interne.
L'implémentation ne pose pas de problèmes particuliers. Voici celle de SetStr qui gère l'entrée d'une nouvelle chaîne à traduire :
procedure
TGVNumber.SetStr(const
St: string
);
// *** forme un nombre si possible ***
begin
fSt := St; // chaîne brute affectée
fValid := TryStrToFloat(St, fNum); // transformation possible ?
end
;
On conserve le paramètre d'entrée et on le teste grâce à la fonction TryStrToFloat. Le drapeau privé fValid sera vérifié avant de renvoyer une valeur : l'utilisateur est ainsi assuré de toujours disposer d'une valeur correcte ou d'obtenir le déclenchement d'une erreur.
En règle générale, il faut éviter de court-circuiter les erreurs et toujours avertir l'utilisateur que la valeur dont il dispose est erronée. En croyant bien faire pour l'utilisateur final, on le prive dans le cas contraire de tout repère sur la stabilité de son système et sur la confiance qu'il peut accorder aux résultats qu'il obtient.
II-C-4-c. La classe TGVWord▲
La classe TGVWord est de loin la plus fournie de cette unité. Elle gère les mots, qu'ils soient des chaînes de caractères ou des nombres, en reprenant toutes les fonctionnalités définies dans la description du langage GVLOGO qui concernent les mots.
La classe utilise deux champs privés de la classe TGVString et autant de la classe TGVNumber : ils servent d'outils de travail et de comparaisons. L'emploi de ces classes évite d'avoir constamment à vérifier la présence des caractères spéciaux.
En fait, la difficulté essentielle tient aux lacunes de Lazarus dans la gestion des caractères accentués. Les fonctions préfixées par UTF8 présentes dans l'unité lazutf8 constituent une solution possible qui règle la plupart des problèmes, excepté ceux relatifs à l'ordre alphabétique : ainsi, la fonction Sort ne triera pas correctement les lettres d'un mot, rejetant les caractères accentués après les caractères normaux. Delphi ne présente pas ce problème. On notera par ailleurs que la fonction Sort utilise un tri à bulles bien suffisant pour des quantités très limitées d'objets à trier.
En voici l'interface :
TGVWord = class
(TObject)
strict
private
fError: TGVErrors; // traitement des erreurs
fText: TGVString; // mot à traiter
fNum, fNum2: TGVNumber; // nombres de travail
function
GetFmtText: string
; // texte formaté
function
GetText: string
; // texte brut
procedure
SetText(const
St: string
); // établit le texte
protected
function
Compare(const
St: string
): Integer
; // comparaison de deux mots
public
constructor
Create; // constructeur
destructor
Destroy; override
; // destructeur
procedure
Clear; // nettoyage
function
First: string
; // premier caractère d'un mot
function
Last: string
; // dernier caractère d'un mot
function
ButFirst: string
; // sauf le premier caractère d'un mot
function
ButLast: string
; // sauf le dernier caractère d'un mot
// concatène les deux mots, le second en premier
function
PutFirst(const
St: string
): string
;
// concatène les deux mots, le premier d'abord
function
PutLast(const
St: string
): string
;
// supprime si nécessaire le " initial d'un mot
function
WithoutQuote: string
;
// supprime si nécessaire le : initial d'un mot
function
WithoutColon: string
;
function
IsValidIdent: Boolean
; // est-ce un identificateur valide ?
// les deux mots sont-ils égaux ?
function
IsEqual(const
St: string
): Boolean
;
// le premier mot est-il à placer avant le second par ordre alphabétique ?
function
IsLower(const
St: string
): Boolean
;
// renvoie le mot qui vient avant par ordre alphabétique
function
Lowest(const
St: string
): string
;
// le premier mot est-il à placer après le second par ordre alphabétique ?
function
IsGreater(const
St: string
): Boolean
;
// renvoie le mot qui vient après par ordre alphabétique
function
Greatest(const
St: string
): string
;
function
IsEmptyWord: Boolean
; // le mot est-il vide ?
// le mot est-il compris dans un autre ?
function
IsMember(const
St: string
): Boolean
;
function
Count: Integer
; // longueur du mot
function
StrCount: string
; // longueur de mot en chaîne
function
GetItem(const
N: Integer
): string
; // élément N d'un mot
// remplacement de l'élément N d'un mot
function
Replace(const
N: Integer
; const
St: string
): string
;
// suppression de l'élément N d'un mot
function
DelItem(const
N: Integer
): string
;
function
Reverse: string
; // mot inversé
function
Shuffle: string
; // mot mélangé
function
AtRandom: string
; // lettre au hasard
function
Uppercase: string
; // mot en majuscules
function
Lowercase: string
; // mot en minuscules
// insertion en position N
function
Insert(const
N: Integer
; const
St: string
): string
;
function
Sort: string
; // tri des lettres du mot
function
IsNumber: Boolean
; // est-ce un nombre ?
function
IsInt: Boolean
; // est-ce un entier ?
function
IsBoolean: Boolean
; // est-ce un booléen ?
function
AsNumber: Double
; // renvoie un nombre
function
AsInt: Integer
; // renvoie un entier
function
AsBoolean: Boolean
; // renvoie un booléen
function
IsNegate: Boolean
; // nombre négatif ?
function
WithEsc: string
; // chaîne formatée
function
WithoutEsc: string
; // chaîne brute
function
IsValid: Boolean
; // le mot est-il valide sans traitement ?
function
Rotate: string
; // rotation des caractères d'un mot
property
Item[N: Integer
]: string
read
GetItem; default
; // élément N
property
Text: string
read
GetText write
SetText; // mot à traiter
property
FmtText: string
read
GetFmtText; // mot à traiter formaté
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
end
;
La méthode protégée Compare s'occupe des comparaisons : il faut tenir compte de la comparaison de chaînes ordinaires et de nombres. En effet, 99 vient après 121 par ordre alphabétique, mais avant par ordre de grandeur mathématique !
function
TGVWord.Compare(const
St: string
): Integer
;
// *** comparaison de deux mots ***
begin
// essai de conversion en nombre des deux mots
fNum.Text := Text;
fNum2.Text := St;
// ce sont des nombres ?
if
fNum.IsValid and
fNum2.IsValid then
// si oui, on les compare
Result := CompareValue(fNum2.AsDouble, fNum.AsDouble)
else
// on compare les mots ordinaires
Result := UTF8CompareText(St, Text); // comparaison UTF8
end
;
II-C-4-d. Test de l'unité GVWords▲
Le meilleur moyen de s'approprier les méthodes de la classe TGVWord est de les voir fonctionner dans un programme.
Les versions proposées sont au nombre de deux :
- une version Lazarus Win32Â ;
- une version Lazarus Linux.
On trouvera les fichiers sources et les exécutables dans le sous-dossier « 01 - testgvwords » du dossier « tests ».
Si Delphi offre de meilleures capacités à traiter les caractères Unicode et s'il facilite une présentation plus moderne grâce aux styles, Lazarus se montre à la hauteur dans sa portabilité sur différents OS : il n'a pas été nécessaire d'apporter la moindre modification aux fichiers pour faire fonctionner le programme aussi bien sur Windows que sur Linux.
Le programme lui-même est trivial puisqu'il se contente d'affecter le résultat d'une méthode de TGVWord à un TLabel si aucune erreur n'a été déclenchée. C'est exactement ce qui était recherché : l'unité remplit parfaitement son rôle en limitant au possible les complications pour l'utilisateur final. On notera en particulier que les erreurs sont affichées correctement sans que les méthodes d'affichage aient à s'occuper de leur traitement.
Il est intéressant de modifier les paramètres des éditeurs pour voir le comportement de l'unité. Par exemple, on pourra entrer des mots tels que "Essai, cou[cou… On peut aussi entrer une phrase complète et constater qu'elle sera normalisée pour être comprise comme un mot ! Dans tout test, il est conseillé de provoquer des erreurs afin de voir comment se comporte l'unité.
Dans ce petit programme d'exemple, une erreur inhibe toutes les fonctions. Il faut cliquer sur le bouton « Effacer » pour réinitialiser l'ensemble.
Une fonction souvent méconnue est utilisée largement dans ce programme : il s'agit de IfThen. La version traitant les chaînes de caractères est définie dans l'unité strutils. Elle prend deux ou trois paramètres en entrée : le premier est une valeur booléenne, les deux suivants des chaînes de caractères. Si le troisième est omis, il est par défaut défini à la chaîne vide. Le principe de fonctionnement est semblable à celui d'un test avec if…then…else : si la valeur booléenne est vraie, le second paramètre est renvoyé, sinon c'est le troisième.
Par exemple :
procedure
TGVMainForm.btnIsValidIdentClick(Sender: TObject);
// test de ISVALIDIDENT
var
LS: string
;
begin
fWord.Text := LabEdtFirst.Text;
LS := IfThen(fWord.IsValidIdent, P_True, P_False);
if
fWord.Error.OK then
lblResult.Caption := LS;
end
;
Le test de la fonction IsValidIdent est effectué sur le composant TLabelEdit : si le résultat est correct, c'est la constante P_True qui est affectée au Caption du composant TLabel, sinon c'est P_False.
Un conseil ("hint") indique que l'unité strutils n'est pas utilisée alors que la compilation échouera si elle est omise ! En effet, elle est indispensable pour le traitement de IfTthen avec des chaînes. Il semblerait que Lazarus soit trompé par l'autre fonction IfThen présente dans l'unité math utilisée par ailleurs.
II-D. Les listes▲
II-D-1. Définitions▲
Une liste simple est composée de mots séparés par des espaces(7). Elle est délimitée par des crochets : [ puis ].
Une liste complexe comprend des mots et des listes. Les imbrications ne sont pas limitées, pourvu que l'ordre d'ouverture et de fermeture des listes imbriquées soit correct.
Il existe une liste particulière qui ne comprend ni mot ni liste. On la nomme la liste vide et elle est représentée par des crochets [] accolés.
II-D-2. Exemples de listes▲
II-D-2-a. Listes simples▲
Voici quelques listes simples :
- [un deux trois quatre cinq]
- [1 2 3 4 5]
- [GVLOGO est un langage facile à apprendre.]
II-D-2-b. Listes imbriquées▲
Voici des listes complexes :
- [élément1 [liste imbriquée 1] élément3]
- [un [deux imbriquée [encore deux mais imbriquée]] trois]
- [[]]
- [un [deux [deux bis [deux ter]]] trois]
- [un (12*45+789) trois]
La dernière liste contient une expression. Une expression est une suite de caractères à interpréter comme un calcul à effectuer(8).
II-D-2-c. Listes fautives▲
Une liste est fautive si elle ne contient pas des crochets correctement agencés et en bon nombre.
Voici des listes incorrectes :
- [oups ! [où est la fermeture ?]
- [fermée trop tôt][ceci est une autre liste]
- [le caractère d'échappement masque la fin de liste !$]
- [Une parenthèse se promène seule (]
Pour contourner le problème des crochets ou des parenthèses mal appariés, il suffit d'utiliser le caractère d'échappement, comme avec les mots :
- [le caractère d'échappement ne masque plus la fin de liste !$]]
- [Une parenthèse se promène seule sans problème $(]
Le caractère d'échappement disparaîtra à l'écriture grâce aux primitives ECRIS et TAPE. ECRIST le fera apparaître si nécessaire.
II-D-3. Opérations sur les listes▲
Les opérations possibles sur les listes sont en majorité celles déjà vues à propos des mots. Elles sont mentionnées ici avec des exemples propres aux listes.
La primitive ECRIS retire les crochets lors de l'envoi d'une liste à l'écran, tout comme elle fait disparaître le caractère d'échappement. Ce comportement explique pourquoi les exemples qui utilisent cette primitive donnent des résultats sans crochets. Si l'on veut conserver les crochets, il faut utiliser la primitive ECRIST.
II-D-3-a. Fabriquer des listes▲
METS.PREMIER (raccourci : MP) : attend deux listes en entrée ou un mot et une liste - renvoie une liste - la liste rendue est composée du premier paramètre placé avant le second.
Exemples :
- ECRIS METS.PREMIER [Coucou] [Tortue !] → [Coucou] Tortue !
- ECRIS METS.PREMIER "Coucou [Tortue !] → Coucou Tortue !
METS.DERNIER (raccourci : MD) : attend deux listes en entrée ou un mot suivi d'une liste - renvoie une liste - la liste rendue est composée du premier paramètre placé après le second.
Exemples :
- ECRIS METS.DERNIER [Coucou] [Tortue !] → Tortue ! [Coucou]
- ECRIS METS.DERNIER "Coucou [Tortue !] → Tortue ! Coucou
PHRASE (raccourci : PH) : attend deux listes ou deux mots ou une combinaison des deux en entrée - renvoie une liste - la liste rendue est composée des deux éléments séparés par un espace sans les crochets et/ou sans les guillemets anglais.
Exemples :
- ECRIS PHRASE "je "viens → je viens
- ECRIS PHRASE [je] [pars] → je pars
- ECRIS PHRASE "je [reviens] → je reviens
- ECRIS PHRASE [je] "repars → je repars
II-D-3-b. Modifier des listes▲
INSERE : attend un entier, un mot ou une liste, puis une liste en entrée - renvoie une liste - la liste rendue est composée du deuxième paramètre inséré à la position précisée par l'entier dans la liste finale.
Exemples :
- ECRIS INSERE 2 "très [c'est bien] → c'est très bien
- ECRIS INSERE 1 [zéro] [[un][deux][trois]] → [zéro][un][deux][trois]
INVERSE : attend une liste en entrée - renvoie une liste - la liste rendue est celle d'entrée dont les éléments ont été inversés.
Exemple :
- ECRIS INVERSE [un deux trois] → trois deux un
MAJUSCULES : attend une liste en entrée - renvoie une liste - la liste rendue est en majuscules.
Exemple :
- ECRIS MAJUCULES [le soleil se lève] → LE SOLEIL SE LÈVE
MELANGE : attend une liste en entrée - renvoie une liste - la liste rendue a les mêmes éléments que celle d'origine, mais dans un ordre aléatoire.
Exemple :
- ECRIS MELANGE [un deux [troisa troisb]] → deux [troisa troisb] un
MINUSCULES : attend une liste en entrée - renvoie une liste - la liste rendue est en minuscules.
Exemple :
- ECRIS MINUSCULES [LES ENFANTS SE LÈVENT] → Les enfants se lèvent
REMPLACE : attend un entier, une liste ou un mot, puis une liste en entrée - renvoie une liste - la liste rendue est composée de tous les éléments de la liste finale, sauf celui visé par l'entier qui a été remplacé par le deuxième paramètre.
Exemples :
- ECRIS REMPLACE 4 "couchent [Les enfants se lèvent] → Les enfants se couchent
- ECRIS REMPLACE 2 [deuxième] [[premier][seconde][troisième]] → [premier][deuxième][troisième]
TRIE : attend une liste en entrée - renvoie une liste - les éléments de la liste sont triés par ordre alphabétique(9).
Exemple :
- ECRIS TRIE [zèbre tartine accent dommage] → accent dommage tartine zèbre
ROTATION : attend une liste en entrée - renvoie une liste - la primitive renvoie la liste dont le premier élément est placé à la fin.
Exemple :
- ECRIS ROTATION [un deux trois quatre] → deux trois quatre un
II-D-3-c. Extraire de listes▲
ELEMENT : attend un entier suivi d'une liste en entrée - renvoie une liste ou un mot - l'objet rendu est l'élément de la liste en entrée indiqué par l'entier.
Exemple :
- ECRIS ELEMENT 2 [un deux [trois]] → deux
DERNIER (raccourci : DER) : attend une liste en entrée - renvoie un mot ou une liste - l'objet renvoyé est composé du dernier élément de la liste en entrée.
Exemple :
- ECRIS DERNIER [un deux] → deux
HASARD : attend une liste en entrée - renvoie un mot ou une liste - l'objet renvoyé est composé d'un élément de la liste en entrée, tiré au hasard.
Exemple :
- ECRIS HASARD [un [deux] trois quatre] → trois
PREMIER (raccourci : PREM) : attend une liste en entrée - renvoie un mot ou une liste - l'objet renvoyé est composé du premier élément de la liste en entrée.
Exemple :
- ECRIS PREMIER [beau laid] → beau
PREMS : attend une liste en entrée - renvoie une liste - l'objet renvoyé est composé de chaque premier élément des éléments de la liste en entrée.
Exemples :
- ECRIS PREMS [beau [laid]] → b laid
- ECRIS PREMS [[moi beau] [toi laid]] → [moi] [toi]
SAUF.DERNIER (raccourci : SD) : attend une liste en entrée - renvoie un mot ou une liste - l'objet renvoyé est composé de la liste en entrée sans son dernier élément.
Exemple :
- ECRIS SAUF.DERNIER [pas cette liste stupide] → pas cette liste
SAUF.PREMIER (raccourci : SP) : attend une liste en entrée - renvoie un mot ou une liste - l'objet renvoyé est composé de la liste en entrée sans son premier élément.
Exemple :
- ECRIS SAUF.PREMIER [pas cette liste stupide] → cette liste stupide
SAUF.PREMS : attend une liste en entrée - renvoie une liste - l'objet renvoyé est composé de la liste en entrée sans chaque premier élément de ses éléments.
Exemples :
- ECRIS SAUF.PREMS [pas cette liste stupide] → as ette iste tupide
- ECRIS SAUF.PREMS [[un chat] [une chouette]] → [chat] [chouette]
II-D-3-d. Tester des listes▲
PLG? : attend deux listes en entrée - renvoie un booléen - la primitive renvoie "VRAI si la première liste vient strictement après la seconde selon l'ordre alphabétique, "FAUX sinon.
Exemple :
- ECRIS PLG? [un petit chat] [un chat] → -1
PLP? : attend deux listes en entrée - renvoie un booléen - la primitive renvoie "VRAI si la première liste vient strictement avant la seconde selon l'ordre alphabétique, "FAUX sinon.
Exemple :
- ECRIS PLP? [le renard] [un renard] → -1
COMPTE : attend une liste en entrée - renvoie un entier - la primitive renvoie le nombre d'éléments de la liste en entrée.
Exemple :
- ECRIS COMPTE [un [deux encore deux] trois] → 3
EGAL? : attend deux listes en entrée - renvoie un booléen - la primitive renvoie "VRAI si la première liste est identique à la seconde, "FAUX sinon.
Exemple :
- ECRIS EGAL? [c'est la même] METSPREMIER "c'est [la même] → -1
MEMBRE? : attend une liste ou un mot, puis une liste en entrée - renvoie un booléen - la primitive renvoie "VRAI si le premier objet est compris dans le second, "FAUX sinon.
Exemples :
- ECRIS MEMBRE? [oui] [un deux [oui]] → -1
- ECRIS MEMBRE? "oui [un deux [oui]] → 0
LISTE? : attend un objet en entrée - renvoie un booléen - la primitive renvoie "VRAI si l'objet est une liste, "FAUX sinon.
Exemples :
- ECRIS LISTE? [coucou] → -1
- ECRIS LISTE? "coucou → 0
PRECEDENT : attend deux listes en entrée - renvoie une liste - la primitive renvoie la liste qui vient la première selon l'ordre alphabétique.
Exemple :
- ECRIS PRECEDENT [je suis premier] [je suis second] → je suis premier
SUIVANT : attend deux listes en entrée - renvoie une liste - la primitive renvoie la liste qui vient la dernière selon l'ordre alphabétique.
Exemple :
- ECRIS SUIVANT [je suis premier] [je suis second] → je suis second
VIDE? : attend une liste en entrée - renvoie un booléen - la primitive renvoie "VRAI si la liste en entrée est la liste vide, "FAUX sinon.
Exemples :
- ECRIS VIDE? [] → -1
- ECRIS VIDE? SAUF.PREMIER [un] → -1
II-D-4. Implémentation des listes▲
L'implémentation des listes passe par un complément de l'unité GVConsts qui contient les constantes centralisées du projet et une unité baptisée GVLists qui abrite les classes nécessaires à la gestion proprement dite des listes. Cette unité utilise par ailleurs l'unité GVWords précédemment étudiée.
II-D-4-a. Constantes▲
Les constantes ont simplement été complétées avec les chaînes nécessaires au signalement des erreurs et un type énuméré baptisé TGVError qui reprend les erreurs sous forme codée.
II-D-4-b. La classe TGVListUtils▲
La classe TGVListUtils concentre les méthodes utiles au traitement des listes. Il aurait été possible de les implémenter en tant que procédures et fonctions indépendantes, mais l'utilisation d'une classe et de son interface permet une meilleure maîtrise des outils : la classe définit précisément ce pour quoi elle est faite.
En voici l'interface :
// *** classe des utilitaires pour les listes ***
TGVListUtils = class
strict
private
fError: TGVErrors; // traitement des erreurs
fWord: TGVWord; // mot de travail
fPos: Integer
; // position de travail
fSt: TGVString; // chaîne de travail
public
constructor
Create; // constructeur
destructor
Destroy; override
; // destructeur
procedure
Clear; // nettoyage
// conversion d'une liste en mot
function
ListToWord(const
St: string
): string
;
// conversion d'un mot en liste
function
WordToList(const
St: string
): string
;
// conversion d'une liste en chaîne
function
ListToStr(const
St: string
): string
; overload
;
function
ListToStr(const
St: string
; out
ASt: string
): Boolean
; overload
;
// conversion d'une chaîne en liste
function
StrToList(const
St: string
): string
;
// retourne la liste vide
function
EmptyList: string
;
// vérifie la validité d'une liste
function
IsValid(const
St: string
): Boolean
;
// teste la validité d'une valeur (mot ou liste)
function
IsValidValue(const
St: string
): Boolean
;
// liste simple ?
function
IsSimpleList(const
St: string
): Boolean
;
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
end
;
En dehors de la fonction EmptyList qui se contente de renvoyer la liste vide, il s'agit essentiellement de méthodes de conversion et de validation.
Ainsi ListToStr et StrToList convertissent une liste en chaîne et réciproquement. ListToWord et WordToList prennent en compte le caractère d'échappement. Pour ces quatre méthodes, un contrôle de la validité des listes est effectué.
Le contrôle de la validité d'une liste s'effectue grâce à IsValid qui est de loin la plus complexe. Elle analyse une chaîne et contrôle caractère par caractère que les règles relatives aux listes sont respectées : autant de crochets ouvrants que de crochets fermants, dans le bon ordre ; autant de parenthèses ouvrantes que de parenthèses fermantes, dans le bon ordre ; prise en compte du caractère d'échappement ; interdiction des sous-listes à l'intérieur d'une expression parenthésée.
Les autres méthodes de contrôle sont plus simples. IsSimpleValue se contente de vérifier la présence des crochets pour une liste. IsValidValue exploite l'unité GVWords pour tester un mot et la méthode précédente pour une liste. TestValue est une procédure qui déclenche une exception si la chaîne en entrée n'est ni un mot ni une liste au sens de GVLOGO.
II-D-4-c. La classe TGVList▲
La classe TGVList implémente la gestion des listes pour GVLOGO. Elle hérite de TStringList qui gère les listes de chaînes. En fait, une liste en LOGO est une chaîne particulière de caractères : elle nécessite d'être entourée de crochets et, si elle comprend d'autres listes, de comprendre à l'intérieur autant de crochets ouvrants que de crochets fermants (dans le bon ordre évidemment !). Cette dernière remarque s'applique aussi pour les éventuelles expressions imbriquées dans la liste. Pour parvenir à cet effet, il faut surcharger les méthodes d'entrée : Add, LoadFromStream, Assign, Insert et Put. Chaque élément de la liste (mot ou sous-liste) est stocké dans une chaîne de la TStringList.
Toutes les nouvelles méthodes renvoient une chaîne ou une valeur sans modifier la liste d'entrée. En cas d'erreur lors de la construction de la liste, la liste interne est la chaîne vide et la propriété LastErrorPos renvoie l'indice de l'erreur détectée.
En voici l'interface(10)Â :
TGVList = class
(TStringList)
strict
private
fRawStr: string
; // chaîne brute en entrée
fError: TGVErrors; // traitement des erreurs
fIsValid: Boolean
; // validité de la liste
fPos: Integer
; // position de travail
fLoading: Boolean
; // chargement en cours ?
fNumLastItem: Integer
; // dernier élément trouvé
fWord: TGVWord; // mot de travail
fUtil: TGVListUtils; // utilitaire pour liste
protected
procedure
Put(Index
: Integer
; const
St: string
); override
; // assignation
function
Get(Index
: Integer
): string
; override
; // élément choisi
function
GetTextStr: string
; override
; // récupération du texte
procedure
SetTextStr(const
AValue: string
); override
; // validation du texte
public
constructor
Create; overload
; // création
destructor
Destroy; override
; // destruction
procedure
Clear; override
; // nettoyage
function
Add(const
St: string
): Integer
; override
; // ajout
procedure
LoadFromStream(Stream: TStream); overload
; override
; // chargement
procedure
Assign(Source: TPersistent); override
; // assignation
procedure
Insert(Index
: Integer
; const
St: string
); override
; // insertion
// nouvelles méthodes (*** ne modifient pas la liste interne ***)
// renvoie la liste sous forme de chaîne
function
ToStr: string
;
// renvoie la liste sous forme de chaîne sans crochets
function
ToWBStr: string
;
// la liste est-elle la liste vide ?
function
IsEmptyList: Boolean
;
// renvoie le premier élément de la liste
function
First: string
;
// renvoie le dernier élément de la liste
function
Last: string
;
// sauf le premier de la liste
function
ButFirst: string
;
// sauf le dernier de la liste
function
ButLast: string
;
// supprime l'élément N
function
DeleteItem(N: Integer
): string
;
// insertion d'un élément en position N
function
InsertAItem(N: Integer
; const
St: string
): string
;
// remplacement de l'élément N
function
ReplaceItem(N: Integer
; const
St: string
): string
;
// met en premier
function
PutFirst(const
St: string
): string
;
// met en dernier
function
PutLast(const
St: string
): string
;
// phrase à droite
function
SentenceRight(const
St: string
): string
;
// phrase à gauche
function
SentenceLeft(const
St: string
): string
;
// tri des éléments
function
SortItems: string
;
// inversion des éléments
function
ReverseItems: string
;
// mélange des éléments
function
ShuffleItems: string
;
// membre présent ?
function
IsItem(const
St: string
): Boolean
;
// ajout d'une paire
function
TwoAdd(const
St1, St2: string
): string
;
// suppression d'une paire
function
TwoDelete(const
N: Integer
): string
;
// liste en majuscules
function
UpperCase: string
;
// liste en minuscules
function
LowerCase: string
;
// rotation de la liste
function
Rotate: string
;
// élément de la liste choisi au hasard
function
AtRandom: string
;
// premier élément de chaque élément d'une liste
function
Firsts: string
;
// tout sauf le premier élément de chaque élément d'une liste
function
ButFirsts: string
;
// dernier élément traité
property
LastItem: Integer
read
fNumLastItem default
-1
;
// chaîne brute en entrée
property
RawStr: string
read
fRawStr; // chaîne brute en entrée
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
// validité de la liste
property
IsValid: Boolean
read
fIsValid default
False
;
// acquisition du texte
property
Text: string
read
GetTextStr write
SetTextStr;
end
;
La méthode la plus complexe est certainement Add. Elle prend en charge l'ensemble du contrôle de la validité de la liste. Cette méthode comprend une partie générale qui teste les mots et les listes avant de renvoyer une éventuelle position d'erreur ou de stocker les chaînes trouvées, et une procédure imbriquée qui examine le contenu de la chaîne. Cette dernière lit les caractères un par un et répartit le travail suivant ce qu'elle rencontre : blanc, crochet ouvrant, parenthèse ouvrante, échappement, crochet et parenthèse fermants mais orphelins, autre caractère.
Elle est en cela similaire à la méthode IsValid de TGVListUtils, sinon qu'elle stocke les éléments en vue de leur exploitation. Du point de vue de l'utilisateur, l'entrée d'une nouvelle liste se fait grâce à la propriété héritée Text de l'unité : il suffit d'affecter une chaîne à cette propriété pour qu'elle soit analysée et stockée.
La propriété LastItem n'est affectée que par l'utilisation de la méthode IsItem. Si cette dernière renvoie True, LastItem contiendra le numéro de l'élément de la liste qui correspond à celui recherché.
On notera que les éléments des listes en Pascal sont numérotés avec une base de 0 alors qu'en GVLOGO la base est de 1, ce qui paraîtra plus naturel à l'utilisateur.
II-D-4-d. Test de l'unité TGVLists▲
Comme lors du test de TGVWords, les versions proposées sont au nombre de deux :
- une version Win32Â ;
- une version Linux.
Le programme lui-même ne pose pas de problèmes particuliers. Il ne s'agit en effet que de mettre à jour la liste de travail et d'appliquer la méthode voulue.
Il est encore une fois conseillé de modifier les différents paramètres pour observer les réactions de l'unité.
II-E. Les listes de propriétés▲
II-E-1. Définitions▲
Les listes de propriétés sont des listes particulières associant chacune à un nom des couples propriété-valeur.
Par exemple, on peut imaginer répertorier des chiens dont les caractéristiques seront stockées ainsi : Zoé aura pour propriétés race, sexe, couleur, taille et âge dont les valeurs respectives seront caniche, femelle, blanc, très petit, 5. Médor aura les propriétés race, sexe, âge, caractère dont les valeurs seront teckel, mâle, 3, tranquille. On voit que l‘agencement des propriétés est libre, contrairement à ce qui se passerait pour un tableau.
GVLOGO fait un usage interne intensif des listes de propriétés. En effet, aussi bien les variables, les procédures, les paquets que les lignes des éditeurs sont des listes de propriétés.
Les noms donnés aux propriétés tout comme le nom des listes sont indifférents aux majuscules et minuscules. Par conséquent, les propriétés MAPROP, MaProp ou encore maprop sont équivalentes. En revanche, ce nom doit être un identificateur correct.
II-E-2. Exemples de listes de propriétés▲
- Liste associée à "caniche" : [couleur blanc taille [tout petit]]
- Liste associée à "labrador" : [couleur blanc vitesse [rapide] ans 8]
Les propriétés d'une liste de propriétés sont organisées en deux éléments consécutifs : le premier définit le nom de la propriété tandis que le second indique la valeur qui lui est associée. Le premier est toujours un mot unique pour la liste considérée alors que le second peut être un mot ou une liste quelconques.
II-E-3. Opérations sur les listes de propriétés▲
DPROP : attend deux mots en entrée suivis d'un mot ou une liste - ne renvoie rien - la primitive crée ou met à jour la propriété de la liste de propriétés fournie en paramètre. Le premier mot est la liste de propriétés et le second la propriété. Le dernier paramètre est soit un mot soit une liste qui définit la valeur attribuée à la propriété.
Exemple :
- DPROP "MALISTE "MAPROP [une valeur] → -
RPROP : attend deux mots en entrée - renvoie un mot ou une liste - la primitive renvoie la valeur de la propriété (second paramètre) de la liste visée (premier paramètre).
Exemple :
- ECRIS RPROP "MALISTE "MAPROP → une valeur
ANPROP : attend deux mots en entrée - ne renvoie rien - la propriété (second paramètre) de la liste spécifiée (premier paramètre) est supprimée.
Exemple :
- ANPROP "MALISTE "MAPROP → -
PROPS : attend un mot en entrée - renvoie une liste - la primitive renvoie la liste des propriétés de la liste en entrée.
Exemple :
- ECRIS PROPS "MALISTE → maprop
ANNULE : attend un mot en entrée - ne renvoie rien - la liste spécifiée par le mot est détruite avec toutes ses propriétés.
Exemple :
- ANNULE "MALISTE → -
COMPTE.PROPS : attend un mot en entrée - renvoie un entier - la primitive renvoie le nombre de propriétés associées à la liste en entrée.
Exemple :
- COMPTEPROPS "MALISTE → 1
PROP? : attend deux mots en entrée - renvoie un booléen - la primitive renvoie "VRAI si le second mot en entrée est une propriété du premier, "FAUX sinon
Exemple :
- ECRIS PROP? "MALISTE "MAPROP → -1
LISTE.PROP? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot en entrée est une liste de propriétés, "FAUX sinon.
Exemple :
- ECRIS LISTEPROP? "MALISTE → -1
PROCEDURE? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot en entrée est une procédure, "FAUX sinon.
Exemple :
- ECRIS PROCEDURE? "MALISTE → 0
PRIMITIVE? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot en entrée est une primitive, "FAUX sinon.
Exemple :
- ECRIS PRIMITIVE? "ECRIS → 0
On notera que la primitive (ou la procédure) est précédée des guillemets anglais. Si elle ne l'était pas, GVLOGO chercherait à l'exécuter, ce qui n'est pas l'effet voulu.
NOM? : attend un mot en entrée - renvoie un booléen - la primitive renvoie "VRAI si le mot en entrée est une variable, "FAUX sinon.
Exemple :
- ECRIS NOM? "MALISTE → 0
II-E-4. Implémentation des listes de propriétés▲
L'unité GVPropLists implémente essentiellement deux classes : TGVPropListEnumerator pour l'énumération et TGVPropList pour les listes elles-mêmes.
II-E-4-a. Constantes▲
Comme pour les unités précédentes, l'unité GVConsts a été complétée afin de prendre en compte les nouveaux besoins. Les ajouts ont été effectués au niveau des constantes elles-mêmes.
On remarquera l'extension pour les fichiers et surtout l'en-tête qui permettra de vérifier qu'un fichier est conforme au format attendu : il précèdera les données proprement dites.
Les chaînes de ressources ont aussi été complétées, de même pour l'énumération des erreurs possibles.
II-E-4-b. La classe TGVPROPLISTENUMERATOR▲
Comme son nom l'indique, l'énumération est un moyen de dresser la liste des éléments contenus dans un objet. Aussi bien Lazarus que Delphi en font un usage étendu à travers les objets tels que les listes.
La technique d'implémentation, finalement assez simple, obéit à un schéma type : on doit fournir l'élément en cours (pointé par une propriété en lecture seule) et être capable d'indiquer si l'on peut se déplacer vers l'élément suivant.
Cela donne dans le cas des listes de propriétés l'interface suivante :
type
// *** classe pour énumération ***
TGVPropListEnumerator = class
(TObject)
private
fLst: TStringList;
fIndex: Integer
;
protected
function
GetCurrent: string
; virtual
; // élément en cours
public
constructor
Create(const
Value: TStrings); // création
destructor
Destroy; override
; // destruction
function
MoveNext: Boolean
; // vers le suivant
property
Current: string
read
GetCurrent; // valeur de l'élément courant
end
;
Comme les listes de propriétés fonctionnent via une liste interne, il suffit d'en manipuler les éléments grâce à un index.
La gestion se fait au moyen du système intégré dans TStringList des paires Names/Values, mais utilise un caractère séparateur personnalisé : | (11). Ce caractère ne doit par conséquent pas figurer dans la liste elle-même.
Les utilisateurs de Delphi pourront imaginer une unité fondée sur la classe TDictionary.
II-E-4-c. La classe TGVPROPLIST▲
La classe TGVPropList fonctionne grâce à un champ privé fNames de type TStringList. Les méthodes sont organisées en trois blocs : méthodes générales, celles concernant les listes de propriétés et celles concernant les propriétés.
En voici l'interface :
// *** classe des listes de propriétés ***
TGVPropList = class
(TObject)
strict
private
fError: TGVErrors; // enregistrement d'une erreur
fNames: TStringList; // listes
fOnchange: TNotifyEvent; // notification de changement
function
GetLPByNum(N: Integer
): string
; // liste par numéro
function
GetLPByName(const
Name: string
): string
; // renvoie liste par nom
procedure
SetLPByName(const
Name, Value: string
); // fixe liste par nom
protected
procedure
Change; // changement
public
// constructeur de la classe
constructor
Create;
// destructeur de la classe
destructor
Destroy; override
; // destructeur
// énumération
function
GetEnumerator: TGVPropListEnumerator;
// nettoie les listes de propriétés
procedure
Clear;
// *** listes de propriétés ***
// renvoie la liste des listes de propriétés
function
ListP: string
;
// la liste existe-t-elle ?
function
IsListP(const
Name: string
): Boolean
;
// renvoie le numéro d'une liste de propriétés
function
NumListP(const
Name: string
): Integer
;
// crée ou met à jour la liste de propriétés
function
UpDateListP(const
Name, Prop, Value: string
): Boolean
;
// renvoie la valeur d'une liste
function
ValListP(const
Name: string
): string
;
// destruction d'une liste de propriétés
function
RemoveListP(const
Name: string
): Boolean
;
// valeur d'une liste de propriétés par numéro
function
ValNumListP(N: Integer
; out
Name, Value: string
): Boolean
;
// la propriété N existe-t-elle?
function
IsListPByNum(N: Integer
): Boolean
;
// renvoie le nombre de listes de propriétés
function
CountListP: Integer
;
// chargement des listes
procedure
LoadFromFile(const
FileName: string
);
// sauvegarde des listes
procedure
SaveToFile(const
FileName: string
);
// *** propriétés
// la propriété existe-t-elle ?
function
IsProp(const
Name, Prop: string
): Boolean
;
// renvoie le numéro d'une propriété
function
NumProp(const
Name, Prop: string
): Integer
;
// valeur d'une propriété
function
ValProp(const
Name, Prop: string
): string
;
// destruction d'une propriété
function
RemoveProp(const
Name, Prop: string
): Boolean
;
// renvoie le nombre de propriétés attachées à une liste
function
CountProps(const
Name: string
): Integer
;
// valeur d'une propriété par numéro
function
ValNumProp(const
Name: string
; N: Integer
; out
Prop: string
)
: Boolean
; overload
;
function
ValNumProp(const
Name: string
; N: Integer
): string
;
// liste des propriétés d'une liste
function
ListOfProps(const
Name: string
): string
;
// nom d'une propriété par numéro
function
NameOfProp(const
Name: string
; N: Integer
): string
; overload
;
// changement dans la liste de propriétés
property
OnChange: TNotifyEvent read
fOnchange write
fOnchange;
// liste de propriétés par numéro
property
ListPByNum[N: Integer
]: string
read
GetLPByNum; default
;
// liste de propriétés par nom
property
LPByName[const
Name: string
]: string
read
GetLPByName
write
SetLPByName;
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
end
;
On remarquera que la propriété ListPByNum est définie comme default, ce qui signifie qu'elle peut être omise en employant simplement le numéro de la liste entre crochets.
Parmi les difficultés, on relèvera :
- Les listes de propriétés sont numérotées à partir de 1 et non de 0 comme en Pascal ;
- La méthode RemoveProp détruit aussi la liste si elle ne contient plus aucune propriété ;
- La méthode UpdateListP crée et met à jour les listes et les propriétés ;
- Il est fait un usage intensif de la fonction Odd qui vérifie qu'un nombre est impair : en effet, comme les propriétés sont organisées par paires, il est nécessaire de bien différencier le nom de la propriété (élément impair) de sa valeur (élément pair).
II-E-4-d. Test de l'unité TGVPROPLists▲
Comme d'habitude, le programme de test est décliné en deux versions :
- Lazarus pour Windows 32Â ;
- Lazarus pour Linux.
Un fichier de listes préenregistrées permet de tester immédiatement LoadFromFile : « ListPs.GPL ». Il peut être édité par n'importe quel lecteur de textes simples (genre NotePad).
II-F. La tortue graphique▲
II-F-1. Présentation▲
La tortue graphique est sans doute l'objet le plus connu du langage LOGO. Elle a fait son succès, mais l'a aussi très longtemps limité à une utilisation avec des enfants, conduisant les utilisateurs à négliger les listes qui permettent d'aborder des sujets plus ardus.
La tortue graphique est habituellement symbolisée par un triangle sur une surface de dessin. GVLOGO propose par ailleurs une tortue plus réaliste au format « png ». Des ordres permettent de la faire dessiner. Les commandes disponibles sont multiples : l'utilisateur maîtrise son orientation, son trait, sa forme, sa couleur…
L'originalité de ce mode de dessin réside dans sa capacité à partir d'une orientation dans l'espace déterminée par la tortue elle-même : même s'il est possible de dessiner dans un repère orthonormé traditionnel, la plupart des commandes sont dépendantes de l'état en cours de la tortue. Ainsi, tourner de 90° à gauche se fera par rapport à l'orientation actuelle de la tortue : si son cap était de 45° par rapport à la surface de dessin, son nouveau cap sera de 135°.
Par défaut, la tortue est au centre de la surface de dessin qui se mesure en pixels et dépend de la taille du composant TPanel d'accueil : sa position d'origine est donc le centre de ce composant. Ses déplacements se comptent en pixels si les échelles des abscisses et des ordonnées sont fixées à 100 (valeur par défaut). Un déplacement négatif fait reculer la tortue.
Le cap de la tortue indique la direction des déplacements. Il est exprimé en degrés et va de 0 à 360. Le cap est de 90° par défaut et pointe vers le sommet de l'écran afin d'être en conformité avec le cercle trigonométrique. On visualise ce cap grâce à l'orientation du triangle qui symbolise la tortue : l'arrière de la tortue est un trait plus épais ; sa tête est donc le sommet du triangle qui marque l'intersection des deux lignes plus fines.
II-F-2. Opérations avec la tortue▲
II-F-2-a. Champ de la tortue▲
Le champ de la tortue est la surface sur laquelle elle peut évoluer. Le coin inférieur gauche de l'écran est la position d'abscisse 0 et d'ordonnée 0. Par défaut, le point supérieur droit a pour abscisse la largeur du composant d'accueil et pour ordonnée sa hauteur(12).
CLOS : n'attend rien en entrée - ne renvoie rien - le champ de la tortue est limité à l'écran visible. Toute tentative de sortir de l'écran se soldera par un échec.
Exemple :
- CLOS → -
Le champ est défini à FENETRE par défaut.
ENROULE : n'attend rien en entrée - ne renvoie rien - le champ de la tortue s'enroule sur lui-même : quand la tortue atteint un bord, elle réapparaît sur le bord opposé.
Exemple :
- ENROULE → -
FENETRE (raccourci : FEN) : n'attend rien en entrée - ne renvoie rien - le champ de la tortue s'étend au-delà de l'écran. La tortue peut donc disparaître du champ de vision de l'utilisateur.
Exemple :
- FENETRE → -
ETAT.ECRAN : n'attend rien en entrée - renvoie une liste - la liste renvoyée indique l'état actuel de l'écran avec les données suivantes : couleur, hauteur et largeur.
Exemple :
- ECRIS ETAT.ECRAN → 0 700 677
FIXE.ECHELLE : attend une liste de deux entiers en entrée - ne renvoie rien - la primitive détermine les proportions du déplacement de la tortue. Par défaut, la valeur pour les abscisses (premier entier) et pour les ordonnées (second paramètre) est de 100. Une valeur moindre réduira les déplacements dans la proportion indiquée et une valeur supérieure l'augmentera. Par exemple, fixer le premier entier à 200 et le second à 50 fera que la tortue se déplacera deux fois plus vite horizontalement que la normale et deux fois plus lentement verticalement.
Exemple :
- FIXE.ECHELLE [200 50] → -
FIXE.ECHELLEX : attend un entier en entrée - ne renvoie rien - la primitive détermine la proportion du déplacement de la tortue selon l'axe des abscisses.
Exemple :
- FIXE.CHELLEX 200 → -
FIXE.ECHELLEY : attend un entier en entrée - ne renvoie rien - la primitive détermine la proportion du déplacement de la tortue selon l'axe des ordonnées.
Exemple :
- FIXE.ECHELLEY 50 → -
ECHELLE : n'attend rien en entrée - renvoie une liste de deux entiers - la liste renvoyée contient un premier entier qui indique l'échelle des déplacements de la tortue selon l'axe des abscisses et un second entier qui indique l'échelle selon l'axe des ordonnées.
Exemple :
- ECRIS ECHELLE → 100 100
VIDE.ECRAN (raccourci : VE) : n'attend rien en entrée - ne renvoie rien - l'espace de la tortue est nettoyé et la tortue retrouve sa place et son cap d'origine, ainsi que toutes ses valeurs par défaut.
Exemple :
- VIDE.ECRAN → -
ORIGINE : n'attend rien en entrée - ne renvoie rien - la tortue retrouve sa position d'origine en [300 300] et son cap initial de 90. L'état du crayon n'est pas modifié. Si le crayon est baissé, le déplacement de la tortue laisse une trace.
Exemple :
- ORIGINE → -
NETTOIE : n'attend rien en entrée - ne renvoie rien - l'espace de la tortue est nettoyé, sans changer la position de la tortue ni son cap, pas plus que les valeurs du crayon.
Exemple :
- NETTOIE → -
FIXE.COULEUR.FOND (raccourci : FCF) : attend un entier en entrée - ne renvoie rien - la primitive détermine la couleur de fond de l'écran de la tortue.
Exemple :
- FIXE.COULEUR.FOND 19 → -
Tableau 1 - Couleurs standards de la tortue |
||
Noir |
0 |
|
Bleu ciel |
1 |
|
Bleu |
2 |
|
Crème |
3 |
|
Gris foncé |
4 |
|
Fuchsia |
5 |
|
Gris |
6 |
|
Vert |
7 |
|
Vert citron |
8 |
|
Gris clair |
9 |
|
Marron |
10 |
|
Gris moyen |
11 |
|
Vert menthe |
12 |
|
Bleu marine |
13 |
|
Vert olive |
14 |
|
Violet |
15 |
|
Rouge |
16 |
|
Argent |
17 |
|
Bleu ciel |
18 |
|
Sarcelle |
19 |
|
Blanc |
20 |
|
Jaune |
21 |
COULEUR.FOND (raccourci : CF) : n'attend rien en entrée - renvoie un entier - l'entier renvoyé correspond à la couleur du fond de l'écran de la tortue.
Exemple :
- ECRIS COULEUR.FOND → 19
DISTANCE : attend deux entiers en entrée - renvoie un entier - l'entier renvoyé correspond à la distance de la tortue au point de coordonnées fournies en entrée (abscisse puis ordonnée).
Exemple :
- ECRIS DISTANCE 50 50 → 190
II-F-2-b. Déplacements de la tortue▲
AVANCE (raccourci : AV) : attend un entier en entrée - ne renvoie rien - la tortue avance du nombre de pixels indiqué par le paramètre en entrée, sauf si les échelles ont été changées. Elle laisse une trace sur la zone de dessin à moins que son crayon ne soit levé ou qu'elle soit en mode gomme.
Exemple :
- AVANCE 45 → -
RECULE (raccourci : RE) : attend un entier en entrée - ne renvoie rien - la tortue recule du nombre de pixels indiqué par le paramètre en entrée, sauf si les échelles ont été changées. Elle laisse une trace sur la zone de dessin à moins que son crayon ne soit levé ou qu'elle soit en mode gomme.
Exemple :
- RECULE 50 → -
GAUCHE (raccourci : TG) : attend un réel en entrée - ne renvoie rien - la tortue tourne à gauche du nombre de degrés indiqué en entrée.
Exemple :
- GAUCHE 90 → -
Pour rappel : la gauche indiquée est celle par rapport à la tortue et non la gauche de l'écran ! Cette remarque vaut évidemment pour la primitive DROITE.
DROITE (raccourci : TD) : attend un réel en entrée - ne renvoie rien - la tortue tourne à droite du nombre de degrés indiqué en entrée.
Exemple :
- DROITE 90 → -
FIXE.POS (raccourci : FPOS) : attend une liste de deux entiers en entrée - ne renvoie rien - la tortue est envoyée à la position indiquée par la liste : le premier élément fixe l'abscisse tandis que le second détermine l'ordonnée.
Exemple :
- FIXE.POS [200 100] → -
FIXE.XY (raccourci : FXY) : attend deux entiers en entrée - ne renvoie rien - la tortue est envoyée à la position indiquée par les paramètres : le premier élément fixe l'abscisse tandis que le second détermine l'ordonnée.
Exemple :
- FIXE.XY 200 100 → -
FIXE.X (raccourci : FX) : attend un entier en entrée - ne renvoie rien - l'abscisse de la tortue est fixée à l'entier fourni en entrée.
Exemple :
- FIXE.X 200 → -
FIXE.Y (raccourci : FY) : attend un entier en entrée - ne renvoie rien - l'ordonnée de la tortue est fixée à l'entier fourni en entrée.
Exemple :
- FIXE.Y 100 → -
POS : n'attend rien en entrée - renvoie une liste de deux entiers - la liste renvoyée contient un premier entier qui indique l'abscisse de la tortue et un second entier qui indique son ordonnée.
Exemple :
- ECRIS POS → 200 100
XCOOR : n'attend rien en entrée - renvoie un entier - l'entier renvoyé indique l'abscisse de la tortue.
Exemple :
- ECRIS XCOOR → 200
YCOOR : n'attend rien en entrée - renvoie un entier - l'entier renvoyé indique l'ordonnée de la tortue.
Exemple :
- ECRIS YCOOR → 100
FIXE.VITESSE : attend un entier en entrée - ne renvoie rien - la vitesse de la tortue est fixée à l'entier fourni en entrée.
Exemple :
- FIXEVITESSE 100 → -
La vitesse est relative aux performances de l'ordinateur sur lequel est installé GVLOGO. Elle est automatiquement limitée à 100.
VITESSE : n'attend rien en entrée - renvoie un entier - l'entier renvoyé indique la vitesse de dessin de la tortue.
Exemple :
- ECRIS VITESSE → 100
II-F-2-c. Cap de la tortue▲
FIXE.CAP (raccourci : FCAP) : attend un réel en entrée - ne renvoie rien - le cap de la tortue est fixé au nombre fourni en entrée. Le cap est compris entre 0 et 360.
Exemple :
- FIXECAP 90 → -
CAP : n'attend rien en entrée - renvoie un entier - l'entier renvoyé indique le cap de la tortue.
Exemple :
- ECRIS CAP → 90
VERS : attend une liste de deux entiers en entrée - renvoie un entier - L'entier renvoyé indique le cap que devrait avoir la tortue pour pointer vers le point dont les coordonnées sont fournies en entrée.
Exemple :
- VERS [0 0] → 214
Pour rappel : le cap 90 est celui qui pointe vers le haut de l'écran. Le cap 0 pointe vers sa droite. En fait, le cap augmente en tournant dans le sens inverse des aiguilles d'une montre.
II-F-2-d. État de la tortue▲
MONTRE.TORTUE (raccourci : MT) : n'attend rien en entrée - ne renvoie rien - la tortue devient visible.
Exemple :
- MONTRE.TORTUE → -
CACHE.TORTUE (raccourci : CT) : n'attend rien en entrée - ne renvoie rien - la tortue devient invisible.
Exemple :
- CACHE.TORTUE → -
La tortue dessine plus rapidement lorsqu'elle est invisible.
VISIBLE? : n'attend rien en entrée - renvoie un booléen - la primitive renvoie "VRAI si la tortue est visible, "FAUX sinon.
Exemple :
- ECRIS VISIBLE? → -1
ETAT.TORTUE : n'attend rien en entrée - renvoie une liste - la liste renvoyée fournit les données relatives à la tortue : son abscisse, son ordonnée, son cap, sa taille, sa vitesse, sa visibilité et si elle est figurée par un triangle.
Exemple :
- ECRIS ETAT.TORTUE → 300 300 98 8 100 -1 0
FIXE.ETAT.TORTUE : attend une liste en entrée - ne renvoie rien - l'état de la tortue est fixé selon le contenu de la liste : abscisse, ordonnée, orientation, taille, vitesse, visibilité et type de tortue. La tortue dessine durant le changement si le crayon est baissé.
Exemple :
- FIXE.ETAT.TORTUE [100 100 0 8 100 -1 -1] → -
TORTUE.NORMALE : n'attend rien en entrée - ne renvoie rien - quand elle est visible, la tortue est symbolisée par un triangle. C'est le mode par défaut.
Exemple :
- TORTUE.NORMALE → -
TORTUE.VERTE : n'attend rien en entrée - ne renvoie rien - quand elle est visible, la tortue est dessinée à partir d'images au format PNG.
Exemple :
- TORTUE.VERTE → -
L'image est plus sympathique, mais le dessin est un peu plus lent et la précision pour les angles moindre (5°).
FIXE.TAILLE : attend un entier en entrée - ne renvoie rien - la taille de la tortue est fixée au nombre fourni en entrée.
Exemple :
- FIXE.TAILLE 10 → -
La taille ne peut excéder 20 et ne concerne que le type par défaut, à savoir le triangle. La valeur par défaut est de 8.
TAILLE : n'attend rien en entrée - renvoie un entier - l'entier renvoyé indique la taille actuelle de la tortue.
Exemple :
- ECRIS TAILLE → 8
Si la tortue est la tortue png, la taille renvoyée n'aura pas de sens puisqu'elle ne varie pas !
II-F-2-e. Le crayon▲
BAISSE.CRAYON (raccourci : BC) : n'attend rien en entrée - ne renvoie rien - le crayon est baissé et donc dessine avec les attributs qui lui ont été affectés. C'est la position par défaut du crayon.
Exemple :
- BAISSE.CRAYON → -
LEVE.CRAYON (raccourci : LC) : n'attend rien en entrée - ne renvoie rien - le crayon est levé et donc ne dessine pas lorsque la tortue est déplacée.
Exemple :
- LEVE.CRAYON → -
BAISSE? : n'attend rien en entrée - renvoie un booléen - la primitive renvoie "VRAI si le crayon est baissé, "FAUX sinon.
Exemple :
- ECRIS BAISSE? → -1
FIXE.COULEUR.CRAYON (raccourci : FCC) : attend un entier en entrée - ne renvoie rien - la primitive détermine la couleur d'écriture du crayon de la tortue(13).
Exemple :
- FIXE.COULEUR.CRAYON 5 → -
COULEUR.CRAYON (raccourci : CC) : n'attend rien en entrée - renvoie un entier - l'entier renvoyé correspond à la couleur du crayon de la tortue.
Exemple :
- ECRIS COULEUR.CRAYON → 5
FIXE.TAILLE.CRAYON : attend un entier en entrée - ne renvoie rien - l'épaisseur du trait du crayon de la tortue est fixée au nombre fourni en entrée.
Exemple :
- FIXE.TAILLE.CRAYON 2 → -
TAILLE.CRAYON : n'attend rien en entrée - renvoie un entier - l'entier renvoyé correspond à l'épaisseur de trait du crayon de la tortue.
Exemple :
- ECRIS TAILLE.CRAYON → 2
INVERSE.CRAYON : n'attend rien en entrée - ne renvoie rien - le crayon écrit dans la couleur complémentaire de la couleur en cours.
Exemple :
- INVERSE.CRAYON → -
GOMME : n'attend rien en entrée - ne renvoie rien - le crayon efface en avançant.
Exemple :
- GOMME → -
Si la couleur de l'écran est modifiée, les traits effacés réapparaîtront.
NORMAL : n'attend rien en entrée - ne renvoie rien - le crayon n'inverse plus et n'efface plus.
Exemple :
- NORMAL → -
ETAT.CRAYON : n'attend rien en entrée - renvoie une liste - la liste renvoyée fournit les données relatives au crayon de la tortue : sa couleur, son épaisseur, s'il écrit ou non, si l'écriture est inversée ou non.
Exemple :
- ECRIS ETAT.CRAYON → 1 1 -1 0
FIXE.CRAYON ou FIXE.ETAT.CRAYON (raccourci : FEC) : attend une liste en entrée - ne renvoie rien - l'état du crayon est fixé selon le contenu de la liste : couleur, épaisseur, écriture ou non, inversion ou non.
Exemple :
- FIXE.ETAT.CRAYON [1 2 -1 0] → -
II-F-2-f. Formes prédéfinies▲
GVLOGO fournit plusieurs formes prédéfinies qui seront dessinées soit à une position indiquée par l'utilisateur soit par rapport à la position de la tortue. Ces formes ne sont pas orientables suivant un angle (par exemple, celui de l'orientation de la tortue), mais de telles formes sont facilement programmables en LOGO lui-même.
Afin d'éviter un conflit entre le nom des éventuelles procédures définies par l'utilisateur et celui des primitives de dessins de formes, on emploie pour nommer ces dernières le préfixe F_.
F.RECTANGLE : attend une liste en entrée - ne renvoie rien - un rectangle est dessiné avec les coordonnées de deux points fournies en entrée(14).
Exemple :
- F.RECTANGLE [100 100 200 200] → -
F.RECTANGLE.ARRONDI : attend une liste en entrée - ne renvoie rien - un rectangle arrondi est dessiné avec les coordonnées de deux points fournies en entrée.
Exemple :
- F.RECTANGLE.ARRONDI [50 100 150 200] → -
F.CARRE : attend une liste en entrée - ne renvoie rien - un carré est dessiné avec les coordonnées du point supérieur gauche suivies de la longueur de son côté.
Exemple :
- F.CARRE [20 20 50] → -
F.ELLIPSE : attend une liste en entrée - ne renvoie rien - une ellipse est dessinée avec les coordonnées de deux points fournies en entrée.
Exemple :
- F.ELLIPSE [100 100 200 200] → -
F.CERCLE : attend une liste en entrée - ne renvoie rien - un cercle est dessiné avec les coordonnées du premier point du cercle suivies de la longueur de son rayon.
Exemple :
- F.CERCLE [20 20 50] → -
REMPLIS : n'attend rien en entrée - ne renvoie rien - les figures seront pleines et par conséquent recouvriront celles qui seront placées dans leur espace de dessin.
Exemple :
- REMPLIS → -
LAISSE.VOIR : n'attend rien en entrée - ne renvoie rien - les figures seront transparentes et par conséquent ne recouvriront pas celles qui seront placées dans leur espace de dessin.
Exemple :
- LAISSE.VOIR → -
II-F-2-g. Texte avec la tortue▲
La tortue peut écrire à son emplacement. Le texte aura alors sa couleur et son orientation. L'utilisateur peut aussi écrire à un emplacement défini par lui avec l'orientation, la taille et la couleur de son choix.
TEXTE.TORTUE : attend un mot ou une liste en entrée - ne renvoie rien - le texte spécifié en paramètre est écrit sur l'écran de la tortue en utilisant ses coordonnées, sa taille, sa couleur et son orientation.
Exemple :
- TEXTE.TORTUE [Bonjour mon ami !] → -
FIXE.ANGLE.TEXTE : attend un entier en entrée - ne renvoie rien - le texte sera écrit avec l'angle spécifié en entrée, selon le même principe que l'orientation de la tortue.
Exemple :
- FIXE.ANGLE.TEXTE 90 → -
ANGLE.TEXTE : n'attend rien en entrée - renvoie un entier - la primitive renvoie l'angle actuel d'écriture d'un texte sur l'écran de la tortue.
Exemple :
- ECRIS ANGLE.TEXTE → 90
FIXE.TAILLE.FONTE : attend un entier en entrée - ne renvoie rien - le texte sera écrit avec la taille de caractère spécifiée en entrée, si cette taille est possible pour la police en cours.
Exemple :
- FIXE.TAILLE.FONTE 16 → -
TAILLE.FONTE : n'attend rien en entrée - renvoie un entier - la primitive renvoie la taille actuelle d'écriture d'un texte sur l'écran de la tortue.
Exemple :
- ECRIS TAILLE.FONTE → 16
FIXE.COULEUR.TEXTE (raccourci : FCT) : attend un entier en entrée - ne renvoie rien - le texte sera écrit avec la couleur de caractère spécifiée en entrée.
Exemple :
- FIXE.COULEUR.TEXTE 5 → -
COULEUR.TEXTE : n'attend rien en entrée - renvoie un entier - la primitive renvoie la couleur actuelle d'écriture d'un texte sur l'écran de la tortue.
Exemple :
- ECRIS COULEUR.TEXTE → 5
TEXTE.LIBRE.TORTUE : attend deux entiers puis un mot ou une liste en entrée - ne renvoie rien - le texte indiqué par le mot ou la liste fourni en paramètre est écrit sur l'écran de la tortue en utilisant les entiers comme coordonnées (abscisse et ordonnée). La taille, la couleur et l'orientation sont celles définies par les primitives définies ci-dessus.
Exemple :
- TEXTE.LIBRE.TORTUE 100 150 [Bonjour mon ami !] → -
II-F-3. Implémentation de la tortue▲
L'implémentation de la tortue pose trois types de problèmes :
- mathématiques : il faut se plonger dans quelques formules de trigonométrie afin de tenir compte de l'orientation de la tortue pendant ses déplacements ;
- graphiques : il faut gérer un fond indépendant du tracé et une tortue qui doit laisser une trace en se déplaçant à la manière d'un sprite(15) ;
- de communication par événements : une tortue personnalisée sera tributaire des dessins envoyés par l'application tandis que le fond dépendra du contrôle sous-jacent à la surface de la tortue (généralement un panneau). Il est aussi intéressant de notifier tout changement intervenu en rapport avec la tortue, par exemple pour afficher son état dans la barre de statut.
Afin de tenir compte de ces problèmes, l'implémentation met en œuvre une bibliothèque tierce qui ne fonctionne qu'avec Lazarus : BGRABitmap. Cette bibliothèque propose un ensemble d'outils pour créer et manipuler des images avec transparence. Elle est libre de droits et régulièrement mise à jour via le projet LazPaint. Outre les améliorations citées, résolues à travers la gestion d'un canal alpha pour la transparence, elle prend en charge l'anti-alias qui permet de réaliser des dessins de meilleure qualité, sans crénelages désagréables. Le seul point noir est qu'elle ne fonctionne qu'avec Lazarus, mais ceci n'est pas vraiment gênant puisque le projet s'appuie avant tout sur cet EDI.
II-F-3-a. Constantes▲
L'unité GVConsts n'est modifiée que modestement par l'ajout de quelques constantes :
// *** tortue graphique ***
// couleurs de base
CColors: array
[0
..19
] of
TColor = (clBlack, clMaroon, clGreen, clOlive,
clNavy, clPurple, clTeal, clGray, clSilver, clRed, clLime, clYellow,
clBlue, clFuchsia, clAqua, clWhite, clMoneyGreen, clSkyBlue, clCream,
clMedGray);
DgToRad = Pi / 180
; // pour les conversions en radians
RadToDg = 180
/ Pi; // pour les conversions en degrés
CDefaultScale = 100
; // échelle par défaut
CDefaultHeading = 90
; // cap par défaut
CDefaultXY = 600
; // taille écran tortue par défaut
CDefaultSize = 8
; // taille d'une tortue par défaut
CMaxSize = 20
; // taille maximale de la tortue
CMaxSpeed = 100
; // vitesse maximum de la tortue
CDefaultPenColor = clWhite; // couleur de fond par défaut
CDefaultBackColor = clBlack; // couleur du crayon par défaut
CDefaultPenWidth = 1
; // largeur du crayon par défaut
Quelques types viennent compléter le tout afin de définir le type d'écran et de tortue, ainsi qu'un enregistrement possible de l'état complet de la tortue :
type
// *** type d'écrans : enroulement, fenêtre illimitée ou champ clos ***
TScreenTurtle = (teWin, teGate, teRoll);
// *** types de tortue : triangle, dessin ou personnalisée (réservée) ***
TTurtleKind = (tkTriangle, tkPng, tkOwner);
// *** définition d'une tortue ***
TTurtle = record
rSaved: Boolean
; // drapeau de sauvegarde
rX: Extended
; // abscisse
rY: Extended
; // ordonnée
rKind: TTurtleKind; // type de tortue
rSize: Integer
; // taille de la tortue
rVisible: Boolean
; // drapeau de visibilité
rHeading: Extended
; // direction
rPenDown: Boolean
; // drapeau de crayon baissé
rPenRubber: Boolean
; // drapeau d'effacement
rScaleX: Integer
; // échelle des X
rScaleY: Integer
; // échelle des Y
rFilled: Boolean
; // remplissage
rPenWidth: Integer
; // largeur de crayon
rBrush: TBrush; // type de brosse
rPen: TPen; // type de crayon
rFont: TFont; // type de fonte
end
;
II-F-3-b. Un peu de mathématiques▲
Afin de déterminer un déplacement de la tortue sur la surface de travail, il faut connaître ses coordonnées pas à pas en fonction de son cap. Pour cela, on utilisera les fonctions sinus et cosinus :
Aussi bien Lazarus que Delphi fournissent une procédure bien pratique(16) pour les déterminer : SinCos définie pour un réel étendu de la manière suivante :
procedure
sincos(theta : float;out
sinus;cosinus : float)Â ;
Theta est l'angle exprimé en radians et les sinus et cosinus sont renvoyés dans les paramètres appropriés.
Voici par exemple comment elle est utilisée dans la procédure Move qui déplace la tortue :
procedure
TGVTurtle.Move(Value: Double
);
// *** la tortue se déplace ***
var
LSinT, LCosT: Extended
;
LTX, LTY: Double
;
begin
// calcul du cosinus et du sinus du cap
SinCos((Heading - 90
) * DgToRad, LSinT, LCosT);
// calcul des nouvelles coordonnées
LTX := fX - Value * LSinT * (ScaleX / CDefaultScale);
LTY := fY + Value * LCosT * (ScaleY / CDefaultScale);
SetPos(LTX, LTY); // déplacement si possible
end
;
fX et fY sont les coordonnées avant le déplacement. On remarquera la transformation des degrés en radians pour l'orientation ainsi que l'utilisation des échelles pour déterminer le nombre réel de pixels à franchir. La position sera par la suite arrondie puisqu'on ne peut dessiner que des pixels à des positions entières.
De la même manière, lorsqu'on voudra dessiner le triangle représentant la tortue, des formules similaires seront utilisées :
procedure
TGVTurtle.DrawTriangleTurtle;
// *** dessin de la tortue triangulaire ***
var
LCosT, LSinT: Extended
;
LX1, LX2, LX3, LY1, LY2, LY3: Integer
;
begin
// on efface la surface
fTtlImg.FillRect(0
,0
,fWidth, fHeight, BGRAPixelTransparent, dmSet);
// calcul des coordonnées des points de la tortue
SinCos((90
+ Heading) * DgToRad, LSinT, LCosT);
LX1 := Round(CoordX + Size * LCosT - LSinT);
LY1 := cY(Round(CoordY + Size * LSinT + LCosT));
LX2 := Round(CoordX - Size * LCosT - LSinT);
LY2 := cY(Round(CoordY - Size * LSinT + LCosT));
LX3 := Round(CoordX - LCosT + (Size shl
1
) * LSinT);
LY3 := cY(Round(CoordY - LSinT - (Size shl
1
) * LCosT));
with
fTtlImg.CanvasBGRA do
begin
if
PenColor <> BGRAToColor(BGRAPixelTransparent) then
Pen.Color := PenColor
else
Pen.Color := CDefaultPenColor;
MoveTo(LX1, LY1); // dessin de la tortue
Pen.Width := 2
;
LineTo(LX2, LY2);
Pen.Width := 1
;
LineTo(LX3, LY3);
LineTo(LX1, LY1);
end
;
end
;
L'utilisation de shl au lieu d'une multiplication par deux traditionnelle est très efficace pour ce qui est de la vitesse d'exécution.
En ce qui concerne la tortue personnalisée, les données sont tout à fait différentes. Le choix a été fait de partir d'une série de « clichés » de la tortue au format « png », car il supporte très bien la transparence. Malheureusement, les algorithmes qui permettent les rotations sont complexes : c'est pourquoi la solution d'images préenregistrées a été choisie. Elle est par ailleurs plutôt rapide.
procedure
TGVTurtle.DrawPNGTurtle;
// *** dessin de la tortue PNG ***
var
LCosT, LSinT: Extended
;
LX, LY: Integer
;
begin
// on efface la surface
fTtlImg.FillRect(0
,0
,fWidth, fHeight, BGRAPixelTransparent, dmSet);
BeforeChange; // récupère la bonne image
// calcul des coordonnées de la tortue
SinCos((90
+ Heading) * DgToRad, LSinT, LCosT);
LX := Round(CoordX + LCosT - LSinT);
LY := Round(CoordY + LSinT + LCosT);
// copie de la tortue .png
fTtlImg.CanvasBGRA.Draw(LX - (fPNGTurtle.Width shr
1
),
cY(LY) - (fPNGTurtle.Height shr
1
), fPNGTurtle);
end
;
Une autre fonction complexe est Towards qui permet de rendre l'orientation nécessaire de la tortue pour qu'elle pointe vers un point. Elle utilise la fonction mathématique ArcTan qui détermine un angle dont la tangente est donnée.
En voici le listing(17)Â :
function
TGVTurtle.Towards(X, Y: Integer
): Double
;
// *** renvoie le cap vers un point ***
var
LPX, LPY: Integer
;
begin
LPX := Round(CoordX) - X; // calcul des différences entre les points
LPY := Y - Round(CoordY);
Result := 0
; // suppose 0
// évalue suivant les calculs
if
((LPX = 0
) and
(LPY < 0
)) then
Result := 270
else
if
((LPX = 0
) and
(LPY > 0
)) then
Result := 90
else
if
((LPX > 0
) and
(LPY >= 0
)) then
Result := 180
- ArcTan(LPY / LPX) * RadToDg
else
if
((LPX < 0
) and
(LPY > 0
)) then
Result := (ArcTan(LPY / Abs(LPX)) * RadToDg)
else
if
((LPX < 0
) and
(LPY <= 0
)) then
Result := 360
- (ArcTan(LPY / LPX) * RadToDg)
else
if
((LPX > 0
) and
(LPY < 0
)) then
Result := 180
+ (ArcTan(Abs(LPY) / LPX) * RadToDg);
end
;
Enfin, la fonction Distance permet de calculer la distance entre la tortue et un point donné. Elle fait appel à de simples calculs à partir de triangles rectangles, donc en appliquant le théorème de Pythagore.
La formule bien connue est :
avec xa, ya les coordonnées du premier point et xb, yb celles du second.
La fonction en Pascal donne(18)Â :
function
TGVTurtle.Distance(X, Y: Integer
): Double
;
// *** renvoie la distance de la tortue à un point donné ***
begin
Result := Sqrt(Sqr(X - CoordX) + Sqr(Y - CoordY));
end
;
II-F-3-c. Se déplacer sur l'écran▲
En dehors de ces quelques notions de mathématiques, le déplacement de la tortue à l'écran pose des problèmes d'affichage. La solution adoptée est celle de la transparence bien maîtrisée par la bibliothèque BGRABitmap.
Sans cette bibliothèque, le comportement de la tortue avec Lazarus était erratique : la transparence fonctionne avec Lazarus dès qu'il s'agit de dessiner une forme simple sur le canevas ; mais si l'on désire déplacer la tortue sur une surface transparente qui prend sa couleur de fond sur un contrôle, le dessin en entier disparaît… du moins, dans la limite de mes connaissances !
Le dessin met en jeu trois couches : la première s'occupe du fond, la deuxième du dessin laissé par la tortue, la troisième par la représentation de cette dernière. On est ainsi assuré que les objets manipulés n'interféreront pas. La bibliothèque fournit en effet les éléments qui permettent de superposer ces couches en gérant la transparence nécessaire.
La méthode centrale se présente ainsi :
function
TGVTurtle.GetImg: TBGRABitmap;
// *** récupération de l'image ***
begin
with
fActualImg do
// on procède par couches
begin
PutImage(0
,0
,fBckImg,dmDrawWithTransparency); // le fond
PutImage(0
,0
,fDrwImg,dmDrawWithTransparency); // le dessin
if
TurtleVisible then
// tortue visible ?
begin
case
Kind of
tkTriangle: DrawTriangleTurtle; // on dessine la tortue triangulaire
tkPNG: DrawPNGTurtle; // tortue PNG
end
;
PutImage(0
,0
,fTtlImg,dmDrawWithTransparency); // on l'affiche
end
;
end
;
Result := fActualImg; // on renvoie l'image
end
;
On utilise fBckImg pour afficher le fond, fDrwImg pour le dessin et enfin fTtlImg pour la tortue. Les trois éléments sont du type TBGRABitmap. C'est la bibliothèque BGRABitmap qui s'occupe de fondre ces trois couches afin d'obtenir le dessin souhaité.
On notera aussi que les procédures LineTo et MoveTo adaptent les coordonnées fournies afin de les rendre cohérentes avec un repère dont l'origine est située en bas à gauche de la surface de dessin et non en haut à gauche comme en Pascal.
procedure
TGVTurtle.LineTo(X, Y: Double
);
// *** déplacement en écrivant ***
begin
fDrwImg.CanvasBGRA.LineTo(Round(X), cY(Round(Y))); // écriture effective
end
;
procedure
TGVTurtle.MoveTo(X, Y: Double
);
// *** déplacement sans écrire ***
begin
fDrwImg.CanvasBGRA.MoveTo(Round(X), cY(Round(Y))); // déplacement effectif
end
;
C'est la fonction cY qui est chargée du travail de modification du repère :
function
TGVTurtle.cY(Y: Integer
): Integer
;
// *** change l'ordonnée pour le nouveau repère ***
begin
Result := fHeight - Y; // inversion des ordonnées
end
;
Le déplacement proprement dit de la tortue est traité par la méthode DoGo qui prend en compte le type d'écran teRoll, c'est-à -dire l'enroulement. En voici l'implémentation :
procedure
TGVTurtle.DoGo(X, Y: Double
);
// *** effectue un déplacement de la tortue ***
begin
// si champ clos et hors limites => erreur
if
(Screen <> teGate) or
IsWithinLimits(X, Y) then
begin
fX := X; // nouvelle abscisse
fY := Y; // nouvelle ordonnée
// ralentit le dessin
Sleep(CMaxSpeed - Speed);
// dessine
if
PenDown then
// crayon baissé ?
LineTo(X, Y) // en écrivant
else
MoveTo(X, Y); // sans écrire
Change; // changement notifié
end
;
// on continue si l'écran s'enroule
if
(Screen = teRoll) and
not
IsWithinLimits(X,Y) then
begin
// mise à jour des coordonnées après enroulement
// débordement à droite ?
if
(X > fWidth) then
begin
MoveTo(0
, Y);
X := X - fWidth;
end
;
// débordement en bas ?
if
(Y > fHeight) then
begin
MoveTo(X, 0
);
Y := Y - fHeight;
end
;
// débordement à gauche ?
if
(X < 0
) then
begin
MoveTo(fWidth, Y);
X := fWidth + X;
end
;
// débordement en haut ?
if
(Y < 0
) then
begin
MoveTo(X, fHeight);
Y := fHeight + Y;
end
;
fX := X; // nouvelle abscisse
fY := Y; // nouvelle ordonnée
// ralentit le dessin
Sleep(CMaxSpeed - Speed);
// dessine ou déplace suivant l'état du crayon
if
PenDown then
LineTo(X, Y)
else
MoveTo(X, Y);
end
;
end
;
Bien que ce soit une erreur que de vouloir faire sortir la tortue de son espace alors qu'elle est en mode champ clos, GVLOGO se contente de ne pas déplacer la tortue dans ce cas. On pourrait aussi déclencher une exception, mais, d'un point de vue pédagogique, il a semblé plus intéressant de laisser à l'utilisateur le soin de comprendre ce qu'il se passe dans ce cas.
II-F-3-d. Les événements▲
La récupération de l'image désirée de la tortue est effectuée par le programme qui exploite la classe TGVTurtle. Même si les multiples liens entre les procédures et les unités rendent l'ensemble plutôt complexe, le principe de fonctionnement est simple : quand l'instance de la classe TGVTurtle a besoin de dessiner la tortue, elle exécute la procédure BeforeChange qui envoie en paramètre essentiel l'orientation de la tortue. En retour, elle s'attend à ce que l'unité appelée ou le programme ait affecté la bonne image à sa propriété TurtleImg.
Autrement dit, il s'agit d'une sorte d'aller-retour entre les deux unités : la première envoie un pointeur vers elle-même et l'orientation de la tortue si elle trouve une procédure affectée à son champ privé fOnBeforeChange ; la seconde récupère l'orientation de la tortue, cherche l'image correspondante et l'affecte en retour à la propriété TurtleImg de l'unité génératrice de l'événement.
La méthode BeforeChange est déclarée ainsi, dans la partie protected de la définition :
procedure
BeforeChange; // avant le changement
Et voici sa propre définition :
procedure
TGVTurtle.BeforeChange;
// *** gestion avant le changement ***
// (permet de mettre à jour une image pour la tortue avant de la dessiner)
begin
if
Assigned(fOnBeforeChange) then
// si le gestionnaire existe
fOnBeforeChange(Self
, Round(Heading)); // on l'exécute
end
;
On voit que la procédure fait elle-même référence au champ privé fOnBeforeChange défini ainsi :
fOnBeforeChange: TTurtleBeforeEvent; // notification avant cap changé
Quant au type TTurtleBeforeEvent, il fournit un squelette de la procédure appelante :
// avant le changement de la tortue
TTurtleBeforeEvent = procedure
(Sender: TObject; cHeading: Integer
) of
object
;
Ce squelette est essentiel puisqu'en fournissant un modèle commun il permet aux unités de communiquer entre elles.
Voici à présent comment le programme de test opère pour répondre à la requête de l'unité GVTurtles :
procedure
TMainForm.TurtleBeforePaint(Sender: TObject; cHeading: Integer
);
// *** image associée à la tortue ***
var
BitM: TBitmap;
begin
// charge l'image de la tortue
BitM := TBitmap.Create;
try
// les images de la tortue sont proposées tous les 5 degrés
ImageListTurtles.GetBitmap(Round(cHeading) div
5
, BitM);
// celle qui correspond est assignée au bitmap
GVTurtle.PNGTurtle.Assign(BitM);
finally
BitM.Free;
end
;
end
;
On remarquera que fOnBeforeChange de l'unité GVTurtles et TurtleBeforePaint du programme de test ont exactement le même en-tête calqué sur TTurtleBeforeEvent. Peu importe que les noms soient différents : ce qui compte, ce sont les paramètres absolument identiques dans leur nombre, leur agencement et leurs types.
Pour que l'ensemble fonctionne, il faut évidemment affecter la procédure appelée au gestionnaire d'événements approprié avec une ligne du genre :
procedure
TMainForm.FormCreate(Sender: TObject);
begin
// on crée la tortue
GVTurtle := TGVTurtle.Create(imgTurtle.Width, imgTurtle.Height);
GVTurtle.OnChange := @TurtleState; // gestionnaire de changement
GVTurtle.OnBeforeChange := @TurtleBeforePaint; // idem avant de dessiner
GVTurtle.ReInit; // initialisation
seSetPosX.Value := imgTurtle.Width shr
1
; // position au centre
seSetPosY.Value := imgTurtle.Height shr
1
;
end
;
Ici, GVTurtle est une instance de la classe TGVTurtle. Cette ligne figure dans la méthode FormCreate de la fiche. Le programme « sait » à présent quelle procédure il doit appeler lorsque l'événement est déclenché.
Contrairement à Delphi, Lazarus exige l'emploi de @ devant la méthode pour l'affecter au gestionnaire d'événements.
Un second événement plus simple est déclenché à chaque modification de la tortue : il s'agit de TTurtleEvent :
type
// changement de la tortue
TTurtleEvent = TNotifyEvent;
Cet événement ne transmet qu'un pointeur vers l'instance de la classe qui a déclenché l'événement, mais permet ainsi d'accéder aux propriétés de la tortue susceptibles d'avoir été modifiées : ses coordonnées, son cap, l'état de sa visibilité, celui de sa capacité à écrire et la couleur d'écriture du crayon. L'intérêt d'une telle notification est, par exemple, de renseigner en temps réel une barre de statut qui affichera en clair ces données pour informer l'utilisateur.
Du côté de l'unité GVTurtles, on retrouve :
- un champ privé qui conservera la procédure affectée si elle existe :
fOnchange: TTurtleEvent; // changement notifié
- une propriété en accès public et renvoyant à ce champ privé :
// événement après le changement de la tortue
property
OnChange: TTurtleEvent read
fOnchange write
fOnchange;
- une procédure dans la partie protected de la définition de la classe, appelée quand nécessaire et en charge d'appeler elle-même la procédure de l'unité qui exploite la tortue :
procedure
Change; // gestion du changement
- la définition de cette procédure :
procedure
TGVTurtle.Change;
// *** notification de changement ***
begin
if
Assigned(fOnchange) then
// on exécute le gestionnaire s'il existe
fOnchange(Self
);
end
;
Il ressemble tout à fait au gestionnaire vu précédemment : si le champ privé fOnChange est affecté, on exécute la méthode à laquelle il renvoie.
Du côté du programme de test, on retrouve :
- la déclaration de la procédure qui sera appelée en se conformant au squelette fourni par TTurtleEvent :
public
// message de la tortue
procedure
TurtleState(Sender: TObject);
- sa définition qui affiche les données fournies sur la barre de statut :
procedure
TMainForm.TurtleState(Sender: TObject);
// état de la tortue
begin
GVTurtle.TurtleBitmap.Draw(imgTurtle.Canvas,0
,0
);
imgTurtle.Invalidate;
// données de la tortue
with
GVTurtle do
statusbar.Panels[1
].Text := Format('X: %.3d Y: %.3d Cap: %.3d'
,
[Round(CoordX), Round(CoordY), Round(Heading)]) +
' Visible: '
+ IfThen(TurtleVisible, P_True, P_False) +
' Baissé: '
+ IfThen(PenDown, P_True, P_False);
cbPenColor.Selected := GVTurtle.PenColor; // couleur du crayon
cbScreenColor.Selected := GVTurtle.ScreenColor; // couleur du fond
end
;
- afin de créer le lien entre les deux unités, son affectation lors de la création de la fiche :
procedure
TMainForm.FormCreate(Sender: TObject);
begin
// on crée la tortue
GVTurtle := TGVTurtle.Create(imgTurtle.Width, imgTurtle.Height);
GVTurtle.OnChange := @TurtleState; // gestionnaire de changement
GVTurtle.OnBeforeChange := @TurtleBeforePaint; // idem avant de dessiner
GVTurtle.ReInit; // initialisation
seSetPosX.Value := imgTurtle.Width shr
1
; // position au centre
seSetPosY.Value := imgTurtle.Height shr
1
;
end
;
On a là un cas très classique de gestion d'événements, avec toutes les étapes de sa mise en place. Le code source de l'unité est alors émaillé d'appels à Change lorsqu'intervient un changement concernant la tortue.
II-F-3-e. La classe TGVTURTLE▲
Voici l'interface de la copieuse classe TGVTurtle :
TGVTurtle = class
(TObject)
strict
private
fError: TGVErrors; // gestion des erreurs
// *** la tortue ***
fX: Double
; // abscisse
fY: Double
; // ordonnée
fHeading: Double
; // cap
fSize: Integer
; // taille
fTurtleKind: TTurtleKind; // type
fTurtleVisible: Boolean
; // visibilité
fSpeed: Integer
; // vitesse
fPNGTurtle: TBGRABitmap; // PNG en cours
fSavedTurtle: TTurtle; // sauvegarde
fOnchange: TTurtleEvent; // changement notifié
fOnBeforeChange: TTurtleBeforeEvent; // notification avant cap changé
// *** le crayon ***
fPenDown: Boolean
; // levé/baissé
fPenRubber: Boolean
; // gomme
fPenWidth: Integer
; // largeur
fPenColor: TColor; // couleur
fTempColor: TColor; // sauvegarde temporaire de la couleur d'écriture
// *** l'écran ***
fScreen: TScreenTurtle; // mode
fHeight: Integer
; // hauteur de la surface d'origine
fWidth: Integer
; // largeur de la surface d'origine
fScaleX: Integer
; // échelle des X
fScaleY: Integer
; // échelle des Y
fScreenColor: TColor; // couleur
fBckImg: TBGRABitmap; // fond
fDrwImg: TBGRABitmap; // surface dessinée
fTtlImg: TBGRABitmap; // dessin de la tortue
fActualImg: TBGRABitmap; // surface réelle
fFilled: Boolean
; // drapeau de remplissage
// *** méthodes internes des propriétés ***
function
GetCoordX: Double
;
function
GetCoordY: Double
;
function
GetImg: TBGRABitmap;
function
GetLocalPenColor: Integer
;
procedure
SetLocalPenColor(AValue: Integer
);
function
GetLocalScreenColor: Integer
;
procedure
SetLocalScreenColor(AValue: Integer
);
procedure
SetCoordX(AValue: Double
);
procedure
SetCoordY(AValue: Double
);
procedure
SetFilled(AValue: Boolean
);
procedure
SetHeading(AValue: Double
);
procedure
SetPenColor(AValue: TColor);
procedure
SetPenDown(AValue: Boolean
);
procedure
SetPenWidth(AValue: Integer
);
procedure
SetScreen(AValue: TScreenTurtle);
procedure
SetScreenColor(AValue: TColor);
procedure
SetSize(AValue: Integer
);
procedure
SetSpeed(AValue: Integer
);
procedure
SetTurtleKind(AValue: TTurtleKind);
procedure
SetTurtleVisible(AValue: Boolean
);
procedure
SetRubberPen(AValue: Boolean
);
// *** états ***
function
GetTurtleState: string
; // état de la tortue
procedure
SetTurtleState(const
St: string
);
function
GetPenState: string
; // état du crayon
procedure
SetPenState(const
St: string
);
function
GetPosState: string
; // position du crayon
procedure
SetPosState(const
St:string
);
function
GetScaleState: string
; // état de l'échelle
procedure
SetScaleState(const
St: string
);
function
GetScreenState: string
; // état de l'écran
procedure
SetScreenState(const
St: string
);
// *** méthodes internes générales ***
function
IsWithinLimits(X, Y: Double
): Boolean
; // dans limites ?
procedure
DoGo(X, Y: Double
); // effectue un déplacement
procedure
LineTo(X, Y: Double
); // déplacement en écrivant
procedure
MoveTo(X, Y: Double
); // déplacement sans écrire
// *** figures prédéfinies générales ***
// arc d'ellipse (b : début, e : fin - en degrés)
procedure
ArcAntialias(x, y, rx, ry: single
; b, e: word
;
c: TBGRAPixel; w: single
);
// arc d'ellipse rempli (b : début, e : fin - en degrés)
procedure
FillArcAntiAlias(x, y, rx, ry: single
; b, e: word
;
c: TBGRAPixel);
// portion d'ellipse (b : début, e : fin - en degrés)
procedure
PieAntialias(x, y, rx, ry: single
; b, e: word
;
c: TBGRAPixel; w: single
);
// portion d'ellipse remplie (b : début, e : fin - en degrés)
procedure
FillPieAntiAlias(x, y, rx, ry: single
; b, e: word
;
c: TBGRAPixel);
// *** fonctions utiles ***
function
ColorToIntColor(N: TColor): Integer
; // couleur en couleur locale
function
IntColorToColor(N: Integer
): TColor; // couleur locale en couleur
protected
function
cY(Y: Integer
): Integer
; virtual
; // ordonnée dans nouveau repère
procedure
DrawTriangleTurtle; virtual
; // tortue triangulaire
procedure
DrawPNGTurtle; virtual
; // tortue PNG
procedure
Change; // gestion du changement
procedure
BeforeChange; // avant le changement
public
constructor
Create(Width, Height: Integer
); // création
destructor
Destroy; override
; // destructeur
procedure
ReInit; // réinitialisation de l'objet
procedure
PenReverse; // inversion de l'écriture
procedure
Move(Value: Double
); // la tortue se déplace
procedure
SetPos(X, Y: Double
); // fixe les coordonnées de la tortue
procedure
Turn(Value: Double
); // la tortue tourne
procedure
Home; // tortue à l'origine
procedure
Wipe; // nettoyage de l'écran
procedure
SaveTurtle; // sauvegarde de la tortue
procedure
ReloadTurtle(Clean: Boolean
); // récupère une tortue sauvée
// renvoie le cap vers un point
function
Towards(X, Y: Integer
): Double
; overload
;
function
Towards(const
St: string
): Double
; overload
;
// renvoie la distance de la tortue à un point donné
function
Distance(X, Y: Integer
): Double
; overload
;
function
Distance(const
St: string
): Double
; overload
;
// *** gestion du texte ***
// texte affiché sur l'écran de la tortue
procedure
Text(const
St: string
; X,Y, Angle: Integer
); overload
;
// texte affiché à l'emplacement de la tortue
procedure
Text(const
St: string
); overload
;
// *** figures prédéfinies ***
// dessine un rectangle
procedure
Rectangle(X1, Y1, X2, Y2: Integer
); overload
;
// dessine un rectangle à l'emplacement de la tortue
procedure
Rectangle(X2, Y2: Integer
); overload
;
// dessine un carré
procedure
Square(X1, Y1, L: Integer
); overload
;
// dessine un carré à l'emplacement de la tortue
procedure
Square(L: Integer
); overload
;
// dessine un rectangle arrondi
procedure
RoundRect(X1, Y1, X2, Y2: Integer
); overload
;
// dessine un rectangle arrondi à l'emplacement de la tortue
procedure
RoundRect(X2, Y2: Integer
); overload
;
// dessine une ellipse
procedure
Ellipse(X1, Y1, X2, Y2: Integer
); overload
;
// dessine une ellipse à l'emplacement de la tortue
procedure
Ellipse(X2, Y2: Integer
); overload
;
// dessine un cercle
procedure
Circle(X1, Y1, R: Integer
); overload
;
// dessine un cercle à l'emplacement de la tortue
procedure
Circle(R: Integer
); overload
;
// dessine un arc d'ellipse
procedure
Arc(X1, Y1, X2, Y2, X3, Y3: Integer
); overload
;
// dessine un arc d'ellipse à l'emplacement de la tortue
procedure
Arc(X2, Y2, X3, Y3: Integer
); overload
;
// dessine une section d'ellipse
procedure
Pie(X1, Y1, X2, Y2, X3, Y3: Integer
); overload
;
// dessine une section d'ellipse à l'emplacement de la tortue
procedure
Pie(X2, Y2, X3, Y3: Integer
); overload
;
// *** propriétés ***
// abscisse de la tortue
property
CoordX: Double
read
GetCoordX write
SetCoordX;
// ordonnée de la tortue
property
CoordY: Double
read
GetCoordY write
SetCoordY;
// type de tortue
property
Kind: TTurtleKind read
fTurtleKind write
SetTurtleKind
default
tkTriangle;
// visibilité de la tortue
property
TurtleVisible: Boolean
read
fTurtleVisible write
SetTurtleVisible
default
True
;
// direction de la tortue
property
Heading: Double
read
fHeading write
SetHeading;
// taille de la tortue
property
Size: Integer
read
fSize write
SetSize default
CDefaultSize;
// drapeau d'écriture
property
PenDown: Boolean
read
fPenDown write
SetPenDown default
True
;
// type de zone de déplacement
property
Screen: TScreenTurtle read
fScreen write
SetScreen
default
teWin;
// échelle des X
property
ScaleX: Integer
read
fScaleX write
fScaleX default
CDefaultScale;
// échelle des Y
property
ScaleY: Integer
read
fScaleY write
fScaleY default
CDefaultScale;
property
PenRubber: Boolean
read
fPenRubber write
SetRubberPen
default
False
; // état de la gomme
property
PenColor: TColor read
fPenColor write
SetPenColor
default
CDefaultPenColor; // couleur du crayon
property
LocalPenColor: Integer
read
GetLocalPenColor
write
SetLocalPenColor; // couleur du crayon en couleur locale
property
PenWidth: Integer
read
fPenWidth write
SetPenWidth default
CDefaultPenWidth; // largeur du crayon
// état du remplissage
property
Filled: Boolean
read
fFilled write
SetFilled default
True
;
// vitesse de dessin de la tortue
property
Speed: Integer
read
fSpeed write
SetSpeed default
CMaxSpeed;
property
ScreenColor: TColor read
fScreenColor write
SetScreenColor
default
CDefaultBackColor; // couleur du fond d'écran
property
LocalScreenColor: Integer
read
GetLocalScreenColor
write
SetLocalScreenColor; // couleur de l'écran en couleur locale
// événement après le changement de la tortue
property
OnChange: TTurtleEvent read
fOnchange write
fOnchange;
// événement avant le changement de la tortue
property
OnBeforeChange: TTurtleBeforeEvent read
fOnBeforeChange
write
fOnBeforeChange;
// surface de dessin de la tortue
property
TurtleBitmap: TBGRABitmap read
GetImg;
// tortue PNG
property
PNGTurtle: TBGRABitmap read
fPNGTurtle write
fPNGTurtle;
// notification d'une erreur
property
Error: TGVErrors read
fError write
fError;
// état de la tortue
property
TurtleState: string
read
GetTurtleState write
SetTurtleState;
// état du crayon
property
PenState: string
read
GetPenState write
SetPenState;
// position du crayon
property
PosState: string
read
GetPosState write
SetPosState;
// échelle
property
ScaleState: string
read
GetScaleState write
SetScaleState;
// écran
property
ScreenState: string
read
GetScreenState write
SetScreenState;
end
;
En dehors des aspects déjà étudiés, la classe ne présente pas de problèmes majeurs. La tortue fait bien sûr appel à des propriétés afin que l'utilisateur puisse en maîtriser l'aspect et le comportement. L'interface est aussi alourdie par de nombreuses fonctions de dessin : une première version dessine en se référant à des coordonnées traditionnelles tandis qu'une seconde version surchargée grâce à overload utilise les coordonnées de la tortue pour amorcer son dessin.
On notera que les coordonnées sont des réels alors que la position finale est un entier : cela permet de calculer au plus près la position de la tortue avant de l'arrondir, évitant ainsi des approximations cumulées.
II-F-3-f. Test de l'unité GVTurtles▲
Comme toujours, le programme de test est décliné en deux versions :
- Lazarus pour Windows 32Â ;
- Lazarus pour Linux.
L'utilisateur pourra tester la plupart des fonctions et propriétés définies pour manipuler la tortue. Le programmeur sera plus intéressé par le mécanisme des notifications d'événements tel que décrit plus haut.
Il faut avoir installé la bibliothèque BGRABitmap pour utiliser l'unité GVTurtles !
Pour avoir une idée des possibilités de dessin de la tortue, un bouton marqué « dessin » dessine une série de 36 carrés en rosace. Afin de se rendre compte de la rapidité de la tortue triangulaire par rapport à celle graphique, il est judicieux d'utiliser ce bouton spécial dans trois configurations : graphique, triangle et tortue cachée.
On remarquera aussi quatre boutons : le premier met en œuvre la méthode Text dans sa version indépendante de la tortue tandis que le second la teste avec des coordonnées fournies par cette même tortue ; le troisième illustre ce que peut réaliser la méthode Text avec une fleur de mots en couleurs ; enfin, le dernier, accompagné d'un TSpinEdit, contrôle l'épaisseur du trait de la tortue lorsqu'elle dessine. De plus, trois boutons TRadioEdit permettent de contrôler le type de champ de la tortue : illimité avec teWin (valeur par défaut), limité par les bords de l'écran (teGate) et enfin enroulé (teRoll).