C.
RECANATI
Interfaces
Graphiques
ILOG
2, septembre 2004
TP 3: Contexte
graphique
Exercice 1: (programme
rectangle.c)
Dans cet exercice, on
souhaite dessiner un rectangle qui suit la souris (par exemple, pour
sélectionner des objets dans un logiciel de dessin).
On va programmer le
comportement suivant :
1) Quand on clique dans la fenêtre principale, la
position de la souris est enregistrée.
2) Si on tire ensuite la souris (en maintenant le bouton
enfoncé), on trace le rectangle dont un sommet est la position
correspondant au clic initial et l’autre sommet le sommet opposé,
donné par la position courante de la souris. (Attention : il faut
réfléchir un peu pour déterminer le point d’origine
du rectangle (sommet en haut à gauche) selon la position de la
souris).
3) A chaque mouvement, le précédent
rectangle est effacé, et le nouveau rectangle tracé. Quand on
relâche la souris, on remplit le rectangle final avec une couleur inverse
de celle du fond de la fenêtre.
Indications : 1) Pour
effacer un rectangle tracé en inverse vidéo, il suffit de le
tracer à nouveau en inverse vidéo.
2) Pour faire de
l’inverse vidéo couleur (ou du noir et blanc sur une machine
couleur !) il faut peindre avec un contexte graphique GC dont la fonction de transfert function
est GXxor, et dont l’attribut foreground est positionné au XOR des deux couleurs que
l’on souhaite inverser.
Ainsi pour de l’inverse
video noir et blanc, on utilise un champ foreground
initialisé à
BlackPixel(dpy,…)
^ WhitePixel(dpy,…) et un
champ function initialisé à GXxor.
Exercice
2: (programme Menu.c)
Dans
une fenêtre initiale, on veut faire apparaître un popup menu
proposant un choix d’items. Le nom de la fonte utilisée pour
écrire les items sera passé en argument au programme, et la
taille du menu sera fonction de la taille de cette fonte. Le menu n’est
pas affiché initialement, mais apparaît sous la souris quand on
clique dans la fenêtre principale. Une fois le menu apparu, on peut
maintenir le bouton de la souris enfoncé, et la faire glisser sur les
différents items. L’item sur lequel se trouve la souris est alors
repeint en inverse video (en réalité on dessine un rectangle
plein en inverse video - ce qui fait à la fois apparaître le fond
et le texte en inverse - mais il n’y a pas réécriture
à proprement parler du texte de l’item). Quand on relâche la
souris sur un item, le texte correspondant à l’item ainsi
« sélectionné » est imprimé à
la console. Si on relâche la souris à l’extérieur du
menu, on n’imprime rien. Dans les deux cas, une fois la souris
relâchée, le menu disparaît.
Indications : 1) La hauteur
des items et celle de la fenêtre menu sont paramétrées par la hauteur de la
fonte et le nombre d’items. On commence par charger la fonte et les
informations sur la fonte. Une
fois la hauteur de fonte déterminée, on peut calculer la hauteur
du menu en fonction du nombre d’items. Pour sa largeur, on calculera la
largeur maximum des différentes chaînes d’items dans cette
fonte (avec XTextWidth).
2) On utilise trois variables
de type Window : une pour la fenêtre principale
(win), une pour le fond de la fenêtre du
menu (menu), et une autre variable (w) pour définir de petites fenêtres de
classe InputOnly permettant de repérer les mouvements de la
souris sur les items. Ces fenêtres
« transparentes » sont positionnées au dessus des
items et permettent de savoir quand la souris pénètre sur un
item. Elles permettent de sélectionner des événements
d’entrée/sortie de la souris, mais on ne dessine pas dedans. Tous
les dessins seront effectués directement dans la fenêtre
menu. Quand la fenêtre menu est exposée initialement, on y dessine tous
les textes d’items. Quand la souris entre ou sort d’une
sous-fenêtre w
délimitant un item, on peindra dans la fenêtre
menu un rectangle en inverse video à la hauteur
concernée.
Les fenêtres
InputOnly seront créées dans une boucle utilisant
toujours la même variable w.
Un contexte d’association (XContext) permet de
sauvegarder après chaque création (et avant la suivante) :
1. la position initiale en y de cette fenêtre, et 2. l’item correspondant. Pour
mémoriser et associer ces informations à chaque fenêtre, on
utilisera un tableau de structures contenant de telles paires. Ainsi, dans la boucle de
création, on associe à chaque fenêtre (via le contexte)
l’adresse de la structure correspondante [oubien simplement un entier qui
serait l’indice correspondant aux données de cette fenêtre
dans le tableau].
Dans la boucle de gestion des
événements, connaissant la fenêtre qui a reçu un
événement de type EnterNotify ou LeaveNotify, on peut récupérer, grâce au
contexte d’association, les informations permettant d’identifier
l’item et sa position dans le menu. On peut alors repeindre un rectangle
en inverse video, pour sélectionner l’item ou pour le
désélectionner.
Le programme aura donc
l’allure suivante :
char * items[] = { « premier item »,
« deuxième »,
…,
« dernier »} ;
#define NITEM sizeof(items)/sizeof(char*)
typedef struct {
int pos_y ;
char* string ;
} infos ;
infos tab[NITEM] ;
void main(argc, argv) {
Window win, menu, root, w ;
…
// charger et récupérer les infos sur la fonte
// XLoadQueryFont (ou XLoadFont et XQueryFont)
// puis déterminer la largeur et la hauteur de la fenêtre menu
// créer un contexte graphique gc ayant cette fonte
// et un contexte graphique gc_invert permettant
// de faire de l’inverse video
// créer un contexte d’association (XContext) context
// pour gérer les données associées aux sous-fenêtres w
// ENSUITE SEULEMENT !
// créer win et menu
// sélectionner ButtonPress sur win (pour afficher le menu)
// sélectionner Expose sur menu (pour écrire les items)
// création des sous-fenêtres InputOnly
for (i=0 ; i<NITEM ; i++) {
w = XCreateWindow(…,x, yi , …) ;
// selectionner les ev. EnterNotify et LeaveNotify
XSelectInput(dpy, w, …) ;
tab[i].pos_y = yi ; // sauvegarde la position en y
tab[i].string = item[i] ; // et la chaîne associée
XSaveContext(dpy, context, w, …);
}
// afficher win et
les sous-fenêtres de menu
// mais ne pas afficher
menu
events() ;
}
F Attention :
sur un clic de souris, il y a un monopole (grab) automatique de la souris pour
la fenêtre qui a sélectionné
l’événement
ButtonPress. Il faut donc ici empêcher cette saisie
automatique au niveau de l’application (en ajoutant OwnerGrabButtonMask à ButtonPressMask), car sinon les sous-fenêtres
w ne recevront pas les événements souris
d’entrée/sortie. En effet, en l’absence de OwnerGrabButtonMask la fenêtre win monopolise la souris et les autres fenêtres ne
reçoivent plus d’événements.