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.