Retour Les fenêtres

C reparti!

Oups, certains des listings de l'article précédent ne se compilent pas avec Dice. Son éditeur de lien affichant :

DLINK: "" L:0 C:0 Error:34 Multiply defined symbol: _SysBase (amiga/c & T:Article)

Ca m'a échappé car je les ai compilés uniquement avec GCC qui permet ce genre d'excentricités.
En fait, il faut savoir que les compilateurs ouvrent d'eux-même certaines librairies, simplement car elles sont utilisées avant que notre main() ne soit appelée : C'est ce qu'on appelle le code de démarrage; entre autres, c'est lui qui gère les arguments du programme.

Dans notre cas, SysBase est déjà utilisé, donc le linker crie car il est à la fois défini dans notre programme et dans son code de démarrage.
Bref, inutile de refaire ce qui a déjà été fait par le compilateur, donc toute la partie initialisation de SysBase est superflue, c'est pourquoi elle n'apparait plus dans les listings de ce mois.
Attention, ce n'est pas toujours vrai car, en cherchant bien dans la doc de votre compilo, vous découvrirez que certaines options empêchent l'utilisation du code de démarrage, dans le but d'obtenir un exécutable de très petite taille. Dans ce cas, il ne faudra pas oublier d'initialiser SysBase sinon ...

La Dos.librarie fait aussi partie des librairies qui sont forcément ouvertes pour nous.

Dans le même genre, certains compilos dont Dice n'apprécient pas la ligne


        char buff[longueur + 5];    /* Buffer de travail */

du programme de notre GURU : ils n'admettent pas que la taille du buffer à allouer soit calculée lors de l'exécution.
La solution est donc d'y mettre une longueur constante, donc la ligne devient

        char buff[1024];

Note : il est évident que si l'utilisateur passe un message de plus de 1019 caractères, il sera trop grand pour être stocké d'où un plantage assuré. Je vous laisse le soin d'ajouter le test qui convient...

Bon passons à l'article du jour.

Qui dit environnement graphique, dit inévitablement fenêtre. On a vu dans le dernier article que certains compilateurs en ouvraient une automatiquement si le programme effectuait des sorties sur le flux de sortie standard, stdout, et s'il était lancé depuis le workbench.
Aujourd'hui nous allons voir comment créer nous-même une fenêtre.

Tous les éléments graphiques de l'Amiga sont gérés par la fameuse "Intuition.library", que nous avons déjà utilisée pour créer notre Guru perso. C'est elle qui gère les écrans, les fenêtres, les alertes mais aussi les requêtes, et les boutons de toutes sortes.

Pour définir notre fenêtre, il faut initialiser une structure nommée NewWindow qui est définie par un : #include <intuition/intuition.h> et dont voici les champs :


struct NewWindow {
	WORD LeftEdge, TopEdge;     /* Origine de la fenêtre */
	WORD Width, Height;         /* Sa taille initiale */

	UBYTE DetailPen, BlockPen;  /* Crayons (couleurs) utilisés */
	ULONG IDCMPFlags;           /* Evennements attendus */
	ULONG Flags;                /* Drapeaux (on vera plus loin) */
	struct Gadget *FirstGadget; /* Boutons persos */

	struct Image *CheckMark;    /* Notre propre image pour les marques des menus */

	UBYTE *Title;               /* Titre de la fenêtre */
	struct Screen *Screen;      /* Sur quel écran elle s'ouvre */
	struct BitMap *BitMap;      /* Notre propre bitmap */

	WORD MinWidth, MinHeight;   /* Taille minimale */
	UWORD MaxWidth, MaxHeight;  /* Taille maximale */

	UWORD Type;                 /* Type de la fenêtre */
};

Comme dit l'adage, un petit exemple valant mieux qu'un long discours, voici un petit listing qui crée notre première fenêtre.


#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>	/* Ces 3 là ne nous servent à rien mais évitent */
#include <exec/semaphores.h>	/* des erreurs à la compilation */
#include <exec/io.h>		/* dans exec_protos.h */
#include <clib/exec_protos.h>
#include <exec/types.h>
#include <clib/intuition_protos.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>		/* Pour le sleep() */

	/*
	 *  On n'oublie pas les bonnes vielles habitudes :
	 *  Faut faire le ménage
	 */
struct ExecBase         *SysBase;
struct IntuitionBase    *IntuitionBase=NULL;
struct Window           *ma_fenetre=NULL;

void fini(){
	if(ma_fenetre){
		CloseWindow(ma_fenetre);
		ma_fenetre = NULL;
	}

	if(IntuitionBase){
		CloseLibrary((struct Library *)IntuitionBase);
		IntuitionBase = NULL;
	}
}

	/*
	 *  Définition de la nouvelle fenêtre
	 */
struct NewWindow nouvelle_fenetre = {
		10,  50,        /* Origine de la fenêtre */
		200, 30,        /* Taille de la fenêtre */
		-1, -1,         /* Couleurs par défaut */
		0,              /* Pas d'évenements attendus */
		WFLG_NOCAREREFRESH, /* Le système gérera les rafréchissements */
		NULL,           /* Y'a pas de gadget dans la fenêtre */
		NULL,           /* On utilise l'image standard */
		"Salut",        /* Titre de la fenêtre */
		NULL,           /* Elle apparaitra sur l'écran par defaut */
		NULL,           /* Le système gére les bitmaps */
		50,50,400,500,  /* Taille minimale et maximale de la fenêtre */
		WBENCHSCREEN    /* C'est une fenêtre pour le workbench */
};

int main(int ac, char **av){
	SysBase = *(struct ExecBase **)4;   /* Recherche d'exec */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0))){
		puts("Impossible d'ouvrir intuition.");
		exit(EXIT_FAILURE);
	}
	atexit(fini);

	if(!(ma_fenetre = OpenWindow( &nouvelle_fenetre ))){
		puts("La fenêtre ne veut pas s'ouvrir.");
		exit(EXIT_FAILURE);
	}

	sleep(10);  /* La fenêtre restera ouverte 10 secondes */

	exit(EXIT_SUCCESS);	/* On laisse fini() fermer tout ce qui doit l'être */
}

Vous aurez bien sûr remarqué que ce programme doit se lancer depuis le Shell (bon, pour la dernière fois, je rappelle qu'il utilise des 'puts()' pour afficher ses erreurs).
On remarque au début du listing, tout le bla-bla habituel, c'est-à-dire les includes qui définissent les librairies et les structures utilisées, les variables globales pour stocker les pointeurs de ces mêmes librairies et un autre pointeur sur la structure de la nouvelle fenêtre créée.
Attention : Contrairement aux ressources allouées par les librairies du compilateur, comme par exemple la mémoire d'un malloc(), les éléments du système ne sont jamais détruits automatiquement. Ainsi, si vous supprimez le atexit(), la fenêtre restera ouverte après que le programme ait terminé son exécution. Ce n'est pas dangereux mais ça fait un peu désordre !

Au passage, on vient de découvrir une nouvelle fonction :


        void Delay( ULONG ticks );

Elle fait partie de la dos.librairie et ne rend la main qu'une fois le délai indiqué passé. L'unité de ce délai est le ticks qui vaut 1/50eme de seconde.
Les autodocs indique cette valeur mais je me demande si le ticks ne vaut pas 1/60eme de seconde sur les machines NTSC...
Ici nous souhaitons une attente de 10 secondes donc l'argument vaut 10*50 = 500.

Pour créer des attentes certains programmeurs utilisent des boucles d'attente genre


int i;
for(i=0; i<32000; i++);

C'est A PROSCRIRE :

Delay() utilisant le timer.device, la durée d'attente est stable quelle que soit le système utilisé et n'utilise pas du tout le CPU.

M'enfin, c'est bien, nous venons de créer une fenêtre de 200 par 30 pixels qui se ferme automatiquement après 10 secondes. Génial non ?

img1.jpg

Pour le moment, on laisse tomber la majorité des champs de la structure NewWindow qui seront abordés le moment venu (toujours des promesses hein ?), en particulier le 'IDCMPFlags' car la gestion des événements nécessite un (voire même plusieurs) article(s) à elle seule.

Regardons par contre de plus près les "Flags" utilisables.
Pour le moment, aucune modification ne nous est permise sur cette fenêtre : elle est là, ne bouge pas, ne peut être renommée et aucune action sur elle-même ne nous permet de la faire passer en avant ou en arrière-plan. Ce n'est pas vraiment vivant !

Pour que ce soit possible, il suffit d'indiquer au système d'ajouter ces propriétés grâce aux flags suivants :

Il existe aussi d'autres flags qui peuvent être mis dans ce champ :

Bon, on va rendre notre fenêtre un peu plus sympathique, en lui permettant de passer devant ou derrière les autres, de bouger et d'être redimensionnable, le tout en ne modifiant qu'une seule ligne : celle des flags !


struct NewWindow nouvelle_fenetre = {
		10,  50,        /* Origine de la fenêtre */
		200, 30,        /* Taille de la fenêtre */
		-1, -1,         /* Couleurs par défaut */
		0,              /* Pas d'évenements attendus */
		WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_SIZEGADGET |
		WFLG_NOCAREREFRESH, /* Le système gérera les rafréchissements */
		NULL,           /* Y'a pas de gadget dans la fenêtre */
		NULL,           /* On utilise l'image standard */
		"Salut",        /* Titre de la fenêtre */
		NULL,           /* Elle apparaitra sur l'écran par defaut */
		NULL,           /* Le système gére les bitmaps */
		50,50,400,500,  /* Taille minimale et maximale de la fenêtre */
		WBENCHSCREEN    /* C'est une fenêtre pour le workbench */
};

Pour ceux qui ne maîtrisent pas entièrement le C, je ferai remarquer que les flags sont associés entre eux par des OU bit à bit. Certains programmeurs utilisent de simples additions mais cette pratique est à proscrire sinon un jour ou l'autre vous aurez de mauvaises surprises ...

La fenêtre correspondante est :

img2.jpg

A vous de faire des expériences avec les autres flags disponibles, à l'exception bien sûr de WFLG_NW_EXTENDED sinon gare au réveil du grand "méditeur". Les drapeaux ayant trait aux rafraîchissements et à la souris n'auront aucun effet visible car concernant la gestion des événements (mais je l'ai déjà dit, non ?).

Quelques considérations sur les tailles des fenêtres.

Si la fenêtre peut être redimensionnée, les champs MinWidth, MinHeight et MaxWidth, MaxHeight doivent être renseignés. Ils contiennent les tailles minimales et maximales de la fenêtre. Une valeur nulle indique qu'il faut prendre la taille définie par défaut comme valeur.
Ainsi, si MinHeight et MaxHeight sont nuls, il sera impossible de changer la hauteur de la fenêtre.

Attention : l'autodoc de OpenWindow() précise bien que "si il n'y a aucune raison de limiter la taille d'une fenêtre, il ne faut pas le faire" (Lapalisse n'aurait pas dit mieux !).
C'est pourquoi, si un champ a la valeur ~0 (ou -1), la seule limite qu'aura à subir la fenêtre sera la taille physique de l'écran sur lequel elle se trouve.

A partir du 2.0, on peut faire mieux avec les tags.

Le 2.0 n'a pas fait que chambouler l'aspect graphique du système, mais a aussi amené de nombreuses améliorations pour le programmeur. En particulier, les développeurs de Commodore ont ajouté une méthode plus souple que les structures pour spécifier les paramètres.
Au lieu de définir une structure, avec toute la lourdeur que ça implique, en particulier d'initialiser correctement les champs que nous n'utilisons pas, pourquoi ne pas spécifier uniquement les valeurs qui nous intéressent > Ainsi sont apparus les fameux tags.

Reprenons notre fenêtre, mais utilisons entièrement la méthode du 2.0.
Voici le nouveau listing :


#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>	/* Ces 3 là ne nous servent à rien mais évitent */
#include <exec/semaphores.h>	/* des erreurs à la compilation */
#include <exec/io.h>		/* dans exec_protos.h */
#include <clib/exec_protos.h>
#include <exec/types.h>
#include <clib/intuition_protos.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>		/* Pour le sleep() */

	/*
	 *  On n'oublie pas les bonnes vielles habitudes :
	 *  Faut faire le ménage
	 */
struct ExecBase         *SysBase;
struct IntuitionBase    *IntuitionBase=NULL;
struct Window           *ma_fenetre=NULL;

void fini(){
	if(ma_fenetre){
		CloseWindow(ma_fenetre);
		ma_fenetre = NULL;
	}

	if(IntuitionBase){
		CloseLibrary((struct Library *)IntuitionBase);
		IntuitionBase = NULL;
	}
}

int main(int ac, char **av){
	SysBase = *(struct ExecBase **)4;   /* Recherche d'exec */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",36))){
		puts("Impossible d'ouvrir intuition.");
		exit(EXIT_FAILURE);
	}
	atexit(fini);

	if(!(ma_fenetre = OpenWindowTags( NULL,
		WA_Left, (Tag)10,
		WA_Top, (Tag)50,
		WA_Width, (Tag)200,
		WA_Height, (Tag)30,
		WA_Title, (Tag)"Salut",
		WA_NoCareRefresh, TRUE,
		TAG_DONE
	))){
		puts("La fenêtre ne veut pas s'ouvrir.");
		exit(EXIT_FAILURE);
	}

	sleep(10);  /* La fenêtre restera ouverte 10 secondes */

	exit(EXIT_SUCCESS); /* On laisse fini() fermer tout ce qui doit l'être */
}

Première remarque : ce coup-ci, on indique que l'on recherche l'Intuition.library de version 36 ou supérieure, c'est-à-dire qu'il faut au moins que l'on soit sous 2.0.
Pour le 3.0, la version aurait été 39 et 40 pour le 3.1. Même si les fans des univers micro$oftiens soutiennent que c'est impossible, remarquez que ceci préserve de fait une compatibilité ascendante : comme on ne peut rechercher une version spécifique, toute évolution de la librairie devra faire tourner les applis prévues pour une version antérieure. Ce n'est pas une option, c'est un postulat (c'est beau non comme phrase, hein ?).

Le meilleur, c'est que ça fonctionne très bien et que les problèmes d'incompatibilité lorsque l'on met à jour son système sont très rares. Hormis bien sûr les jeux et les démos, dans ma vie d'Amigaïste (depuis 88 tout de même), je n'ai eu que 2 problèmes :

De quoi faire méditer les défenseurs des windowseries, où la mise à jour de Word (par exemple) entraîne la mise à jour de DLL pour les liens OLE ... qui ne fonctionneront plus avec d'autres applis. Mais bon, je m'éloigne du sujet !

Revenons à nos moutons, heu, à notre fenêtre.

Nous utilisons une nouvelle fonction introduite par le 2.0, OpenWindowTags(). Le premier argument devrait être une structure NewWindow comme avec le 1.3. Comme elle nous est inutile, on n'en donne pas (simple non; j'ai essayé de faire le parallèle avec mes impôts mais mon percepteur, sans doute un fan de M$, n'a pas vraiment apprécié !). Jetez un oeil dans le fichier intuition/intuition.h des includes système et vous y découvrirez que chaque champ de NewWindows a son équivalent sous forme de tag mais d'autres permettent aussi d'ajouter de nouvelles caractéristiques. En voici quelques uns :

Il y'en a d'autres (beaucoup d'autres) qui permettent de changer la représentation de la touche Amiga des menus, différentes formes de pointeurs et tout ce qui concerne l'implémentation de l'aide en ligne.

Et la portabilité ?

Problème, avec notre listing précédent, notre programme ne fonctionne plus sur les Amiga sous 1.3 ! Heureusement, à nouveau les concepteurs de l'AmigaOS ont bien fait les choses puisqu'il existe une méthode permettant de générer un programme qui fonctionnera sous 1.x, mais qui en présence du 2.0 gagnera de nouvelles possibilités.
Voici le dernier listing :

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>	/* Ces 3 là ne nous servent à rien mais évitent */
#include <exec/semaphores.h>	/* des erreurs à la compilation */
#include <exec/io.h>		/* dans exec_protos.h */
#include <clib/exec_protos.h>
#include <exec/types.h>
#include <clib/intuition_protos.h>
#include <stdlib.h>
#include <stdio.h>

#ifndef __DCC__
#include  /* Pour le sleep() */
#endif

	/*
	 *  On n'oublie pas les bonnes vielles habitudes :
	 *  Faut faire le ménage
	 */
struct ExecBase         *SysBase;
struct IntuitionBase    *IntuitionBase=NULL;
struct Window           *ma_fenetre=NULL;

	/*
	 *  Définition de la nouvelle fenêtre
	 */
WORD stockage_zoom[4] = { 20, 30, 50, 10 };

struct TagItem tags[] = {
	{ WA_Zoom, (Tag)&stockage_zoom },
	{ WA_ScreenTitle, (Tag) "Hô, c'est ma fenêtre qui est active !" },
	{ TAG_DONE, NULL }
};

struct ExtNewWindow nouvelle_fenetre = {
		10,  50,        /* Origine de la fenêtre */
		200, 30,        /* Taille de la fenêtre */
		-1, -1,         /* Couleurs par défaut */
		0,              /* Pas d'évenements attendus */
		WFLG_NW_EXTENDED |	/* C'est une structure étandue */
		WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_SIZEGADGET |
		WFLG_NOCAREREFRESH, /* Le système gérera les rafréchissements */
		NULL,           /* Y'a pas de gadget dans la fenêtre */
		NULL,           /* On utilise l'image standard */
		"Salut",        /* Titre de la fenêtre */
		NULL,           /* Elle apparaitra sur l'écran par defaut */
		NULL,           /* Le système gére les bitmaps */
		50,50,400,500,  /* Taille minimale et maximale de la fenêtre */
		WBENCHSCREEN,	/* C'est une fenêtre pour le workbench */
	tags
};

void fini(){
	if(ma_fenetre){
		CloseWindow(ma_fenetre);
		ma_fenetre = NULL;
	}

	if(IntuitionBase){
		CloseLibrary((struct Library *)IntuitionBase);
		IntuitionBase = NULL;
	}
}

int main(int ac, char **av){
	SysBase = *(struct ExecBase **)4;   /* Recherche d'exec */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0))){
		puts("Impossible d'ouvrir intuition.");
		exit(EXIT_FAILURE);
	}
	atexit(fini);

	if(!(ma_fenetre = OpenWindow( (struct NewWindow *)&nouvelle_fenetre ))){
		puts("La fenêtre ne veut pas s'ouvrir.");
		exit(EXIT_FAILURE);
	}

	sleep(30);  /* La fenêtre restera ouverte 10 secondes */

	exit(EXIT_SUCCESS); /* On laisse fini() fermer tout ce qui doit l'être */
}

Remarquez qu'à nouveau, nous ouvrons n'importe quelle version d'intuition : ça marchera donc quelque soit la version du système, délire !

La portabilité : le mot de la fin.

Il ne faut pas non plus être plus royaliste que le roi : la compatibilité ne doit être conservée que lorsque cela ne bride pas trop le programme. Par exemple, inutile de continuer à développer pour le 1.x des programmes de gestion du système (par exemple, lorsque l'on gère les éléments du DOS comme les assigns), car on perdrait tout l'intérêt du 2.0 qui nous facilite beaucoup les choses. C'est d'ailleurs la politique d'Amiga International qui ne supporte plus le 1.x.
Par contre, sauf si des spécificités propres au 3.0 sont nécessaires comme les DataTypes, la cible du développement devrait être le 2.0, avec les extensions nécessaires pour les nouveautés des 3.0 et 3.1. L'utilisation de tags rend ce travail très trivial...
C'est généralement la voie prise par les développeurs de DP Amiga (moi y compris).

Voilà, voilà, c'est tout pour aujourd'hui, la prochaine fois on attaque les choses sérieuses en parlant, enfin, de la gestion des événements ... à nous les boutons, gadgets et autres joyeusetés. (hé ! allez chercher une serpillière, vous bavez !).

A nouveau, remercions mes correcteurs (Alain Liverneaux et Alexandre Stotzer) qui ont fait un travail éreintant pour sortir cet article de la nullité orthographique affligeante dans laquelle il était !