La librairie OSF/MOTIF - Xm


Dernière modification : 29/10/96

Table des matières


Introduction

Un Toolkit X fournit au programmeur de GUI (Graphic User Interface) une boîte à outil comprenant des éléments graphiques (Widgets) et un ensemble d'outils pour gérer leur comportement (fonctions, structures de données, etc...).

Il existe deux niveaux de Toolkits au-dessus de X :

Si l'on utilise correctement les Widgets d'un Toolkit, cela simplifie considérablement la programmation d'une GUI. En outre, toutes les GUI développées avec un même Toolkit auront le même look and feel.

Lorsque nous écrirons des applications avec MOTIF nous aurons en sus des librairies MOTIF, à appeler certaines fonctions de la librairie Xt puisque MOTIF est construit sur cette dernière (il n'est cependant pas nécessaire de connaître en détail les mécanismes de la librairie Xt car MOTIF effectue la plus grande partie du travail d'interfaçage pour nous). Nous devrons utiliser la Xlib pour dessiner ou pour gérer les événements d'une manière plus fine que celle proposée par MOTIF.

Remarque : MOTIF a été développé par Open Software Foundation (OSF) et le nom complet de MOTIF est en fait OSF/MOTIF.


Premiers pas avec MOTIF

Notre premier programme MOTIF

Dans cette section, nous allons détailler un petit programme d'exemple, notre premier programme MOTIF.

Il ne va pas faire grand chose mais nous allons apprendre beaucoup en l'étudiant.

Que fait notre petit programme ?

Le programme push.c ouvre une fenêtre contenant un unique bouton poussoir. Ce bouton contient une chaîne de caractères : "Push_me". Lorsqu'on clique dessus (avec le bouton gauche de la souris), un message est affiché sur stdout (et non pas dans la fenêtre).

Pour sortir du programme, il faut tuer la fenêtre soit par ctrl-c dans le xterm d'oú on l'a lancée, soit à l'aide du window manager.

Nous verrons bientôt comment créer d'autres types de Widgets, des menus, etc...

Le binaire exécutable se trouve dans :

~buffa/cours/minfo/motif/pushbutton/push.

Vous pouvez l'essayer. Par curiosité, regardez la taille du binaire!

Qu'allons-nous apprendre de ce petit programme ?

On apprend toujours beaucoup du premier programme que l'on écrit avec une nouvelle librairie. Ce petit programme est celui proposé dans le tutorial de la documentation MOTIF. En l'étudiant, vous marchez dans mes traces car comme vous, j'ai commencé l'apprentissage de MOTIF avec ce petit programme de 20 lignes.

Nous allons voir (et comprendre) comment :

Le programme push.c



#include <Xm/PushB.h>

/*--------------------------*/
main(int argc, char **argv)
/*--------------------------*/
{   

  Widget top_wid, button;
  XtAppContext  app;
  void pushed_fn();
  
  top_wid = XtVaAppInitialize(&app, "Push", NULL, 0,
                              &argc, argv, NULL, NULL);

  button = XmCreatePushButton(top_wid, "Push_me", NULL, 0);


  /* On dit à Xt de manager le bouton */
  XtManageChild(button);
   
  /* On attache un CallBack au bouton */
  XtAddCallback(button, XmNactivateCallback, activateCB, NULL);

  /* Affichage de la fenêtre principale (top_wid) et de tous ses enfants */
  XtRealizeWidget(top_wid); 

  /* boucle de gestion des événements */
  XtAppMainLoop(app); 
}

/*----------------------------------------------*/
void activateCB(Widget w, XtPointer client_data, 
               XmPushButtonCallbackStruct *cbs)
/*----------------------------------------------*/
{   
  printf("Don't Push Me!!\n"); 
}



L'appel de fonctions MOTIF, Xt et Xlib

Lorsqu'on écrit une application MOTIF, on appelle explicitement des fonctions, on manipule des types de données appartenant aux librairies Xm, Xt et/ou à la Xlib.

En général, on utilise les fonctions Xlib pour dessiner ou effectuer une gestion plus fine des événements.

Conventions de nommage :

Remarque : il y a quelques exceptions notoires comme Widget (Xt) ou Window (Xlib).


Compiler un programme MOTIF

Fichiers à inclure

Pour qu'une application MOTIF compile sans problèmes, il faut inclure les fichiers suivants :

Remarque : ce n'est pas la peine d'inclure des fichiers pour la librairie Xt car <Xm/Xm.h> les inclut déjà.

Edition de liens

Il faut linker avec les librairies MOTIF, Xt et Xlib. On utilisera donc les options suivantes : -lXm -lXt -lX11 pendant l'édition de liens.

Remarque : l'ordre d'inclusion de ces librairies est très important !

Ces librairies se trouvent dans le répertoire /ust/dt/lib (Solaris) ou /usr/X11R6/lib (Linux, SunOS).

En général, les includes, librairies, manuels de MOTIF se trouvent dans les chemins standards de la librairie X11.


Principes de base de la programmation MOTIF

Initialisation de la toolkit :

L'initialisation de la toolkit Xt doit se faire avant toute chose !

Il existe plusieurs moyens de réaliser cette opération, celui proposé dans le petit programme push.c, à l'aide de la fonction XtVaAppInitialize() est le plus commun. C'est celui que nous utiliserons dans la plupart de nos exemples.

L'appel de XtVaAppInitialize() réalise les tâches suivantes :

Description des paramètres de XtVaAppInitialize() :

Application context
Structure nécessaire à la librairie pour fonctionner correctement. On ne va pas s'en occuper car l'utilité de cette structure est réservée aux utilisateurs avancés. On se contentera toujours d'utiliser l'application context de la même manière que dans push.c tout au long des exemples que nous étudierons ensemble.
Application class name
Une chaîne de caractère qui définit la classe de l'application. Par convention, le nom de la classe est le même que celui de l'application mais avec la première lettre en majuscule. Attention de ne pas vous tromper car le nom du fichier de ressources pour l'application sera le même. Si votre application commence par un "x" alors le nom de sa classe aura les deux premières lettres en majuscules.
Par exemple un programme appelé visu aura comme classe Visu et comme fichier de ressources par défaut /usr/lib/X11/app-defaults/Visu (sous linux : /usr/X11R6/lib/X11/app-defaults/Visu)
Troisième et quatrième paramètres (Special X command line arguments)
Réservé aux utilisateurs avancés de MOTIF. L'utilisation de ces paramètres sort du cadre de ce cours, se réfèrer à la documentation Xt pour plus de détails. On se contentera de mettre le troisième paramètre à NULL et le quatrième à zéro.
argc et argv
Contiennent les arguments de la ligne de commande. Standard...
Septième et huitième paramètres (Fallback Ressources)
Sortent du cadre du cours. On mettra tout ça à NULL!

Création d'un Widget

Avec MOTIF, il existe une fonction de création unique pour chaque type de Widget. Il est facile de deviner le nom de la fonction de création lorsqu'on connaît le type de Widget que l'on désire créer. La règle est la suivante :



 Pour créer <widget name>, on utilise la fonction 
   XmCreate<widget name>. 
 

Presque toutes les fonctions de création possèdent 4 paramètres :

Le troisième paramètre sert à initialiser les ressources des Widgets au moment de leur création (hauteur, largeur, couleur, etc...).

En général ces fonctions retrournent l'ID du Widget créé. Faites cependant attention, il existe des exceptions notoires ! MOTIF permet de créer des objets complexes, comprenant plusieurs Widgets assemblés. Ces objets complexes sont ensuite manipulés comme s'il s'agissait d'un seul Widget. Par exemple le Widget de type ScrolledText est en réalité une fenêtre de type Text (on peut écrire du texte à l'intérieur) accompagnée d'une SrollBar. Mais dans quoi allons nous insérer du texte ? Dans la ScrollBar ? Non ! La plupart du temps, ce qui nous intéresse c'est le Widget Text, donc, c'est l'ID de ce dernier qui est renvoyé.

Manager un Widget :

Une fois créé, un Widget doit être pris en charge, il doit être managé. Cette prise en charge est réalisée par l'appel de la fonction XtManageChild().

Lorsque un Widget est managé, tous les aspects liés à sa taille son pris en compte par son père, il est rattaché à l'arborescence des Widgets qui composent l'application.

Si on ne manage pas un Widget, il demeure invisible!

Autre méthode de création à l'aide de fonctions Xt

Il est parfois plus pratique de créer des WIdgets en utilisant directement des fonctions de la librairie Xt telles que XtVaCreateWidget() et XtVaCreateManagedWidget().

L'avantage de ces fonctions est qu'elles permettent de :

Nous verront de nombreux exemples d'utilisation de ces deux fonctions lors des prochains chapitres de ce cours.

Rappel : les fonctions de création proposées par MOTIF créent des widgets qui ne sont pas managés et si l'on veut spécifier des ressources, il faut le faire à l'aide de fonctions supplémentaires.

Ressources d'un Widget

Chaque Widget appartient à une classe. Chaque classe possède des ressources propres et hérite des ressources de ses superclasses. Ca en fait un paquet !

Il est très important d'avoir une assez bonne idée de la hiérarchie des classes MOTIF. On reconnaît un programmeur MOTIF expérimenté car il devine les ressources des Widgets qu'il manipule.

Le nom des ressources de chaque classe se trouve dans le manuel de chaque objet. Lorsqu'on fait man XmPushButton, on a droit tout d'abord à un tableau listant les ressources de la classe PushButton, puis un tableau moins détaillé listant les ressources de la classe Primitive dont hérite le bouton, etc...

Nommage des ressources

Ou spécifier les ressources ? Quelques recommendations...

En savoir plus sur les ressources

Un chapitre entier de ce cours est consacré à l'étude détaillée des mécanismes de gestion des ressources. Inutile d'en dire trop maintenant.

Gestion des événements, les Callbacks

Principe de gestion des événements sous MOTIF :

Lorsqu'un Widget est créé il sait de lui-même comment répondre à certains événements : changer de taille lors d'une requête du Window Manager, changer son apparence lors d'un click souris (dans le cas d'un bouton poussoir), se redessiner si besoin est (gestion automatique des Expose events) etc...

Nous avons déjà vu dans la partie du cours consacrée à la Xlib les différents types d'événements et la complexité de leur prise en compte. Fort heureusement, Xt va grandement faciliter la tâche du programmeur car la plupart des Widgets possèdent des mécanismes simples et puissants pour prendre en compte les événements les plus courants.

Pour qu'un Widget utilise des fonctions spécifiées par le programmeur, il faut positionner certaines ressources du Widget que l'on appelle des Callback Resources.

Les tables de translation :

Chaque Widget possède une table de translation qui définit la manière dont il va réagir à certains événements particuliers.

La liste complète des tables de translation de chaque Widget se trouve dans le manuel de référence Motif que chacun d'entre vous devrait avoir sur les genoux si cette monstruosité ne pesait pas dix kilos et n'était pas impossible à photocopier. Tout ceci rend la tâche du pauvre enseignant bien difficile...

Heureusement, il reste les manuels en ligne.

Voici un extrait de la table de translation du bouton poussoir :



 BSelectPress: Arm()

 BSelectClick: Activate(), Disarm()


BSelectPress correspond à un appui sur le bouton de gauche de la souris. L'action correspondante est un appel à la fonction interne Arm() qui modifie l'affichage du bouton pour simuler son enfoncement.

BSelectClick correspond à un click souris, c'est-à-dire lorsque le bouton de gauche est enfoncé puis relâché, alors l'action sera Activate() suivi de Disarm(), cette dernière redessine le bouton comme s'il avait repris son apparence "non enfoncé".

Les événements clavier peuvent également figurer dans la table de translation. Le programmeur peut spécifier des raccourcis clavier, prendre en compte l'appui de certaines touches aux fonctions particulières...

Par exemple : KActivate - typiquement la touche return (lorsque on entre un texte dans un sélécteur, <return> sera équivalent à presser le bouton Ok par exemple), KHelp, etc...

Spécifier des callbacks :

Les fonctions Arm(), Activate() ou Disarm() sont des exemples de callbacks prédéfinis. On les appelle des actions. Si l'on veut exécuter une fonction particulière lorsque l'utilisateur appuie sur un bouton, il faut attacher des callbacks aux Widgets.

Dans push.c la fonction activateCB() est un callback. On l'attache au bouton poussoir à l'aide de la fonction XtAddCallBack() qui est la fonction la plus commune pour effectuer cette opération.

Elle possède 4 paramètres :

Dans cet exemple, lors d'un click souris sur le bouton, MOTIF va bien ajouter à la liste des actions l'action précisé par le programmeur, qui consiste à appeler la fonction activateCB(), mais les mécanismes internes du Toolkit vont quand même exécuter les actions prédéfinies Activate() et Disarm(). Il s'agit d'un mécanisme d'ajout (XtAddCallBack). Le programmeur peut très bien spécifier plus d'un callback pour un même type d'événement.

Par exemple, dans push.c, si l'on voulait également appeler la fonction utilisateur quit(), on ajouterait la ligne suivante :



   XtAddCallback(button, XmNdisarmCallback, quit, NULL);



Déclaration des fonctions de callback :

Regardons maintenant dans le programme push.c comment la fonction de callback activateCB() a été déclarée :



   void activateCB(Widget w, XtPointer client_data, 
                   XmPushButtonCallbackStruct *cbs)



Les fonctions de callback possèdent trois paramètres :

Par exemple, dans push.c, cette structure devra être castée par le type XmPushButtonCallbackStruct puisque le widget qui a provoqué l'appel est un bouton poussoir.

D'une maniére générale, la Callback Structure du widget <widget name> a la forme suivante :



   typdef struct {
      int reason;
      XEvent *event;
      ... champs spécifiques au Widget
   } Xm<widget name>CallbackStruct;



Le champ reason contient des informations sur le callback telles que "arm ou disarm ont provoqué l'appel de la fonction de callback".

Affichage des Widgets et boucle de gestion des événements :

Nous avons presque terminé l'étude de notre premier programme. Il ne reste plus que deux étapes avant la fin, deux étapes essentielles que tout programnme MOTIF doit effectuer en dernier :


michel.buffa@essi.fr