|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Voici le listing d'un programme qui devrait vous montrer comment créer et gérer une véritable requête sous Intuition. Ce programme met en fait en pratique toute la théorie que nous avons vue jusqu'ici : écran, fenêtres, menus et bien sûr requêtes. D'une taille conséquente (plus de 550 lignes de code source !), il a été écrit avec l'assembleur Devpac Amiga version 2, mais son adaptation à tout autre assembleur ne devrait poser aucun problème. J'ai évité l'emploi de fichiers "include", ce qui permettra aux possesseurs du K-Seka d'en profiter également (et du même coup, accélère les temps d'assemblage...). Cela dit, rien ne vous empêche, si vous avez la flemme de taper tout les EQUates préliminaires, d'inclure les fichiers exec.i, exec lib.i, intuition.i, intuition lib.i, graphics.i, graphics lib.i et diskfont lib.i.
Toute la première partie du programme consiste en la définition de constantes que nous utiliserons par la suite, constantes qui simplifient à la fois la lisibilité et la maintenance du programme. La plupart des assembleurs d'aujourd'hui offrent la possibilité d'inclure, grâce à la directive "include", des fichiers au sein d'un source, fichiers qui contiennent les définitions propres au système sur lequel on travaille (pour les ceusses qui ne l'auraient pas remarqué, nous, c'est l'Amiga). Cette méthode possède certes de nombreux avantages (par exemple, en cas de nouvelle version du système, il suffit de réassembler le programme avec les nouveaux includes), mais j'ai toujours préféré m'en passer, pour des raisons de vitesse d'assemblage. J'ai horreur d'aller boire un café pendant que l'ordinateur travaille. Arrive alors le début du programme, avec le label "Start". On sauvegarde tous les registres, à l'exception de d0 qui nous servira à signaler une éventuelle erreur durant l'initialisation. La suite, vous la connaissez déjà : ouverture des différentes bibliothèques utilisées, avec éventuellement sortie du programme en cas de défaillance (très improbable pour les bibliothèques en ROM, mais on ne sait jamais), puis ouverture d'un nouvel écran Intuition, ouverture d'une fenêtre dans cet écran, et enfin mise en place d'un menu. Vous aurez remarqué que toutes ces "ouvertures" se font par l'appel de routines (par exemple, bsr OpenS pour l'écran), ce qui n'est peut-être pas très économique en place mémoire, mais rend le programme modulaire. On appelle ensuite la routine principale, baptisée "main" en hommage à mes (désastreux) début en C, avant de tout refermer (menu, fenêtre, écran et bibliothèques), de récupérer les registres sauvegardés, et de retourner au CLI. Car en effet, ce programme n'est accessible qu'à partir du CLI (ou du Shell, bien sûr, c'est pareil). Lancé depuis le Workbench (ce qui supposerait que vous lui ayiez dessiné une icône), il plante. Nous verrons le mois prochain pourquoi et comment y remédier. La main dans la main La routine principale "main" est en fait une boucle qui ne se terminera que lorsque l'utilisateur aura choisi l'option "Quitter" dans le menu. Elle comprend un test permanent dudit menu, grâce à la fonction GetMsg de la bibliothèque Exec. Cette fonction renvoie le cas échéant dans d0.L un pointeur sur une structure Message dans laquelle on trouvera de plus amples informations sur la teneur du message, ou bien "0" s'il ne s'est rien passé. Je ne vais pas m'amuser à décrire en détails cette structure, seulement les éléments intéressants pour nous :
La classe du message, appelée dans le listing MsgClass, indique quel est le type du message reçu. Étant donné que notre fenêtre n'autorise qu'un seul type de message (voir les drapeaux IDCMP dans la structure NewWindow), en l'occurrence MENUPICK, c'est le seul que nous serons habilités à recevoir. Il nous suffit donc de comparer MsgClass à la valeur MENUPICK (définie en début de listing, ça fait plaisir de voir qu'il y en a qui suivent) pour savoir si une option du menu a été sélectionnée. Le code du message (noté MsgCode) nous aidera, lui, à déterminer quel point de menu ou de sous-menu a été sélectionné. Il s'agit d'un mot (16 bits, donc) codé comme suit :
Quelques décalages à droite (à l'aide de l'instruction "lsr") suffisent donc à déterminer précisémment quel point a été choisi. Quant à MsgAddress, il ne nous est d'aucune utilité pour le menu, alors que pour la requête, si ; nous verrons donc plus loin comment l'utiliser. Au menu ce soir Il nous faut maintenant réagir à la sélection du menu, ce qui est justement le but de la routine judicieusement intitulée "DoMenu". Cette routine est divisée en plusieurs sous-routines : DoMenu1, DoMenu1_1, DoMenu2, etc. Les plus perspicaces d'entre vous auront tout de suite compris que DoMenu1 s'occuppe du premier menu (celui intitulé "Projet"), que DoMenu1_1 s'occupe du premier point de ce premier menu (ici, Requester), etc. Par contre, il peut sembler étrange qu'une routine DoMenu2 existe, alors que nous n'avons précisément pas défini de second menu. La raison est toute bête : cette manière de procéder, qui soit dit en passant n'économise pas non plus les octets, permet toutefois de modifier très aisément la structure même de la barre de menus, en ajoutant ou en enlevant des points, des sous-menus ou carrément des menu entiers. Ainsi, DoMenu2 se contente-t-elle de sauter à la boucle principale (bra MainLoop), mais si je désire ajouter un second menu, je n'aurai qu'à remplacer ce saut par une routine, identique dans l'esprit à DoMenu1. Concrètement, comment DoMenu et ses consoeurs fonctionnent-elle ? En vérité, c'est très simple. DoMenu se charge d'abord de déterminer quel point de menu a été sélectionné. Pour ce faire, elle calcule, à l'aide de MsgCode, le numéro de menu qu'elle stocke dans le registre d0, le numéro du point de menu, stocké dans d1 et le numéro d'un éventuel sous-menu, stocké dans d2. Dans notre cas, si l'utilisateur choisit la rubrique "Quitter", d0 contiendra la valeur 0 (indiquant la premier menu), d1, la valeur 1 (indiquant le second point de menu) et d2, la valeur $3f (indiquant qu'il ne s'agit pas d'un sous-menu). Une fois ces trois valeurs initialisées, on arrive à DoMenu1. Celle-ci se charge de déterminer, simplement en testant le registre d0, si c'est bien le premier menu qui a été choisi, sinon, elle saute à DoMenu2. Si oui, on arrive à DoMenu1_1 qui, en testant le registre d1, est capable de déterminer s'il s'agissait du premier point du premier menu (Quitter), auquel cas elle sort de la boucle principale par un simple "rts", sinon elle saute à DoMenu1_2. De la même manière, DoMenu1_2 compare le registre d1 à pour s'assurer que c'est bien le second point du menu qui a été sélectionné, auquel cas elle appelle le sous-programme de gestion de la requête. Dans le cas contraire, elle saute à DoMenu1_3, et ainsi de suite. Rusé, non ? Plat de résistance : la requête Voici donc (enfin) la partie la plus intéressante de ce programme. La requête d'exemple que j'ai construite comprend deux gadgets booléens ("OK" et "Ça clignote"), un gadget de chaîne pour écrire ce qui vous passe par la tête, un gadget proportionnel horizontal, ainsi qu'un troisième gadget booléen, désactivé celui-là, contenant le texte d'illustration de la police chargée depuis le disque. Une requête supplémentaire est également mise en place par la fonction "AutoRequest()" d'Intuition lorsque l'utilisateur confirme son entrée de texte dans le string-gadget par la touche "Entrée". Tout ça est finalement très simple, mais prend beaucoup de temps à construire. La routine qui gère tout ça s'appelle, encore une fois assez judicieusement, DoRequest. C'est une bonne habitude à prendre que de définir un modèle pour les noms des routines, cela permet, plusieurs mois plus tard, de replonger dans un programme sans se demander pendant trois heures à quoi telle partie du listing peut bien servir... Chez moi par exemple (je prends cet exemple parce que c'est celui que je connais le mieux ; rien ne vous oblige à le suivre à la lettre), tout ce qui ouvre quelque chose s'appelle OpenMachin, ce qui ferme, CloseBidule et ce qui gère un élément quelconque, DoTruc. Les anti-franglais risquent de ne pas apprécier, mais on n'est pas là pour discuter de ce genre de choses. Je disais donc : DoRequest. La première chose à faire pour ouvrir une requête, est d'ouvrir une fenêtre dans laquelle elle sera dessinée. C'est ainsi, on n'a pas le choix. J'ai donc défini une structure NewWindow particulière baptisée ReqWindowDefs, qui contient toutes les éléments nécessaires à notre requête. Cette fenêtre pourra être déplacée à volonté, mais pas agrandie (ni même rétrécie, ça va de soi), pour des questions d'esthétique. Elle possède de plus les attributs "ACTIVATE" pour être activée dès son ouverture et "RMBTRAP" qui a pour effet de court-circuiter les effets du bouton droit de la souris. En clair, tant que la fenêtre de cette requête sera active, il sera impossible d'appeler le menu (qui appartiènt à la première fenêtre, je vous le rappelle). Concernant les drapeaux IDCMP, cette fenêtre n'autorise les messages "GADGETUP" et "GADGETDOWN", qui indiquent lorsqu'un gagdet est cliqué. Sa structure NewWindow contient par contre un pointeur essentiel sur le premier gadget de la requête. The gadgets Ce premier gadget est défini dans le listing au label ReqGadgetDefs, zone de mémoire divisée en plusieurs structures Gadget, IntuiText, Border et Image, nécessaires à la mise en place de la requête. Le premier gadget, baptisé ReqGadget1, est le bouton "OK" qui met fin à la requête. Il s'agit d'un gadget booléen, c'est-à-dire pouvant prendre un état "ON" ou "OFF" uniquement. En l'occurrence, lorsqu'il sera "OFF", il faudra fermer la requête et retourner à la boucle principale MainLoop. Ce premier gadget pointe sur le second, baptisé ReqGadget2. Il s'agit encore d'un gadget booléen dans lequel figure le texte "ça clignote !". Lorsqu'il sera cliqué, la routine DoRequest se chargera de faire clignoter l'écran à l'aide de la fonction "DisplayBeep()" de la bibliothèque Intuition. ReqGadget2 pointe sur ReqGadget3, qui n'est autre qu'un string-gadget dans lequel l'utilisateur (jusqu'à preuve du contraire, vous) pourra écrire tout ce qui lui passe par la tête, avec toutefois une limite de 33 caractères de long. Il convient ici de noter qu'Intuition n'envoie de message concernant un string-gadget que lorsque l'entrée de texte a été confirmée par la touche "Entrée". En recevant un tel message, DoRequest bâtira une seconde requête, du type SimpeRequester celui-là, qu'elle affichera à l'aide de la fonction "AutoRequest()" d'Intuition. Un SimpleRequester ne peut comporter qu'un texte et deux boutons (en général, "OK" et "Cancel"). Le fameux "Please insert volume xxxx in any drive" est un excellent exemple de SimpleRequester. ReqGadget3 pointe, on commence à en avoir l'habitude, sur ReqGadget4, le gadget proportionnel, qu'on appelle également parfois "ascenseur". Disposé horizontalement, il n'est d'aucune utilité propre dans notre requête, sinon de montrer comment on met en place un gadget proportionnel. Quant à le gérer, Intuition ne nous facilite pas la tâche : il convient en effet de tester en permanence, donc au sein d'un boucle, la position de l'ascenseur (en lisant ses coordonnées dans sa structure de définition) et, par soustraction de ses anciennes coordonnées que l'on aura pris soin de sauvegarder quelque part, de déterminer s'il a été bougé ou non. Bref, tout le travail est à faire, ce qui peut prendre une place énorme et explique que ce ne soit point inclus dans notre programme. Finalement, ReqGadget4 pointe sur ReqGadget5, qui terminera la liste des gadgets de la requête. Il s'agit d'un gadget de type booléen (encore), dans lequel figure un texte écrit avec la fonte Emerald 17 (figurant sur la disquette Workbench 1.3) qui a normalement été chargée lors de l'ouverture de l'écran (dans le cas contraire, aucun problème, le texte apparaîtra avec la police normale). Ce gadget comporte le drapeau "GADGHNONE", qui spécifie à Intuition qu'elle ne doit rien en faire de particulier s'il est cliqué. De fait, nous n'avons même pas à nous en préoccuper. Changement d'adresse Mais maintenant, comment DoRequest va-t-elle déterminer quel gadget de la requête a été cliquée ? La réponse figure dans MsgAddress, que nous avons évoqué plus haut. Le principe est toujours le même : appel de la fonction "GetMsg()" d'Exec pour déterminer s'il est survenu un évènement quelconque à la fenêtre contenant la requête. Si rien ne se passe, on boucle sur le label DoRequestLoop. Dans le cas contraire, on commence par répondre au message, histoire qu'Exec sache qu'on l'a bien reçu et puisse le virer se sa liste des messages à nous envoyer. Ceci n'est absolument pas obligatoire, mais c'est beaucoup plus propre. De plus, on aura l'air malin devant le guru si une future version de l'AmigaOS exige absolument que l'on y réponde, à ce message. On utilise pour ce faire la fonction "ReplyMsg()" d'Exec, qui attend pour unique paramètre le pointeur sur le message reçu dans le registre a1. C'est là qu'intervient "MsgAddress". On y trouve l'adresse d'une structure décrivant plus précisément le message envoyé. Dans le cas qui nous intéresse, on y trouvera, à l'adresse MsgAddress+$26, le numéro d'identification du gadget cliqué. Ce numéro n'est autre que celui que nous avons nous-même défini dans les différentes structures Gadget (ReqGadget1 à ReqGadget5), voyez cet article. En langage Intuition, ce numéro s'appelle GadgetID. Je me suis amusé, totalement arbitrairement, à donner des numéros d'identification compris entre 61 et 65, mais rien n'y oblige. Donnez les numéros que vous voulez, sans oublier le complémentaire. Donc, une fois que DoRequest aura déterminé le GadgetID du gadget cliqué, elle n'aura plus qu'à appeler la routine correspondante, à savoir DoReqGad1), DoReqGad2 ou DoReqGad3 (le string-gadget), puisque ce sont les seuls dont nous nous occupons. C'est aussi simple que ça. Définitions La suite du listing, vous la connaissez : elle regroupe les différentes structures NewScreen, NewWindow, Gadget, etc. nécessaires à tous les éléments utilisés. On y trouve également des adresses utiles, comme IntBase ou ScrHandle, ainsi que les noms des différentes bibliothèques utilisées (intuition.lbrary, graphics.library et diskfont.library). En tout, ce sont quelques 558 lignes de code source qui, une fois assemblées, produiront un programme exécutable de 1884 octets... Impressionnant, non ?
|