Suivez-nous sur X
|
|
|
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,
ALL
|
|
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
|
|
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
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
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 arrête 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 le système d'exploitation 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.
|