STATUT : COMPLET

Téléchargement du tutoriel complet : tutoriel 2
Décompressez l'archive et copiez le dossier "2" dans "%scol%/partition/tutoriels/codes/" où %scol% est le dossier d'installation de Scol. Ok pour toute version de Scol Windows, MacOS et Linux.


Objectif : Affichage de bitmaps dans une fenêtre.

  • utilisation exclusive de variables locales
  • utilisation de la fonction Scol 'mutate'
  • utilisation des boucles 'if ... then ... else' et 'while ... do'
  • création et gestion de menus
  • chargement de fichiers image supportés par Scol
  • gestion simple des ObjBitmap et des AlphaBitmaps
  • affichage de bitmap dans une fenêtre 2d

Ne pas oublier de consulter la doc de référence (liste des fonctions Scol au format html) pour toutes les fonctions Scol croisées dans ce package et dans les suivants ! Cette doc devrait être toujours à portée de clic lorsque vous codez en Scol ;-)

Une partie de la doc de l'API multimedia n'existe que dans la 3.5 par exemple. N'hésitez pas à demander en cas de doute.


DÉCLARATIONS

Il n'y a aucune déclaration à faire ici car aucune variable globale n'est utilisée pour réaliser cette application. Il n'y a que des fonctions.
Leur non-utilisation n'est pas une fin en soi, elles sont très souvent utiles mais, comme on peut le voir dans cet exemple, on peut très bien s'en passer dans certains blocs. Cela peut avoir un effet sur la mémoire allouée à l'application, sur la lisibilité ou le compactage du code. L'utilisation de variables locales obligent la plupart du temps à traiter le sujet d'une autre façon.

Une variable globale n'est pas nécessairement définie en tête de package même si cela simplifie la lecture et la compréhension du code. Le seul impératif est qu'elle doit être déclaré avant d'être utilisée. Comme vu dans le tutorial 1, une variable globale peut être déclarée via le mot clé 'typeof' auquel cas son type seul est défini ou via le mot clé 'var' auquel cas sa valeur de départ est définie (ce qui définit par là même son type) :

typeof mavariable = S;; déclare la variable globale comme étant de type S
var mavariable = "Scol";; déclare la variable globale comme étant de type S car sa valeur par défaut est une chaîne de caractères.
La portée d'une variable globale est totale, à partir du moment où elle a été déclarée.

Une variable locale n'est pas définie explicitement. Cependant son type est parfaitement définie par le compilateur. S'il y a ambiguïté, une erreur se produira.
Une variable locale est déclarée par le mot clé 'let nom_de_la_variable_locale -> valeur_de_la_variable_locale in' :

let "Scol" -> machaine in déclare la variable locale 'machaine' qui est du type S puisque sa valeur initiale est une chaine de caractères.
On peut effectuer les mêmes opérations qu'avec une variable globale mais sa portée est strictement limitée à l'instruction suivante. Toutefois, ce tutoriel montre que cette limitation n'est pas handicapante.


FONCTIONS

Cette fonction recherche la position du dernier point (s'il existe) contenu dans une chaine quelconque de caractères.

La bouche 'while' EXPRESSION 'do' INSTRCUTION est un "TANT QUE" l'expression "EXPRESSION" est vraie "FAIRE" l'instruction "INSTRUCTION".
INSTRUCTION peut être une instruction simple ou un bloc d'instruction délimité par des parenthèses.

Ici, on va parcourir la chaine jusqu'à soit avoir un point ((nth_char s i) != '.) soit atteindre la fin de la chaine (i >=0).
La fonction retournera la valeur atteinte par ce parcours : soit la position du point, soit 0.

Le type de 'rechercheDernierPoint' est fun [S] I

fun rechercheDernierPoint(s)=
	let strlen s -> n in
	let n-1 -> i in
	(
	 while (i >=0) && ((nth_char s i) != '.) do
		set i = i-1;
	 i
	);;
	
fun verifExtension(nomfichier, ext)=
	let rechercheDernierPoint nomfichier -> i in
	if !strcmp ext substr nomfichier i strlen ext then
		1
	else
0;;

Le fichier porte une extension "*.png". On charge alors ce fichier via la fonction Scol '_LDalphaBitmap'. On obtient alors un AlphaBitmap, avec, notamment, sa couche alpha éventuelle qu'un ObjBitmap ne peut contenir. Cependant, dans notre application, on a besoin que d'un ObjBitmap. On va donc extraire ce qui nous intéresse de l'AlphaBitmap via la fonction Scol '_GETalphaBitmaps' qui, elle, retourne un tuple avec le bitmap et sa couche alpha (couche alpha qui ne nous intéresse pas ici, je le rappelle).

La fonction ne retourne donc que l'ObjBitmap.

fun chargerUnAlphaBitmap(fichierP)=
	let _LDalphaBitmap _channel fichierP -> abmp in
	let _GETalphaBitmaps abmp -> [bmp _] in
	bmp;;

Cette fonction simple charge un fichier graphique supporté par Scol. Son argument est une référence de fichier en lecture seule (type P). Si cette référence vaut nil, la fonction retourne nil et rien n'est chargé, normal. Si cette référence ne vaut pas nil, alors le processus de chargement s'amorce.

Les ObjBitmap sont des objets graphiques qui peuvent avoir pour origine un fichier graphique d'un des formats suivants : BMP, JPEG ou TARGA. Dans un autre tutoriel, on verra qu'on peut aussi en créer de toutes pièces.
Les AlphaBitmaps sont eux-aussi des objets graphiques qui peuvent avoir pour origine un fichier graphique de format PNG.

Les contenus de ces deux types d'objets peuvent être créés de toutes pièces sans l'intervention de fichier graphique et des passerelles permettent d'aller d'un type d'objet à un autre ou encore de créer un AlphaBitmap d'après un ObjBitmap, etc. Cependant, ce sont deux types bien distincts en Scol, il n'est donc pas possible de les interchanger dans une fonction ou une variable. C'est pourquoi, ici, on fait appel à une fonction "fille" pour traiter les PNG éventuels.

On vérifie l'extension du fichier. Ce n'est pas une méthode particulièrement sure mais suffisante ici. S'il s'agit d'un PNG, on appelle la fonction 'chargerUnAlphaBitmap' sinon on fait exécute la fonction Sol '_LDbitmap' qui charge un fichier BMP.
Si le fichier n'est pas un BMP, la fonction retourne nil et on exécute alors la fonction '_LDjpeg' qui charge un fichier JPEG.
Si le fichier n'est pas un JPEG, la fonction retourne nil et on exécute alors la fonction '_LDtga' qui charge un fichier TARGA.
Si le fichier n'est pas un TARGA, la fonction retourne nil et aucun bitmap n'est finalement créé.

Son type est fun [P] ObjBitmap

fun chargerUnBitmap(fichierP)=
	if fichierP == nil then
		nil
	else if verifExtension _PtoScol fichierP ".png" then
		chargerUnAlphaBitmap fichierP
	else
		let _LDbitmap _channel fichierP -> bmp in
		if bmp == nil then
			let _LDjpeg _channel fichierP -> bmp in
			if bmp == nil then
				_LDtga _channel fichierP
			else
				bmp
		else
			bmp;;

Callback de rafraichissement de la fenêtre de l'application.

La fonction Scol '_CLRwindow' permet de supprimer le fond graphique éventuel d'une fenêtre. La fonction Scol '_BLTbitmap' permet quant à elle d'intégréer un fond graphique à une fenêtre, ce fond graphique étant un bitmap dont le coin supérieur gauche sera placé à x et y pixels du coin supérieur gauche de la fenêtre (ici, à 0 et 0 pixel du bord).

Son type : fun [ObjWin [ObjWin ObjMenu ObjBitmap]] ObjWin

fun paint(fenetre, z)=
	let z -> [win _ bmp] in
	if bmp == nil then
		_CLRwindow win
	else
		_BLTbitmap win bmp 0 0;;

Callback de redimensionnement de la fenêtre par l'utilisateur.

Lorsque la fenêtre est redimensionnée, on la repeint (on la rafraichit). 'largeur' et 'hauteur' représente respectivement les nouvelles lageur et hauteur de la fenêtre.

Son type : fun [ObjWin [ObjWin ObjMenu ObjBitmap] I I] I

fun resize(fenetre, z, largeur, hauteur)=
	paint nil z;
	0;;

On ferme l'interface et on détruit l'application.
Dans la mesure du possible, essayez de détruire explicitement tous les objets utilisés qui sont encore actifs au moment de la fermture, notamment les bitmaps (ObjBitmap).

Le typage de cette fonction est fun [ObjWin [ObjWin ObjMenu ObjBitmap]] I

Notez une subtilité :
ici, l'argument 'fenetre' et le premier élément du tuple 'z' non seulement ont le même type mais représente exactement le même objet. Cela ne pose pas de problème particulier. Mais ce n'est pas toujours le cas. En effet, lorsque cette callback 'end' a été définie (cf fonction 'setCallbacks'), il existait une certaine fenêtre et c'est sur cet objet précis qu'est associé la callback.
Or, au niveau du tuple, celui-ci a été de fonction en fonction et sa valeur aurait pu être modifiée (par exemple, on aurait pu créer une fenêtre fille et associée cette dernière au premier élément du tuple via un 'mutate') auquel cas, si, initialement, les deux arguments étaient identiques, il ne le serait plus à l'arrivée ...
Là encore, ce n'est pas problématique et aucune erreur de compilation se serait produite mais les effets observés, eux, peuvent être différents (par exemple, on aurait voulu que seule la fenêtre fille soit fermée alors que c'est toute l'application qui serait détruite).

Autre remarque :
voilà, à aucun moment on a eu besoin de passer par une variable globale pour utiliser, dans tous les traitements supportés par l'application, les 3 objets contenus de le tuple 'z'. Et ce, de la création à la destruction de l'application.

fun end(fenetre, z)=
	let z -> [_ menu bmp] in
	(
	 _DSmenu menu;
	 _DSbitmap bmp;
	);
	_closemachine;;

Lorsqu'on valide la boite de dialogue d'ouverture de fichiers, la callback est appelée. Elle donne le chemin du fichier sélectionné. La fonction Scol 'mutate' est expliquée dans les commentaires de la fonction 'close'.

On fait appel à une fonction préalablement définie 'chargerUnBitmap' avec comme argument, le fichier choisi. En retour on aura un ObjBitmap. On remplace alors dans le tuple 'z', l'ancien bitmap par le nouveau ('newbmp').

Le typage de cette fonction est fun [[OpenBox [ObjWin ObjMenu ObjBitmap] P] ObjWin

    En effet :
  • dlg -> l'objet boite de dialogue (OpenBox)
  • z -> notre tuple "pseudo-local" que nous nous trimballons depuis le début ([ObjWin ObjMenu ObjBitmap])
  • filep -> la référence en lecture du fichier choisi (P)
  • et retourne l'objet fenêtre de la fonction '_paint'.

Notez qu'il aurait été mieux de retourner un entier, comme indiqué précédemment, plutôt qu'un objet fenêtre qui nous est d'aucune utilité ici.

fun load(dlg, z, filep)=
	let z -> [win menu bmp] in
	let chargerUnBitmap filep -> newbmp in
	mutate z <- [_ _ newbmp];
	paint nil z;;

Cette fonction supprime le bitmap de la fenêtre.
Pour cela on le détruit grâce à la fonction Scol '_DSbitmap'. C'est une bonne habitude de détruire explicitement un bitmap dès qu'on en a plus besoin.

'mutate' est une fonction très utile mais aussi très dangeureuse. Elle remplace certains (voire tous les) éléments d'un tuple sans toucher aux autres. Par exemple, le tuple

	let ["a" "c" "c"] -> tuple in
	...
	mutate tuple <- [_ "b" _];

On a remplacé la valeur du 2e élément : de "c" elle est passée à "b". Le tuple vaut désormais ["a" "b" "c"]. Là où il y a des undescores, les éléments restent inchangés. Remarquez également le sens de la "flèche", de la gauche vers la droite, ce qui est logique.
Bien sur, le typage du tuple doit être respecté ! 'mutate <- [_ 2 _] ' provoquera une erreur ! Autre exemple :

	let [a b c] -> tuple in
	let 2*b -> d in
	...
	mutate <- [a d _];

Ici, on a un tuple composé de trois variables a, b et c. On a une autre variable, d, définit comme valant le double de b (ce qui implique que b est un entier donc de type I, le type de a et c restant indéterminé).
Puis on remplace le 2e élément du tuple par d, comme lors du premier exemple. Mais on a aussi remplacé le 1er élément par la même variable. Si la vairable a n'a pas subi de traitement entre temps, le 1er élément restera inchngé sinon il sera modifié. De plus, 'mutate' modifie toutes les variables qui pointent directement ou non vers le tuple. C'est parfois très utile, c'est parfois très dangereux ...

Note : 'mutate' ne fonctionne qu'avec des tuples. Pour modifier une variable unique, il faut la placer dans un tuple de taille 1 :

	let [mavariable] -> tuple in
	...
	mutate tuple <- [mavariable];

Cela peut paraître idiot (on pourrait modifier directement 'mavariable' mais dans un certain nombre de cas, c'est la seule solution !

Le type de la fonction 'close' est fun [[ObjWin ObjMenu ObjBitmap]] I

	fun close(z)=
	let z -> [win menu bmp] in
	let _DSbitmap bmp -> _ in
	mutate z <- [win menu nil];
	paint nil z;
	0;;

Cette fonction est la callback du menu de l'application : lorsqu'on clique sur un item, cette fonction est appelée.

Ces arguments sont l'objet menu lui-même et le paramètre utilisateur. Lors de la définition de la callback (cf 'setMenu'), on a vu que le type de ce paramètre utilisateur était [S [ObjWin ObjMenu ObjBitmap]] ce qui est bien traduit par let u -> [item z] in, 'z' étant ensuite décomposé grâce à let z -> [win _ _] in (les underscores indiquent que les autres éléments du tuple sont bien présents mais qu'on ne s'en sert pas). On peut appeller 'z' d'un autre nom mais c'est plus commode de garder son nom initial pour un meilleure compréhension du code.

'strcmp' est une fonction Scol qui compare deux chaines de caractères. Elle est sensible à la casse. Par exemple 'strcmp "mon texte" "mon texte" renvoie 0 (= identique) alors que ce ne sera pas le cas avec 'strcmp "mon texte" "Mon texte".
Pour une comparaison insensible à la casse, utiliser la fonction Scol 'strcmpi'.
Donc, si les deux chaînes sont identiques, cette fonction renvoie la valeur 0. Pour alléger le code, on lui applique une NEGATION logique : dans ce cas, ce qui ne vaut pas 0 (négation logique) vaut 1. C'est pourquoi on trouvera souvent l'expression '!strcmp chaine1 chaine2' plutôt que 'strcmp chaine1 chaine2', mais c'est juste une histoire d'habitudes et de préférences. Ainsi, grâce à l'utilisation d'une NEGATION logique ('!') sur le résultat de 'strcmp', on obtient une valeur '1' si l'expression est vraie.

La boucle IF permet de faire intervenir des conditions dans une routine Scol.

La boucle IF est une instruction à part entière (rigoureusement une fonction mais ce n'est pas l'objet de ces tutoriaux de rentrer dans ces détails), éventuellement composés de sous-blocs de plusieurs instructions.

Ainsi, la syntaxe d'une boucle IF est la suivante :

	
	IF condition THEN
		sous-bloc d'instructions
	ELSE
		sous-bloc d'instructions;

ou

	
	IF condition THEN
		sous-bloc d'instructions
	ELSE IF condition2 THEN
		sous-bloc d'instructions
	ELSE IF condition3 THEN
		sous-bloc d'instructions
	...
	ELSE IF conditionN THEN
		sous-bloc d'instructions
	ELSE
		sous-bloc d'instructions;

Par exemple :

	
	if A>10 then
		_fooS "A est supérieur à 10"
	else if A==10 then
		_fooS "A est égal à 10"
	else
		_fooS "A est inférieur à 10";

Le 'else' est impératif !
Il n'y a pas de ';' à la fin d'un sous-bloc d'instruction puisque c'est l'ensemble de la boucle qui forme une instruction. En revanche, à la fin de la boucle, on trouve bien le ';' qui clôt cette instruction. Attention ! Chaque sous-bloc doit impérativement retourné le même type !

Ici, les conditions portent sur une comparaison de chaînes via la fonction Scol 'strcmp'.

Le type de cette fonction est fun [ObjMenu [S [ObjWin ObjMenu ObjBitmap]]] I

En effet, 'm' est le menu créé précédemment, de type ObjMenu
'u' est le paramètre utilisateur : il s'agit d'un type contenant une chaine de caractères donc un type S et un autre tuple, le fameux 'z', donc le type est toujours [ObjWin ObjMenu ObjBitmap].

	
fun cbMenu(m, u)=
	let u -> [item z] in
	let z -> [win _ _] in
	if !strcmp item "bmp ouvrir" then		// 'item' est identique à "bmp ouvrir"
		(
		 _DLGrflopen _DLGOpenFile _channel	win nil	nil	"BITMAPS\0*.bmp;*.jpg;*.tga;*.png\0\0" @load z;
		 0
		)
	else if !strcmp item "bmp fermer" then	// 'item' est identique à "bmp fermer"
		close z
	else if !strcmp item "app quitter" then	// 'item' est identique à "app quitter"
		end nil z
	else									// tous les autres cas
		0;;

Cette fonction définit les callbacks de la fenêtre de l'application : destruction, rafraîchissement et redimensionnement.

'z' se perpétue toujours en étant passé comme argument de fonction. Tant que ces callbacks ne sont pas explicitement purgées, 'z' sera connu.

Le commentaire de 'setMenu' reste valable ...

On appelle la fonction 'paint' (définie en amont comme il se doit !) afin d'afficher tout de suite dans la fenêtre le bitmap chargé précédemment.

On note le '0' final pour avoir un retour avec un type simple I et une valeur simple '0' !

Le type de cette fonction est fun [[ObjWin ObjMenu ObjBitmap]] I

	
fun setCallback(z)=
	let z -> [win _ _] in
	(
	 _CBwinDestroy win @end z;
	 _CBwinPaint win @paint z;
	 _CBwinSize win @resize z;
	 paint nil z;
	 0
	);;

Cette fonction ajoute les items du menu créé précédemment puis demande l'exécution de la fonction 'setCallback'. Voir le commentaire de 'createInterface' au sujet de l'argument 'z'.

let z -> [_ menu _] in : on déconstruit 'z' afin de récupérer les éléments qu'il contient. On sait que le type de 'z' (cf commentaire de 'createInterface') est de type [ObjWin ObjMenu ObjBitmap]. Ici, on a besoin que de l'objet menu. Pour ne pas s'encombrer de variables qui nous intéressent pas mais qui existent néanmoins dans ce tuple, on place un '_' (underscore).
Bien sur, comme le mot clé 'let' est utilisé, 'menu' est une variable locale et sa portée se limite uniquement à l'instruction suivante. C'est pourquoi des parenthèses sont utilisées : on forme UNE instruction composé d'un bloc de plusieurs instructions.

Les variables locales 'menuBmp' et 'menuAbmp' sont plus "classiques" : on les déclare pour les utiliser dans l'instruction suivante (le bloc constitué par les parenthèses), point barre. Elles seront ensuite perdues (purgées de la mémoire) et on ne pourra plus y accéder.

La callback définie par '_CBmenu' prend, comme paramètre utilisateur, un tuple composé d'une chaîne et de z ([ "bmp ouvrir" z ]). La chaine indiquera sur quel item du menu l'utilisateur a cliqué. Ce tuple est donc de type [S [ObjWin ObjMenu ObjBitmap]].

Le type de 'setMenu' est donc fun [[ObjWin ObjMenu ObjBitmap]] I

	
fun setMenu(z)=
	let z -> [_ menu _] in
	let _APPpopup _channel menu "BITMAPS" -> menuBmp in
	let _APPpopup _channel menu "APPLICATION" -> menuAbmp in
	(
	 _CBmenu 
		_APPitem _channel menuBmp ME_ENABLED "Ouvrir" 
		@cbMenu 
		[ "bmp ouvrir" z ];
	 _CBmenu
		_APPitem _channel menuBmp ME_ENABLED "Fermer" 
		@cbMenu 
		[ "bmp fermer" z ];
		
	 _CBmenu
		_APPitem _channel menuAbmp ME_ENABLED "Quitter" 
		@cbMenu 
		[ "app quitter" z ];
		
	 setCallback z
	);;

Cette fonction crée l'interface principale de l'application : sa fenêtre et un menu. On en profite pour charger un bitmap (le logo scol présent à la racine de la partition Scol). Ainsi, il devient possible de définir une variable locale 'z' (un nom comme un autre souvent utilisé dans ce cas) dont la portée sera artificiellement augmentée en la passant de fonction en fonction via un argument.

La variable locale 'z', de type [ObjWin ObjMenu ObjBitmap] (win menu bmp), est passée comme paramètre de la fonction 'setMenu' lors de son appel. On peut en effet la passer en paramètre car l'appel de 'setMenu' est l'instruction qui suit immédiatement sa définition.
Ensuite, dans la fonction 'setMenu' (ci-dessus), comme elle est passée en argument, la portée d'un argument, quel qu'il soit, est la fonction entière. On peut ainsi perpétuer l'utilisation de 'z'. Il est alors possible de modifier le contenu de 'z' plus tard dans le code grâce à la fonction Scol 'mutate'. C'est montré dans ce package.
Cette notion subtile peut être développée en lisant le "Tutoriel Scol" version 3.0 de Sylvain Huet.

'_CRmenu' est une fonction Scol
'chargerUnBitmap' est une fonction que nous définissons dans ce package (cf plus haut)
Il en est de même de 'setMenu'.

Le type de 'createInterface' est fun [] I

	
fun createInterface()=
	let _CRwindow _channel nil 0 0 800 600 WN_NORMAL " tutoriel 2" -> win in
	let _CRmenu _channel win -> menu in
	let chargerUnBitmap _checkpack "logo.bmp" -> bmp in
	let [win menu bmp] -> z in
	setMenu z;;

Fonction qui appelée par le script de lancement du programme (launch.scol dans le même dossier que ce fichier).

La fonction 'createInterface' est appelée.
Le type de 'main' est fun [] I (cf tutoriel 1)

	
fun main()=
	createInterface;;