INITIATION A LA PROGRAMMATION GEM (1ere partie)

Olivier LANDEMARRE

I) Introduction

 Les articles qui vous seront proposés visent à donner les grandes lignes de la programmation GEM à un débutant. Ces articles seront probablement suivis d'articles plus poussés visant à montrer les possibilités fantastiques de ce système de fenêtrage lorsque l'on a un peu d'imagination! Le GEM est assez bas niveau, cela a ses avantages et ses inconvénients, et surtout pour ceux qui veulent apprendre à programmer de façon propre, GEM leur montrera les grands trucs que l'on ne voit pas sous un systême comme Windows.

  Si vous voulez comprendre comment peut marcher un systeme de fenêtrage, GEM est une approche royale, vous pourrez discuter avec le système sans passer par des couches de plus en plus haut niveau qui cachent de mieux en mieux le fonctionnement du système. Le WEB se prètant peu aux développements écrits importants, le style de ces articles sera plutôt télégraphique en ne présentant que le stricte nécessaire (que j'espère suffisant) à la compréhension.

II) Les outils de programmation

 La totalité des exemples proposés dans ces articles de programmation ont été compilés avec GCC (gratuit) pour Atari. Vous pouvez retrouver cet ensemble de programmation ici.

 Comme j'utilise la méthode ancienne de passage des paramètres timer pour evnt_multi() et evnt_timer(), il est nécessaire de récupérer le fichier osbind.h ici, que j'ai modifié pour que le flag OLDWAY marche correctement. Les exemples devraient pouvoir se compiler sans problème sur Mark Williams C et Laser C. Sur Pure C (Turbo C) il faudra sans doute adapter le passage des timers (un long au lieu de 2 int). Enfin pour le Sozobon C (gratuit) je ne sais pas comment les paramètres sont passés, mais il a la réputation d'être léger ce que n'est pas le cas pour GCC et donc pour les petites configurations certainement le plus apte sans avoir a dépenser d'argent à rendre service!

 Pour GCC il est nécessaire d'avoir une machine de plus de 4Mo de RAM libre sinon il ne marchera pas correctement dès que le programme prendra du volume. Pour l'utiliser il existe des shells texte, mais pour ma part je prefere les shells graphiques! A ma connaissance il existe deux bons produits dédié à GCC: GNUShell de Roland Schäuble que vous pourrez trouver sur tout bon serveur FTP (ftp.lip6.fr ex ftp Atari du Cnam) ou GCCShell d'Olivier Landemarre (de moi quoi!) que vous pourrez trouver ici.

 Le GEM est intimement lié aux "ressources" qui permettent en grande partie de donner l'aspect de l'interface, par la description des menus et boites de dialogue. Pour réaliser de manière simple cette interface un éditeur de ressource est nécessaire, il se chargera de vous faire dessiner l'interface puis de l'enregistrer dans un fichier et de créer les entêtes nécessaires à la programmation pour pouvoir le réutiliser via des fonctions standard de l'AES. Il en existe un en shareware: ORCS de Thorsten Otto qui est parfois un peu difficile à trouver sur les sites FTP (on le trouvera assurément sur shareware.com), sinon Interface 2  (commercial ) est certainement le programme le plus aboutit dans ce domaine, il permet en plus de créer des icônes couleur, mais attention le système ne sait pas l'utiliser il faut donc lui apprendre à le faire (des sources sont fournies avec le logiciel il me semble).

III) Options de compilation

Pour l'opération de compilation il sera nécessaire de forcer la taille des int à 16 bits grâce à l'option -mshort. Il s'en suit pour l'opération de link qu'il sera nécessaire d'utiliser la librairie GEM : gem16.olb

IV) Termes employés

AES : Système de fenêtrage du GEM

VDI : Système de gestion des graphiques

TOS: Tramiel Operating Système : l'équivalent du DOS qui comprend les sous groupes GEMDOS, BIOS et XBIOS. Nota: un programme dit TOS est un programme qui ne fait pas appel à l'AES.

Line A: Procédures bas niveau pour les graphiques, il ne vaut mieux pas l'utiliser quand on désire faire de la programmation GEM et passer plutôt par le VDI

V) Les invariables dans un programme GEM

IV.1) Variables systèmes à déclarer

 La première chose à faire pour pouvoir initialiser l'AES et le VDI, c'est déclarer quelques variables aux noms prédéfinis, ne pas les déclarer peut avoir des résultats surprenants et aléatoires, le compilateur ne refusera pas de compiler même si vous ne les déclarez pas!

Les variables sont:

int work_in[]= {1,1,1,1,1,1,1,1,1,1,2}, work_out[57]; /* c'est pour le VDI
*/
int contrl[12]; /* variables dans tous les cas pour GEM */
int intin[256], ptsin[256], intout[256], ptsout[256];

IV.2) Initialisation

 Une seule fonction est nécessaire: appl_init() elle retourne un int qui est le numéro de votre programme GEM, il n'y a pas de paramètres à cette fonction.

 Généralement on initialise le VDI pour nous permettre d'écrire dans les fenêtres (nous les étudieront dans un prochain article). Pour cela on ouvre une station virtuelle graphique à l'aide de la fonction v_opnvwk():

v_opnvwk(work_in,vdihandle,work_out) : avec int work_in[11], *vdihandle, work_out[57].

work_in sont des paramètres pour l'initialisation de VDI, je les détaillerais dans un autre article.

vdihandle : Valeur retournée par la fonction c'est le numéro de de station graphique ouverte, chaque appel à une fonction graphique du VDI nécessitera ce numéro.

work_out : renseignements retournés par la fonction sur la station graphique, je détaillerais dans un autre article.

La fonction ne retourne rien.

IV.3) Fin du programme

Fermeture de toutes les fenêtres si il y en a encore d'ouvertes (nous verrons cela dans un prochain article)

Si on a ouvert une station graphique VDI, il faut la fermer: v_clsvwk(*vdihandle) avec int *vdihandle, le numéro de la station retourné par v_opnvwk(). La fonction ne retourne rien.

Forcer le redraw du bureau par: wind_set(0,WF_NEWDESK,0,0,0);

Fermeture de l'AES par: appl_exit();

VI) Corps principal d'un programme GEM

 A ma connaissance tous les systèmes de gestion d'interface graphique tournent autour de la gestion de messages et de l'attente de ceux ci dans une boucle presque sans fin (il faut bien sortir du programme!). C'est plus ou moins visible suivant le système et les outils de programmation utilisés, dans les cas du GEM c'est on ne peut plus visible. En fonction des messages reçus le programme doit réaliser un certain nombre d'actions. Sur GEM il faut tout faire ! (rassurez vous des fonctions sont déjà là pour faire un peu de travail mais rien ne vous oblige à les utiliser si vous désirez que le programme réagisse d'une manière particulière). Rassurez vous la caractéristique de GEM c'est qu'il n'y a qu'un tout petit nombre de messages standard contrairement à d'autres, mais c'est tout à fait suffisant!

VI.1) Attente des messages

Deux fonctions de l'AES sont disponibles pour attendre les messages GEM:

evnt_mesag() et evnt_multi(). Si evnt_mesag() est très simple à utiliser et ne sait faire que l'attente des messages GEM, les programmeurs généralement ne l'utilisent pas car ils préfèrent utiliser la seconde fonction beaucoup plus puissante qui sait outre l'attente des messages, gére aussi les évènements clavier, souris, timer. Elle a par contre le défaut d'avoir un nombre fabuleux de paramètres (23!).

Pour illustrer notre article nous nous contenteront tout de même d'evnt_mesag().

Le corps du programme pourrait avoir l'aspect suivant:

int flag, buffer[8];
flag=1;
while(flag)
{
 (void)evnt_mesag(buffer);  /* la fonction ne ressort que si il y a un message c'est très économique en CPU, evnt_mesag() retourne toujours 1! */
 flag=fct_gere_mesag(buffer); /* notre fonction qui nous servira à gérer les messages */
}

VI.2) Gestion des messages

Mon but n'est pas ici de vous donner l'ensemble des explications des messages GEM, mais vous pouvez les retrouver ici.

evnt_mesag() ou evnt_multi() retourne un tableau de 8 int pour décrire le message.

Mot 0 : type du message

Mot 1 : numéro du programme qui a envoyé le message (si c'est le système c'est 0), mais les accessoires ou les autres programmes (dans les systèmes multitâches comme Multitos, Magic, Geneva, NAES...).

Mot 2 : nombres d'octets supplémentaires du message au delà des 16 de base (Nota tous les messages système sont dans les 16 octets de base mais il est possible entre process d'avoir des messages plus long, il faut alors relire le message avec la fonction appl_read(), nous n'en avons pas besoin pour le moment)

Mots 3 à 7 : Corps du message, son contenu dépend du message.

Pour illustrer les messages nous allons expliquer les messages AP_TERM (valeur 50) et MN_SELECTED (valeur 10).

AP_TERM = Terminaison de l'application, quand on reçoit ce message le programme GEM devrait normalement quitter (après avoir fait ses sauvegardes si besoin!), ce message est généralement envoyé quand le système va s'arrêter. Le mot 5 donne la raison (50 arrêt système, 51 changement de résolution)

MN_SELECTED = Un élément du menu a été sélectionné. Le mot 3 donne le numéro de l'objet titre de l'élément sélectionné (on ne s'en sert en général qu'a désélectionner ce titre une fois l'action réalisée), le mot 4 donne le numéro de l'élément sélectionné du menu.

Nous n'avons pas encore parlé des menus et plus généralement des ressources une des composantes essentielles de GEM, c'est ce que vous pouvez retrouver dans le chapitre VII.

Notre fonction fct_gere_mesag() pourrait alors avoir l'aspect suivant:

int fct_gere_mesag(buffer)  /* retourne 0 si l'on veut sortir du programme */
int *buffer;
{ int flag=1;
  switch(buffer[0])  /* le numéro de message à gérer*/
  {
   case AP_TERM: flag=0;
   break;
   case MN_SELECTED: flag=gere_menu(buffer[4]);
   break;
   default: /* on gèrera les autres messages une autre fois */
   break;   
  }
  return(flag);
}

VII) Ressources

 Les ressources dans GEM permettent de décrire l'interface graphique (boîtes, boutons, champs éditables, menus, icônes...), généralement ces ressources sont enregistrés dans un fichier séparé du programme que celui ci devra charger et reloger grâce à une fonction de l'AES. Ces fichiers ressource portent l'extension ".RSC". Leur intérêt est tout à fait évident, séparé du programme il est facilement lisible avec un éditeur de ressource et modifiable (attention à ne pas changer la structure tout de même!) pour principalement le traduire dans une autre langue.

 Pour illustrer l'article vous trouverez dans l'archive d'exemple le fichier ARTICLE1.RSC, nous allons l'utiliser pour expliquer les rudiments de gestion de ces ressources.

VII.1) Chargement et relogement du ressource

 Pour un ressource simple comme celui étudié (sans icônes couleur et d'objets USERDEF (objets utilisateurs)), cela est assuré par le système en une seule fonction : rsrc_load(). Il faut passer à cette fonction le nom du fichier ressource à charger (vous n'avez le droit qu'à charger qu'un ressource à la fois). Si le ressource n'a pu être chargé la fonction retourne 0.

 En fin de programme vous devrez libérer la mémoire du ressource, pour cela vous devrez utiliser la fonction rsrc_free() qui n'a pas de paramètres (c'est la principale raison qui limite en fait le nombre de ressources à 1 dans un programme)

VII.2) Récupération des adresses des arbres d'objet

 Pour utiliser les divers arbres d'objets enregistrés dans le ressource relogé en mémoire, il est nécessaire de passer par la fonction spécifique:

int rsrc_gaddr(option,num_arbre,&l_objet) avec int option, num_arbre; OBJECT *l_objet;

rsrc_gaddr() retourne 0 si la fonction n'a pas pu trouver l'objet désiré.

 Cette fonction possède plusieurs options pour accéder aux divers objets du ressource, pour retrouver un arbre d'objet l'option doit alors être égale à 0.

VII.3) Installation, utilisation et désinstallation d'un menu

VII.3.a) Installation du menu

 Le menu créé par l'éditeur de ressource, puis chargé en mémoire et dont on a obtenu l'adresse, peut être mis en place. Pour cela une fonction AES est nécessaire menu_bar(l_objet,option); OBJECT *l_objet; int option; Pour installer le menu option=1.

VII.3.b) Utilisation du menu

 L'AES envoi au programme le message MN_SELECTED comme nous avons pu le voir plus haut, le mot 4 du buffer contient l'élément sélectionné en fonction de cet élément vous pouvez réaliser votre action. Le système automatiquement a inversé vidéo l'objet titre de l'objet sélectionné (celui qui est sur la barre de menu), c'est alors au programme de remettre cet élément à l'état normal quand il en a fini avec le traitement du message.

Cela est réalisé grâce à la fonction menu_tnormal(le_menu,num_titre,option); avec OBJECT *le_menu; int num_titre, option;

le_menu = pointeur sur l'adresse de l'objet menu num_titre = le numéro de l'objet titre de la barre de menu à désélectionner

option = 1 -> mise en mode normal de l'élément

VII.3.c) Désinstallation du menu

Pour désinstaller le menu, la même procédure est utilisée pour son installation (menu_bar()) avec option=0

VII.4) Les boîtes de dialogue

Une boîte de dialogue est un ensemble d'objets pouvant comporter des champs éditables, des objets sélectionnables, des radios boutons, des icônes, du texte, des boutons... Ils peuvent, par exemple, renseigner le programme des désirs de l'utilisateur.

 Il est assez facile d'utiliser ces boîtes de dialogue en très peu de fonctions, mais disons le tout de suite l'AES n'excelle pas dans ce domaine, n'espérez pas réaliser une superbe interface avec ce que je vais vous apprendre immédiatement. Mais avec GEM, ce n'est pas un gros problème, il faut un peu d'imagination et un peu de code pour avoir un résultat intéressant, ca a été prévu par les développeurs du système. Mais ce sera pour un peu plus tard, d'autres notions sont nécessaires avant.

VII.4.a) Les étapes pour l'utilisation d'une boîte de dialogue

- Récupérer l'adresse de l'arbre d'objet constituant la boîte : voir rsrc_gaddr() (VII.2)

- Positionner la boîte

- Attente que l'écran soit prêt à être utilisé et réservation

- Réservation de l'espace écran pour la boîte

- Affichage de la boîte

- Libération de l'affichage écran

- Gestionnaire de boîte de dialogue de GEM

- Libération de l'espace écran

VII.4.b) Positionner la boîte de dialogue

Il est bien sur possible de positionner la boîte comme bon nous semble comme nous le verrons dans un article supplémentaire lorsque nous aborderons une utilisation plus avancée des ressources. Pour cet article on va se contenter de mettre la boîte de dialogue au centre de l'écran à l'aide de la fonction form_center(boite,&x,&y,&w,&h); avec: OBJECT *boite; int x,y,w,h;

*boite: pointeur sur l'adresse de la boîte de dialogue (obtenu par rsrc_gaddr())

x,y,w,h sont les positions et taille de la boîte telles qu'elles ont été réglées par la fonction.

VII.4.c) Attente que l'écran soit prêt à l'affichage et réservation de celui ci

 Dans les premiers systèmes GEM monotâche, mono-application, cette précaution avait peu d'importance car à part notre programme personne d'autre ne risquait de dessiner sur l'écran! Mais maintenant le GEM est multitâche préemptif donc multi-applications, applications qui comme vous ne rêvent que d'une chose dessiner à l'écran! Pour réglementer cette anarchie apparente il existe une fonction GEM qui permet d'informer le programme si un autre programme est en train de dessiner ou pas et de réserver cette espace le temps qu'il en a besoin. Nota: Les programmes non respectueux des normes GEM passent parfois outre ces recommandations, car rien n'empèche malgré tout de dessiner à l'écran!, le résultat est parfois étrange!

La fonction pour réserver l'écran est la suivante: wind_update(option) avec int option

Pour réserver l'écran option=BEG_UPDATE, la fonction retourne 0 si l'écran est déjà occupé dans ce cas il faut donc patienter le code pour cela peut être celui ci:

while(!wind_update(BEG_UPDATE));

VII.4.d) Réservation de l'espace écran pour la boîte

 Cela est à réaliser avec la fonction form_dial(). Cette fonction à plusieurs options pour la fonctionnalité souhaitée on doit écrire : form_dial(option,0,0,0,0,x,y,w,h); avec x,y,w,h les valeurs déja retournées par form_center() et int option=0

VII.4.e) Affichage de la boîte

Une seule fonction nécessaire objc_draw(), pour notre pb on peut écrire:

objc_draw(boite,objet,profondeur,x,y,w,h); avec OBJECT *boite; int objet, profondeur, x, y, w, h;

boite = pointeur sur l'adresse de l'arbre d'objet de la boîte.

objet = numéro de l'objet dans l'arbre à partir duquel on veut le dessin, dans notre cas on veut toute la boîte donc le premier objet qui est 0.

profondeur = niveau de redessin (objets emboité les un dans les autres), pour la boîte on veut l'ensemble on met alors la valeur MAX_DEPTH (=7)

x, y, w, h =toujours les coordonnées de la boîte

VII.4.f) Libération de l'affichage écran

Avec la même fonction que nous avions retenu l'écran nous le rendons par:

wind_update(END_UPDATE);

VII.4.g) Gestionnaire de boîte de dialogue de GEM

Une fonction assez sommaire mais suffisante pour nombre de problèmes courant est à notre disposition : form_do()

Ses principaux défauts sont:

- Boîtes de dialogues non déplaçables automatiquement sans bidouille.

- Le menu n'est plus accessible, même pour les accessoires ainsi que la gestion timer ainsi que l'accès aux autres messages GEM pour le programme.

- Aucun autre programme n'est normalement accessible tant que l'on n'a pas quitté cette fonction.

- Les icônes ne peuvent pas être déplacées, pas de sliders gérables.

Utilisation: int form_do(boite,edit); avec OBJECT *boite; int edit;

edit = numéro du premier objet possédant un champs éditable (mettre 0 si il n'y a pas de champs éditable)

form_do() renvoi le numéro de l'objet qui a provoqué la sortie de la boîte de dialogue.

VII.4.h) Libération de l'espace écran

 Il faut libérer l'espace écran réservé pour la boîte de dialogue, la même fonction que celle qui nous avait permis de réserver cette espace permet de faire l'inverse avec l'option=3:

form_dial(3,0,0,0,0,x,y,w,h); Cette procédure va simplement demander aux autres application de redessiner la zone concernée ou se situait la boîte. Cette option est aussi souvent utilisée par les programmes GEM pour demander le redessin d'une zone quelconque de l'écran même si il n'y a pas eu de réservation écran avec l'option=0 pour une boîte de dialogue. Au lieu de demander individuellement le redessin à toutes les applications, c'est alors l'AES qui s'occupe de ce travaille.

VIII) Avant de nous quitter

 Pour illustrer les quelques possibilités exposées jusqu'ici, voilà un petit source très limité, vous pouvez charger l'archive qui contient son source ainsi que les fichiers associés (ressource...) ici.

 Pour plus de renseignements n'hésitez pas à me contacter : Olivier.Landemarre@utbm.fr

Le source:

#include <stdio.h>
#include <aesbind.h>
#include "article1.h"  /* .h généré par l'éditeur de ressource */

#define AP_TERM 50

int work_in[]= {1,1,1,1,1,1,1,1,1,1,2}, work_out[57]; /* c'est pour le VDI */
int contrl[12];  /* variables dans tous les cas pour GEM */
int intin[256],  ptsin[256], intout[256], ptsout[256];

OBJECT *le_menu;

int dial_do(arbre)   /* gestion d'une boite de dialogue de manière simple */
int arbre;   /* le numéro de la boite arbre d'objet à afficher */
{ OBJECT *boite;
	int bouton,x,y,w,h;
	rsrc_gaddr(0,arbre,&boite);            /* recherche de l'adresse de la boite d'infos BOITE1 */
	(void)form_center(boite,&x,&y,&w,&h);  /* centrage de la boite et coordonnées */
	while(!wind_update(BEG_UPDATE));       /* attente que l'on ai la main pour dessiner et réservation de l'écran*/
	form_dial(0,0,0,0,0,x,y,w,h);          /* réservation de la place écran */
	objc_draw(boite,0,MAX_DEPTH,x,y,w,h);  /* dessin de la boite de dialogue */
	wind_update(END_UPDATE);               /* fin de réservation de l'écran */
	bouton=form_do(boite,0);               /* gestion de la boite de dialogue par le système */
	        /* bouton est le numéro d'objet qui a provoqué la sortie du form_do() */
	form_dial(3,0,0,0,0,x,y,w,h);          /* demande de redessin à la place de la boite */
	return(bouton);
}

int gere_menu(element)  /* gestion du menu de article1.rsc */
int element;
{ int flag=1;
	
	switch(element)
	{
		case INFO1: (void)dial_do(BOITE1); /* demande de la gestion de la boite de dialogue d'information */
			break;
		case QUIT: flag=0;                 /* pour quitter le programme */
			break;
		default: break;
	}
	return(flag);
} 

int fct_gere_mesag(buffer)  /* retourne 0 si l'on veut sortir du programme */
int *buffer;
{ int flag=1;
  switch(buffer[0])        /* le numéro de message à gérer*/
  {
   case AP_TERM: flag=0;
   	break;
   case MN_SELECTED:  flag=gere_menu(buffer[4]);  /* gestionnaire du menu courant */
   	menu_tnormal(le_menu,buffer[3],1);   /* on remet en normal le titre du menu sélectionné une fois terminé*/
   	break;
   default:          /* on gèrera les autres messages une autre fois */
   	break;   
  }
  return(flag);
}

main()
{ int vdihandle,     /* numéro de station graphique VDI pour le programme */
      aeshandle,     /* numéro AES du programme */
      flag=1,        /* quand flag == 0 on sort du programme */
      buffer[8];     /* buffer pour recevoir les messages */
	
		/* partie initialisation GEM */
	aeshandle=appl_init();                /* initialisation de l'AES */
	v_opnvwk(work_in,&vdihandle,work_out);/* initialisation du VDI */ 	
		/* chargement du ressource */
	if(rsrc_load("ARTICLE1.RSC"))         /* chargement du ressource par GEM*/
	{    /* ressource bien chargé installation du menu*/
		if(rsrc_gaddr(0,MENU,&le_menu))     /* recherche de l'arbre d'objets du menu dans le ressource chargé */
		{
			menu_bar(le_menu,1);              /* mise en place du menu */
			 
			 /* boucle de gestion des messages GEM */
			while(flag)                       /* boucle presque sans fin */
			{
				(void)evnt_mesag(buffer);       /* la fonction ne ressort que si il y a un message c'est très économique en CPU, evnt_mesag() retourne toujours 1! */
 				flag=fct_gere_mesag(buffer);    /* notre fonction qui nous servira à gérer les messages */
			}
			
			menu_bar(le_menu,0);              /* on efface le menu */
		}
		(void)rsrc_free();                  /* fermeture du ressource */	
	} 	else  form_alert(1,"[0][Impossible d'ouvrir le ressource|Terminé ...][OK]"); /* petit message pour indiquer pourquoi le programme quitte brutalement */
	
	  
		
		/* fermeture de GEM*/
	v_clsvwk(vdihandle);                  /* Fermeture de la station graphique VDI */
	(void)wind_set(0,WF_NEWDESK,0,0,0);   /* demande de redessin du bureau */
	appl_exit();                          /* fermeture de l'AES pour le programme */
}