Obligement - L'Amiga au maximum

Vendredi 23 mai 2025 - 11:00  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

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

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des 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,
ALL


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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : C - ReAction, AppWindows et AppMessages sur AmigaOS 4
(Article écrit par Daniel Jedlicka et extrait de OS4Coding - août 2011)


1. Introduction

L'interface utilisateur d'un programme moderne doit permettre, entre autres, de manipuler rapidement et facilement des fichiers de données. Imaginez un utilisateur qui doit fréquemment cliquer sur un système de menus, de barres d'outils et de requêtes pour pouvoir ouvrir un fichier : se sentira-t-il à l'aise avec le programme ? Ne serait-il pas plus rapide et plus simple si l'utilisateur pouvait simplement "déposer" le fichier dans la fenêtre, puis laisser le programme faire ce qu'il est censé faire avec le fichier ?

L'environnement de bureau de l'Amiga, le Workbench, offre cette fonctionnalité depuis des décennies. Dans ce tutoriel, nous allons voir comment vous pouvez fournir et utiliser cette fonctionnalité pratique dans vos programmes basés sur ReAction. L'article donne également un aperçu de l'utilisation des crochets sous AmigaOS 4.

2. La terminologie

Le sous-système chargé d'informer un programme qu'un fichier (ou plus précisément : son icône) a été déposé dans la fenêtre du programme est la bibliothèque Workbench. Cependant, les programmes ne reçoivent pas cette information par défaut : deux étapes sont nécessaires. Tout d'abord, la fenêtre du programme doit être configurée pour être informée des messages provenant du Workbench. Ensuite, le programme doit mettre en oeuvre un code pour sa routine de gestion des entrées qui identifie les messages du Workbench et réagit en conséquence.

Une fenêtre qui peut recevoir des messages du Workbench est appelée "AppWindow" ; les messages eux-mêmes sont appelés "AppMessages" (d'après la structure AppMessage, une structure de données système qui contient les informations envoyées par le Workbench).

Attention à un éventuel malentendu. La bibliothèque application.library d'AmigaOS 4 peut également envoyer des messages aux programmes en cours d'exécution. Bien que ces "messages d'application" soient fondamentalement différents des AppMessages du Workbench, la similitude de nom peut prêter à confusion (peut-être est-il temps de donner aux messages de l'application.library un nom sans ambiguïté).

3. Ai-je besoin d'AppWindows ?

Cela dépend. En règle générale, si votre programme possède une interface graphique et implémente une fonction "Ouvrir", "Charger" ou "Importer" pour utiliser des fichiers de données, vous devez en faire une AppWindow et permettre à l'utilisateur de déposer des fichiers comme alternative aux fonctions de votre menu ou de votre barre d'outils. Les éditeurs de texte, les visionneurs/éditeurs d'images, les lecteurs de musique et de vidéo... doivent tous se comporter comme une AppWindow.

La différence en termes de confort d'utilisation peut être énorme ! Comme exemple, je citerai le célèbre émulateur de Commodore 64, VICE. Dans la version actuelle d'AmigaOS 4, le processus de chargement d'un jeu est incroyablement compliqué. Pourtant, tous les tracas liés au chargement de fichiers images et à la saisie de commandes BASIC disparaîtraient si la fenêtre de l'émulateur était implémentée en tant qu'AppWindow (c'est ce que fait la version Windows de VICE, et elle est donc beaucoup plus agréable à utiliser).

N'oubliez pas : le mot clé ici est "workflow", et non "workslow" !

4. ReAction et AppMessages

La classe de fenêtre ReAction permet non seulement de recevoir des AppMessages, mais aussi de créer très facilement des AppWindows. Transformer une fenêtre ReAction en AppWindow est en effet plus rapide que de faire la même chose avec une fenêtre Intuition classique. Votre code d'interface graphique n'a pas besoin d'appeler les fonctions de la bibliothèque Workbench, la classe les appelle en interne. Tout ce que vous avez à faire pour que votre fenêtre devienne une AppWindow est de :
  • Fournir un port d'application pour la fenêtre (AppPort).
  • Spécifier que votre fenêtre sera une AppWindow, en fixant la balise "WINDOW_AppWindow" à "TRUE".
L'existence d'un AppPort est également une condition préalable à l'iconification de la fenêtre du programme. Même si vous spécifiez "WINDOW_IconifyGadget, TRUE" dans la définition de votre objet fenêtre (et que la fenêtre résultante affiche un joli gadget Iconify), l'iconification ne fonctionnera pas si vous ne spécifiez pas également l'AppPort. C'est tout à fait logique : lorsqu'un programme est iconifié, le Workbench a besoin de lui dire quand il est temps de se "réveiller" et de rouvrir la fenêtre - sans AppPort, le Workbench n'aurait nulle part où envoyer le message.

Un port de message est un type de ressource système, donc l'AppPort pour votre fenêtre doit être alloué et initialisé par la fonction AllocSysObject() (et libéré en conséquence ; voir section 7) :

struct MsgPort *winAppPort;
 
winAppPort = (struct MsgPort *)IExec->AllocSysObject(ASOT_PORT, NULL);

Pour que tout cela fonctionne, la définition de l'objet de votre fenêtre doit contenir les balises suivantes :

...
WINDOW_AppPort, winAppPort,   /* le port application   */
WINDOW_AppWindow, TRUE,       /* nous voulons une AppWindow  */
WINDOW_IconifyGadget, TRUE,   /* autorise iconification  */
...

Il n'est pas nécessaire de fournir la balise "WINDOW_AppWindow" si vous n'avez besoin que d'une iconification, il suffit de fournir l'AppPort.

5. Gestion des AppMessages

La clé du mécanisme de gestion des messages de ReAction est la méthode "WM_HANDLEINPUT", qui traite tous les événements d'entrée : sélections de gadgets, choix de menus, pressions de touches, etc. Le résultat de l'appel de la méthode vous informe de la plupart des événements reçus par la fenêtre. Je dis bien la plupart, mais pas tous. Les valeurs de résultat prédéfinies (voir "classes/window.h") ne couvrent que les types d'événements d'entrée les plus courants : si vous avez besoin d'autres événements, vous devez utiliser une extension appelée "hook" (crochet). Un exemple typique serait un événement IDCMP "non géré", tel que GADGETDOWN. La classe Window gère ces événements, mais elle les considère comme moins courants et moins susceptibles de se produire que d'autres types d'événements (tels que GADGETUP ou MENUPICK). Elle laisse donc au programmeur le soin d'installer un crochet IDCMP et d'y traiter tous les autres événements.

Les AppMessages sont traités de la même manière. Le seul AppMessage que la méthode WM_HANDLEINPUT peut identifier par la valeur du résultat est "WMHI_UNICONIFY", c'est-à-dire que le Workbench demande à l'application de rouvrir sa fenêtre. Pour les autres AppMessages, vous devez fournir un crochet AppMessage et passer le pointeur du crochet à l'objet fenêtre via la balise "WINDOW_AppMsgHook".

Vous remarquerez que dans une documentation plus ancienne, à savoir le "ROM Kernel Manual: Libraries", les AppMessages sont traités différemment dans la boucle d'entrée. La technique consistait à mettre en place un signal pour la fonction Wait() d'Exec, puis à traiter le message via GetMsg()/ReplyMsg(). Ce n'est pas la façon de procéder dans ReAction. L'utilisation de l'ancienne technique en combinaison avec la méthode WM_HANDLEINPUT reviendrait à mélanger les choses. Utilisez plutôt le crochet AppMessage.

6. Le crochet AppMessage

Au cas où vous n'auriez jamais utilisé de crochet auparavant et que vous ne sauriez pas de quoi nous parlons : le crochet ("hook") est une pratique qui consiste à intercepter des appels de fonction, des messages ou des événements avec du code personnalisé. Ce code est appelé une fonction de crochet. Lorsque des conditions spécifiques sont remplies, la section du code du programme en cours d'exécution donne le contrôle à la fonction d'accroche et le code personnalisé est exécuté. Pour faire le lien entre la section de code interceptée et la fonction d'accroche, vous avez besoin d'une structure de données appelée "hook". Une fois en place, le crochet est chargé d'appeler la fonction de crochet et de lui fournir les données du code intercepté.

L'API d'AmigaOS utilise les crochets de manière assez extensive : de nombreux composants du système permettent l'accroche ("hooking") afin d'étendre leur fonctionnalité de manière transparente et standardisée. La méthode WM_HANDLEINPUT de ReAction fonctionne également de cette manière : au lieu de vérifier tous les types d'événements possibles, elle en gère un nombre limité et vous permet d'installer un crochet si vous en avez besoin de plus.

Pour utiliser un crochet, vous devez :
  • ouvrir la bibliothèque Utility.library (qui fournit la fonctionnalité de crochet).
  • Écrire le code de la fonction d'accrochage (voir 6.1).
  • Allouer et initialiser la mémoire pour la structure de données du crochet (voir 6.2).
  • Installer le crochet en passant le pointeur de la structure de données au composant approprié qui doit être intercepté (voir 6.3).
6.1 La fonction "hook"

La fonction "hook" est une fonction personnalisée que vous écrivez afin de pouvoir gérer une situation spécifique résultant de l'interception du code principal (par exemple, le traitement d'un message). La fonction doit être conçue pour accepter trois arguments, dont l'ordre et le type sont donnés. Sur Amiga 68k, les arguments devaient être placés dans des registres spécifiques du processeur (A0, A2 et A1). Ce n'est plus le cas sous AmigaOS 4. L'utilisation des crochets est maintenant plus facile et plus transparente.

Le premier argument est un pointeur sur "struct Hook", la structure de données du crochet (voir 6.2 ci-dessous). Le second est un pointeur générique vers un objet et dépend du contexte dans lequel la fonction "hook" a été appelée. Par exemple, si l'appel au crochet a été déclenché par un gadget ou une fenêtre ReAction, l'argument pointera sur l'instance de l'objet correspondant (les crochets AppMessage sont appelés par la classe Window, l'objet pointera donc vers l'objet "window"). Le troisième argument est un pointeur vers un message, dont le type dépend également du contexte. Par exemple, si nous fournissons un crochet IDCMP, le troisième argument pointera vers une "struct IntuiMessage" ; si nous fournissons un crochet AppMessage, l'argument pointera vers une "struct AppMessage".

Tout ce qu'il faut pour traiter les AppMessages à l'intérieur de la fonction de crochet est de lire la structure du message, d'identifier le type de message et d'agir en conséquence. Dans l'extrait de code suivant, nous allons traiter un message AppWindow, censé notifier qu'une icône de fichier a été déposée dans notre fenêtre. Nous obtiendrons le nom du fichier et appellerons notre hypothétique routine de chargement :

#include <workbench/workbench.h>
#include <workbench/startup.h>
 
#define FNAME_MAX  2048  /* maximum filename length */
 
void my_hook_function(struct Hook *hook, Object *object, struct AppMessage *msg)
{
 struct WBArg *argPtr = NULL;
 char fileName[FNAME_MAX];
 
 
 if ( msg->am_Type == AMTYPE_APPWINDOW )
  {
   /* An icon was dropped into the AppWindow.
      Check the AppMessage class and the number of arguments
      to make sure that we really have a file to open. */
   if ( (msg->am_Class == AMCLASSICON_Open) && (msg->am_NumArgs > 0) )
    {
     /* Obtain the filename and load the file. */
     argPtr = msg->am_ArgList;
     IDOS->NameFromLock(argPtr->wa_Lock, fileName, FNAME_MAX);
     IDOS->AddPart(fileName, argPtr->wa_Name, FNAME_MAX);
     load_file(fileName);
    }
  }
}

6.2 La structure du crochet

Maintenant que notre fonction de crochet est prête, nous devons préparer la structure de données du crochet. Sous AmigaOS 4, cette structure est considérée comme un objet ressource système et vous êtes donc supposé l'allouer en utilisant AllocSysObjectTags(). N'initialisez pas ses membres directement, comme le montre l'ancienne documentation. Au lieu de cela, déclarez un pointeur sur "struct Hook" et initialisez-le en lui passant les balises :

struct Hook *appMessageHook;
 
appMessageHook = IExec->AllocSysObjectTags(ASOT_HOOK,
          ASOHOOK_Entry, my_hook_function,
          ASOHOOK_Subentry, NULL,
          ASOHOOK_Data, NULL,
          TAG_END);

ASOHOOK_Entry pointe directement vers la fonction de crochet. La fonction peut recevoir des données arbitraires via la balise ASOHOOK_Data (nous n'en avons pas besoin pour notre crochet AppWindow, la valeur du pointeur est donc NULL ici). Ces données sont ensuite accessibles à l'intérieur de la fonction de crochet en utilisant le pointeur de crochet, que la fonction reçoit comme premier argument. Par exemple, si nous passons la chaîne de caractères "Hello world!" comme donnée de crochet (ASOHOOK_Data, "Hello world!", ...), vous pouvez afficher la chaîne de caractères à l'intérieur de la fonction de crochet comme ceci :

void my_hook_function(struct Hook *hook, Object *object, struct AppMessage *msg)
{
 
 puts( (STRPTR) hook->h_Data);
 
}

6.3 Installation du crochet

Il ne reste plus qu'à installer le crochet "AppWindow" dans l'objet "ReAction window". Vous pouvez le faire au moment de la création de l'objet, en mettant "WINDOW_AppMsgHook, appMessageHook, ..." dans la liste des balises de définition de la fenêtre, mais vous pouvez aussi le faire plus tard, en utilisant SetAttrs() :

IIntuition->SetAttrs(windowObject, WINDOW_AppMsgHook, appMessageHook, TAG_END);

7. Élimination des ressources

Outre la fenêtre ReAction que nous avons définie et utilisée comme AppWindow, nous devons nous débarrasser de deux autres ressources lorsque le programme est arrivé à son terme : le port d'application et le crochet. Il ne s'agit pas d'objets BOOPSI et la classe Window ne les libère donc pas automatiquement au moment de DisposeObject(). Après avoir disposé de l'objet "window", vous devez appeler FreeSysObject() pour libérer la mémoire allouée à l'AppPort et au crochet, respectivement :

IExec->FreeSysObject(ASOT_PORT, winAppPort) ;
IExec->FreeSysObject(ASOT_HOOK, appMessageHook) ;

8. Exemple de code

Pour vous donner une meilleure idée de la manière dont les AppMessages sont gérés par la classe Window et traités dans le code, j'ai créé un exemple complet en C (voir ci-dessous). Le programme ouvre une fenêtre ReAction avec une zone d'affichage qui est, en fait, un gros gadget Button en lecture seule. La fenêtre est configurée comme une AppWindow bien sûr. Si vous déposez une icône de fichier dans la fenêtre, le gadget modifiera son texte et affichera le nom du fichier.

/*
** ra_appwindow.c
**
** Exemple de code pour démontrer comment
** les AppWindows et les AppMessages
** sont faits dans ReAction.
**
** By Daniel "Trixie" Jedlicka
**
**
**
*/

#include <classes/window.h>
#include <gadgets/layout.h>
#include <gadgets/button.h>

#include <workbench/workbench.h>
#include <workbench/startup.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>


#define MINVERSION  52
#define FNAME_MAX   2048



/* object identifiers */
enum
{
 OID_WINDOW,
 OID_LAYOUT,
 OID_BUTTON,
 OID_LAST
};

struct Library *IntuitionBase, *UtilityBase;
struct IntuitionIFace *IIntuition;

/* class library bases */
struct ClassLibrary *WindowBase, *LayoutBase, *ButtonBase;

/* class pointers */
Class *WindowClass, *LayoutClass, *ButtonClass;

Object         *objects[OID_LAST]; /* object pointer field */
struct MsgPort *winAppPort;        /* window AppPort */
struct Hook    *appMsgHook;        /* AppMessage hook */




/* *************************************   hook function   ************************************** */


static void appmessage_func(struct Hook *hook, Object *winObj, struct AppMessage *appMsg)
{
 struct WBArg  *argPtr = NULL;
 struct Window *window = NULL;
 char           fileName[FNAME_MAX];


 if ( appMsg->am_Type == AMTYPE_APPWINDOW )
  {
   if ( (appMsg->am_Class == AMCLASSICON_Open) && (appMsg->am_NumArgs > 0) )
    {
     /* Obtain the Intuition window pointer from the window object. */
     IIntuition->GetAttrs(winObj, WINDOW_Window, (uint32 *)&window, TAG_END);
     IIntuition->ActivateWindow(window);
     /* Obtain the file name from the AppMessage. */
     argPtr = appMsg->am_ArgList;
     IDOS->NameFromLock(argPtr->wa_Lock, fileName, FNAME_MAX);
     IDOS->AddPart(fileName, argPtr->wa_Name, FNAME_MAX);
     /* Change the gadget text to the file name. */
     IIntuition->SetGadgetAttrs((struct Gadget *)objects[OID_BUTTON], window, NULL,
                 GA_Text, fileName,
                 TAG_END);
    }
  }

}



/* **************************************   input loop   **************************************** */

void handle_input(Object *winObj)
{
 struct Window *window = NULL;
 uint32 sigMask = 0, sigGot = 0, result = 0;
 uint16 code = 0;
 BOOL   done = FALSE;


 /* Obtain window pointer and signal mask from the window object. */
 IIntuition->GetAttrs(winObj,
                WINDOW_Window,  (uint32 *)&window,
                WINDOW_SigMask, &sigMask, TAG_END);

 while ( !done )
  {
   sigGot = IExec->Wait(sigMask);

   while ( (result = IIntuition->IDoMethod(winObj, WM_HANDLEINPUT, &code)) != WMHI_LASTMSG )
    {
     switch (result & WMHI_CLASSMASK)
      {
       case WMHI_CLOSEWINDOW:
         done = TRUE;
       break;

       case WMHI_ICONIFY:
         if ( (IIntuition->IDoMethod(winObj, WM_ICONIFY, NULL)) ) window = NULL;
       break;

       case WMHI_UNICONIFY:
         if ( !(window = (struct Window *)IIntuition->IDoMethod(winObj, WM_OPEN, NULL)) ) done = TRUE;
       break;
      }
    }
  }

}



/* *****************************************   main   ******************************************* */

int main(void)
{
 struct Window *window  = NULL;
 int            errCode = RETURN_FAIL;


 /* Open libraries and the Intuition interface. */
 IntuitionBase = IExec->OpenLibrary("intuition.library", MINVERSION);
 UtilityBase   = IExec->OpenLibrary("utility.library", MINVERSION);
 if ( ! IntuitionBase || !UtilityBase ) goto cleanup;
 IIntuition = (APTR)IExec->GetInterface(IntuitionBase, "main", 1L, NULL);

 /* Open the ReAction classes we need. */
 WindowBase  = IIntuition->OpenClass("window.class", MINVERSION, &WindowClass);
 LayoutBase  = IIntuition->OpenClass("gadgets/layout.gadget", MINVERSION, &LayoutClass);
 ButtonBase  = IIntuition->OpenClass("gadgets/button.gadget", MINVERSION, &ButtonClass);
 if ( ! UtilityBase || !WindowBase || !LayoutBase || !ButtonBase ) goto cleanup;

 /* Create messageport for window iconification and AppWindow messages. */
 if ( !(winAppPort = (struct MsgPort *)IExec->AllocSysObject(ASOT_PORT, NULL)) ) goto cleanup;

 /* Initialize the AppMessage hook. */
 if ( !(appMsgHook = (struct Hook *)IExec->AllocSysObjectTags(ASOT_HOOK,
                ASOHOOK_Entry,    appmessage_func,
                ASOHOOK_Subentry, NULL,
                ASOHOOK_Data,     NULL, TAG_END)) ) goto cleanup;

 /* Create the window object. You do not normally have to specify window dimensions
    but we want a big button here so... The button is read-only and does not do anything,
    it just works as a display zone for the file name. */
 if ( (objects[OID_WINDOW] = IIntuition->NewObject(WindowClass, NULL,
      WA_Title,             "ReAction AppWindow",
      WA_DragBar,           TRUE,
      WA_CloseGadget,       TRUE,
      WA_SizeGadget,        TRUE,
      WA_DragBar,           TRUE,
      WA_DepthGadget,       TRUE,
      WA_Activate,          TRUE,
      WA_InnerWidth,        320,
      WA_InnerHeight,       200,
      WINDOW_Position,      WPOS_CENTERSCREEN,
      WINDOW_AppPort,       winAppPort,
      WINDOW_AppWindow,     TRUE,
      WINDOW_AppMsgHook,    appMsgHook,
      WINDOW_IconifyGadget, TRUE,
      WINDOW_Layout, objects[OID_LAYOUT] = IIntuition->NewObject(LayoutClass, NULL,
        LAYOUT_Orientation, LAYOUT_ORIENT_VERT,
        LAYOUT_SpaceOuter,  TRUE,
        LAYOUT_DeferLayout, TRUE,
        LAYOUT_AddChild, objects[OID_BUTTON] = IIntuition->NewObject(ButtonClass, NULL,
          GA_ID, OID_BUTTON,
          GA_ReadOnly, TRUE,
          GA_Text, "Drop a file icon here!",
        TAG_END),
      TAG_END),
    TAG_END)) )
  {
   /* Open the window. */
   if ( (window = (struct Window *)IIntuition->IDoMethod(objects[OID_WINDOW], WM_OPEN, NULL)) )
    {
     /* Enter the input loop. */
     handle_input(objects[OID_WINDOW]);
     /* Close the window when we're done. */
     IIntuition->IDoMethod(objects[OID_WINDOW], WM_CLOSE);
     errCode = RETURN_OK;
    }
   /* And dispose of the window object. */
   IIntuition->DisposeObject(objects[OID_WINDOW]);
  }


 cleanup:

 /* Free resources. */
 if (appMsgHook)  IExec->FreeSysObject(ASOT_HOOK, appMsgHook);
 if (winAppPort)  IExec->FreeSysObject(ASOT_PORT, winAppPort);

 if (ButtonBase)  IIntuition->CloseClass(ButtonBase);
 if (LayoutBase)  IIntuition->CloseClass(LayoutBase);
 if (WindowBase)  IIntuition->CloseClass(WindowBase);

 if (IIntuition)    IExec->DropInterface((struct Interface *)IIntuition);
 if (IntuitionBase) IExec->CloseLibrary(IntuitionBase);
 if (UtilityBase)   IExec->CloseLibrary(UtilityBase);


 return errCode;
}



[Retour en haut] / [Retour aux articles]