4ème partie: ouvrir un écran intuition

Un article de GuruMed.

par StAn.

Aujourd'hui, nous allons voir comment ouvrir notre fenêtre sur un écran privé plutôt que sur le Workbench. Deux intérêts à cela: avoir accès à toutes les couleurs de la palette, et bien sûr faire du plein écran à la résolution souhaitée au lieu d'avoir une petite fenêtre perdue au milieu du WB. De plus, cela permet de faire un meilleur multibuffering (double ou triple buffer, pour améliorer les animations), mais ce sujet sera abordé dans un autre article.

La partie "compliquée" consiste à ouvrir un écran qui soit visible sur la configuration de l'utilisateur... A priori, sur AGA, pas de problème. Cependant, quand on a une carte graphique, le système a une facheuse tendance à ouvrir un écran PAL, ou d'une taille n'ayant rien à voir avec la résolution demandée, ce qui est assez peu pratique.

Une des solutions à ce problème est de demander à l'utilisateur sur quel écran il veut que le programme s'ouvre, à l'aide d'un requester ASL. De cette façon, si l'écran ne convient pas, ça sera la faute de l'utilisateur. Nah.


Sommaire

Utiliser un requester ASL

Nous allons utiliser l'asl.library v38 ou supérieure (le requester de mode d'écran n'existe que depuis cette version). Comme expliqué précédemment, je n'ouvrirai cependant pas explicitement cette bibliothèque dans le programme d'exemple afin d'aller droit au but; mais si vous voulez programmer proprement pour que votre programme ne plante pas sur une version inférieure de l'OS, il vous faudra tester d'une façon ou d'une autre que la version est la bonne.

Il faut d'abord utiliser la fonction AllocAslRequest() pour préciser ce qu'on veut comme requester:

	
struct ScreenModeRequester *req;

req = (struct ScreenModeRequester *) AllocAslRequestTags( ASL_ScreenModeRequest,
   ASLSM_TitleText, (ULONG)"Choisissez une résolution",
   TAG_END );
  • ASL_ScreenModeRequest
Ce premier paramètre signifie que l'on veut ouvrir un requester de mode d'écran.
  • ASLSM_TitleText
Ce tag permet de choisir le titre du requester.

Il existe bien sûr d'autres tags, parmis lesquels: ASLSM_InitialLeftEdge, ASLSM_InitialTopEdge, ASLSM_InitialWidth, ASLSM_InitialHeight

Ces tags permettent de choisir la position et la taille du requester.

  • ASLSM_InitialDisplayID
Celui-ci permet de choisir le ModeID qui sera présélectionné dans la liste.
  • ASLSM_Window
Pour associer le requester à une fenêtre particulière (par exemple une fenêtre de préférences contenant un bouton "Choisir la résolution" qui ouvre le requester quand on clique dessus).
  • ASLSM_SleepWindow (booléen)
Afin de bloquer tous les évènement à destination de la fenêtre associée au requester tant que celui-ci est ouvert.

Filtrer la liste des modes d'écran...

Il existe également toute une série de tags destinés à filtrer les modes d'écrans qui feront partie de la liste affichée dans le requester: ASLSM_MinDepth, ASLSM_MaxDepth, ASLSM_MinWidth, ASLSM_MinHeight, ASLSM_MaxWidth et ASLSM_MaxHeight.

Par exemple vous pourriez limiter la liste aux modes d'une largeur supérieure ou égale à 640 pixels en rajoutant "ASLSM_MinWidth, 640" dans la liste des tags.

Malheureusement, ça ne marche pas. En tout cas pas sous CyberGraphics. La seule solution sûre est d'utiliser un hook.

  • ASLSM_FilterFunc
Ce tag prend en paramètre l'adresse d'un hook, qui est une structure contenant l'adresse d'une fonction à appeler pour chaque mode d'écran présent dans le système. Si cette fonction renvoie TRUE, alors le mode d'écran sera présent dans la liste. Sinon, il est ignoré.


Le hook est un peu plus difficile à utiliser que les tags Min/Max cités ci-dessus, mais heureusement il permet un contrôle encore plus grand sur les modes d'écrans affichés dans le requester.

Voici un exemple de hook qui filtre tous les modes d'écran inférieurs à 640x400 et d'une profondeur maximum différente de 8 bits:

	
BOOL scrModeFilter( struct Hook *hook, struct ScreenModeRequester *smr, ULONG modeID )
{
   struct DimensionInfo di;

   if( GetDisplayInfoData( NULL, (UBYTE*)&di, sizeof(struct DimensionInfo), DTAG_DIMS, modeID )
      >= (BYTE*)&di.Nominal.MaxY + 2 - (BYTE*)&di ) {
      if( di.Nominal.MaxX+1 < 640 || di.Nominal.MaxY+1 < 400 || di.MaxDepth != 8 ) return FALSE;
   }
   return TRUE;
}

Ce hook utilise la fonction GetDisplayInfoData() de la graphics.library pour récupérer des informations sur le mode d'écran. Cette fonction permet de remplir quatre structures de données différentes; nous avons ici besoin des informations qui sont dans la structure DimensionInfo. Une petite explication au sujet du test sur la valeur de retour de GetDisplayInfoData(): la fonction retourne le nombre d'octets écrits dans la structure. Parmis les trois champs que l'on utilise ici, Nominal.MaxY étant le plus éloigné du début de la structure, on vérifie que la fonction a copié suffisament d'octets pour que ce champ (et donc également les autres qui nous intéressent) ait été rempli. Je ne peux que vous conseiller de consulter l'AutoDoc de GetDisplayInfoData()ainsi que le fichier d'include graphics/displayinfo.h pour en apprendre plus.

Le hook s'utilise de cette façon:

	
struct ScreenModeRequester *req;
struct Hook hook = { NULL, NULL, (HOOKFUNC)HookEntry, (HOOKFUNC)scrModeFilter, 0 };

req = (struct ScreenModeRequester *) AllocAslRequestTags( ASL_ScreenModeRequest,
   ASLSM_FilterFunc, (ULONG)&hook,
   TAG_END );

2002-08-07: Dans cette nouvelle version, nous utilisons la fonction HookEntry() de l'amiga.lib qui se charge elle-même de placer les paramètres dans les bons registres. L'intérêt est multiple: d'une part le source sera forcément compilable avec tous les compilateurs sans utiliser de macros additionnelles, et d'autre part le hook pourra être compilé pour PowerPC (ou autre). (Cependant, l'amiga.lib de VBCC pour WarpOS et MorphOS ne contient pas cette fonction à l'heure où j'écris ces lignes...)


Ouvrir le requester de mode d'écran

Une fois le requester préparé avec AllocAslRequest(), on peut l'ouvrir:

	
LONG modeID = INVALID_ID;

if( AslRequest( req, NULL ) ) {
   modeID = req->sm_DisplayID;
}
FreeAslRequest( req );

La variable modeID contient après ceci le ModeID à utiliser lors de l'ouverture de l'écran, ou INVALID_ID si le requester a échoué.


Ouvrir un écran intuition

On peut maintenant ouvrir l'écran, avec la fonction OpenScreenTagList() (ou OpenScreenTags()) de l'intuition.library:

	
struct Screen *scr;
UWORD depth = DEPTH;

scr = OpenScreenTags( NULL,
   SA_Title,     (ULONG) "Super titre",
   SA_Type,      CUSTOMSCREEN,
   SA_DisplayID, modeID,
   SA_Depth,     depth,
   TAG_END );
  • NULL
Tout comme pour OpenWindowTags(), le premier paramètre est un pointeur vers une structure dont on peut très bien se passer (une structure NewScreen). Nous nous en passons donc.
  • SA_Title
Ce tag sert évidemment à choisir le titre de l'écran. Il est demandé dans l'AutoDoc de OpenScreenTagList() de toujours fournir un titre lorsqu'on ouvre un écran.
  • SA_Type
L'écran peut être privé (CUSTOMSCREEN) ou public (PUBLICSCREEN). S'il est public, alors d'autres programmes auront le droit d'ouvrir leurs fenêtres dessus.
  • SA_DisplayID
Voici le tag qui nous concerne plus particulièrement aujourd'hui. Il permet de donner le mode d'écran que l'on désire utiliser. On passe donc ici en paramètre la variable modeID retournée par le requester ASL.
  • SA_Depth
Ce tag sert à préciser la profondeur de l'écran. Il est très important de la spécifier, car la profondeur par défaut sous AGA et P96 est d'un bitplane (2 couleurs), tandis que CGX choisit lui 8 bitplanes (256 couleurs)...


Précision importante: sur carte graphique, il vaut mieux choisir une profondeur de 8 bits même si on utilise moins de 256 couleurs, car c'est souvent beaucoup plus rapide. Cela dépend du système utilisé, de certains réglages, ainsi que des fonctions de tracé utilisées, mais en tout cas ce n'est jamais plus lent que sur un écran de profondeur plus réduite. Par contre, en AGA il faut réduire au maximum le nombre de plans pour aller plus vite.

Nous devons donc déterminer si le ModeID est celui d'un mode natif ou d'un mode sur carte graphique. Si c'est une carte graphique, on utilisera une profondeur de 8 bitplans.

Pour cela, nous allons utiliser cette fois encore la fonction GetDisplayInfoData():

	
UWORD depth = DEPTH;
struct DisplayInfo di;

if( GetDisplayInfoData( NULL, (UBYTE*)&di, sizeof(struct DisplayInfo), DTAG_DISP, modeID )
   >= (BYTE*)&di.PropertyFlags + 4 - (BYTE*)&di ) {
   if( di.PropertyFlags & DIPF_IS_FOREIGN ) depth = 8;
}

DEPTH est ici sensé être une constante correspondant à la profondeur minimum nécessaire pour votre écran.


Ouvrir une fenêtre sur l'écran...

Une fois l'écran ouvert, il ne reste plus qu'à ouvrir notre fenêtre dessus. Il n'est pas forcément indispensable d'ouvrir une fenêtre puisqu'on peut tracer directement dans le bitmap de l'écran, celui-ci étant privé... Mais c'est souvent utile, notamment pour obtenir des évènements clavier ou souris de la part d'intuition.

	
struct Window *win;

win = OpenWindowTags( NULL,
   WA_CustomScreen, (ULONG) scr,
   WA_InnerWidth,   WIN_WIDTH,
   WA_InnerHeight,  WIN_HEIGHT,
   WA_Activate,     TRUE,
   TAG_END );
  • WA_CustomScreen
Voici le tag à utiliser pour que la fenêtre ne s'ouvre pas sur le WB mais sur un écran privé. En paramètre, l'adresse de l'écran qu'on vient d'ouvrir...
  • WA_Activate (booléen)
Rend la fenêtre active dès son ouverture.

Si vous souhaitez faire du plein écran en cachant les bordures de la fenêtre, deux autres tags vous seront utile:

  • WA_Borderless (booléen)
Pour tracer une fenêtre sans bordures.
  • WA_Backdrop (booléen)
Pour mettre cette fenêtre en arrière plan...


Le source

Afin de vérifier que tout cela fonctionne comme prévu, voici le source d'exemple du jour, qui reprend plus ou moins tous les bouts de code de l'article mis bout à bout. Ceci permet également de voir la fonction CloseScreen(), qui sert comme son nom l'indique à fermer un écran intuition.

	
/*
** Test de requête de mode d'écran ASL.
** Ouvre une fenêtre sur un écran et attend que le gadget de fermeture soit pressé.
*/

#include <clib/alib_protos.h>
#include <graphics/displayinfo.h>
#include <libraries/asl.h>
#include <proto/asl.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>

#define WIN_WIDTH  640
#define WIN_HEIGHT 400
#define DEPTH      4

BOOL scrModeFilter( struct Hook *hook, struct ScreenModeRequester *smr, ULONG modeID )
{
   struct DimensionInfo di;

   if( GetDisplayInfoData( NULL, (UBYTE*)&di, sizeof(struct DimensionInfo), DTAG_DIMS, modeID )
      >= (BYTE*)&di.Nominal.MaxY + 2 - (BYTE*)&di ) {
      if( di.Nominal.MaxX+1 < WIN_WIDTH || di.Nominal.MaxY+1 < WIN_HEIGHT || di.MaxDepth < DEPTH )
         return FALSE;
   }
   return TRUE;
}

int main( void )
{
   struct ScreenModeRequester *req;
   struct Hook hook = { NULL, NULL, (HOOKFUNC)HookEntry, (HOOKFUNC)scrModeFilter, 0 };
   LONG modeID = INVALID_ID;
   struct Screen *scr;
   UWORD depth = DEPTH;
   struct DisplayInfo di;
   struct Window *win;

   req = (struct ScreenModeRequester *) AllocAslRequestTags( ASL_ScreenModeRequest,
      ASLSM_FilterFunc, (ULONG)&hook,
      ASLSM_TitleText,  (ULONG)"Choisissez une résolution",
      TAG_END );

   if( AslRequest( req, NULL ) ) {
      modeID = req->sm_DisplayID;
   }
   FreeAslRequest( req );

   if( GetDisplayInfoData( NULL, (UBYTE*)&di, sizeof(struct DisplayInfo), DTAG_DISP, modeID )
      >= (BYTE*)&di.PropertyFlags + 4 - (BYTE*)&di ) {
      if( di.PropertyFlags & DIPF_IS_FOREIGN ) depth = 8;
   }

   scr = OpenScreenTags( NULL,
      SA_Title,     (ULONG) "Super titre",
      SA_Type,      CUSTOMSCREEN,
      SA_DisplayID, modeID,
      SA_Depth,     depth,
      TAG_END );

   win = OpenWindowTags( NULL,
      WA_CustomScreen, (ULONG) scr,
      WA_Width,        WIN_WIDTH,
      WA_Height,       WIN_HEIGHT,
      WA_Activate,     TRUE,
      WA_CloseGadget,  TRUE,
      WA_IDCMP,        IDCMP_CLOSEWINDOW,
      TAG_END );

   if( win ) {
      WaitPort( win->UserPort );
      CloseWindow( win );
   }

   if( scr ) CloseScreen( scr );
   return 0;
}

Peut-être avez-vous l'impression que j'ai oublié de nombreux tests de sécurité (allocation du requester ASL, ouverture de l'écran...); mais en fait, ce programme devrait se comporter parfaitement en cas de problème: AslRequest() échouera si req vaut NULL, modeID vaudra alors INVALID_ID et l'ouverture de l'écran échouera à cause de ça, et enfin l'ouverture de la fenêtre échouera si elle reçoit NULL comme paramètre à WA_CustomScreen. Tout est sous contrôle ;-).

Ah, un petit détail pour ceux qui ne le savent pas: avec VBCC, il suffit de rajouter "-+" ou "-c99" sur la ligne de commande pour que les commentaires "//" soient acceptés. Je vous rappelle également qu'il faut rajouter "-lauto" pour que les bibliothèques soient ouvertes automatiquement.

N'hésitez pas à écrire dans le forum pour toute demande de précisions ou autre...

Merci à Zapek et Sara pour leur aide sur les hooks et à Corto pour les tests en AGA.