Obligement - L'Amiga au maximum

Jeudi 21 septembre 2017 - 17:59  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Contact

David Brunet

Courriel

 


Programmation : MUI - réorganisation des sources, présentation des hooks
(Article écrit par Mathias Parnaudeau - juillet 2004)


Chapitre 4

Après l'exécution de l'exemple précédent, on s'aperçoit qu'il reste du chemin à parcourir dans la finalisation du lecteur, malgré une meilleure apparence. Mais des améliorations conséquentes ne peuvent être envisagées sur des fondations faibles. Or, il faut avouer qu'à force d'ajouts, la structure de notre programme mérite une session de nettoyage. Sans oublier d'acquérir de nouvelles connaissances, y compris sur cette importante notion qu'est le hook.

Organisation du code

On n'est plus efficace et on ne peut garantir un programme sain si le source est illisible et désordonné. Désormais, nous savons utiliser MUI : construire une interface, choisir ses composants et communiquer avec eux. Nous allons pouvoir mettre tout ça à profit pour que notre lecteur devienne digne de ce nom. Comme la taille du source est amenée à grossir, il est en effet grand temps d'envisager un nouveau découpage de l'application, plus adapté.

La fonction principale main() doit retrouver son rôle central : c'est elle qui va commander les initialisations, l'ouverture de la fenêtre ainsi que la gestion des événements de cette dernière. C'est pourquoi, par une simple opération de couper-coller, on a déplacé la boucle qui guette la fermeture de l'application. Les bibliothèques (ici intuition et muimaster) sont prises en charge par le couple de fonctions Initialize()/DeInitialize() dont le contenu peut évoluer sans perturber la fonction main(). En plus des bibliothèques, il faut prendre en compte les classes MUI (MCC) et tester leur présence pour pouvoir fournir clairement à l'utilisateur un message si ce n'est pas le cas. Cela évite de courir après SnoopDOS. On pourrait également vérifier la version minimum à utiliser mais nos besoins actuels ne le réclament pas.
La dernière fonction, que l'on a épurée, se charge de décrire l'application pour assurer son ouverture, avec les appels aux méthodes qui vont bien. Comme les variables app et window sont utilisées par cette fonction et par le programme principal, elles sont désormais déclarées en global.

Une autre stratégie à mettre en place concerne le nommage des variables et principalement des composants MUI. Dans un programme conséquent, on se retrouve avec des dizaines de composants et autant de variables supplémentaires qui s'y réfèrent. Pour s'y retrouver, il est indispensable d'adopter une manière de nommer les variables, en choisissant par exemple un préfixe comme LV_ ou BT_ pour identifier respectivement une listview ou un bouton. On peut aussi envisager quelque chose de plus poussé. Par exemple, un bouton OK va se retrouver sur plusieurs fenêtres, c'est pourquoi il faut pouvoir les localiser indépendamment. On peut donc choisir de préfixer encore en amont avec le nom de la fenêtre ou du groupe : config_bt_ok par exemple. Mais libre à vous de choisir vos propres conventions.

Pour simplifier le source, nous retirons la partie chargée des notifications destinées à griser ou dégriser les boutons d'actions. Nous avons vu leur intérêt mais avec la multitude de boutons et les actions auxquels ils correspondent, cela alourdirait inutilement les sources (par rapport bien sûr à notre objectif dans ce tutoriel).

Voilà à quoi ressemble le source ainsi nettoyé :

/*
 * DisKo4.c (01/07/04)
 *
 */

#include <stdio.h>
#include <libraries/mui.h>
#include <proto/intuition.h>
#include <proto/muimaster.h>
#include <proto/exec.h>
#include <clib/alib_protos.h>

#include <mui/Busy_mcc.h>

#include <SDI_compiler.h>
#include <SDI_hook.h>

#define MAKE_ID(a,b,c,d)  ((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))

struct Library *MUIMasterBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;

#ifdef __amigaos4__
struct IntuitionIFace *IIntuition;
struct MUIMasterIFace *IMUIMaster;
#endif

#define	MUIA_Application_UsedClasses	0x8042e9a7	/* V20 STRPTR *	i..	*/

static char *ClassList[] =
{
	"Busy.mcc",
	NULL
};

static Object *app = NULL;
static Object *window = NULL;


/*
 * Hook lié à l'action 'changement de volume' via le slider
 */

HOOKPROTONH(ChangeVolume, ULONG, APTR obj, struct TagItem *tag_list)
{
	int val;

	get(obj, MUIA_Numeric_Value, &val);

	printf("Nouvelle valeur : %d\n", val);

	return TRUE;
}

MakeStaticHook(hook_ChangeVolume, ChangeVolume);


/*
 * Construction et ouverture de la fenêtre principale.
 * Cette fonction est appelée quand on sait que tout a bien été initialisé.
 */
Object * OpenMainWindow(void)
{
	Object *bt_play, *bt_stop, *bt_previous, *bt_next, *bt_pause, *bt_eject;
	Object *busy;
	Object *lv_pistes;
	Object *sl_volume;
	Object *list;

	/* Description de l'interface et de ses propriétés */	

	app = (Object *)ApplicationObject,
		MUIA_Application_Author, "corto@guru-meditation.net",
		MUIA_Application_Base, "DISKO",
		MUIA_Application_Title, "DisKo - Exemple 4",
		MUIA_Application_Version, "$VER: DisKo 1.04 (01/07/04)",
		MUIA_Application_Copyright, "Mathias PARNAUDEAU",
		MUIA_Application_Description, "Player de CD audio minimaliste",
		MUIA_Application_HelpFile, NULL,
		MUIA_Application_UsedClasses, ClassList,

			SubWindow, window = WindowObject,
				MUIA_Window_Title, "DisKo - release 4",
            MUIA_Window_ID, MAKE_ID('W', 'I', 'N', '1'),
            WindowContents, VGroup,

					Child, HGroup,
						Child, lv_pistes = ListviewObject,
							MUIA_Listview_List, list = ListObject,
								MUIA_Frame, MUIV_Frame_InputList,
								MUIA_List_Format, "P=\33r",
								MUIA_List_Active, MUIV_List_Active_Top,
							End,
						End,

						Child, VGroup,
							Child, Label("Volume"),
							Child, sl_volume = SliderObject,
								MUIA_Group_Horiz, FALSE,
								MUIA_Numeric_Min, 0,
								MUIA_Numeric_Max, 100,
								MUIA_Numeric_Value, 38,
								MUIA_Numeric_Reverse, TRUE,
							End,
						End,
					End,

					/* Utilisation d'un groupe horizontal pourvu de boutons */
					Child, HGroup,
						Child, bt_previous = KeyButton("Précédent", 'p'),
						Child, bt_next = KeyButton("Suivant", 'v'),
						Child, bt_play = KeyButton("Jouer", 'j'),
						Child, bt_pause = KeyButton("Pause", 'a'),
						Child, bt_stop = KeyButton("Stopper", 's'),
						Child, bt_eject = KeyButton("Ejecter", 'e'),
					End,

					Child, VSpace(2),

					Child, busy = BusyObject,
						MUIA_Busy_Speed, MUIV_Busy_Speed_Off,
					End,
				End,
        End,
    End;

	if (app){
		/* On fixe quelques valeurs et notifications */
	
		DoMethod(window,
			MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
			app, 2,
			MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);

		DoMethod(busy, MUIM_Busy_Move, FALSE);

		DoMethod(bt_play, MUIM_Notify, MUIA_Pressed, FALSE,
					busy, 3, MUIM_Set, MUIA_Busy_Speed, 20);
		DoMethod(bt_stop, MUIM_Notify, MUIA_Pressed, FALSE,
					busy, 3, MUIM_Set, MUIA_Busy_Speed, MUIV_Busy_Speed_Off);

		DoMethod(list, MUIM_List_InsertSingle, "1 - Première piste", MUIV_List_Insert_Bottom);
		DoMethod(list, MUIM_List_InsertSingle, "2 - Deuxième piste", MUIV_List_Insert_Bottom);

		DoMethod(sl_volume, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime,
					sl_volume, 2, MUIM_CallHook, &hook_ChangeVolume);
/*
		DoMethod(sl_volume, MUIM_Notify, MUIA_Pressed, FALSE,
					sl_volume, 2, MUIM_CallHook, &hook_ChangeVolume);
*/
		set(list, MUIA_List_Active, MUIV_List_Active_Top);

		SetAttrs(window, MUIA_Window_Open, TRUE, TAG_END);
	}

	return app;
}


/*
 * Initialisation et vérification de tout ce qui est nécessaire à la bonne exécution
 * de l'application : ouverture des bibliothèques, test de présence des classes MCC, ...
 */
int Initialize(void)
{
	int res = 1;
	Object *busy = NULL;

	IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39L);
	MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN);

	if (IntuitionBase == NULL){
		printf("Impossible d'ouvrir 'intuition.library' V39\n");
		res = 0;
	}
	if (MUIMasterBase == NULL){
		printf("Impossible d'ouvrir '%s' V%d\n", MUIMASTER_NAME, MUIMASTER_VMIN);
		res = 0;
	}

#ifdef __amigaos4__
	IIntuition = (struct IntuitionIFace *)GetInterface((struct Library *)IntuitionBase, "main", 1, NULL);
	if (!IIntuition){
		printf("Impossible d'obtenir l'interface IIntuition\n");
		res = 0;
	}

	IMUIMaster = (struct MUIMasterIFace *)GetInterface(MUIMasterBase, "main", 1, NULL);
	if (!IMUIMaster){
		printf("Impossible d'obtenir l'interface IMUIMaster\n");
		res = 0;
	}
#endif

	busy = BusyObject, End;
	if (busy == NULL){
		printf("Classe Busy manquante\n");
		res = 0;
	}
	MUI_DisposeObject(busy);

	return res;
}


/*
 * Fermeture et libération de tout ce qui a été initialisé au démarrage.
 */
void DeInitialize(void)
{
#ifdef __amigaos4__
	if (IMUIMaster) {
		DropInterface((struct Interface *)IMUIMaster);
	}
	if (IIntuition) {
		DropInterface((struct Interface *)IIntuition);
	}
#endif
	CloseLibrary(MUIMasterBase);
	CloseLibrary((struct Library *)IntuitionBase);
}


/*
 * Programme principal : il appelle les initialisations, ouvre la fenêtre puis gère
 * les événements jusqu'à ce qu'on ferme l'application, condition de libération
 * des ressources
 */
int main(int argc, char **argv)
{
	int res = 0;

	if (Initialize()){
		app = OpenMainWindow();
		if (app){
			/* Boucle de gestion des événements, toujours la même */
			ULONG sigs = 0;

			while (DoMethod(app,MUIM_Application_NewInput,&sigs) != MUIV_Application_ReturnID_Quit)
			{
				if (sigs)
				{
					sigs = Wait(sigs | SIGBREAKF_CTRL_C);
					if (sigs & SIGBREAKF_CTRL_C) break;
				}
			}

			/* Libération des ressources et fermeture */
			set(window, MUIA_Window_Open, FALSE);
			MUI_DisposeObject(app);
		}else{
			res = 2;
		}
	}else{
		res = 1;
	}

	DeInitialize();

	return res;
}

Notion de hook

Grâce aux notifications, les composants intéragissent entre eux : un clic sur le bouton qui stoppe la chanson en cours fige le composant Busy. Mais qu'en est-il de la fonction qui va devoir changer le volume ou éjecter le disque ? Il faut pouvoir exécuter une fonction sur un événement donné. Il existe plusieurs moyens :
  • Soit on met en place un hook, c'est un mécanisme qui permet d'affecter une fonction qui sera appelée à chaque fois qu'une action a lieu (clic sur un bouton, utilisation d'un glisseur, etc.).
  • Soit on dérive la classe du composant pour pouvoir lui ajouter une méthode ! On commence à sentir l'intérêt de la programmation objet.
Le hook n'est pas propre à MUI et il est préférable de ne limiter son utilisation que pour les cas où il est impossible de faire autrement. La dérivation de classe est préférée... mais va-t-on devoir dériver chaque composant, un à un ? Non, pas forcément, il est possible de dériver un groupe mais ne brûlons pas les étapes. :) Nous voyons en attendant l'utilisation d'un hook qui affiche la nouvelle valeur du volume après que l'on ait relâché le glisseur. La sortie du texte se fait dans la console Shell, ça n'est pas très beau quand on travaille sur une interface mais l'important est ici de comprendre les hooks.

Un hook ne doit pas faire peur. Les difficultés éventuelles sont masquées grâce à l'inclusion du fichier d'en-tête M_Hook.h qui propose des macros très pratiques. C'est aussi l'opportunité de n'utiliser qu'une seule notation quel que soit l'OS concerné. Et vu que les hooks touchent aux registres processeurs, ces macros sont les bienvenues ! Pour une cible 68k, cette en-tête a besoin du fichier "SDI_compiler.h" tout aussi utile au programmeur en général. On trouve cette include dans l'archive "CLib-SDI.lha" disponible sur Aminet.

M_HOOK(ChangeVolume, APTR obj, struct TagItem *tag_list)

{

        int val;



        get(obj, MUIA_Numeric_Value, &val);



        printf("Nouvelle valeur : %d\n", val);



        return TRUE;

}

Un autre moyen existe pour manipuler les hooks et même... c'est le système lui-même qui nous l'offre à travers la fonction HookEntry de l'amiga.lib. Ainsi, on s'affranchit complètement de la notion de registres spécifiques à passer en paramètres. On se contente d'utiliser HookEntry comme n'importe quelle fonction système. En l'occurrence, on l'affecte, elle et la fonction cible, au hook qui est transmis à la méthode MUIM_CallHook. Voici ce que devient le code précédent :

static ULONG hook_ChangeVolumeFunc(struct Hook *hook, APTR obj, struct TagItem *tag_list)

{

        int val;



        get(obj, MUIA_Numeric_Value, &val);



        printf("Nouvelle valeur : %d\n", val);



        return TRUE;

}



struct Hook hook_ChangeVolume = 

{

        {NULL, NULL}, (HOOKFUNC) HookEntry, (HOOKFUNC) hook_ChangeVolumeFunc, NULL

};

La fonction appelée par le hook ne mérite pas de commentaire particulier : on lit juste la valeur numérique de l'objet qui a déclenché l'appel de cette fonction et l'on affiche dans la sortie standard. Si le niveau sonore du lecteur CD était modifiable (via le mixer de MorphOS par exemple) grâce à une fonction système, c'est d'ici qu'elle serait appelée.

Remarque : de nouveaux éléments font que vbcc bloque à la compilation en génération de code 68k. Il est nécessaire d'ajouter -DNO_INLINE_STDARG sur la ligne de compilation.

Notions acquises dans ce chapitre
  • Importance de la clarté du source.
  • Convention pour le nommage des variables.
  • Découverte et pratique du hook.


[Retour en haut] / [Retour aux articles] [Article précédent] / [Article suivant]