IV. Motif▲
IV-A. Mise en œuvre de Motif▲
IV-A-1. Introduction▲
Un Toolkit X fournit au programmeur de GUI (Graphic User Interface) une boite à outils 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 :
- le X Toolkit Intrinsincs présenté dans le chapitre précédent du cours, qui permet aux programmeurs de créer de nouveaux widgets ;
- les third party Toolkits comme Athena ou MOTIF qui proposent un ensemble de widgets au look and feel différents.
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 bibliothèques MOTIF, à appeler certaines fonctions de la bibliothèque 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 bibliothèque Xt, car MOTIF effectue la plus grande partie du travail d'interfaçage pour nous).
MOTIF a été développé par Open Software Foundation (OSF) et le nom complet de MOTIF est en fait OSF/MOTIF.
IV-A-2. Premiers pas avec MOTIF▲
IV-A-2-a. Notre premier programme▲
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.
IV-A-2-b. 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.
IV-A-2-c. Qu'allons-nous apprendre de ce petit programme ?▲
On apprend toujours beaucoup du premier programme que l'on écrit avec une nouvelle bibliothèque. Ce petit programme est celui proposé dans le tutoriel de la documentation MOTIF.
Nous allons voir (et comprendre) comment :
- écrire et compiler un programme MOTIF ;
- s'effectuent les relations entre la Xlib, la bibliothèque MOTIF et les Intrinsincs ;
- créer un widget et configurer ses ressources ;
- gérer les événements ;
- appeler des fonctions à partir des événements.
IV-A-2-d. Le programme push.c▲
#include <Xm/Xm.h>
#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
"
);
}
IV-A-2-e. 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 bibliothèques 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.
Toutes les fonctions et les types de données MOTIF commencent par Xm. Par exemple, dans push.c, XmCreatePushButton() est une fonction MOTIF.
Toutes les fonctions et types de données des Intrinsincs commencent par Xt. Par exemple, dans push.c, XtVaAppInitialize() et XtManageChild() sont des fonctions Xt.
Toutes les fonctions et les types de données de la Xlib commencent par X. Nous avons vu des exemples nombreux dans les premiers chapitres du cours.
Il y a quelques exceptions notoires comme widget (Xt) ou Window (Xlib).
IV-A-3. Compiler un programme MOTIF▲
IV-A-3-a. Fichiers à inclure▲
Pour qu'une application MOTIF compile sans problème, il faut inclure les fichiers suivants : <Xm/Xm.h>. Obligatoire !
Un fichier par type de widget utilisé. Chaque widget possède son propre fichier include. Par exemple, dans push.c on a utilisé un bouton-poussoir de type PushButton, il faut donc inclure <Xm/PushB.h>.
Ce n'est pas la peine d'inclure des fichiers pour la bibliothèque Xt, car <Xm/Xm.h> les inclut déjà.
IV-A-3-b. Édition de liens▲
Il faut linker avec les bibliothèques MOTIF, Xt et Xlib. On utilisera donc les options suivantes : -lXm -lXt -lX11 pendant l'édition de liens.
L'ordre d'inclusion de ces bibliothèques est très important ! En général, les includes, bibliothèques, manuels de MOTIF se trouvent dans les chemins standards de la bibliothèque X11.
IV-A-4. Bases de la programmation MOTIF▲
IV-A-4-a. 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 :
- ouverture d'une connexion avec le serveur X : ouverture du Display ;
- prise en compte des options X standards (-display, -geometry, etc.) sur la ligne de commande.
IV-A-4-b. Lecture et prise en compte des ressources▲
Une fenêtre top_level (container principal de l'application) est créée, et un pointeur retourné.
Description des paramètres de XtVaAppInitialize() :
- Application context : structure nécessaire à la bibliothèque 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ères 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 ;
- 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 !
IV-A-4-c. 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 quatre paramètres :
- Un pointeur vers le widget père, top_wid dans le programme push.c. ;
- Le nom du widget, « Push_me » dans l'exemple. Ce nom servira à positionner les ressources du bouton dans le fichier de ressources ;
- Une liste d'attributs/ressources. NULL dans l'exemple, mais nous verrons plus tard l'utilité de ce paramètre ;
- Le nombre d'éléments dans la liste précédente.
Le troisième paramètre sert à initialiser les ressources des widgets au moment de leur création (hauteur, largeur, couleur, etc.).
IV-A-4-d. 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().
Lorsqu'un widget est managé, tous les aspects liés à sa taille sont pris en compte par son père.
Nous étudierons ce mécanisme plus en détail dans la suite du cours.
IV-A-4-e. 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 clic 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.
IV-A-4-e-i. 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.
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 clic 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 (lorsqu'on entre un texte dans un sélecteur <return> sera équivalent à presser le bouton OK par exemple), KHelp, etc.
IV-A-4-e-ii. Spécifier des callbacks▲
Les fonctions Arm(), Activate() ou Disarm() sont des exemples de callbacks prédéfinis. 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 quatre paramètres :
- Un pointeur sur le widget (bouton avec l'exemple de push.c) ;
- Le nom de la Callback Ressouce du widget, dans notre exemple, on veut appeler activateCB() chaque fois que le bouton est activé, c'est-à-dire chaque fois qu'on clique dessus (appui et relâche du bouton souris), on positionne donc la ressource XmNactivateCallback ;
- Un pointeur sur la fonction utilisateur que l'on désire appeler ;
- Des paramètres d'appel que l'on appelle Client Data.
Nous verrons des exemples d'utilisation de ce paramètre dans la suite du cours, mais pour le moment ne compliquons pas et mettons-le à NULL.
Dans cet exemple, lors d'un clic souris sur le bouton, MOTIF va bien ajouter à la liste des actions l'action précisée 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. 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
);
IV-A-4-e-iii. 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 :
- Un pointeur sur le(s) widget(s) associé(s) à ce callback. Plusieurs widgets peuvent appeler une même fonction de callback ;
- Le second paramètre sert à passer des client data à la fonction de callback. Nous verrons plus tard dans le cours comment utiliser ce paramètre. Pour le moment, laissons-le comme ça ;
- Un pointeur sur une structure qui contient tous les renseignements relatifs à l'événement qui a provoqué l'appel de la fonction de callback, ainsi que des renseignements relatifs au type de widget passé en 1er paramètre.
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 » qui ont provoqué l'appel de la fonction de callback.
IV-A-4-e-iv. 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 programme MOTIF doit effectuer en dernier :
- afficher, réaliser (realize) les widgets. Ceci est fait en appelant la fonction des Intrinsincs XtRealizeWidget().
Nous passons comme paramètre de cette fonction un pointeur vers la fenêtre racine de notre application, le top level widget top_wid. Ainsi, lorsque ce widget sera affiché, tous ses enfants le seront aussi ; - effectuer la gestion des événements. L'appel de XtAppMainLoop(app) le fait pour nous. Après cet appel, Xt prend le contrôle du programme, gère les événements, appelle les fonctions de callback que nous avons définies, etc.
IV-B. Généralités sur les widgets▲
IV-B-1. Introduction▲
MOTIF possède deux grandes classes de widgets : primitive et manager.
Plusieurs choses à savoir :
- ces deux grandes classes sont découpées en nombreuses sous-classes ;
- un widget de la classe primitive est destiné à fonctionner de manière atomique : il ne contiendra pas d'autre widget. L'exemple typique de widget appartenant à cette classe est le bouton-poussoir ;
- un widget de la classe manager est destiné à servir de container et pourra contenir d'autres widgets.
IV-B-2. Les différents types de widgets▲
IV-B-2-a. Les widgets de la classe primitive▲
IV-B-2-a-i. ArrowButton ▲
Un bouton contenant non pas un label, mais un pixmap en forme de flèche. Une ressource permet de choisir le sens de la flèche.
Exemple d'ArrowButtons :
IV-B-2-a-ii. Label▲
Cette classe possède quatre sous-classes :
- PushButton : un bouton-poussoir contenant un texte.
Voici un exemple de PushButtons : - DrawnButton : un bouton-poussoir contenant un pixmap ;
- CascadeButton : Un bouton-poussoir particulier utilisé pour faire des menus déroulants. Voici un exemple de CascadeButtons :
- ToggleButton : Un bouton On/Off. Voici un exemple de ToggleButtons :
IV-B-2-a-iii. Scrollbar▲
Permet d'afficher le contenu d'une fenêtre partiellement. À l'aide d'une Scrollbar l'utilisateur peut sélectionner la partie de la fenêtre qui est visible à l'écran. Voici un exemple de Scrollbar :
IV-B-2-a-iv. Separator▲
Éléments graphiques permettant d'afficher des lignes verticales ou horizontales pour séparer différentes parties d'un menu par exemple. Voici un exemple de Separator :
IV-B-2-a-v. List▲
Une boite contenant une liste d'éléments que l'utilisateur peut choisir à la souris. Voici un exemple de List :
IV-B-2-a-vi. Text▲
Ce widget est un petit éditeur de texte à lui tout seul. Voici un exemple de Text :
IV-B-2-a-vii. TextField▲
Un éditeur de texte d'une seule ligne. Voici un exemple de TextField :
IV-B-2-a-viii. Les gadgets▲
Parfois, en lisant les manuels MOTIF, vous allez entendre parler de gadgets. Les gadgets sont des « Widgets simplifiés ». Il n'en existe que de trois types : ArrowButton, Label et Separator.
Ils ne disposent pas d'autant de ressources que les widgets correspondants et leur gestion par les bibliothèques Xm et Xt est plus rapide bien que leur comportement soit équivalent.
Nous ne nous attarderons pas sur les gadgets, car avec la puissance des machines actuelles et la complexité relativement faible des applications que nous allons développer dans ce cours, la différence entre gadgets et widgets est imperceptible.
IV-B-2-b. Les widgets de la classe Manager▲
La classe Manager est divisée en de nombreuses sous-classes.
IV-B-2-b-i. Frame▲
Ce type de widget permet d'encadrer d'autres widgets en spécifiant la taille du cadre, son style, etc. Voici un exemple de Frame :
IV-B-2-b-ii. Scrolled Window▲
Une fenêtre possédant des Scrollbars. On peut mettre dans une Scrolled Window d'autres widgets dont la taille excède celle de la ScrolledWindow.
IV-B-2-b-iii. MainWindow▲
Un container de type TopLevel spécialement désigné pour contenir une application simple. On peut mettre dans une MainWindow plusieurs types de widgets. Voici un exemple de MainWindow :
IV-B-2-b-iv. DrawingArea▲
On peut dessiner dans ce widget.
MOTIF ne fournit aucune fonction de dessin. Il faudra faire appel à des fonctions de la Xlib. Voici un exemple de DrawingArea :
IV-B-2-b-v. PanedWindow▲
Un container pour ranger verticalement plusieurs widgets.
IV-B-2-b-vi. RowColumn▲
Widget gérant automatiquement le positionnement de plusieurs widgets en lignes et colonnes.
IV-B-2-b-vii. Scale▲
Un ascenseur horizontal ou vertical permettant à l'utilisateur de spécifier à la souris une valeur appartenant à un intervalle borné linéaire.
IV-B-2-b-viii. BulletinBoard▲
Cette classe de widgets possède deux sous-classes :
- Form : même utilité que RowColumn : permet de contrôler le positionnement des widgets créés à l'intérieur de ce widget. Ce contrôle est plus puissant, mais plus complexe qu'avec les RowColumns ;
- Dialog : il existe deux classes de Dialog widgets :
- MessageBox qui permet d'afficher un message dans une « boite » (une fenêtre de type TopLevel) pour informer l'utilisateur,
- SelectionBox qui permet l'interaction avec l'utilisateur.
MOTIF fournit un widget de type Command pour la saisie de commandes utilisateur et un widget de type FileSelectionBox pour la sélection des fichiers.
IV-C. Spécification des ressources▲
IV-C-1. Le fichier app-defaults▲
Le mécanisme de gestion des ressources d'une application permet de personnaliser les widgets sans avoir à recompiler. Les ressources peuvent être spécifiées à plusieurs endroits : sur la ligne de commande, dans le fichier $HOME/.Xdefaults de l'utilisateur, dans un fichier utilisateur dédié à l'application (par exemple dans le répertoire $HOME/.app-defaults), ou encore dans un fichier de ressources par défaut (dans le répertoire /usr/lib/app-defaults).
Chaque ressource d'un widget possède une valeur par défaut déterminée par sa classe. Il est cependant possible de modifier cette valeur par défaut et/ou rendre la ressource configurable par l'utilisateur.
Prenons l'exemple du bouton-poussoir du cours précédent (le programme push.c).
#include <Xm/Xm.h>
#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
"
);
}
Dans ce programme, la chaîne de caractères affichée dans le bouton est égale à « Push_me », car par défaut, dans le cas d'un bouton-poussoir, le label est égal au nom du widget.
Ceci n'est vrai que dans le cas des widgets appartenant à la classe Label, et n'est pas vérifié en ce qui concerne les autres ressources de notre bouton-poussoir.
Xt propose un système très pratique pour spécifier des valeurs par défaut aux ressources utilisées par les widgets d'une application : les fichiers de ressources par défaut, que l'on trouve dans le répertoire /usr/lib/X11/appdefaults (sous Unix).
Le fichier de ressources par défaut d'une application devra être égal au nom de la classe de l'application, deuxième paramètre de la fonction XtVaAppInitialize(). Dans notre exemple, le fichier devra s'appeler Push.
Par convention, la classe d'une application sera égale au nom de l'application, mais avec la première lettre en majuscule. Si l'application commence par un X, on capitalisera les deux premières lettres (voilà pourquoi le fichier de ressources de l'application xterm s'appelle XTerm).
IV-D. Le widget de type RowColumn▲
IV-D-1. Présentation▲
Il s'agit du plus simple des « container widgets », car il permet aisément de gérer le positionnement de ses widgets fils.
Dans un RowColumn, les widgets sont positionnés de la manière suivante :
- les widgets fils sont alignés horizontalement ou verticalement dans l'ordre de création. La ressource XmNorientation spécifie le sens de l'alignement, et possède XmVERTICAL comme valeur par défaut, XmHORIZONTAL est l'autre valeur possible ;
- les widgets peuvent être alignés sur plusieurs lignes ou colonnes selon la valeur de XmNorientation. La ressource XmNnumColumns spécifie le nombre de lignes/colonnes (uniquement si la ressource XmNpacking vaut XmPACK_COLUMNS) ;
- les widgets fils doivent avoir la même taille. Si ce n'est pas le cas ou bien si la taille de ces widgets n'est pas spécifiée par leurs propres ressources, alors on peut demander au RowColumn de « forcer » leur taille. Ceci peut être fait de plusieurs manières selon la valeur de la ressource XmNpacking :
- XmNpacking vaut XmPACK_TIGHT (valeur par défaut) : les widgets auront toutes la même largeur égale à la largeur du plus large des fils. Si la taille de la RowColumn change, la taille des fils changera en conséquence,
- XmNpacking vaut XmPACK_COLUMNS : tous les widgets fils auront la même taille et seront alignés sur le nombre de lignes/colonnes spécifié par la ressource XmNnumColumns,
- XmNpacking vaut XmPACK_NONE : le RowColumn ne va pas essayer de changer la taille ou la position des widgets fils.
Examinons maintenant quelques exemples…
IV-D-2. Les programmes rowcol1.c et rowcol2.c▲
Ces deux programmes créent une petite boite qui contient quatre boutons-poussoirs (alignés verticalement dans rowcol1.c et horizontalement dans rowcol2.c). Le résultat est le suivant :
IV-D-2-a. rowcol1.c▲
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
main
(
argc, argv)
int
argc;
char
*
argv[];
{
Widget top_widget, row_column, button1, button2, button3, button4;
XtAppContext app;
int
n;
Arg args[10
];
top_widget =
XtVaAppInitialize
(&
app, "
rowcol1
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
n=
0
;
row_column =
XmCreateRowColumn (
top_widget, "
row_column
"
, args, n);
XtManageChild
(
row_column);
n=
0
;
button1 =
XmCreatePushButton
(
row_column, "
button1
"
, args, n);
XtManageChild
(
button1);
n=
0
;
button2 =
XmCreatePushButton
(
row_column, "
button2
"
, args, n);
XtManageChild
(
button2);
n=
0
;
button3 =
XmCreatePushButton
(
row_column, "
button3
"
, args, n);
XtManageChild
(
button3);
n=
0
;
button4 =
XmCreatePushButton
(
row_column, "
button4
"
, args, n);
XtManageChild
(
button4);
XtRealizeWidget
(
top_widget);
XtAppMainLoop
(
app);
}
IV-D-2-b. rowcol2.c▲
Remplacer juste les lignes de création du RowColumn par :
n=
0
;
XtSetArg
(
args[n], XmNorientation, XmHORIZONTAL); n++
;
row_column =
XmCreateRowColumn (
top_widget, "
row_column
"
, args, n);
XtManageChild
(
row_column);
Seule la valeur de la ressource XmNorientation a changé. Au lieu de valoir XmVERTICAL qui est la valeur par défaut, comme dans rowcol1.c, elle vaut ici XmHORIZONTAL. Le reste du code est le même.
Pour compiler sans erreur, nous avons inclus le fichier <Xm/RowColumn.h>.
Il existe bien d'autres ressources pour les widgets de type RowColumn, mais nous avons vu les trois plus importantes : XmNorientation, XmNpacking et XmNnumColumns.
IV-E. Le widget de type Form▲
IV-E-1. Introduction▲
Après le RowColumn relativement simple à utiliser, le widget de type Form est l'autre widget « container » proposé par Motif.
Par rapport au RowColumn, le widget de type Form permet un contrôle plus précis et plus puissant du positionnement géométrique des widgets fils, au prix d'une certaine complexité d'utilisation. Avec un Form, les widgets fils peuvent avoir des tailles différentes et on peut leur donner des attachements relatifs les uns par rapport aux autres.
Le positionnement des widgets fils dans un Form widget peut être réalisé de plusieurs manières.
Examinons tout d'abord trois petits programmes form1.c, form2.c et form3.c qui utilisent trois approches différentes pour produire le même résultat :
IV-E-2. Attachement simple des widgets fils▲
Lorsqu'un widget est créé dans une Form, il hérite de nouveaux attributs qu'il ne possède pas naturellement.
Ces attributs concernent son positionnement à l'intérieur de la Form.
Parmi ceux-ci, certains attributs servent à attacher le widget à ses widgets voisins dans la Form, ou bien directement aux bords de la Form.
Les ressources concernant l'attachement sont les suivantes :
- XmNtopAttachment spécifie à quoi est attaché le widget par le haut ;
- XmNbottomAttachment spécifie à quoi est attaché le widget par le bas ;
- XmNleftAttachment spécifie à quoi est attaché le widget par la gauche ;
- XmNrightAttachment spécifie à quoi est attaché le widget par la droite.
Les valeurs possibles sont :
- XmATTACH_FORM pour attacher le widget à un bord intérieur de la Form ;
- XmATTACH_WIDGET pour attacher le widget à un des autres widgets qui se trouvent dans la Form. Il faudra alors positionner une autre ressource pour spécifier le widget auquel on s'attache :
- XmNtopWidget pour un attachement par le haut,
- XmNbottomWidget pour un attachement par le bas,
- XmNleftWidget pour un attachement par la gauche,
- XmNrightWidget pour un attachement par la droite.
Dans l'exemple suivant les widgets fils sont placés dans une Form en précisant pour chaque widget son attachement soit à un ou plusieurs côtés de la Form, soit à d'autres widgets.
IV-E-3. Le programme form1.c▲
On crée quatre boutons dans une Form :
- bouton1 est attaché par le haut et la gauche à la form ;
- bouton2 est attaché par le bas et la gauche à la form, par le haut à bouton1 ;
- bouton3 est attaché par le haut et la droite à la form, par la gauche à bouton1 ;
- bouton4 est attaché par le bas et la droite à la form, par le haut à bouton3 et par la gauche à bouton2.
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
main (
int
argc, char
**
argv)
{
XtAppContext app;
Widget top_widget, form, button1, button2, button3, button4;
int
n=
0
;
Arg args[10
];
top_widget =
XtVaAppInitialize
(&
app, "
form1
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
n=
0
;
form =
XmCreateForm
(
top_widget, "
form
"
, args, n);
XtManageChild
(
form);
n=
0
;
/* bouton 1 : bords haut et gauche attachés à la Form */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_FORM); n++
;
button1 =
XmCreatePushButton
(
form, "
button1
"
, args, n);
XtManageChild
(
button1);
n=
0
;
/* bouton 2: bords haut attaché au bouton 1, bas et gauche
à la Form */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_WIDGET); n++
;
XtSetArg
(
args[n], XmNtopWidget, button1); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_FORM); n++
;
button2 =
XmCreatePushButton
(
form, "
button2
"
, args, n);
XtManageChild
(
button2);
n=
0
;
/* bouton 3: bords gauche attaché au bouton 1, haut et
droite à la Form */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_WIDGET); n++
;
XtSetArg
(
args[n], XmNleftWidget, button1); n++
;
button3 =
XmCreatePushButton
(
form, "
button3
"
, args, n);
XtManageChild
(
button3);
n=
0
;
/* bouton 4: bords haut attaché au bouton 3, gauche au bouton 2,
bas et droite à la Form */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_WIDGET); n++
;
XtSetArg
(
args[n], XmNtopWidget, button3); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_WIDGET); n++
;
XtSetArg
(
args[n], XmNleftWidget, button2); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_FORM); n++
;
button4 =
XmCreatePushButton
(
form, "
button4
"
, args, n);
XtManageChild
(
button4);
XtRealizeWidget
(
top_widget);
XtAppMainLoop
(
app);
}
Pour compiler sans erreur il faut inclure le fichier <Xm/Form.h>.
IV-E-4. Attachement à des positions prédéfinies dans la Form▲
Il est possible de positionner un widget à un endroit particulier dans une Form sans l'attacher à un autre widget. En effet, MOTIF suppose qu'une Form est découpée en segments verticaux et horizontaux formant une grille régulière. La position d'un widget pourra ainsi être spécifiée en indiquant un nombre de segments à partir du coin en haut à gauche de la Form.
Par défaut une Form possède 100 divisions horizontales et verticales.
On peut changer cette valeur à l'aide de la ressource XmNfractionBase.
La position d'un côté particulier d'un widget dans une Form sera précisée à l'aide des ressources d'attachement vues précédemment XmNtopAttachment, XmNbottomAttachment, etc., simplement on leur donnera une nouvelle valeur : XmATTACH_POSITION.
Il faudra ensuite positionner la ressource correspondante XmNtopPosition, XmNbottomPosition, etc. à la valeur désirée (un entier).
IV-E-5. Le programme form2.c▲
Dans cet exemple nous allons indiquer la position des différents boutons de la manière suivante :
- bouton1 sera attaché par le haut et la gauche à la Form, et ses bords droits et bas s'arrêteront à la position 50 (comme il y a 100 positions, ce bouton occupera exactement le quart haut et gauche de la Form) ;
- bouton2 sera attaché par le bas et la gauche à la Form, et ses bords haut et droit s'arrêteront à la position 50 ;
- bouton3 sera attaché par le haut et la droite à la Form, et ses bords bas et gauche s'arrêteront à la position 50 ;
- bouton4 sera attaché par le bas et la droite à la Form, et ses bords haut et gauche s'arrêteront à la position 50.
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
main (
int
argc, char
**
argv)
{
XtAppContext app;
Widget top_widget, form, button1, button2, button3, button4;
int
n=
0
;
Arg args[10
];
top_widget =
XtVaAppInitialize
(&
app, "
form1
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
n=
0
;
form =
XmCreateForm
(
top_widget, "
form
"
, args, n);
XtManageChild
(
form);
n=
0
;
/* bouton 1 : bords haut et gauche attachés à la Form,
bords droit et bas à la position 50/100 */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 50
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 50
); n++
;
button1 =
XmCreatePushButton
(
form, "
button1
"
, args, n);
XtManageChild
(
button1);
n=
0
;
/* bouton 2: bords bas et gauche attachés à la Form,
droit et haut à la position 50/100 */
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 50
); n++
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 50
); n++
;
button2 =
XmCreatePushButton
(
form, "
button2
"
, args, n);
XtManageChild
(
button2);
n=
0
;
/* bouton 3: bords haut et droit attachés à la Form,
bas et gauche à la position 50/100 */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 50
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 50
); n++
;
button3 =
XmCreatePushButton
(
form, "
button3
"
, args, n);
XtManageChild
(
button3);
n=
0
;
/* bouton 4: bas et droit attachés à la Form,
haut et gauche à la position 50/100 */
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 50
); n++
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 50
); n++
;
button4 =
XmCreatePushButton
(
form, "
button4
"
, args, n);
XtManageChild
(
button4);
XtRealizeWidget
(
top_widget);
XtAppMainLoop
(
app);
}
Essayez de modifier à l'aide du window manager la taille de la fenêtre correspondant aux programmes form1 et form2 obtenus à partir des sources présentées ici.
IV-E-6. Attachements opposés▲
L'attachement opposé est un autre moyen d'attacher les bords d'un widget.
On réalise un tel attachement en positionnant comme auparavant les ressources XmntopAttachment, XmNbottomAttachment, etc., mais cette fois-ci, on leur donne la valeur XmATTACH_OPPOSITE_WIDGET. On précisera le widget auquel on s'attache à l'aide de la ressource XmNwidget déjà étudiée.
Avec ce type d'attachement « opposé », on attache les côtés « similaires ». Au lieu de dire « Je vais attacher le bord droit de mon widget au bord gauche du widget voisin », on va dire « J'attache le bord droit de mon widget au bord droit d'un autre widget de manière à ce qu'ils soient alignés ».
IV-E-7. Le programme form3.c▲
Cet exemple produit exactement le même résultat que le programme form2.c. Seules les ressources des widgets button2 et button4 ont été modifiées. Pour button2, on a indiqué que l'on voulait son bord droit aligné sur celui de button1. Pour button4, on a indiqué que l'on voulait son bord gauche aligné sur celui de button3.
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
main (
int
argc, char
**
argv)
{
XtAppContext app;
Widget top_widget, form, button1, button2, button3, button4;
int
n=
0
;
Arg args[10
];
top_widget =
XtVaAppInitialize
(&
app, "
form1
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
n=
0
;
form =
XmCreateForm
(
top_widget, "
form
"
, args, n);
XtManageChild
(
form);
n=
0
;
/* bouton 1 : bords haut et gauche attachés à la Form,
bords droit et bas à la position 50/100 */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 50
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 50
); n++
;
button1 =
XmCreatePushButton
(
form, "
button1
"
, args, n);
XtManageChild
(
button1);
n=
0
;
/* bouton 2: bords bas et gauche attachés à la Form,
droit et haut à la position 50/100 */
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_OPPOSITE_WIDGET); n++
;
XtSetArg
(
args[n], XmNrightWidget, button1); n++
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 50
); n++
;
button2 =
XmCreatePushButton
(
form, "
button2
"
, args, n);
XtManageChild
(
button2);
n=
0
;
/* bouton 3: bords haut et droit attachés à la Form,
bas et gauche à la position 50/100 */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 50
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 50
); n++
;
button3 =
XmCreatePushButton
(
form, "
button3
"
, args, n);
XtManageChild
(
button3);
n=
0
;
/* bouton 4: bas et droit attachés à la Form,
haut et gauche à la position 50/100 */
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_FORM); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET); n++
;
XtSetArg
(
args[n], XmNleftWidget, button3); n++
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 50
); n++
;
button4 =
XmCreatePushButton
(
form, "
button4
"
, args, n);
XtManageChild
(
button4);
XtRealizeWidget
(
top_widget);
XtAppMainLoop
(
app);
}
IV-E-8. Un exemple plus complet▲
Nous terminerons l'étude des widgets de type Form avec cet exemple.
Nous allons créer une Form contenant deux types de widgets différents. Nous allons également utiliser des fonctions de callbacks.
Ce petit programme s'appelle arrows.c. Il crée quatre widgets de type ArrowButton autour d'un PushButton labellé « Quit » :
Lorsqu'on clique sur les boutons, ils affichent simplement un message sur stdout.
Ce petit programme utilise une astuce : la ressource XmNfractionBase de la Form est positionnée à 3. Ceci crée une grille de 3 x 3 cases qui est plus facile à manipuler dans ce cas précis.
Le programme arrows.c :
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/ArrowB.h>
#include <Xm/Form.h>
void
north
(
), south
(
), east
(
), west
(
), quitb
(
);
/*------------------------------------------*/
main (
int
argc, char
**
argv)
/*------------------------------------------*/
{
XtAppContext app;
Widget top_widget, form, arrow1, arrow2, arrow3, arrow4, quit;
int
n=
0
;
Arg args[10
];
top_widget =
XtVaAppInitialize
(&
app, "
form1
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
n=
0
;
XtSetArg
(
args[n], XmNfractionBase, 3
); n++
;
form =
XmCreateForm
(
top_widget, "
form
"
, args, n);
XtManageChild
(
form);
n=
0
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtop, 0
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 1
); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 1
); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 2
); n++
;
XtSetArg
(
args[n], XmNarrowDirection, XmARROW_UP); n++
;
arrow1 =
XmCreateArrowButton
(
form, "
arrow1
"
, args, n);
XtAddCallback
(
arrow1, XmNactivateCallback, north, NULL
);
XtManageChild
(
arrow1);
n=
0
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 1
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 2
); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 0
); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 1
); n++
;
XtSetArg
(
args[n], XmNarrowDirection, XmARROW_LEFT); n++
;
arrow2 =
XmCreateArrowButton
(
form, "
arrow2
"
, args, n);
XtAddCallback
(
arrow2, XmNactivateCallback, west, NULL
);
XtManageChild
(
arrow2);
n=
0
;
/* bouton 3: bords haut et droit attachés à la Form,
bas et gauche à la position 50/100 */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 1
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 2
); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 2
); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 3
); n++
;
XtSetArg
(
args[n], XmNarrowDirection, XmARROW_RIGHT); n++
;
arrow3 =
XmCreateArrowButton
(
form, "
arrow3
"
, args, n);
XtAddCallback
(
arrow3, XmNactivateCallback, east, NULL
);
XtManageChild
(
arrow3);
n=
0
;
/* bouton 4: bas et droit attachés à la Form,
haut et gauche à la position 50/100 */
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 2
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 3
); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 1
); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 2
); n++
;
XtSetArg
(
args[n], XmNarrowDirection, XmARROW_DOWN); n++
;
arrow4 =
XmCreateArrowButton
(
form, "
arrow4
"
, args, n);
XtAddCallback
(
arrow4, XmNactivateCallback, south, NULL
);
XtManageChild
(
arrow4);
n=
0
;
XtSetArg
(
args[n], XmNtopAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNtopPosition, 1
); n++
;
XtSetArg
(
args[n], XmNbottomAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNbottomPosition, 2
); n++
;
XtSetArg
(
args[n], XmNleftAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNleftPosition, 1
); n++
;
XtSetArg
(
args[n], XmNrightAttachment, XmATTACH_POSITION); n++
;
XtSetArg
(
args[n], XmNrightPosition, 2
); n++
;
quit =
XmCreatePushButton
(
form, "
quit
"
, args, n);
XtAddCallback
(
quit, XmNactivateCallback, quitb, NULL
);
XtManageChild
(
quit);
XtRealizeWidget
(
top_widget);
XtAppMainLoop
(
app);
}
/* CALLBACKS */
/*------------------------------------------*/
void
north
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs)
/*------------------------------------------*/
{
printf
(
"
Going North
\n
"
);
}
/*------------------------------------------*/
void
west
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs)
/*------------------------------------------*/
{
printf
(
"
Going West
\n
"
);
}
/*------------------------------------------*/
void
east
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs)
/*------------------------------------------*/
{
printf
(
"
Going East
\n
"
);
}
/*------------------------------------------*/
void
south
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs)
/*------------------------------------------*/
{
printf
(
"
Going South
\n
"
);
}
/*------------------------------------------*/
void
quitb
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs)
/*------------------------------------------*/
{
printf
(
"
quit button pressed
\n
"
);
exit
(
0
);
}
IV-F. Le widget de type MainWindow, les Menus▲
IV-F-1. Introduction▲
Dans cette section nous allons étudier un autre type de « container », le widget de type MainWindow. Nous étudierons particulièrement les relations entre les widgets de ce type et les différents menus. Nous verrons qu'il est également possible de mettre d'autres types de widgets dans une MainWindow.
IV-F-2. Le widget de type MainWindow▲
Les MainWindow sont utilisés comme top level « container » pour des applications simples.
Une MainWindow consiste en général en une MenuBar (barre de menu) située en haut de la fenêtre de l'application, et une work area en dessous pouvant contenir n'importe quel type de widgets. Voici un exemple d'application bâtie sur une MainWindow :
On peut mettre dans une barre de menu :
- des boutons ;
- des menus déroulants ou Pulldown menus, qui peuvent à leur tour contenir des sous-menus déroulants.
En plus de la work area, le widget de type MainWindow gère une zone appelée command area dans laquelle l'utilisateur peut entrer des commandes (du texte sur une ligne), et une message area dans laquelle l'application peut écrire.
Pour le moment, étudions les menus !
IV-F-3. Le widget MenuBar▲
La création d'un véritable menu comme on en retrouve dans la plupart des applications est une tâche relativement complexe. C'est pourquoi nous allons découper l'étude des menus en deux étapes.
Dans un premier temps nous allons voir comment créer une barre de menu simple contenant seulement des boutons.
Dans un second temps nous verrons comment ajouter à cette barre de menu des menus déroulants.
Les boutons que va contenir une barre de menu ou un menu déroulant sont d'un type particulier : le type CascadeButton. Vous comprendrez bien vite pourquoi on les a nommés de la sorte.
Étudions maintenant un petit exemple.
IV-F-4. Le programme menu_cascade.c▲
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/RowColumn.h>
void
quit_call
(
), help_call
(
); /* callbacks */
/*---------------------------*/
main
(
int
argc, char
*
argv[])
/*---------------------------*/
{
Widget top_wid, main_win, menu_bar, quit, help;
XtAppContext app;
int
n=
0
;
Arg args[10
];
/* create application, main and menubar widgets */
top_wid =
XtVaAppInitialize
(&
app, "
menu_cascade
"
,
NULL
, 0
, &
argc, argv, NULL
, NULL
);
n=
0
;
main_win =
XmCreateMainWindow
(
top_wid, "
main_window
"
, args, n);
XtManageChild
(
main_win);
n=
0
;
menu_bar =
XmCreateMenuBar
(
main_win, "
main_list
"
, args, n);
XtManageChild
(
menu_bar);
/* create quit widget + callback */
n=
0
;
XtSetArg
(
args[n], XmNmnemonic, ‘Q'
); n++;
quit = XmCreateCascadeButton(menu_bar, "Quit", args, n); n++;
XtManageChild(quit);
XtAddCallback(quit, XmNactivateCallback, quit_call, NULL);
/* create help widget + callback */
n=0;
XtSetArg(args[n], XmNmnemonic, ‘H
'
); n++
;
help =
XmCreateCascadeButton
(
menu_bar, "
Help
"
, args, n); n++
;
XtManageChild
(
help);
XtAddCallback
(
help, XmNactivateCallback, help_call, NULL
);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*---------------*/
void
quit_call
(
)
/*---------------*/
{
printf
(
"
On sort du programme!
\n
"
);
exit
(
0
);
}
/*---------------*/
void
help_call
(
)
/*---------------*/
{
printf
(
"
Désolé je ne peux pas vous aider
\n
"
);
}
Les premières lignes de ce programme devraient maintenant vous être familières.
Dans la fenêtre top_level nous créons une MainWindow (variable main_win), nous créons aussi une MenuBar (variable menu_bar) comme enfant de main_win, et finalement nous créons deux CascadeButton (quit et help) dans la MenuBar.
En général, un widget de type CascadeButton sert à indiquer le nom d'un menu déroulant. Lorsqu'on clique dessus, le menu se déroule en cascade, c'est pourquoi on les appelle des CascadeButton. Ils se conduisent exactement comme des PushButton et peuvent avoir des callbacks (ressource XmNactivateCallback).
Un CascadeButton peut très bien ne pas avoir de menu déroulant. Il se conduit alors exactement comme un bouton-poussoir. C'est le cas dans le programme menu_cascade.c. Dans ce programme d'exemple, nous avons créé deux boutons auxquels nous avons attaché des callbacks très simples.
Raccourcis clavier : nous avons installé pour chacun des boutons un raccourci clavier équivalent à un clic souris sur le bouton. Dans l'exemple, si on tape meta-q on sort du programme comme si on avait cliqué sur le bouton quit. Idem pour le bouton help : l'appui de meta-h est équivalent à cliquer dessus.
Nous avons utilisé pour cela la Ressource XmNmnemonic. Son utilisation est très simple (voir menu_cascade.c).
Autre exemple d'école
On va tout d'abord créer une MainWindow contenant une MenuBar et un widget de type Frame dans la WorkArea.
#include <Xm/Xm.h>
#include <Xm/RepType.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/RowColumn.h>
#include <Xm/Frame.h>
/*---------------------------*/
main
(
int
argc, char
*
argv[])
/*---------------------------*/
{
XtAppContext app_context;
Widget topLevel, mainWindow, menuBar, frame;
Widget fileButton, fileMenu, quit, help, helpButton, helpMenu, helpBox;
/* On indique que l'on désire utiliser le langage par défaut
(anglais). Cette fonction initialise le support international de
l'application. Les paramètres indiquent que l'on prend les
valeurs par defaut */
XtSetLanguageProc
(
NULL
, (
XtLanguageProc) NULL
, NULL
);
topLevel =
XtVaAppInitialize
(&
app_context, "
XMainWIndow
"
,
NULL
, 0
, &
argc, argv, NULL
, NULL
);
/* On crée la main window */
mainWindow =
XtVaCreateManagedWidget
(
"
mainWindow
"
,
xmMainWindowWidgetClass,
topLevel,
NULL
);
/* On crée la barre de menus. On utilise une fonction Xm ici, car
la fonction XmCreateMenuBar effectue des opérations qui n'ont pas
d'équivalent avec Xt */
menuBar =
XmCreateMenuBar
(
mainWindow, "
menuBar
"
, NULL
, 0
);
XtManageChild
(
menuBar);
/* On crée une frame dans la work area de la main window */
frame =
XtVaCreateManagedWidget
(
"
frame
"
,
xmFrameWidgetClass,
mainWindow,
NULL
);
/* On positionne les parties de la main window */
XmMainWindowSetAreas
(
mainWindow, menuBar, NULL
, NULL
, NULL
, frame);
XtRealizeWidget
(
topLevel);
XtAppMainLoop
(
app_context);
}
Exercice : créer le fichier de ressources pour cette application tel que la MainWindow soit large de 200 pixels, et la Frame fasse 300 x 300 pixels. La WorkingArea de la MainWindow étant en fait une ScrolledWindow, on pourra positionner la ressource scrollingPolicy de la MainWindow pour faire apparaître des ScrollBars.
Nous étudierons plus en détail les ScrolledWindows et les ScrollBars dans la suite du cours.
On va maintenant ajouter des menus à cette application.
IV-F-5. Création d'un menu▲
Motif fournit trois types de menus : Popup, Pulldown et Option. Les Popups menus apparaissent à l'écran lorsqu'on presse une touche particulière, une combinaison de touches, ou un bouton de la souris dans un widget. Les Pulldown menus et les Option menus possèdent des boutons à l'écran qui déclenchent leur apparition. Les Pulldown menus sont des menus déroulants (sous le bouton qui a déclenché leur déroulement) alors que les Option menus apparaissent « sur le bouton », parfois en montant.
Les Option menus se souviennent de la dernière sélection. Les menus accrochés à une MenuBar sont des PulldownMenus.
IV-F-6. Étapes▲
- Créer un CascadeButton comme fils de la MenuBar. C'est le bouton qui va déclencher l'apparition du menu.
- Créer un PulldownMenu vide, fils de la MenuBar, qui va contenir les différents items. On utilisera la fonction XmCreatePulldownMenu().
Il ne faut pas le manager puisque le menu apparaît à la demande. C'est le CascadeButton qui s'occupera de ce travail pour nous !
- Pour information, un PulldownMenu est en réalité une RowColumn, tout comme la MenuBar.
- Créer des PushButtons comme fils du Pulldown.
- Indiquer au CascadeButton le menu qu'il doit dérouler.
Cette action est réalisée en positionnant la ressource XmNsubMenuId du CascadeButton avec l'identificateur du PullDownMenu.
Code de la nouvelle version :
#include <Xm/Xm.h>
#include <Xm/RepType.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/RowColumn.h>
#include <Xm/Frame.h>
#include <Xm/PushB.h>
void
QuitCB
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs);
/*---------------------------*/
main
(
int
argc, char
*
argv[])
/*---------------------------*/
{
.
.
.
.
/* Création du menu :
1) On crée un cascade bouton File dans la menubar */
fileButton =
XtVaCreateManagedWidget
(
"
fileButton
"
,
xmCascadeButtonWidgetClass,
menuBar,
NULL
);
/* 2) Création du PullDownMenu vide. ATTENTION : on utilise une
fonction Xm! Remarquez que le menu n'est pas manage !*/
fileMenu =
XmCreatePulldownMenu
(
menuBar,
"
fileMenu
"
,
NULL
,
0
);
/* 3) Création d'un bouton quit dans le menu File */
quit =
XtVaCreateManagedWidget
(
"
quit
"
,
xmPushButtonWidgetClass,
fileMenu,
NULL
);
/* 4) On indique au CascadeButton le menu qu'il doit dérouler */
XtVaSetValues
(
fileButton,
XmNsubMenuId, fileMenu,
NULL
);
/* On crée un callback pour le bouton quit */
XtAddCallback
(
quit, XmNactivateCallback, QuitCB, 0
);
XtRealizeWidget
(
topLevel);
XtAppMainLoop
(
app_context);
}
/*----------------------------------------------*/
void
QuitCB
(
Widget w, XtPointer client_data,
XmPushButtonCallbackStruct *
cbs)
/*----------------------------------------------*/
{
exit
(
0
);
}
IV-F-7. Exercices▲
Essayez de créer un des sous-menus. Il suffit de mettre un CascadeButton dans le Pulldown Menu et de recommencer le processus de création d'un menu que l'on attachera à ce bouton.
Créez une Form dans la Frame et positionnez des objets à l'intérieur.
Observez le comportement des ScrollBars.
IV-F-8. Un exemple un peu plus compliqué▲
Nous allons développer le programme menu_pull.c qui va contenir deux menus déroulants. Le premier, « Quitter », va contenir un seul item (une seule entrée) qui nous permettra de sortir du programme.
Le second menu, « Couleur » proposera cinq items qui permettront de changer la couleur du fond du Label widget attaché à la MainWindow.
Le début du programme ressemble à l'exemple précédent : on crée un top_level, on crée une MainWindow dans le top_level, une MenuBar dans la MainWindow, des CascadeButton dans la MenuBar, et ensuite, plein de trucs nouveaux ! Alors, étudiez bien ce petit bout de code sans vous affoler, et rendez-vous après ces quelques lignes de source C !
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/Label.h>
#include <Xm/RowColumn.h>
Widget top_wid, label, main_w;
String colours[] =
{
"
Black
"
, "
Red
"
, "
Green
"
, "
Blue
"
, "
Grey
"
}
;
Display *
display; /* xlib id of display */
Colormap cmap;
/*---------------*/
main
(
argc, argv)
/*---------------*/
int
argc;
char
*
argv[];
{
Widget menubar, menu, quit_w, widget;
XtAppContext app;
XColor back, fore, spare;
XmString quit, colour, red, green, blue, black, grey, label_str;
void
quit_call
(
), colour_call
(
);
int
n =
0
;
Arg args[2
];
/* Initialize toolkit */
top_wid =
XtVaAppInitialize
(&
app, "
Demos
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
/* MainWindow will contain a MenuBar and a Label */
n=
0
;
XtSetArg
(
args[n], XmNwidth, 300
); n++
;
XtSetArg
(
args[n], XmNheight, 300
); n++
;
main_w =
XmCreateMainWindow
(
top_wid, "
main_window
"
, args, n);
XtManageChild
(
main_w);
/* Create a simple MenuBar that contains three menus */
quit =
XmStringCreateSimple
(
"
Quit
"
);
colour =
XmStringCreateSimple
(
"
Colour
"
);
menubar =
XmVaCreateSimpleMenuBar
(
main_w, "
menubar
"
,
XmVaCASCADEBUTTON, quit, ‘Q'
,
XmVaCASCADEBUTTON, colour, ‘C
'
,
NULL
);
XmStringFree
(
colour); /* finished with this so free */
/* First menu is the quit menu -- callback is quit_call() */
quit_w =
XmVaCreateSimplePulldownMenu
(
menubar, "
quit_menu
"
,
0
, quit_call,
XmVaPUSHBUTTON, quit, ‘Q'
, NULL, NULL,
NULL);
XmStringFree(quit);
/* Second menu is the color menu -- callback
is colour_call() */
black = XmStringCreateSimple(colours[0]);
red = XmStringCreateSimple(colours[1]);
green = XmStringCreateSimple(colours[2]);
blue = XmStringCreateSimple(colours[3]);
grey = XmStringCreateSimple(colours[4]);
menu = XmVaCreateSimplePulldownMenu(menubar, "edit_menu",
1, colour_call,
XmVaRADIOBUTTON, black, ‘k
'
, NULL
, NULL
,
XmVaRADIOBUTTON, red, ‘R'
, NULL, NULL,
XmVaRADIOBUTTON, green, ‘G
'
, NULL
, NULL
,
XmVaRADIOBUTTON, blue, ‘B'
, NULL, NULL,
XmVaRADIOBUTTON, grey, ‘e
'
, NULL
, NULL
,
/* RowColumn resources to enforce */
XmNradioBehavior, True,
/* radio behavior in Menu */
XmNradioAlwaysOne, True,
NULL
);
XmStringFree
(
black);
XmStringFree
(
red);
XmStringFree
(
green);
XmStringFree
(
blue);
/* Initialize menu so that "black" is selected. */
if
(
widget =
XtNameToWidget
(
menu, "
button_0
"
))
XtVaSetValues
(
widget, XmNset, True, NULL
);
XtManageChild
(
menubar);
/* create a label text widget that will
be "work area" we colour */
label_str =
XmStringCreateSimple
(
"
Colour Me
"
);
label =
XtVaCreateManagedWidget
(
"
main_window
"
,
xmLabelWidgetClass, main_w,
XmNlabelString, label_str,
NULL
);
XmStringFree
(
label_str);
/* set the label as the "work area"
of the main window */
XtVaSetValues
(
main_w,
XmNmenuBar, menubar,
XmNworkWindow, label,
NULL
);
/* background pixel to black foreground to white */
cmap =
DefaultColormapOfScreen
(
XtScreen
(
label));
display =
XtDisplay
(
label);
XAllocNamedColor
(
display, cmap, colours[0
], &
back, &
spare);
XAllocNamedColor
(
display, cmap, "
white
"
, &
fore, &
spare);
XtSetArg
(
args[n],XmNbackground, back.pixel);
++
n;
XtSetArg
(
args[n],XmNforeground, fore.pixel);
++
n;
XtSetValues
(
label, args, n);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*-------------------------*/
void
quit_call
(
w, item_no)
/*-------------------------*/
/* Any item the user selects from the File menu calls
this function. It will "Quit" (item_no == 0).
*/
Widget w; /* menu item that was selected */
int
item_no; /* the index into the menu */
{
if
(
item_no ==
0
) /* the "quit" item */
exit
(
0
);
}
/*--------------------------*/
void
colour_call
(
w, item_no)
/*--------------------------*/
/* called from any of the "Colour" menu items.
Change the color of the label widget.
Note: we have to use dynamic setting with setargs()..
*/
Widget w; /* menu item that was selected */
int
item_no; /* the index into the menu */
{
int
n =
0
;
Arg args[1
];
XColor xcolour, spare; /* xlib color struct */
if
(
XAllocNamedColor
(
display, cmap, colours[item_no],
&
xcolour, &
spare) ==
0
)
return
;
XtSetArg
(
args[n],XmNbackground, xcolour.pixel);
++
n;
XtSetValues
(
label, args, n);
}
Bon. Respirez un grand coup… Hmmmmmffffff ! Pas de panique !
Beaucoup de nouveautés ici, des fonctions bizarres qui ressemblent à celles déjà étudiées, mais légèrement différentes, de la couleur (oui, de la couleur !), des chaînes de caractères utilisées de manière bizarre, etc. Nous allons étudier tout ceci calmement…
La fonction XmVaCreateSimpleMenuBar() : dans cet exemple, nous avons créé la MenuBar à l'aide de la fonction XmVaCreateSimpleMenuBar() qui est plus facile à utiliser que la fonction XmCreateMenuBar() de l'exemple précédent. Elle permet d'indiquer les CascadeButton que l'on veut mettre dans la barre de menu dès la création de cette dernière.
Cette fonction possède trois types d'arguments :
- un pointeur sur le widget père ;
- un nom de type chaîne de caractères :
- une liste de CascadeButton terminée par NULL.
Trois paramètres sont nécessaires pour spécifier un CascadeButton :
- Le nom de la ressource : XmVaCASCADEBUTTON permet d'initialiser le texte qui apparaît sur le bouton (son label) ;
- La valeur du label qui doit être de type XmString. On a converti une chaîne de caractères normale à l'aide de la fonction XmStringCreateSimple() qui est détaillée dans la partie du cours dédiée aux chaînes de caractères sous MOTIF ;
- Un raccourci clavier.
Il faut prendre l'habitude de libérer systématiquement la place occupée par une XmString dès qu'on en a plus besoin. Cette opération est réalisée en appelant la fonction XmStringFree().
Les fonctions contenant « Va » après « Xt » ou « Xm » créent des widgets qui n'ont pas besoin d'être managés (pas besoin d'appel à XtManageChild()) la fonction XmVaCreateSimplePulldownMenu().
Pour créer un menu déroulant, il faut créer un widget de type PulldownMenu. Pour attacher un PulldownMenu à une barre de menu, il suffit de lui donner comme père un des CascadeButton de la barre de menu.
Regardons comment nous avons créé le menu déroulant Quit :
quit_w =
XmVaCreateSimplePulldownMenu
(
menubar, "
quit_menu
"
,
0
, quit_call,
XmVaPUSHBUTTON, quit, '
Q
'
, NULL
, NULL
,
NULL
);
Étudions ensemble les paramètres de la fonction XmVaCreateSimplePulldownMenu() :
- un pointeur sur le widget père (menu_bar dans cet exemple) ;
- un nom de type chaîne de caractères ;
- un entier qui est l'identificateur du CascadeButton auquel le PulldownMenu va s'attacher. Ici la valeur de cet Id est 0, le menu va donc s'attacher au CascadeButton Quit. Si cet Id avait valu 1, le menu se serait attaché au deuxième bouton, c'est-à-dire au bouton Colour ;
- le quatrième paramètre spécifie un callback associé à n'importe quel choix dans ce menu. Tous les choix appelleront le même callback. Remarque : on ne spécifie pas la fonction de callback à l'aide de XtAddCallback() dans ce cas ;
- on a ensuite une liste terminée par NULL qui spécifie les différents choix dans le menu. Chaque élément de cette liste comprend cinq paramètres :
- XmVaPUSHBUTTON,
- un élément de type XmString qui va contenir le label du choix correspondant dans le menu,
- un raccourci clavier,
- les deux derniers paramètres sont réservés aux utilisateurs avancés de MOTIF et sortent du cadre de ce cours.
Le menu Colour est créé de manière similaire si ce n'est qu'il possède cinq choix au lieu d'un seul dans le menu Quit.
IV-F-9. Les callbacks des menus▲
Il reste à étudier une dernière chose : comment traiter les choix faits par l'utilisateur lors de l'exécution de notre programme ?
Chaque PullDown menu possède sa propre fonction de callback. Les fonctions de callback des menus possèdent deux paramètres :
- un pointeur sur le widget qui a provoqué l'appel de la fonction de callback ;
- un index correspondant au numéro du choix que l'utilisateur a fait.
Ainsi, dans la fonction de callback du menu Quit, quit_call(), une seule sélection est possible (la variable item_no doit être égale à zéro).
Dans la fonction de call du menu Colour, colour_call(), la valeur du choix va provoquer une action (changer la couleur du fond de widget de type Label qui se trouve dans la work area de la MainWindow.
Ne vous affolez pas à propos de la couleur, nous l'étudierons en détail dans les prochains cours. Pour comprendre l'exemple, il suffit de savoir que la structure de données xcolour, de type XColor stocke la valeur de la couleur que nous désirons dans le champ xcolour.pixel après allocation par l'appel de XAllocNamedColor(). Il suffit ensuite de positionner la ressource XmNbackgound du widget de type Label à l'aide des fonctions XtSetArg() et XtSetValues().
IV-G. Les widgets de type Dialog▲
IV-G-1. Introduction▲
IV-G-1-a. Rôle des Dialog widgets▲
Les widgets de type Dialog permettent le dialogue entre l'application et l'utilisateur. Les widgets de type Dialog « poppent » une fenêtre contenant un message et peuvent selon les cas demander à l'utilisateur d'entrer du texte au clavier.
Ils peuvent fournir en standard trois boutons-poussoirs :
- Un bouton-poussoir OK qui ferme la fenêtre de dialogue et valide l'entrée clavier effectuée par l'utilisateur, s'il y en a eu une ;
- Un bouton-poussoir Cancel qui ferme la fenêtre de dialogue et ne valide pas l'entrée clavier ;
- Un bouton-poussoir Help qui sert à afficher une aide en ligne pour l'utilisateur.
Exemple typique de Dialog widget : le message affiché par Xemacs lorsque vous n'avez pas sauvegardé vos fichiers avant de compiler, le message affiché par Netscape « Unable to locate file », etc.
IV-G-1-b. Les différents types de Dialog widgets▲
Les Dialogs jouent de nombreux rôles : afficher de l'information, un message d'erreur, des warnings, proposer l'entrée d'une commande. Ainsi, pour chacun de ces usages MOTIF propose un type de widget différent :
- BulletinBoardDialog : permet la création de widgets de dialogue customisés ;
- ErrorDialog : affichage de messages d'erreur ;
- SelectionDialog : sélection d'un item parmi une liste d'options ;
- FileSelectionDialog : widget spécialisé dans le choix de fichiers et/ou de répertoires ;
- InformationDialog : affichage de messages d'information, documentation en ligne, etc. ;
- PromptDialog : permet la saisie clavier de données utilisateur ;
- QuestionDialog : demande à l'utilisateur d'effectuer un choix de type Oui/Non ;
- WarningDialog : affichage de messages d'alerte ;
- WorkingDialog : avertit l'utilisateur que l'application est en train d'effectuer une opération.
Pour créer un Dialog, il faut utiliser des fonctions de type XmCreate…Dialog(). Par exemple, XmCreateFileSelectionBoxDialog().
Pour faire apparaître un Dialog à l'écran, il suffit de le manager à l'aide de la fonction XtManageChild().
IV-G-2. Le widget de type WarningDialog▲
Ce widget sert à signaler une erreur à l'utilisateur. Il peut s'agir d'une erreur du programme lui-même ou d'une erreur de manipulation de la part de l'utilisateur.
Un exemple typique d'utilisation : lorsque l'utilisateur clique sur le bouton « Quit » d'une application, il vaut mieux lui demander s'il est bien sûr de vouloir effectuer cette action (surtout s'il n'a pas sauvegardé le travail en cours).
Nous allons bientôt étudier ensemble le petit programme dialog1.c qui met en place un tel mécanisme :
Tout ce que le WarningDialog contient est une chaîne de caractères de type XmString qui informe l'utilisateur sur la nature du message.
Différentes étapes pour créer ce WarningDialog widget :
- initialiser la variable de type XmString avec la valeur du message d'information. En l'occurrence « Are you sure you want to quit ? » ;
- initialiser la ressource XmNmessageString du WarningDialog widget avec la variable précédente ;
- créer le WarningDialog widget à l'aide de la fonction XmCreateWarningDialog() ;
- ajouter des fonctions de callback aux boutons OK, Cancel et Help du WarningDialog widget.
Dans un premier temps, on se contentera d'ajouter un callback au bouton OK en positionnant la ressource XmNokCallback du WarningDialog widget.
Manager le widget à l'aide de la fonction XtManageChild() afin de l'afficher.
L'utilisation de la fonction XtPopup() permet de l'afficher en le « poppant » à l'écran.
IV-G-3. Le widget de type InformationDialog▲
Ce widget ressemble énormément à celui de type WarningDialog. La seule différence est le dessin de l'icône qui est affichée juste avant le message (un grand « i » au lieu d'un point d'exclamation).
En général les InformationDialog sont utilisés pour afficher une petite aide en ligne ou bien des informations concernant l'application (par exemple un menu contenant une entrée « about » pourra popper un InformationDialog widget contenant le numéro de la version, les auteurs, etc.).
Pour créer un InformationDialog, on procède de la même manière que pour le WarningDialog, excepté que l'on utilise la fonction XmCreateInformationDialog().
Exemple d'InformationDialog widget :
Le programme dialog1.c :
Ce petit programme contient des widgets de type InformationDialog et WarningDialog.
Étant donné que ces widgets sont dérivés de la classe MessageBox, nous devons inclure le fichier <Xm/MessageB.h>.
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/MessageB.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
/*---------------------------*/
main
(
int
argc, char
*
argv[])
/*---------------------------*/
{
XtAppContext app;
Widget top_wid, main_w, menu_bar, info, quit;
/* callback for the pushbuttons. pops up dialog */
void
info_pop_up
(
), quit_pop_up
(
);
top_wid =
XtVaAppInitialize
(&
app, "
Demos
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
main_w =
XtVaCreateManagedWidget
(
"
main_window
"
,
xmMainWindowWidgetClass, top_wid,
XmNheight, 300
,
XmNwidth,300
,
NULL
);
menu_bar =
XmCreateMenuBar
(
main_w, "
main_list
"
,
NULL
, 0
);
XtManageChild
(
menu_bar);
/* create quit widget + callback */
quit =
XtVaCreateManagedWidget
(
"
Quit
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, ‘Q'
,
NULL);
/* Callback has data passed to */
XtAddCallback(quit, XmNactivateCallback, quit_pop_up,
"Are you sure you want to quit?");
/* create help widget + callback */
info = XtVaCreateManagedWidget( "Info",
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, ‘I
'
,
NULL
);
XtAddCallback
(
info, XmNactivateCallback, info_pop_up,
"
Dialog widgets added to give info and check quit choice
"
);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*-------------------------------------------------*/
void
info_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*-------------------------------------------------*/
{
Widget dialog;
XmString xm_string;
extern
void
info_activate
(
);
Arg args[1
];
int
n;
/* label the dialog */
xm_string =
XmStringCreateSimple
(
text);
n=
0
;
XtSetArg
(
args[n], XmNmessageString, xm_string); n++
;
/* Create the InformationDialog as child
of cascade_button passed in */
dialog =
XmCreateInformationDialog
(
cascade_button,
"
info
"
, args, n);
XmStringFree
(
xm_string);
XtAddCallback
(
dialog, XmNokCallback, info_activate, NULL
);
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*-------------------------------------------------*/
void
quit_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*-------------------------------------------------*/
{
Widget dialog;
XmString xm_string;
void
quit_activate
(
);
Arg args[1
];
int
n;
/* label the dialog */
xm_string =
XmStringCreateSimple
(
text);
n=
0
;
XtSetArg
(
args[n], XmNmessageString, xm_string); n++
;
/* Create the WarningDialog */
dialog =
XmCreateWarningDialog
(
cascade_button, "
quit
"
, args, n);
XmStringFree
(
xm_string);
XtAddCallback
(
dialog, XmNokCallback, quit_activate, NULL
);
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/* callback routines for dialogs */
/*-------------------------------------------------*/
void
info_activate
(
Widget dialog)
/*-------------------------------------------------*/
{
printf
(
"
Info Ok was pressed.
\n
"
);
}
/*-------------------------------------------------*/
void
quit_activate
(
Widget dialog)
/*-------------------------------------------------*/
{
printf
(
"
Quit Ok was pressed.
\n
"
);
exit
(
0
);
}
IV-G-4. Les widgets de type ErrorDialog, WorkingDialog et QuestionDialog▲
Ces trois widgets fonctionnent de la même manière que les widgets de type WarningDialog ou InformationDialog.
À titre d'exercice, modifiez le programme dialog1.c pour créer ces widgets et voir à quoi ils ressemblent.
Par défaut lorsque vous créez un des Dialog widget étudiés jusqu'à présent, MOTIF propose les trois boutons OK, Cancel et Help.
Cependant dans certains cas on aimerait bien se débarrasser d'un ou deux de ces boutons, par exemple ne conserver que le bouton OK.
Pour supprimer un bouton :
- utiliser la fonction XmMessageBoxGetChild (dialog, Xm_<type du bouton>_BUTTON) pour obtenir un pointeur vers l'ID du bouton que l'on veut supprimer. Les différents types de boutons sont :
- XmDIALOG_OK_BUTTON,
- XmDIALOG_CANCEL_BUTTON,
- XmDIALOG_HELP_BUTTON ;
- unmanager le bouton dont on a récupéré l'ID à l'aide de la fonction XtUnmanageChild().
Par exemple, dans le programme dialog1.c, si l'on ne veut conserver que le bouton OK dans l'InformationDialog, on ajoutera les lignes suivantes justes après l'appel de la fonction XmCreateInformationDialog() :
Widget button;
.
.
.
dialog =
XmCreateInformationDialog
(
cascade_button, "
info
"
, args, 1
);
button =
XmMessageBoxGetChild
(
dialog,
XmDIALOG_CANCEL_BUTTON);
XtUnmanageChild
(
button);
button =
XmMessageBoxGetChild
(
dialog,
XmDIALOG_HELP_BUTTON);
XtUnmanageChild
(
button);
IV-G-5. Le widget de type PromptDialog▲
Le PromptDialog widget invite l'utilisateur à entrer des données au clavier.
Pour créer un PromptDialog, on utilise la fonction XmCreatePromptDialog().
Ce widget possède deux ressources importantes :
- XmNselectionLabelString : le message d'invitation ;
- XmNtextString : valeur par défaut des données. Si l'utilisateur clique sur le bouton OK sans avoir rien entré au clavier, la valeur par défaut de sa saisie sera celle spécifiée par la ressource XmNtextString.
Le PromptDialog est basé sur la classe SelectionBox, pour compiler sans erreur il faut donc inclure le fichier <Xm/SelectionB.h>.
Nous allons ensemble étudier le petit programme prompt.c, extension du programme dialog2.c étudié précédemment.
Exemple de PromptDialog : celui créé par le programme prompt.c :
Le programme prompt.c :
Nous ajoutons un nouveau menu « Prompt » à l'exemple chaînes de caractères que nous connaissons déjà : le précédent (dialog2.c). Ce menu ne contient qu'un CascadeButton qui déclenche l'affichage du PromptWidget.
Le texte saisi par l'utilisateur sera affiché dans un InformationDialog créé par la fonction de callback du PromptDialog, la fonction prompt_activate().
Une fonction de callback d'un PromptDialog possède la structure suivante :
void
prompt_callback
(
Widget widget,
XtPointer client_data,
XmSelectionBoxCallbackStruct *
selection)
En général, nous sommes surtout intéressés par la chaîne de caractères qui a été saisie dans le PromptDialog. Cette information est située dans le champ value (type XmString) de la structure XmSelectionBoxCallbackStruct.
Dans la fonction de callback du PromptDialog, prompt_activate() (voir ci-dessus l'exemple de code), cette valeur se trouve dans selection->value. La fonction utilise cette valeur pour mettre à jour la ressource XmNmessageString de l'InformationDialog.
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/MessageB.h>
#include <Xm/PushB.h>
#include <Xm/SelectioB.h>
#include <Xm/RowColumn.h>
void
ScrubDial
(
Widget, int
);
Widget top_wid;
/*--------------------------*/
main
(
int
argc, char
*
argv[])
/*--------------------------*/
{
XtAppContext app;
Widget main_w, menu_bar, info, prompt, quit;
void
info_pop_up
(
), quit_pop_up
(
), prompt_pop_up
(
);
top_wid =
XtVaAppInitialize
(&
app, "
Demos
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
main_w =
XtVaCreateManagedWidget
(
"
main_window
"
,
xmMainWindowWidgetClass, top_wid,
XmNheight, 300
,
XmNwidth,300
,
NULL
);
menu_bar =
XmCreateMenuBar
(
main_w, "
main_list
"
, NULL
, 0
);
XtManageChild
(
menu_bar);
/* create prompt widget + callback */
prompt =
XtVaCreateManagedWidget
(
"
Prompt
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
P
'
,
NULL
);
/* Callback has data passed to */
XtAddCallback
(
prompt, XmNactivateCallback,
prompt_pop_up, NULL
);
/* create quit widget + callback */
quit =
XtVaCreateManagedWidget
(
"
Quit
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
Q
'
,
NULL
);
/* Callback has data passed to */
XtAddCallback
(
quit, XmNactivateCallback, quit_pop_up,
"
Are you sure you want to quit?
"
);
/* create help widget + callback */
info =
XtVaCreateManagedWidget
(
"
Info
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
I
'
,
NULL
);
XtAddCallback
(
info, XmNactivateCallback, info_pop_up,
"
Select Prompt Option To Get Program Going.
"
);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*----------------------------------------------------*/
void
prompt_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*----------------------------------------------------*/
{
Widget dialog, remove;
XmString xm_string1, xm_string2;
void
prompt_activate
(
);
Arg args[3
];
int
n;
/* label the dialog */
xm_string1 =
XmStringCreateSimple
(
"
Enter Text Here:
"
);
/* default text string */
xm_string2 =
XmStringCreateSimple
(
"
Default String
"
);
n=
0
;
XtSetArg
(
args[n], XmNselectionLabelString, xm_string1); n++
;
XtSetArg
(
args[n], XmNtextString, xm_string2); n++
;
/* set up default button for cancel callback */
XtSetArg
(
args[n], XmNdefaultButtonType,
XmDIALOG_CANCEL_BUTTON); n++
;
/* Create the WarningDialog */
dialog =
XmCreatePromptDialog
(
cascade_button, "
prompt
"
, args, n);
XmStringFree
(
xm_string1);
XmStringFree
(
xm_string2);
XtAddCallback
(
dialog, XmNokCallback, prompt_activate, NULL
);
/* Scrub Prompt Help Button */
remove =
XmSelectionBoxGetChild
(
dialog,
XmDIALOG_HELP_BUTTON);
XtUnmanageChild
(
remove); /* scrub HELP button */
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*----------------------------------------------------*/
void
info_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*----------------------------------------------------*/
{
Widget dialog;
XmString xm_string;
extern
void
info_activate
(
);
Arg args[2
];
int
n;
/* label the dialog */
xm_string =
XmStringCreateSimple
(
text);
n=
0
;
XtSetArg
(
args[n], XmNmessageString, xm_string); n++
;
/* set up default button for OK callback */
XtSetArg
(
args[n], XmNdefaultButtonType,
XmDIALOG_OK_BUTTON); n++
;
/* Create the InformationDialog as child of
cascade_button passed in */
dialog =
XmCreateInformationDialog
(
cascade_button,
"
info
"
, args, n);
ScrubDial
(
dialog, XmDIALOG_CANCEL_BUTTON);
ScrubDial
(
dialog, XmDIALOG_HELP_BUTTON);
XmStringFree
(
xm_string);
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*-------------------------------------------------*/
void
quit_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*-------------------------------------------------*/
{
Widget dialog;
XmString xm_string;
void
quit_activate
(
);
Arg args[1
];
int
n;
/* label the dialog */
xm_string =
XmStringCreateSimple
(
text);
n=
0
;
XtSetArg
(
args[n], XmNmessageString, xm_string); n++
;
/* set up default button for cancel callback */
XtSetArg
(
args[n], XmNdefaultButtonType,
XmDIALOG_CANCEL_BUTTON); n++
;
/* Create the WarningDialog */
dialog =
XmCreateWarningDialog
(
cascade_button, "
quit
"
, args, n);
ScrubDial
(
dialog, XmDIALOG_HELP_BUTTON);
XmStringFree
(
xm_string);
XtAddCallback
(
dialog, XmNokCallback, quit_activate, NULL
);
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*-----------------------------------*/
void
ScrubDial
(
Widget wid, int
dial)
/*-----------------------------------*/
/* routine to remove a DialButton from a Dialog */
{
Widget remove;
remove =
XmMessageBoxGetChild
(
wid, dial);
XtUnmanageChild
(
remove);
}
/*---------------------------------------------------------*/
void
prompt_activate
(
Widget widget, XtPointer client_data,
XmSelectionBoxCallbackStruct *
selection)
/*---------------------------------------------------------*/
/* callback function for Prompt activate */
{
Widget dialog;
Arg args[2
];
XmString xm_string;
int
n;
/* compose InformationDialog output string */
/* selection->value holds XmString entered to prompt */
xm_string =
XmStringCreateSimple
(
"
You typed:
"
);
xm_string =
XmStringConcat
(
xm_string,selection->
value);
n=
0
;
XtSetArg
(
args[n], XmNmessageString, xm_string); n++
;
/* set up default button for OK callback */
XtSetArg
(
args[n], XmNdefaultButtonType,
XmDIALOG_OK_BUTTON); n++
;
/* Create the InformationDialog to echo
string grabbed from prompt */
dialog =
XmCreateInformationDialog
(
top_wid,
"
prompt_message
"
, args, n);
ScrubDial
(
dialog, XmDIALOG_CANCEL_BUTTON);
ScrubDial
(
dialog, XmDIALOG_HELP_BUTTON);
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*---------------------------------*/
void
quit_activate
(
Widget dialog)
/*---------------------------------*/
/* callback routines for quit ok dialog */
{
printf
(
"
Quit Ok was pressed.
\n
"
);
exit
(
0
);
}
IV-G-6. Widgets de type SelectionDialog et FileSelectionDialog▲
IV-G-6-a. Présentation▲
Ces deux widgets permettent à l'utilisateur d'effectuer un ou plusieurs choix dans une liste d'items.
Nous allons étudier le FileSelectionDialog, car bien que plus complexe à utiliser que le SelectionDialog son usage est plus fréquent.
Le FileSelectionDialog est spécialisé dans la sélection de fichier(s) dans un répertoire. On le rencontre dans toutes les applications qui nécessitent la lecture/écriture de fichiers.
Le widget de type SelectionDialog est moins spécialisé et permet de sélectionner des objets moins spécifiques que des fichiers.
Exemple de FileSelectionDialog :
Pour créer un FileSelectionDialog, on utilise la fonction XmCreateFileSelectionDialog(). Pour compiler sans erreur, il faut inclure le fichier <Xm/FileSB.h>, car le widget de type FileSelectionDialog est basé sur la classe FileSelectionBox.
Si on utilise des fonctions de manipulation, des SelectionDialog telles que XmSelectionBoxGetChild() sont nécessaires si on veut enlever un des boutons par défaut d'un FileSelectionDialog (voir programme file_select.c), nous devons aussi inclure <Xm/SelectioB.h> pour ne pas qu'il y ait d'erreur de compilation.
IV-G-6-b. Ressources des FileSelectionDialog▲
Un FileSelectionDialog possède de nombreuses ressources que l'on peut positionner pour contrôler la recherche de fichiers, utiliser des métacaractères tels que « * » pour ne proposer que les fichiers ayant un suffixe donné, etc. En général ces ressources sont du type XmString.
- XmNdirectory : le répertoire où se trouvent les fichiers que l'on va proposer à l'utilisateur. Par défaut : le répertoire courant.
- XmNdirLabelString : texte affiché au-dessus de la boite du FileSelectionDialog contenant la liste des noms de répertoires.
- XmNdirMask : masque utilisé pour filtrer certains noms de fichiers. Par exemple, dans le programme file_select.c ce masque vaut *.c afin de ne proposer que des fichiers sources C dans le FileSelctionDialog.
- XmNdirSpec : le nom du fichier choisi par défaut.
- XmNfileListLabelString : texte affiché au-dessus de la boite du FileSelectionDialog contenant la liste des noms de fichiers.
- XmNfileTypeMask : type des fichiers devant être listés.
Valeurs possibles : XmFILE_REGULAR, XmFILE_DIRECTORY, XmFILE_TYPE_ANY. - XmNfilterLabelString : texte affiché au-dessus de la boite du FileSelectionDialog contenant le filtre sur les noms de fichiers.
Certaines de ces ressources peuvent être modifiées pendant l'exécution du programme par l'utilisateur, en saisissant du texte directement dans les fenêtres correspondantes du FileSelectionDialog.
IV-G-6-c. Le programme file_select.c▲
Ce petit programme ouvre un FileSelectionDialog avec un filtre pour ne proposer que des fichiers sources C (valeur du filtre XmNdirMask : *.c).
Lorsqu'un fichier est sélectionné, son listing est imprimé sur stdout.
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/MessageB.h>
#include <Xm/SelectioB.h>
#include <Xm/PushB.h>
#include <Xm/FileSB.h>
#include <Xm/RowColumn.h>
void
ScrubDial
(
Widget, int
);
Widget top_wid;
/*---------------------------*/
main
(
int
argc, char
*
argv[])
/*---------------------------*/
{
XtAppContext app;
Widget main_w, menu_bar, file_select, quit;
void
quit_pop_up
(
), select_pop_up
(
); /* callbacks */
top_wid =
XtVaAppInitialize
(&
app, "
Demos
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
main_w =
XtVaCreateManagedWidget
(
"
main_window
"
,
xmMainWindowWidgetClass, top_wid,
XmNheight, 300
,
XmNwidth,300
,
NULL
);
menu_bar =
XmCreateMenuBar
(
main_w, "
main_list
"
, NULL
, 0
);
XtManageChild
(
menu_bar);
/* create prompt widget + callback */
file_select =
XtVaCreateManagedWidget
(
"
Select
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
S
'
,
NULL
);
/* Callback has data passed to */
XtAddCallback
(
file_select, XmNactivateCallback,
select_pop_up, NULL
);
/* create quit widget + callback */
quit =
XtVaCreateManagedWidget
(
"
Quit
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
Q
'
,
NULL
);
/* Callback has data passed to */
XtAddCallback
(
quit, XmNactivateCallback, quit_pop_up,
"
Are you sure you want to quit?
"
);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*----------------------------------------------------*/
void
select_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*----------------------------------------------------*/
{
Widget dialog, remove;
void
select_activate
(
), cancel
(
);
XmString mask;
Arg args[1
];
int
n;
/* Create the FileSelectionDialog */
mask =
XmStringCreateSimple
(
"
*.c
"
);
n=
0
;
XtSetArg
(
args[n], XmNdirMask, mask); n++
;
dialog =
XmCreateFileSelectionDialog
(
cascade_button,
"
select
"
, args, n);
XtAddCallback
(
dialog, XmNokCallback, select_activate, NULL
);
XtAddCallback
(
dialog, XmNcancelCallback, cancel, NULL
);
remove =
XmSelectionBoxGetChild
(
dialog, XmDIALOG_HELP_BUTTON);
XtUnmanageChild
(
remove); /* delete HELP BUTTON */
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*-------------------------------------------------*/
void
quit_pop_up
(
Widget cascade_button, char
*
text,
XmPushButtonCallbackStruct *
cbs)
/*-------------------------------------------------*/
{
Widget dialog;
XmString xm_string;
void
quit_activate
(
);
Arg args[2
];
int
n;
/* label the dialog */
xm_string =
XmStringCreateSimple
(
text);
n =
0
;
XtSetArg
(
args[n], XmNmessageString, xm_string); n++
;
/* set up default button for cancel callback */
XtSetArg
(
args[n], XmNdefaultButtonType,
XmDIALOG_CANCEL_BUTTON); n++
;
/* Create the WarningDialog */
dialog =
XmCreateWarningDialog
(
cascade_button, "
quit
"
, args, n);
ScrubDial
(
dialog, XmDIALOG_HELP_BUTTON);
XmStringFree
(
xm_string);
XtAddCallback
(
dialog, XmNokCallback, quit_activate, NULL
);
XtManageChild
(
dialog);
XtPopup
(
XtParent
(
dialog), XtGrabNone);
}
/*----------------------------------*/
void
ScrubDial
(
Widget wid, int
dial)
/*----------------------------------*/
/* routine to remove a DialButton from a Dialog */
{
Widget remove;
remove =
XmMessageBoxGetChild
(
wid, dial);
XtUnmanageChild
(
remove);
}
/*-------------------------------------------------------*/
void
select_activate
(
Widget widget, caddr_t client_data,
XmFileSelectionBoxCallbackStruct *
selection)
/*-------------------------------------------------------*/
/* callback function for Prompt activate */
/* function opens file (text) and prints to stdout */
{
FILE *
fp, *
fopen
(
);
char
*
filename, line[200
];
void
error
(
);
XmStringGetLtoR
(
selection->
value,
XmSTRING_DEFAULT_CHARSET, &
filename);
if
((
fp =
fopen
(
filename,"
r
"
)) ==
NULL
)
error
(
"
CANNOT OPEN FILE
"
, filename);
else
{
while
(!
feof
(
fp)) {
fgets
(
line,200
,fp);
printf
(
"
%s
\n
"
,line);
}
fclose
(
fp);
}
}
/*----------------------------------------------*/
void
cancel
(
Widget widget, caddr_t client_data,
XmFileSelectionBoxCallbackStruct *
selection)
/*----------------------------------------------*/
{
XtUnmanageChild
(
widget); /* undisplay widget */
}
/*-----------------------------*/
void
error
(
char
*
s1, char
*
s2)
/*-----------------------------*/
/* prints error to stdout */
{
/* EXERCICE :
REECRIRE CETTE ROUTINE POUR AFFICHER LE TEXTE DANS UN
ErrorDialog widget. */
printf
(
"
%s: %s
\n
"
, s1, s2);
exit
(-
1
);
}
/*-------------------------------*/
void
quit_activate
(
Widget dialog)
/*-------------------------------*/
{
/* callback routines for quit ok dialog */
printf
(
"
Quit Ok was pressed.
\n
"
);
exit
(
0
);
}
IV-H. Les widgets de type Text▲
IV-H-1. Introduction▲
MOTIF fournit deux types de Text widgets :
- XmText : ce widget est un petit éditeur de texte multiligne ;
- XmTextField : un éditeur comportant une seule ligne.
Dans certaines applications, l'édition de texte occupe une place très importante. Le fait que MOTIF fournisse de puissants outils de ce type facilite grandement la vie du programmeur. Par exemple, dans Netscape, lorsque vous utilisez l'option « View Source » une fenêtre contenant un éditeur de texte simplifié vous propose d'éditer le code HTML de la page courante. Il s'agit d'un Text widget de MOTIF !
Si on couple les Text widgets avec d'autres widgets déjà étudiés comme le FileSelectionDialog, on peut aisément écrire son propre éditeur de texte (il ne sera pas ridicule, surtout comparé à textedit !)
Nous étudierons surtout le widget Text étant donné que le widget de type TextField n'en est qu'une version très simplifiée. (De plus, si on positionne la ressource XmNeditMode du widget Text avec la valeur XmSINGLE_LINE_EDIT, on transforme le widget Text en widget de type TextField.)
IV-H-2. Création d'un widget de type Text▲
Il existe plusieurs manières de créer un widget de type Text.
Nous pouvons utiliser les fonctions courantes maintenant bien assimilées XmCreateText() ou XtVaCreateManagedWidget().
Il ne faudra pas oublier de mettre le widget de type Text dans une ScrolledWindow, car le texte édité pourra excéder la taille de la fenêtre.
MOTIF fournit un métawidget de type ScrolledText qui comprend un Text widget dans une ScrolledWindow. On peut le créer à l'aide de la fonction XmCreateScrolledText().
Ressources d'un Text widget :
- XmNrows : nombre de lignes visibles ;
- XmNcolumns : nombre de colonnes visibles ;
- XmNeditable : valeurs possibles : True ou False selon que l'édition du texte est autorisée ou pas ;
- XmNscrollHorizontal : valeurs possibles : True ou False selon que l'on autorise le scrolling horizontal du texte lorsque la longueur des lignes excède la largeur de la fenêtre d'édition ;
- XmNscrollVertical : valeurs possibles : True ou False selon que l'on autorise le scrolling vertical du texte lorsque le texte ne contient plus dans la page en hauteur ;
- XmNeditMode : valeurs possibles : XmSINGLE_LINE_EDIT ou XmMULTI_LINE_EDIT selon que l'on désire éditer du texte sur une ou plusieurs lignes.
IV-H-3. Mettre du texte dans un Text widget▲
La fonction XmTextSetString() met une chaîne de caractères ordinaire (au sens du langage C) dans un Text widget.
Elle accepte deux paramètres :
- L'ID du Text widget ;
- La chaîne de caractères à éditer.
Le petit programme text.c présente un exemple d'utilisation de cette fonction.
IV-H-4. Le programme text.c▲
Ce programme est un petit exemple d'utilisation de Text widget. Il crée un widget de type ScrolledText et affiche à l'intérieur son propre source C : le contenu du programme text.c.
L'édition n'est pas autorisée (lors de la création, la ressource XmNeditable vaut False).
Voici un dump d'écran de l'exécution de ce petit programme :
Pour compiler sans erreur il faut inclure le fichier <Xm/Text.h>.
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <Xm/Xm.h>
#include <Xm/Text.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/RowColumn.h>
/*--------------------------*/
main
(
int
argc, char
*
argv[])
/*--------------------------*/
{
Widget top_wid, main_w, menu_bar,
menu, quit, help, text_wid;
XtAppContext app;
void
quit_call
(
), help_call
(
), read_file
(
);
Arg args[10
];
int
n;
/* initialize */
top_wid =
XtVaAppInitialize
(&
app, "
Text
"
,
NULL
, 0
, &
argc, argv, NULL
, NULL
);
main_w =
XtVaCreateManagedWidget
(
"
main_w
"
,
xmMainWindowWidgetClass, top_wid,
/* XmNscrollingPolicy, XmVARIABLE, */
NULL
);
menu_bar =
XmCreateMenuBar
(
main_w, "
main_list
"
, NULL
, 0
);
XtManageChild
(
menu_bar);
/* create quit widget + callback */
quit =
XtVaCreateManagedWidget
(
"
Quit
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
Q
'
,
NULL
);
XtAddCallback
(
quit, XmNactivateCallback,
quit_call, NULL
);
/* Create ScrolledText -- this is work area for the
MainWindow */
n=
0
;
XtSetArg
(
args[n], XmNrows, 30
); n++
;
XtSetArg
(
args[n], XmNcolumns, 80
); n++
;
XtSetArg
(
args[n], XmNeditable, False); n++
;
XtSetArg
(
args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++
;
text_wid =
XmCreateScrolledText
(
main_w, "
text_wid
"
, args, n);
XtManageChild
(
text_wid);
/* read file and put data in text widget */
read_file
(
text_wid);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*---------------------------*/
void
read_file
(
Widget text_wid)
/*---------------------------*/
{
static
char
*
filename =
"
text.c
"
;
char
*
text;
struct
stat statb;
FILE *
fp;
/* check file is a regular text file and open it */
if
((
stat
(
filename, &
statb) ==
-
1
)
||
!(
fp =
fopen
(
filename, "
r
"
))) {
fprintf
(
stderr, "
Cannot open file: %s
\n
"
, filename);
XtFree
(
filename);
return
;
}
/* Map file text in the TextWidget */
if
(!(
text =
XtMalloc
((
unsigned
)(
statb.st_size+
1
)))) {
fprintf
(
stderr, "
Can't alloc enough space for %s
"
,
filename);
XtFree
(
filename);
fclose
(
fp);
return
;
}
if
(!
fread
(
text, sizeof
(
char
), statb.st_size+
1
, fp))
fprintf
(
stderr, "
File read error
\n
"
);
text[statb.st_size] =
0
; /* be sure to NULL-terminate */
/* insert file contents in TextWidget */
XmTextSetString
(
text_wid, text);
/* free memory
*/
XtFree
(
text);
XtFree
(
filename);
fclose
(
fp);
}
/*--------------*/
void
quit_call
(
)
/*--------------*/
{
printf
(
"
Quitting program
\n
"
);
exit
(
0
);
}
EXERCICE : à partir du programme text.c, écrire le programme file_lister.c permettant à l'aide d'un FileSelectionDialog de lister dans un ScrolledText widget le contenu de n'importe quel programme source C.
Dump d'écran du programme (à écrire) file_lister.c :
IV-H-5. Édition de texte dans un Text Widget▲
IV-H-5-a. Remplacement de texte▲
La fonction XmTextReplace() permet de remplacer tout ou partie du texte qui se trouve dans un TextWidget.
Paramètres de cette fonction :
- Widget : l'ID du Text widget ;
- StartPosition (de type XmTextPosition) : un long int (défini dans <Xm/Text.h>) mesuré en bytes. Indique le point d'insertion du texte ;
- EndPosition (de type XmTextPosition) : même type que le paramètre précédent ;
- NewText : la chaîne de caractères (au sens du langage C) qui va remplacer le texte situé entre les deux positions StartPosition et EndPosition.
Quelques remarques :
Si StartPosition est égal à EndPosition alors le texte est inséré juste après le point d'insertion.
Le texte est remplacé uniquement entre les deux positions spécifiées par StartPosition et EndPosition.
IV-H-5-b. Insertion de texte▲
Pour insérer du texte, il faut utiliser la fonction XmTextInsert().
Elle accepte trois paramètres :
- Widget : l'ID du Text widget ;
- InsertPosition (de type XmTextPosition) : un long int (défini dans <Xm/Text.h>) mesuré en bytes. Indique le point d'insertion du texte ;
- String : la chaîne de caractères (au sens du langage C) à insérer.
IV-H-5-c. Rechercher un texte▲
Pour rechercher un texte dans un Text widget, il faut utiliser la fonction XmTextFindString().
Cette fonction possède les paramètres suivants :
- Widget : l'ID du Text widget ;
- StartPosition (de type XmTextPosition) : un long int (défini dans <Xm/Text.h> mesuré en bytes. Indique la position dans le texte à partir de laquelle la recherche va s'effectuer ;
- String : la chaîne de caractères que l'on recherche ;
- SearchDirection : sens de la recherche. Valeurs possibles : XmTEXT_FORWARD ou XmTEXT_BACKWARD ;
- Position : un pointeur de type XmTextPosition retourné par la fonction de recherche. Indique à quelle position la chaîne de caractères a été trouvée.
XmTextFindString() renvoie une valeur de type Boolean : True si la chaîne a été trouvée dans le texte, False sinon.
IV-H-5-d. Sauvegarder le contenu d'un Text widget dans un fichier▲
La fonction XmTextGetString() permet d'obtenir le contenu d'un Text widget. Ainsi, pour sauvegarder le contenu d'un Text widget dans un fichier, il suffit de :
- appeler XmTextGetString() ;
- fprintf() le texte dans un fichier.
IV-H-5-e. Contrôle de l'affichage du texte▲
Pour afficher le texte à une position donnée, il faut utiliser la fonction XmTextShowPosition().
Pour positionner une ligne du texte en haut de la fenêtre du ScrolledText widget, il faut utiliser la fonction XmTextSetTopCharacter().
Pour positionner le point d'insertion (le curseur) à une position donnée, il faut utiliser la fonction XmTextSetInsertionPosition().
Par défaut, un Text widget contient des fonctions d'édition de texte très simples. Si l'on désire un meilleur contrôle sur l'édition, il faut utiliser des fonctions de callback.
MOTIF fournit de nombreuses ressources pour les callbacks des Text widgets.
Voici les principales :
- XmNactivateCallback : uniquement pour les TextField ou les Text widgets d'une seule ligne. Ce callback est appelé lorsque l'utilisateur appuie sur la touche Enter ;
- XmNverifyCallback : appelé avant un changement dans le texte ;
- XmNvalueChangedCallback : appelé après un changement dans le texte ;
- XmNmotionCallback : appelé lorsque l'utilisateur a déplacé le curseur ou bien lorsqu'il a effectué une sélection à la souris ;
- XmNfocusCallback : appelé lorsque le text widget obtient le focus du clavier ;
- XmNfocusCallback : appelé lorsque le text widget perd le focus du clavier.
Les verifyCallbacks peuvent être utilisés pour la saisie de mots de passe par exemple…
IV-I. Les widgets de type DrawingArea▲
IV-I-1. Introduction▲
Le widget de type DrawingArea permet de dessiner à l'aide des fonctions graphiques de la Xlib que nous avons étudiées.
Pour créer une DrawingArea on utilisera au choix XmCreateDrawingArea() ou XtVaCreateManagedWidget() avec comme classe XmDrawingAreaWidgetClass.
Pour compiler sans erreurs il faut inclure le fichier <Xm/DrawingA.h>.
Il existe aussi un widget de type DrawnButton qui est la combinaison d'une DrawingArea et d'un PushButton. Si on utilise des DrawnButton dans une application, il faut inclure <Xm/DrawnB.h>.
IV-I-2. Ressources et callbacks des widgets de type DrawingArea▲
En général on place les DrawingArea à l'intérieur d'un container widget : une Form, une MainWindow, une ScrolledWindow, etc. si bien qu'il est rare que l'on doive spécifier sa taille, car cette dernière est héritée du widget père. On peut cependant positionner les ressources XmNheight et XmNwidth pour fixer la taille.
La ressource XmNresizePolicy permet de régler les autorisations de changement de taille par le window manager. Trois valeurs possibles : XmRESIZE_ANY, XmRESIZE_GROW et XmRESIZE_NONE.
Par défaut on peut associer trois types de callbacks à une DrawingArea :
- XmNexposeCallback : ce callback est appelé lorsqu'une partie de la DrawingArea doit être redessinnée ;
- XmNresizeCallback : si la taille de la DrawingArea change ce callback est appelé ;
- XmNinputCallback : si une entrée clavier ou un clic souris est effectué dans la DrawingArea, ce callback est appelé.
IV-I-3. Utilisation pratique d'une DrawingArea▲
Nous allons maintenant réaliser du dessin 2D dans une application MOTIF.
Toutes les opérations graphiques seront réalisées à l'aide de fonctions de la Xlib, il va falloir récupérer des informations de bas niveau à partir des widgets MOTIF possédant un de haut niveau d'abstraction. Nous allons étudier de petits programmes d'exemples. Tous procèdent de la même manière :
- Création des différents widgets de l'application, y compris bien sûr une DrawingArea ;
- Obtention d'informations nécessaires aux fonctions Xlib : Display, pointeurs sur l'ID de la fenêtre X de la DrawingArea, etc. ;
- Dessin dans la fenêtre X.
IV-I-3-a. Premier programme d'exemple : draw.c▲
Ce petit programme illustre les principes de base de l'utilisation conjointe de fonctions Xlib, Xt et MOTIF.
Il crée une DrawingArea, draw, dans une MainWindow. Les ID du Display et du Screen (au sens Xlib) sont obtenus à l'aide des fonctions XtDisplay() et XtScreen().
Le contexte graphique est créé avec comme une couleur de foreground noire, obtenue à l'aide de la macro Xlib BlackPixelOfScreen() (remarque : on aurait pu aussi utiliser BlackPixel(), l'une prend un screen en argument, l'autre un display).
Le contexte graphique est placé dans la ressource XmNuserData de la DrawingArea, qui sert à passer des informations de la même manière que le paramètre ClientData des fonctions de callback.
Pour récupérer la valeur de la ressource XmNuserData dans la fonction de callback, on utilisera XtGetValues().
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>
#include <Xm/RowColumn.h>
/* XLIB Data */
Display *
display;
Screen *
screen_ptr;
/*--------------------------*/
main
(
int
argc, char
*
argv[])
/*--------------------------*/
{
Widget top_wid, main_w, menu_bar, draw, quit;
XtAppContext app;
XGCValues gcv;
GC gc;
void
quit_call
(
), draw_cbk
(
);
top_wid =
XtVaAppInitialize
(&
app, "
Draw
"
, NULL
, 0
,
&
argc, argv, NULL
,
XmNwidth, 500
,
XmNheight, 500
,
NULL
);
/* create a main window */
main_w =
XtVaCreateManagedWidget
(
"
main_window
"
,
xmMainWindowWidgetClass, top_wid,
NULL
);
/* create a menu bar */
menu_bar =
XmCreateMenuBar
(
main_w, "
main_list
"
,
NULL
, 0
);
XtManageChild
(
menu_bar);
/* create quit widget in menu bar + callback */
quit =
XtVaCreateManagedWidget
(
"
Quit
"
,
xmCascadeButtonWidgetClass, menu_bar,
XmNmnemonic, '
Q
'
,
NULL
);
XtAddCallback
(
quit, XmNactivateCallback, quit_call,
NULL
);
/* Create a DrawingArea widget. */
draw =
XtVaCreateWidget
(
"
draw
"
,
xmDrawingAreaWidgetClass, main_w,
NULL
);
/* get XLib Display Screen and Window ID's for draw */
display =
XtDisplay
(
draw);
screen_ptr =
XtScreen
(
draw);
/* set the MenuBar and the DrawingArea as menu bar and the
"work area" of main window. Like that they are partly managed
by the MainWindow */
XtVaSetValues
(
main_w,
XmNmenuBar, menu_bar,
XmNworkWindow, draw,
NULL
);
/* add callback for exposure event */
XtAddCallback
(
draw, XmNexposeCallback, draw_cbk, NULL
);
/* Create a GC. Attach GC to the DrawingArea's
XmNuserData.
NOTE: This is a useful method to pass data */
gcv.foreground =
BlackPixelOfScreen
(
screen_ptr);
gc =
XCreateGC
(
display,
RootWindowOfScreen
(
screen_ptr), GCForeground, &
gcv);
XtVaSetValues
(
draw, XmNuserData, gc, NULL
);
/* because we changed some ressources after the Managed Creation */
XtManageChild
(
draw);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/* CALL BACKS */
/*--------------*/
void
quit_call
(
)
/*--------------*/
{
printf
(
"
Quitting program
\n
"
);
exit
(
0
);
}
/*----------------------------------------------*/
void
draw_cbk
(
Widget w, XtPointer data,
XmDrawingAreaCallbackStruct *
cbk)
/*----------------------------------------------*/
/* DrawingArea Callback. NOTE: cbk->reason says type of
callback event */
{
char
str1[25
];
int
len1, width1, font_height;
unsigned
int
width, height;
int
x, y, angle1, angle2, x_end, y_end;
unsigned
int
line_width =
1
;
int
line_style =
LineSolid;
int
cap_style =
CapRound;
int
join_style =
JoinRound;
XFontStruct *
font_info;
XEvent *
event =
cbk->
event;
void
load_font
(
);
GC gc;
Window win =
XtWindow
(
w);
if
(
cbk->
reason !=
XmCR_EXPOSE) {
/* Should NEVER HAPPEN for this program */
printf
(
"
X is screwed up!!
\n
"
);
exit
(
0
);
}
/* get font info */
load_font
(&
font_info);
font_height =
font_info->
ascent +
font_info->
descent;
/* get gc from Drawing Area user data */
XtVaGetValues
(
w, XmNuserData, &
gc, NULL
);
/* Draw A Rectangle */
x =
y =
10
;
width =
100
;
height =
50
;
XDrawRectangle
(
display, win, gc, x, y, width, height);
strcpy
(
str1,"
RECTANGLE
"
);
len1 =
strlen
(
str1);
y +=
height +
font_height +
1
;
if
((
x =
(
x +
width/
2
) -
len1/
2
) <
0
)
x =
10
;
XDrawString
(
display, win, gc, x, y, str1, len1);
/* Draw a filled rectangle */
x =
10
; y =
150
;
width =
80
;
height =
70
;
XFillRectangle
(
display, win, gc, x, y, width, height);
strcpy
(
str1,"
FILLED RECTANGLE
"
);
len1 =
strlen
(
str1);
y +=
height +
font_height +
1
;
if
((
x =
(
x +
width/
2
) -
len1/
2
) <
0
)
x =
10
;
XDrawString
(
display, win, gc, x, y, str1, len1);
/* draw an arc */
x =
200
; y =
10
;
width =
80
;
height =
70
;
angle1 =
180
*
64
; /* 180 degrees */
angle2 =
90
*
64
; /* 90 degrees */
XDrawArc
(
display, win, gc, x, y, width, height,
angle1, angle2);
strcpy
(
str1,"
ARC
"
);
len1 =
strlen
(
str1);
y +=
height +
font_height +
1
;
if
((
x =
(
x +
width/
2
) -
len1/
2
) <
0
)
x =
200
;
XDrawString
(
display, win, gc, x, y, str1, len1);
/* draw a filled arc */
x =
200
; y =
200
;
width =
100
;
height =
50
;
angle1 =
270
*
64
; /* 270 degrees */
angle2 =
180
*
64
; /* 180 degrees */
XFillArc
(
display, win, gc, x, y, width, height,
angle1, angle2);
strcpy
(
str1,"
FILLED ARC
"
);
len1 =
strlen
(
str1);
y +=
height +
font_height +
1
;
if
((
x =
(
x +
width/
2
) -
len1/
2
) <
0
)
x =
200
;
XDrawString
(
display, win, gc, x, y, str1, len1);
/* SOLID LINE */
x =
10
; y =
300
;
/* start and end points of line */
x_end =
200
; y_end =
y -
30
;
XDrawLine
(
display, win, gc, x, y, x_end, y_end);
strcpy
(
str1,"
SOLID LINE
"
);
len1 =
strlen
(
str1);
y +=
font_height +
1
;
if
((
x =
(
x +
x_end)/
2
-
len1/
2
) <
0
)
x =
10
;
XDrawString
(
display, win, gc, x, y, str1, len1);
/* DASHED LINE */
line_style =
LineOnOffDash;
line_width =
2
;
/* set line attributes */
XSetLineAttributes
(
display, gc, line_width, line_style,
cap_style, join_style);
x =
10
; y =
350
;
/* start and end points of line */
x_end =
200
; y_end =
y -
30
;
XDrawLine
(
display, win, gc, x, y, x_end, y_end);
strcpy
(
str1,"
DASHED LINE
"
);
len1 =
strlen
(
str1);
y +=
font_height +
1
;
if
((
x =
(
x +
x_end)/
2
-
len1/
2
) <
0
) x =
10
;
XDrawString
(
display, win, gc, x, y, str1, len1);
}
/*--------------------------------------*/
void
load_font
(
XFontStruct **
font_info)
/*--------------------------------------*/
{
char
*
fontname =
"
fixed
"
;
XFontStruct *
XLoadQueryFont
(
);
/* load and get font info structure */
if
((
*
font_info =
XLoadQueryFont
(
display, fontname)) ==
NULL
) {
/* error - quit early */
printf
(
"
%s: Cannot load %s font
\n
"
, "
draw.c
"
,
fontname);
exit
(-
1
);
}
}
IV-I-3-b. Deuxième exemple : le programme draw_input.c▲
Avec cet exemple, nous allons voir comment traiter les entrées/sorties dans une DrawingArea.
Nous reprenons le programme menu_pull.c déjà étudié lors du cours sur les MainWindow et les menus, et le modifions pour intégrer une DrawingArea. Ce programme permet le dessin de rectangles « à la Mac Draw », le menu « colors » servant à sélectionner la couleur du rectangle que l'on veut dessiner.
Dump d'écran de draw_input.c :
Nous avons déjà vu pendant les TD Xlib comment il fallait procéder pour dessiner des rectangles « élastiques ». Il faut traiter trois types d'événements : l'appui d'un bouton de la souris, le déplacement de la souris et le relâchage du bouton souris.
Les DrawingArea widgets possèdent deux ressources permettant d'appeler des callbacks en cas d'entrées clavier ou souris, mais ils ne possèdent pas de ressources permettant de traiter le déplacement de la souris.
La solution consiste à enrichir la table de translations de la DrawingArea.
Rappel : chaque widget possède une table de translations qui se compose de :
- une liste d'événements susceptibles de se produire dans le widget.
- Des actions associées à ces événements.
Extrait d'une table de translation (celle de Netscape prise dans son fichier de ressources /usr1/local/lib/X11/app-defaults/Netscape) :
.
<Btn1Down>: ArmLink()
<Btn2Down>: ArmLink()
.
Pour ajouter le traitement de l'événement « déplacement de la souris bouton 1 appuyé », il faut ajouter la translation suivante :
<Btn1Motion>: draw_cbk(motion)
où draw_cbk() est la fonction de callback qui effectue le dessin des rectangles. Chaque fois que la souris sera déplacée bouton 1 appuyé dans la DrawingArea, draw_cbk() sera appelée. De plus, il est possible de passer à la fonction de callback des paramètres de type String ! Avec l'exemple ci-dessus, la chaîne de caractères « motion » sera passée (plus de détails sur le traitement de ces paramètres seront donnés page suivante).
Bien que l'on puisse traiter les autres événements souris avec les méthodes d'ajout de callback standards (XtAddCallBack(), etc.), nous allons nous servir de la table de translation pour traiter l'appui et le relâchement du bouton 1 de la souris. La table de translation finale ressemble à ceci :
<Btn1Motion>: draw_cbk(motion)
<Btn1Up>: draw_cbk(up)
<Btn1Down>: draw_cbk(down)
En pratique, dans un programme MOTIF, nous initialisons d'abord la table de translations dans une variable de type String, puis l'on attache cette table à la DrawingArea en positionnant la ressource XmNtranslations avec comme valeur le résultat de XtParseTranslationTable(String translations).
Il faut également, lors de la création de la DrawingArea, indiquer que nous avons enrichi la table de translations par défaut avec les actions associées aux nouvelles translations. Ceci est réalisé à l'aide de la fonction XtAppAddActions(). Ainsi il sera possible de positionner ces nouvelles translations dans un fichier de ressources, l'application reconnaîtra les nouveaux événements.
Avec notre exemple :
XtActionsRec actions; ... actions.string =
"
draw_cbk
"
; actions.proc =
draw_cbk;
XtAppAddActions
(
app, &
actions, 1
);
Voici l'en-tête de la fonction de callback :
void
draw_cbk
(
Widget w, XButtonEvent *
event,
String *
args, int
*
num_args)
{
.
.
.
}
Les paramètres sont passés dans la variable args, le nombre de paramètres dans num_args. Comme nous ne passons qu'un seul paramètre avec la table de translations correspondant à cet exemple, nous testerons juste la valeur de la variable args[0]. Les différentes valeurs up, motion et down nous renseigneront sur la nature de l'événement qui a déclenché l'appel de la fonction de callback.
Listing complet du programme draw_input.c :
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>
#include <Xm/RowColumn.h>
GC gc;
XGCValues gcv;
Widget draw;
String colours[] =
{
"
Black
"
, "
Red
"
, "
Green
"
, "
Blue
"
,
"
Grey
"
, "
White
"
}
;
long
int
fill_pixel =
1
; /* stores current colour
of fill - black default */
Display *
display; /* xlib id of display */
Colormap cmap;
/*---------------------------*/
main
(
int
argc, char
*
argv[])
/*---------------------------*/
{
Widget top_wid, main_w, menu_bar, quit, clear, colour;
XtAppContext app;
XmString quits, clears, colourss, red, green,
blue, black, grey, white;
void
quit_call
(
), clear_call
(
), colour_call
(
),
draw_cbk
(
);
XtActionsRec actions;
String translations =
"
<Btn1Motion>: draw_cbk(motion)
\n
\
<Btn1Down>: draw_cbk(down)
\n
\
<Btn1Up>: draw_cbk(up)
"
;
top_wid =
XtVaAppInitialize
(&
app, "
Draw
"
, NULL
, 0
,
&
argc, argv, NULL
,
XmNwidth, 500
,
XmNheight, 500
,
NULL
);
/* Create MainWindow */
main_w =
XtVaCreateManagedWidget
(
"
main_window
"
,
xmMainWindowWidgetClass, top_wid,
XmNwidth, 500
,
XmNheight, 500
,
NULL
);
/* Create a simple MenuBar that contains three menus */
quits =
XmStringCreateSimple
(
"
Quit
"
);
clears =
XmStringCreateSimple
(
"
Clear
"
);
colourss =
XmStringCreateSimple
(
"
Colour
"
);
menu_bar =
XmVaCreateSimpleMenuBar
(
main_w, "
main_list
"
,
XmVaCASCADEBUTTON, quits, ‘Q'
,
XmVaCASCADEBUTTON, clears, ‘C
'
,
XmVaCASCADEBUTTON, colourss, ‘o'
,
NULL);
XtManageChild(menu_bar);
/* First menu is quit menu -- callback is quit_call() */
XmVaCreateSimplePulldownMenu(menu_bar, "quit_menu", 0,
quit_call, XmVaPUSHBUTTON, quits, ‘Q
'
, NULL
, NULL
,
NULL
);
XmStringFree
(
quits);
/* Second menu is clear menu -- callback is clear_call() */
XmVaCreateSimplePulldownMenu
(
menu_bar, "
clear_menu
"
, 1
,
clear_call, XmVaPUSHBUTTON, clears, ‘C'
, NULL, NULL,
NULL);
XmStringFree(clears);
/* create colour pull down menu */
black = XmStringCreateSimple(colours[0]);
red = XmStringCreateSimple(colours[1]);
green = XmStringCreateSimple(colours[2]);
blue = XmStringCreateSimple(colours[3]);
grey = XmStringCreateSimple(colours[4]);
white = XmStringCreateSimple(colours[5]);
colour = XmVaCreateSimplePulldownMenu(menu_bar,
"edit_menu", 2, colour_call,
XmVaRADIOBUTTON, black, ‘k
'
, NULL
, NULL
,
XmVaRADIOBUTTON, red, ‘R'
, NULL, NULL,
XmVaRADIOBUTTON, green, ‘G
'
, NULL
, NULL
,
XmVaRADIOBUTTON, blue, ‘B'
, NULL, NULL,
XmVaRADIOBUTTON, grey, ‘e
'
, NULL
, NULL
,
XmVaRADIOBUTTON, white, ‘W'
, NULL, NULL,
XmNradioBehavior, True,
/* RowColumn resources to enforce */
XmNradioAlwaysOne, True,
/* radio behavior in Menu */
NULL);
XmStringFree(black);
XmStringFree(red);
XmStringFree(green);
XmStringFree(blue);
XmStringFree(grey);
XmStringFree(white);
/* Create a DrawingArea widget. */
/* make new actions */
actions.string = "draw_cbk";
actions.proc = draw_cbk;
XtAppAddActions(app, &actions, 1);
draw = XtVaCreateWidget("draw",
xmDrawingAreaWidgetClass, main_w,
XmNtranslations, XtParseTranslationTable(translations),
XmNbackground, WhitePixelOfScreen(XtScreen(main_w)),
NULL);
cmap = DefaultColormapOfScreen(XtScreen(draw));
display = XtDisplay(draw);
/* set the DrawingArea as the "work area" of main window */
XtVaSetValues(main_w,
XmNmenuBar, menu_bar,
XmNworkWindow, draw,
NULL);
/* Create a GC. Attach GC to DrawingArea
'
s XmNuserData. */
gcv.foreground =
BlackPixelOfScreen
(
XtScreen
(
draw));
gc =
XCreateGC
(
XtDisplay
(
draw),
RootWindowOfScreen
(
XtScreen
(
draw)),
GCForeground, &
gcv);
XtManageChild
(
draw);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/* CALL BACKS */
/*--------------*/
void
quit_call
(
)
/*--------------*/
{
printf
(
"
Quitting program
\n
"
);
exit
(
0
);
}
/*---------------*/
void
clear_call
(
)
/*---------------*/
/* clear work area */
{
XClearWindow
(
display, XtWindow
(
draw));
}
/*---------------------------*/
void
colour_call
(
w, item_no)
/*---------------------------*/
/* called from any of the "Colour" menu items.
Change the color of the label widget.
Note: we have to use dynamic setting with setargs()..
*/
Widget w; /* menu item that was selected */
int
item_no; /* the index into the menu */
{
int
n =
0
;
Arg args[1
];
XColor xcolour, spare; /* xlib color struct */
if
(
XAllocNamedColor
(
display, cmap, colours[item_no],
&
xcolour, &
spare) ==
0
)
return
;
/* remember new colour */
fill_pixel =
xcolour.pixel;
}
/* DrawingArea Callback.*/
/*--------------------------------------------*/
void
draw_cbk
(
Widget w, XButtonEvent *
event,
String *
args, int
*
num_args)
/*--------------------------------------------*/
{
static
Position x, y, last_x, last_y;
Position width, height;
int
line_style;
unsigned
int
line_width =
1
;
int
cap_style =
CapRound;
int
join_style =
JoinRound;
if
(
strcmp
(
args[0
], "
down
"
) ==
0
) {
/* anchor initial point (save its value) */
x =
event->
x;
y =
event->
y;
}
else
if
(
strcmp
(
args[0
], "
motion
"
) ==
0
) {
/* draw "ghost" box to show where it could go */
/* undraw last box */
line_style =
LineOnOffDash;
/* set line attributes */
XSetLineAttributes
(
event->
display, gc,
line_width, line_style, cap_style, join_style);
gcv.foreground
=
WhitePixelOfScreen
(
XtScreen
(
w));
XSetForeground
(
event->
display, gc,
gcv.foreground);
XSetFunction
(
event->
display, gc, GXinvert);
XDrawLine
(
event->
display, event->
window, gc,
x, y, last_x, y);
XDrawLine
(
event->
display, event->
window, gc,
last_x, y, last_x, last_y);
XDrawLine
(
event->
display, event->
window, gc,
last_x, last_y, x, last_y);
XDrawLine
(
event->
display, event->
window, gc,
x, last_y, x, y);
/* Draw New Box */
gcv.foreground
=
BlackPixelOfScreen
(
XtScreen
(
w));
XSetForeground
(
event->
display, gc,
gcv.foreground);
XDrawLine
(
event->
display, event->
window, gc,
x, y, event->
x, y);
XDrawLine
(
event->
display, event->
window, gc,
event->
x, y, event->
x, event->
y);
XDrawLine
(
event->
display, event->
window, gc,
event->
x, event->
y, x, event->
y);
XDrawLine
(
event->
display, event->
window, gc,
x, event->
y, x, y);
}
else
if
(
strcmp
(
args[0
], "
up
"
) ==
0
) {
/* draw full line */
XSetFunction
(
event->
display, gc, GXcopy);
line_style =
LineSolid;
/* set line attributes */
XSetLineAttributes
(
event->
display, gc,
line_width, line_style, cap_style, join_style);
XSetForeground
(
event->
display, gc, fill_pixel);
XDrawLine
(
event->
display, event->
window, gc,
x, y, event->
x, y);
XDrawLine
(
event->
display, event->
window, gc,
event->
x, y, event->
x, event->
y);
XDrawLine
(
event->
display, event->
window, gc,
event->
x, event->
y, x, event->
y);
XDrawLine
(
event->
display, event->
window, gc,
x, event->
y, x, y);
width =
event->
x -
x;
height =
event->
y -
y;
XFillRectangle
(
event->
display, event->
window,
gc, x, y, width, height);
}
last_x =
event->
x;
last_y =
event->
y;
}
IV-J. Les widgets de type List▲
IV-J-1. Introduction▲
Les widgets de type List permettent à l'utilisateur de choisir un ou plusieurs éléments appartenant à une liste d'éléments !
Nous avons déjà rencontré inconsciemment des widgets de type List, par exemple lors de l'étude des FileSelectionDialog. Les deux boites contenant les noms des répertoires et les noms des fichiers d'un FileSelectionDialog sont des widgets de type List.
La manipulation des List est similaire à celle des PulldownMenus.
Cependant, avec un widget de type List :
- il est possible d'ajouter des éléments dans la liste et aussi d'en enlever ;
- les List widgets peuvent être « scrollés » ;
- il existe plusieurs modes de sélection des éléments.
Voici un exemple de widget de type List :
IV-J-2. Modes de sélection des éléments d'une liste▲
Quatre modes de sélection sont disponibles, on choisit l'un des modes en positionnant la ressource XmNselectionPolicy avec l'une de ces valeurs :
- XmSINGLE_SELECT : un seul item de la liste peut être sélectionné ;
- XmBROWSE_SELECT : similaire à XmSINGLE_SELECT sauf qu'une valeur par défaut peut être choisie. L'interaction avec l'utilisateur est par conséquent légèrement différente ;
- XmMULTIPLE_SELECT : plusieurs sélections sont possibles. Un élément est choisi en cliquant dessus. Si on clique à nouveau sur un élément déjà choisi, on annule sa sélection ;
- XmEXTENDED_SELECT : similaire au mode précédent sauf que l'on peut sélectionner plusieurs éléments contigus en cliquant avec la souris sur un élément puis en faisant glisser la souris sur d'autres éléments en maintenant le bouton enfoncé.
IV-J-3. Principes de base d'utilisation des List widget▲
IV-J-3-a. Création widgets de type List ou ScrolledList▲
Pour créer un List widget, on utilisera les fonctions habituelles XmCreateList() ou XtVaCreateManagedWidget() avec comme classe xmListWidgetClass.
Le plus souvent, on désire créer des listes possédant des barres de scrolling. On utilisera alors la fonction XmCreateScrolledList().
IV-J-3-b. Principales ressources▲
- XmNitemCount : nombre d'éléments dans la liste ;
- XmNitems : la liste des éléments. Elle doit être de type XmStringTable, qui est un type correspondant à un tableau unidimensionnel d'objets de type XmString ;
- XmNselectionPolicy : mode de sélection des éléments. Voir plus haut ;
- XmNvisibleItemCount : nombre d'éléments visibles dans la fenêtre du widget de type List. Cette ressource détermine en fait la hauteur du widget ;
- XmNdisplayScrollBarPolicy : cette ressource permet de sélectionner l'apparition de la barre de scrolling. Deux valeurs sont possibles :
- XmSTATIC : une barre de scrolling verticale sera toujours présente sur le bord de la boite contenant les éléments de la liste,
- XmAS_NEEDED : la barre de scrolling ne sera affichée que si tous les éléments ne contiennent pas dans la boite d'affichage ;
- XmNlistSizePolicy : cette ressource permet de sélectionner les différents modes de retaillage du widget de type List. Trois valeurs sont possibles :
- XmCONSTANT : la taille est constante,
- XmRESIZE_IF_POSSIBLE : la taille peut varier si possible, c'est-à-dire si le widget parent le permet,
- XmVARIABLE ;
- XmNselectedItemCount : nombre d'éléments sélectionnés par défaut ;
- XmNselectedItems : la liste des éléments sélectionnés.
IV-J-3-c. Ajout et retrait d'éléments▲
Pour ajouter un élément dans un List widget, on utilise la fonction MOTIF XmListAddItem(), qui accepte trois arguments :
- un pointeur sur le widget de type List auquel on veut ajouter un élément ;
- l'élément, de type XmString ;
- la position où l'on veut insérer l'élément. Il s'agit d'un entier (un int). Cette position commence par la valeur 1, la position 0 servant à désigner la dernière position dans la liste.
Une autre fonction, XmListAddItemUnselected() possède exactement la même syntaxe. Elle garantit qu'un élément n'est jamais sélectionné lorsqu'il est ajouté à la liste, ce qui n'est pas toujours le cas avec la fonction XmListAddItem().
Pour supprimer un seul élément d'une liste, il faut utiliser la fonction XmListDeleteItem(Widget, XmString).
Pour supprimer plusieurs éléments d'une liste, il faut utiliser la fonction XmListDeleteItems(Widget, XmString*).
Si la position du ou des éléments(s) à supprimer est connue, on peut utiliser également la fonction XmListDeletePos(Widget w, int num, int Pos).
Elle permet de détruire num éléments à partir de la position pos.
Pour détruire tous les éléments d'une liste, on utilisera la fonction XmListDeleteAllItems(Widget).
IV-J-3-d. Sélection d'éléments dans une liste▲
Pour sélectionner des éléments dans une liste, on utilisera les fonctions XmListSelectItem(Widget, XmString, Boolean) et XmListSelectPos(Widget, int, Boolean).
Si la valeur de type Boolean est égale à True, alors la fonction de callback de la liste sera appelée lors de la sélection (les fonctions de callback sont détaillées dans la section suivante).
Pour désélectionner des éléments d'une liste, on utilisera une des fonctions : XmListDeselectItem(Widget, XmString), XmListDeselectPos(Widget, int) ou XmListDeselectAllItems(Widget).
IV-J-3-e. Obtenir des informations sur une liste▲
Étant donné que les widgets de type List évoluent dynamiquement (on ajoute et on enlève des éléments de la liste pendant l'exécution de l'application), il peut être nécessaire de connaître à un moment donné la valeur de certains attributs de la liste, par exemple le nombre d'éléments se trouvant dans la liste, l'ensemble des éléments sélectionnés, etc.
On obtient toutes ces informations à l'aide de la fonction XtGetValues(), déjà étudiée, qui permet de consulter la valeur des ressources d'un widget. En effet, les ressources des List widgets sont mises à jour dynamiquement pendant l'exécution de l'application.
IV-J-3-f. Les fonctions de callback▲
Il existe une ressource de callback pour chaque mode de sélection :XmNsingleSelectionCallback, XmNmultipleSelectionCallback, XmNbrowseSelectionCallback et XmNextendedSelectionCallback et une ressource de callback par défaut : XmNdefaultActionCallback.
La fonction de callback par défaut est toujours appelée après la fonction de callback correspondant à la sélection d'un ou plusieurs éléments.
Le callback de sélection est déclenché lors d'un double-clic sur un élément.
Les fonctions de callback pour les List widgets ont la forme suivante :
list_cbk
(
Widget w, XtPointerData data, XmListCallbackStruct *
cbk)
Les champs intéressants de la structure XmListCallbackStruct sont les suivants :
• item : l'élément sélectionné (cas d'une sélection simple), de type XmString ;
• item_position : la position de l'élément sélectionné dans la liste ;
• selected_items : liste des éléments sélectionnés (cas d'une sélection multiple), il s'agit d'un tableau de XmString ;
• selected_item_count : nombre d'éléments sélectionnés (taille du tableau selected_items) ;
• selected_item_positions : tableau contenant les positions des éléments sélectionnés.
Vous pouvez vous référer à la doc MOTIF ou bien regarder le contenu de <XM/List.h> pour obtenir plus d'informations.
IV-J-4. Petit programme d'exemple : list.c▲
Ce petit programme crée une liste de noms de couleurs. La sélection des différents éléments change la couleur de fond du List widget, en utilisant une méthode similaire à celle déjà étudiée avec le programme menu_pull.c.
Voici à quoi ressemble l'exécution de ce petit programme :
#include <Xm/Xm.h>
#include <Xm/List.h>
String colours[] =
{
"
Black
"
, "
Red
"
, "
Green
"
,
"
Blue
"
, "
Grey
"
}
;
Display *
display; /* xlib id of display */
Colormap cmap;
/*---------------*/
main
(
argc, argv)
/*---------------*/
char
*
argv[];
{
Widget top_wid, list;
XtAppContext app;
int
i, nb_items =
XtNumber
(
colours);
XColor back, fore, spare;
XmStringTable str_list;
int
n =
0
;
Arg args[10
];
void
list_cbk
(
);
top_wid =
XtVaAppInitialize
(&
app, "
List_top
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
str_list =
(
XmStringTable) XtMalloc
(
nb_items *
sizeof
(
XmString *
));
for
(
i =
0
; i <
nb_items; i++
)
str_list[i] =
XmStringCreateSimple
(
colours[i]);
n=
0
;
XtSetArg
(
args[n], XmNvisibleItemCount, nb_items); n++
;
XtSetArg
(
args[n], XmNitemCount, nb_items); n++
;
XtSetArg
(
args[n], XmNitems, str_list); n++
;
/* The next ressources should be in a ressource file */
XtSetArg
(
args[n], XmNwidth, 300
); n++
;
XtSetArg
(
args[n], XmNheight, 300
); n++
;
list =
XmCreateList
(
top_wid, "
list1
"
, args, n);
XtManageChild
(
list);
for
(
i =
0
; i <
nb_items; i++
)
XmStringFree
(
str_list[i]);
XtFree
((
char
*
) str_list);
/* background pixel to black foreground to white */
cmap =
DefaultColormapOfScreen
(
XtScreen
(
list));
display =
XtDisplay
(
list);
XAllocNamedColor
(
display, cmap, colours[0
], &
back,
&
spare);
XAllocNamedColor
(
display, cmap, "
white
"
, &
fore, &
spare);
n =
0
;
XtSetArg
(
args[n],XmNbackground, back.pixel); n++
;
XtSetArg
(
args[n],XmNforeground, fore.pixel); n++
;
XtSetValues
(
list, args, n);
XtAddCallback
(
list, XmNdefaultActionCallback, list_cbk,
NULL
);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*-----------------------------------------*/
void
list_cbk
(
Widget w, XtPointer data,
XmListCallbackStruct *
list_cbs)
/*-----------------------------------------*/
/* called from any of the "Colour" list items.
Change the color of the list widget.
Note: we have to use dynamic setting with setargs() */
{
int
n =
0
;
Arg args[1
];
String selection;
XColor xcolour, spare; /* xlib color struct */
/* list->cbs holds XmString of selected list item */
/* map this to "ordinary" string */
XmStringGetLtoR
(
list_cbs->
item, XmSTRING_DEFAULT_CHARSET,
&
selection);
if
(
XAllocNamedColor
(
display, cmap, selection,
&
xcolour, &
spare) ==
0
)
return
;
XtSetArg
(
args[n],XmNbackground, xcolour.pixel); n++
;
/* w id of list widget passed in */
XtSetValues
(
w, args, n);
}
IV-K. Les widgets de type Scale▲
IV-K-1. Introduction▲
Les widgets de type Scale permettent à l'utilisateur d'entrer des valeurs numériques dans un programme à l'aide d'ascenseurs. Voici un exemple de Scale widget :
IV-K-2. Bases d'utilisation des widgets de type Scale▲
Pour créer un Scale widget on utilisera par exemple une de ces fonctions : XmCreateScale() ou encore XtVaCreateManagedWidget() avec comme classe de widget xmScaleWidgetClass. Il existe encore d'autres manières, mais à ce stade du cours vous devriez les connaître ou mieux les deviner.
Les ressources les plus intéressantes des Scale widgets sont :
- XmNmaximum : la plus grande valeur sélectionnable par l'utilisateur (lorsque l'ascenseur est en butée) ;
- XmNminimum : la plus petite valeur sélectionnable par l'utilisateur (lorsque l'ascenseur est en butée) ;
- XmNorientation : deux valeurs possibles selon que l'on désire un ascenseur horizontal ou vertical : XmHORIZONTAL et XmVERTICAL ;
- XmNtitleString : le label affiché au-dessus de l'ascenseur (facultatif), de type XmString ;
- XmNdecimalPoints : la valeur renvoyée par un Scale widget est toujours un entier (0 par défaut). En positionnant cette ressource, on peut donner à l'utilisateur l'impression qu'il choisit en déplaçant l'ascenseur une valeur décimale. Par exemple si on choisit comme intervalle [0, 1000] (à l'aide des ressources XmNminimum et XmNmaximum) et que l'on positionne XmNdecimalPoints avec la valeur 2, l'intervalle affiché sera égal à [0.00, 10.00]. C'est au programmeur de faire attention à effectuer la division par 100 et la conversion int -> float ou double ;
- XmNshowValue : deux valeurs possibles : True ou False selon que l'on désire que la valeur correspondant à la position de l'ascenseur soit affichée dans le Scale widget ou pas ;
- XmNvalue : la valeur courante correspondant à la position de l'ascenseur. De type int ;
- XmNprocessingDirection : plusieurs valeurs possibles : XmMAX_ON_TOP, XmMAX_ON_BOTTOM, XmMAX_ON_LEFT ou encore XmMAX_ON_RIGHT.
Cette ressource indique où va se trouver la valeur maximale sélectionnable. Seulement deux valeurs sont possibles selon que le widget est vertical ou horizontal.
IV-K-3. Fonctions de callback des Scale widgets▲
Il existe deux types de callback pour les Scale widgets :
- XmNvalueChangedCallback : la fonction de callback est appelée lorsque la valeur correspondant à la position de l'ascenseur a changé (l'utilisateur a déplacé le curseur).
- XmNdragCallback : le callback est appelé de manière continue lors du déplacement de l'ascenseur.
Ceci peut affecter les performances de l'application si le traitement effectué dans la fonction de callback est consommateur de CPU.
La fonction de callback d'un Scale widget est standard :
scale_cbk
(
Widget w, XtPointerData data, XmScaleCallbackStruct *
cbk)
Le champ value de la structure XmScaleCallbackStruct *cbk contient la valeur courante (un int) correspondant à la position du curseur.
IV-K-4. Petit programme d'exemple : scale▲
Ce petit programme simule une interface pour régler le volume sonore d'une quelconque application musicale du type xaudio, etc.
#include <Xm/Xm.h>
#include <Xm/Scale.h>
/*--------------------------*/
main
(
int
argc, char
**
argv)
/*--------------------------*/
{
Widget top_wid, scale;
XmString title;
XtAppContext app;
void
scale_cbk
(
);
top_wid =
XtVaAppInitialize
(&
app, "
Scale
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
title =
XmStringCreateSimple
(
"
Volume sonore
"
);
scale =
XtVaCreateManagedWidget
(
"
scale
"
,
xmScaleWidgetClass, top_wid,
XmNtitleString, title,
XmNorientation, XmHORIZONTAL,
XmNmaximum, 11
,
XmNdecimalPoints, 0
,
XmNshowValue, True,
XmNwidth, 200
,
XmNheight, 100
,
NULL
);
XtAddCallback
(
scale,XmNvalueChangedCallback, scale_cbk,
NULL
);
XtRealizeWidget
(
top_wid);
XtAppMainLoop
(
app);
}
/*--------------------------------------------------*/
void
scale_cbk
(
Widget widget, int
data,
XmScaleCallbackStruct *
scale_struct)
/*--------------------------------------------------*/
{
if
(
scale_struct->
value <
4
)
printf
(
"
Le volume sonore est trop bas (%d)
\n
"
,
scale_struct->
value);
else
if
(
scale_struct->
value <
7
)
printf
(
"
le volume sonore est Ok (%d)
\n
"
,
scale_struct->
value);
else
if
(
scale_struct->
value <
10
)
printf
(
"
Le volume sonore est fort (%d)
\n
"
,
scale_struct->
value);
else
/* Volume == 11 */
printf
(
"
le volume sonore est vraiment trop fort (%d)
\n
"
,
scale_struct->
value);
}
IV-K-5. Changer le look d'un Scale widget▲
Le document The MOTIF Style Guide suggère d'utiliser des petits marqueurs pour décorer l'échelle des valeurs des Scale widgets et ainsi leur donner l'allure de « règles ». Voici un exemple :
Chaque marqueur est un widget de type Separator qui aura pour père le Scale widget. Voici les modifications que l'on pourrait apporter au programme d'exemple précédent pour qu'il ressemble à la figure ci-dessus :
#include <Xm/SeparatoG.h>
Widget ...,tics[11
];
.
.
/* Création des marqueurs le long de l'axe horizontal */
for
(
i=
0
; i <
11
; ++
i ) {
n=
0
;
XtSetArg
(
args[n], XmNseparatorType, XmSINGLE_LINE); n++
;
XtSetArg
(
args[n], XmNorientation, XmVERTICAL); n++
;
XtSetArg
(
args[n], XmNwidth, 10
); n++
;
XtSetArg
(
args[n], XmNheight, 5
); n++
;
tics[i] =
XmCreateSeparatorGadget
(
scale, "
|
"
, args, n);
}
XtManageChildren
(
tics, 11
);
.
.
IV-L. Les chaînes de caractères sous MOTIF▲
IV-L-1. Introduction▲
Motif reconnaît deux types de chaînes de caractères :
- Les chaînes de type char *, les chaînes de caractères que nous connaissons déjà (au sens de la programmation en langage C) ;
- Les chaînes de type XmString. Le programmeur n'a pas besoin de connaître les détails concernant ce type d'objet.
Après avoir vu la complexité de l'affichage de texte à l'aide des fonctions de la Xlib, vous vous doutez bien que MOTIF doit stocker dans le type String des informations telles que la taille en pixels de la chaîne en fonction de la police de caractères utilisée, le jambage, la baseline, etc.
MOTIF fournit de nombreuses fonctions de manipulation de chaînes de type XmString (concaténation, recopie, etc.) et de conversion entre les char * et les XmString.
En voici quelques-unes :
- XmStringCreateSimple() : création d'une chaîne simple avec la police de caractères par défaut ;
- XmStringCreateLtoR() : idem sauf que la chaîne de caractères est créée à l'aide d'une police passée en paramètre ;
- XmStringGetLtoR() : conversion de XmString vers char * ;
- XmStringCompare() : comparaison entre deux XmStrings ;
- XmStringConcat() : concaténation de deux XmStrings ;
- XmStringCopy() : copie d'une XmString dans une autre.
Pour plus d'informations, voir le manuel de ces différentes fonctions. Leur utilisation est relativement simple.
IV-L-2. Spécifier une police de caractères▲
Dans une application MOTIF, une police de caractères est une variable de type XmFontList, créée à partir d'un objet Xlib de type XFontStruct, déjà étudié dans la partie du cours Xlib consacrée à l'affichage de texte.
Typiquement, voici les étapes nécessaires pour modifier la police de caractères utilisée par un widget :
- Charger une police de caractères à l'aide de fonctions Xlib. La fonction load_font() (cf le cours Xlib pour des explications détaillées) fait parfaitement l'affaire :Sélectionnez
/*-----------------------------*/
load_font
(
font_info, font_name)/*-----------------------------*/
XFontStruct**
font_info;char
*
font_name;{
/* on accède à la fonte */
if
((*
font_info=
XLoadQueryFont
(
display, font_name))==
NULL
){
(
void
)fprintf
(
stderr,"
Police de caractères %s non trouvée
\n
"
, font_name); exit(-
1
);}
}
- Créer un objet de type XmStringCharSet. En général on lui donne la valeur XmSTRING_DEFAULT_CHARSET. Cet objet, une fois initialisé, va contenir toutes les informations concernant une police de caractères : taille des caractères, jambage, etc. Il sera utilisé par les fonctions de création de XmString telles que XmStringCreateLtoR() ;
- Appeler la fonction XmFontListCreate(XFontStruct *fnt, XmStringCharSet charset), qui effectue deux actions :
- Elle initialise la variable de type XmStringCharSet avec toutes les informations relatives à la police de caractères XFontStruct *fnt,
- Elle renvoie une valeur de type XmFontList qui sera utilisée pour positionner les ressources des widgets possédant des labels.
IV-L-3. Exemple d'utilisation▲
Ce petit exemple montre comment on peut changer la fonte de caractères d'un PushButton figurant dans une barre de menu. Étudier en particulier le rôle de chacun des deux objets : charset et menu_bar_font.
XFontStruct *
fnt;
XmFontList menu_bar_font; /* font pour les boutons du menu */
XmStringCharSet charset =
(
XmStringCharSet) XmSTRING_DEFAULT_CHARSET;
.
.
.
/* C'EST ICI QUE C'EST INTERESSANT !!! */
load_font
(&
fnt,"
vr-20
"
);
menu_bar_font =
XmFontListCreate
(
fnt, charset);
menu_bar =
XmCreateMenuBar (
parent, "
menu_bar
"
, NULL
, 0
);
XtManageChild (
menu_bar);
menu_pane =
XmCreatePulldownMenu (
menu_bar, "
menu_pane
"
, NULL
, 0
);
/* ET ICI AUSSI !!! */
label_string =
XmStringCreateLtoR
(
"
Exit
"
, charset);
n =
0
;
XtSetArg (
args[n], XmNlabelString, label_string); n++
;
XtSetArg (
args[n], XmNalignment,XmALIGNMENT_CENTER); n++
;
XtSetArg (
args[n], XmNfontList, menu_bar_font); n++
;
button =
XmCreatePushButton (
menu_pane, "
Quit
"
, args, n);
XtManageChild (
button);
IV-M. Les widgets de type ScrollBar et ScrolledWindow.▲
IV-M-1. Introduction▲
Motif fournit des widgets permettant de visualiser partiellement d'autres widgets en les faisant « scroller » : les ScrollBar et les ScrolledWindow.
Nous avons déjà vu des ScrollBar widgets en action lors de l'étude des Text widgets et des List widgets. Motif propose en effet des métawidgets tels que les SrolledList ou les ScrolledText widgets qui contiennent une ScrolledWindow.
IV-M-2. Les widgets de type ScrolledWindow▲
On crée les ScrolledWindow à l'aide d'une des fonctions XtVaCreateManagedWidget() avec comme classe xmScrolledWindowWidgetClass, ou XmCreateScrolledWindow(), etc. vous devez maintenant être capable de deviner les deux autres manières de créer n'importe quel type de widget connaissant son nom.
Lorsqu'on utilise des ScrolledWindow, il faut inclure le fichier <Xm/ScrolledW.h>.
Ressources les plus intéressantes :
- XmNhorizontal, XmNvertical : ID des ScrollBars de la ScrolledWindow. Ces dernières doivent donc être créées avant la ScrolledWindow ;
- XmNScrollingPolicy : deux valeurs possibles : XmAUTOMATIC ou XmAPPLICATION_DEFINED.
Dans le premier : cas le scrolling est effectué automatiquement lorsque l'utilisateur clique sur les flèches des ScrollBars, dans l'autre cas rien n'est effectué et le traitement du scrolling devra être fait à la main dans les fonctions de callback des ScrollBars ; - XmNscrollBarDisplayPolicy : cette ressource permet de sélectionner l'apparition de la barre de scrolling. Deux valeurs sont possibles :
- XmSTATIC : une barre de scrolling verticale sera toujours présente sur le bord de la ScrolledWIndow,
- XmAS_NEEDED : la barre de scrolling ne sera affichée que si le contenu de la Scrolled Window est plus grand que la taille de cette dernière ;
- XmNvisualPolicy : deux valeurs possibles : XmCONSTANT ou XmVARIABLE. Dans le premier cas, la ScrolledWindow ne peut pas se resizer d'elle-même ;
- XmNworkWindow : ID du widget qui va se trouver dans la ScrolledWindow et qui va être scrollée. Il doit être créé avant la ScrolledWindow.
IV-M-3. Les widgets de type ScrollBar▲
Les fonctions de création sont standards :
XtVaCreateManagedWidget() (classe xmScrollBarWidgetClass), etc.
IV-M-3-a. Ressources les plus intéressantes▲
- XmNsliderSize : un slider (l'ascenseur, le petit curseur que l'on déplace entre les deux flèches de la ScrollBar) est divisé en unités de longueur. Cette ressource permet de spécifier le nombre d'unités que l'on désire ;
- XmNmaximum : la plus grande valeur sélectionnable par l'utilisateur (lorsque le slider est en butée), en unités de longueur ;
- XmNmaximum : la plus petite valeur sélectionnable par l'utilisateur (lorsque le slider est en butée), en unités de longueur ;
- XmNincrement : le nombre d'unités de valeur correspondant au plus petit déplacement souris du slider ;
- XmNorientation : deux valeurs possibles selon que l'on désire un ascenseur horizontal ou vertical : XmHORIZONTAL et XmVERTICAL ;
- XmNvalue : la valeur courante correspondant à la position du slider. De type int ;
- XmNpageIncrement : cette ressource contrôle le déplacement du widget se trouvant à l'intérieur de la ScrolledWindow en fonction du déplacement du slider.
IV-M-3-b. Ressources de callback▲
- XmNvalueChangedCallback : la fonction de callback est appelée lorsque la valeur correspondant à la position de l'ascenseur a changé (l'utilisateur a déplacé le curseur) ;
- XmNdragCallback : le callback est appelé de manière continue lors du déplacement de l'ascenseur. Attention, ceci peut affecter les performances de l'application si le traitement effectué dans la fonction de callback est consommateur de CPU ;
- XmNdecrementCallback, XmNincrementCallback : le callback est appelé lorsque la valeur correspondant à la position du slider a diminué/augmenté ;
- XmNpageDecrementCallback, XmNpageIncrementCallback : idem au précédent sauf que le déplacement est suffisamment grand pour que le widget contenu dans la Scrolled Window soit scrollé ;
- XmNtoTopCallback, XmNtoBottomCallback : le callback est appelé lorsque le slider atteint une des extrémités de la ScrollBar.
IV-N. Les widgets de type Toggle▲
IV-N-1. Introduction▲
Un widget de type Toggle est un bouton (ou une entrée de menu) qui possède deux valeurs : On ou Off. Il peut avoir la forme d'un carré (square) ou d'un diamant (diamond).
Les Toggle widgets possèdent, comme les boutons-poussoirs, un label qui peut être de type texte ou bien de type pixmap (un dessin).
On peut grouper plusieurs Toggle widgets pour contrôler leur comportement les uns par rapport aux autres. Motif fournit deux types de Toggle widgets qui réagissent différemment lorsqu'ils sont regroupés :
- le widget de type RadioBox : un seul de ces widgets peut être sur la position On lorsqu'on en groupe plusieurs. Il y a exclusion mutuelle, lorsqu'un d'entre eux est sélectionné, les autres sont mis sur Off. Ils sont en forme de diamant ;
- le widget de type CheckBox : plusieurs CheckBox peuvent être sélectionnés en même temps. Ils sont en forme de carré.
IV-N-2. Bases d'utilisation des Toggle widgets▲
On crée les Toggle widgets de manière standard : à l'aide des fonctions XmCreateToggleButton() ou encore XtVaCreateManagedWidget() avec comme classe de widget xmToggleButtonWidgetClass, etc.
IV-N-2-a. Ressources les plus importantes▲
- XmNindicatorType : on positionnera cette ressource avec la valeur XmN_OF_MANY pour créer un CheckBox widget ou bien avec la valeur XmONE_OF_MANY pour créer un RadioBox widget ;
- XmNindicatorOn : mettre à True pour positionner le widget sur On (valeur par défaut = False) ;
- XmNindicatorSize : taille de l'indicateur (diamant ou carré), en pixels ;
- XmNlabelType : deux valeurs possibles : XmSTRING (défaut) ou XmPIXMAP selon que le label est textuel ou graphique ;
- XmLabelString : le texte du label, de type XmString ;
- XmNPixmap : pixmap du widget lorsqu'il n'est pas sélectionné (uniquement si la ressource XmNlabelType vaut XmPIXMAP) ;
- XmNselectedPixmap : pixmap du widget lorsqu'il est sélectionné (uniquement si la ressource XmNlabelType vaut XmPIXMAP) ;
- XmNselectColour : couleur de l'indicateur du widget sélectionné. De type Pixel. Le Pixmap est un type X standard. Pour charger un pixmap, on utilisera la fonction XmGetPixmap().
IV-N-2-b. Callback des Toggle widgets▲
On précise la fonction de callback en positionnant la ressource XmNvalueChangedCallback.
La fonction de Callback d'un widget de type Toggle est standard :
toggle_cbk
(
Widget w, XtPointerData data, XmToggleCallbackStruct *
cbk)
Le champ set de la structure XmToggleCallbackStruct permet de savoir si le widget est sélectionné ou non. Avec l'exemple ci-dessus, si cbk->set vaut True alors le widget w est sélectionné.
IV-N-3. Petit programme d'exemple : toggle.c▲
#include <Xm/Xm.h>
#include <Xm/ToggleB.h>
#include <Xm/RowColumn.h>
/*--------------------------*/
main
(
int
argc, char
**
argv)
/*--------------------------*/
{
Widget toplevel, rowcol, toggle1, toggle2;
XtAppContext app;
void
toggle1_cbk
(
), toggle2_cbk
(
);
toplevel =
XtVaAppInitialize
(&
app, "
Toggle
"
, NULL
, 0
,
&
argc, argv, NULL
, NULL
);
rowcol =
XtVaCreateWidget
(
"
rowcol
"
,
xmRowColumnWidgetClass, toplevel,
XmNwidth, 300
,
XmNheight, 200
,
NULL
);
toggle1 =
XtVaCreateManagedWidget
(
"
Dolby ON/OFF
"
,
xmToggleButtonWidgetClass, rowcol, NULL
);
XtAddCallback
(
toggle1, XmNvalueChangedCallback,
toggle1_cbk, NULL
);
toggle2 =
XtVaCreateManagedWidget
(
"
Dolby B/C
"
,
xmToggleButtonWidgetClass, rowcol, NULL
);
XtAddCallback
(
toggle2, XmNvalueChangedCallback,
toggle2_cbk, NULL
);
XtManageChild
(
rowcol);
XtRealizeWidget
(
toplevel);
XtAppMainLoop
(
app);
}
/*-------------------------------------------------------*/
void
toggle1_cbk
(
Widget widget, XtPointer client_data,
XmToggleButtonCallbackStruct *
state)
/*-------------------------------------------------------*/
{
printf
(
"
%s: %s
\n
"
, XtName
(
widget),
state->
set? "
on
"
: "
off
"
);
}
/*-------------------------------------------------------*/
void
toggle2_cbk
(
Widget widget, XtPointer client_data,
XmToggleButtonCallbackStruct *
state)
/*-------------------------------------------------------*/
{
printf
(
"
%s: %s
\n
"
, XtName
(
widget), state->
set? "
B
"
: "
C
"
);
}
IV-N-4. Grouper des Toggle widgets automatiquement▲
On peut regrouper « à la main » des RadioBox ou des CheckBox widgets dans une RowColumn ou une Form, comme dans le programme d'exemple toggle.c.
Motif propose cependant des fonctions de convénience effectuant ce travail : XmCreateSimpleRadioBox() et XmCreateSimpleCheckBox(). Il en existe d'autres…
Ces fonctions permettent de créer plusieurs Toggle widgets d'un seul coup. Ils sont automatiquement rassemblés dans une RowColumn, et la plupart de leurs ressources, comme XmNindicatorType, sont positionnées par la fonction de convénience. Ces fonctions sont très pratiques, mais ne permettent pas de tout faire.