Programmation C++ : TP séance 02
- Rendu du TP sur Ametice :
- Suivez les instructions pour le rendu du TP en mettant le bon numéro de séance dans le nom du répertoire.
- N'oubliez pas de rendre votre travail à la fin de la séance comme indiqué, même si vous n'avez pas fini la planche de TP, puis de rendre la version finale avant le début de la séance suivante.
Exercice 1. Dessins de dominos en Cairo avec GTKmm, classes
On se propose de réaliser un programme utilisant la librairie GTKmm, qui est un wrapper C++ pour le toolkit GTK ; cette dernière est la librairie graphique originellement écrite pour réaliser l'interface de GIMP, et utilisée en particulier dans le bureau GNOME et de nombreux utilitaires.
La librairie GTK fournit un widget DrawingArea
dans lequel on peut réaliser
des dessins en 2D avec la librairie de dessin vectoriel Cairo. Cette librairie
utilise la carte graphique et produit des dessins avec anti-crénelage,
transparence et transformations géométriques.
1.a. Programme démo
Les onglets ci-dessous contiennent les instructions pour l'installation, deux fichiers à copier-coller dans un répertoire vide, les instructions pour la compilation et l'exécution du programme, et des tests à effectuer.
Sur les machines en salle de TP, les librairies et les fichiers nécessaires pour le développement sont déjà installés.
Si vous travaillez sur votre ordinateur, vous pouvez installer les packages nécessaires sur Ubuntu ou WSL en tapant :
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install g++ make libgtkmm-3.0-dev
Ensuite pour vérifier, tapez simplement
$ pkg-config gtkmm-3.0 --modversion
Vous obtiendrez soit la version installée, soit un message d'erreur.
Copiez le code de départ ci-dessous dans un fichier nommé demo1.cpp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
|
Copiez le code ci-dessous et enregistrez-le dans un fichier
nommé Makefile
; attention, les indentations doivent être des
caractères tabulation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Pour compiler le programme demo.cpp
à l'aide du fichier Makefile
,
il faut que ces deux fichiers soient copiés dans le même répertoire,
qui ne doit pour le moment contenir rien d'autre.
$ ls
demo1.cpp Makefile
Pour compiler le programme, tapez :
$ make all
Pour l'exécuter, tapez :
./demo1
Il est également possible de supprimer les fichiers compilés en tapant :
$ make clean
ou encore, de forcer une recompilation générale en tapant :
$ make clean all
Cliquez plusieurs fois sur le bouton Cliquez-moi
.
Observez les traces dans le terminal lorsque vous cliquez dans le dessin
avec les différents boutons de la souris, ou tirez la souris avec un bouton
enfoncé, ou tapez sur des touches du clavier, ou agrandissez la fenêtre.
Cherchez dans le code quelles fonctions ont affiché ces messages.
Étudiez la structure du code, repérez la partie où sont instanciés les
widgets (window gadget, c'est-à-dire les objets graphiques),
comment dans le constructeur de la fenêtre principale ils sont attachés
les uns aux autres dans des boîtes horizontales
ou verticales, et comment les événements sont CONNECT
-és à des
callbacks (des fonctions appelées automatiquement après un événement).
1.b. Gestion d'événements
Recopier demo1.cpp
sous le nom demo2.cpp
dans le même répertoire.
Pour le compiler et l'exécuter il suffira de taper :
$ make all && ./demo2
Modifier le programme afin que le titre de la fenêtre soit Demo 2
.
Rajouter un bouton à droite du bouton Cliquez-moi
, dont l'action échangera
la largeur et la hauteur de l'un des rectangles dessinés (toujours le même).
Pour y parvenir, il faut mémoriser ces deux valeurs dans la classe MyData
,
les utiliser dans darea_on_draw
, et les échanger dans la callback du nouveau
bouton. Que faut-il faire à la fin de la callback pour forcer darea
à se
redessiner ?
Rajouter la possibilité de déplacer le cercle en cliquant dedans et en tirant
la souris.
Pour y parvenir, mémoriser son centre et son rayon dans la classe MyData
,
les utiliser dans darea_on_draw
. Mémoriser également un flag permettant
de savoir si le cercle est actuellement cliqué. Modifier ce flag dans
les callbacks appelées lorsque la souris est enfoncée (si la distance entre
la souris et le centre du cercle est inférieure au rayon) ou relâchée.
Enfin dans la callback appelée lorsque la souris est tirée, si le flag est
vrai, déplacer les coordonnées du centre du cercle à partir de la différence
de coordonnées actuelles et précédemment enregistrées de la souris, et rafraîchir
l'affichage. Le but est que l'on puisse "attraper" le cercle à n'importe quel
endroit et que lors du déplacement, cet endroit reste fixe par rapport
à la souris.
Remarque : toutes les coordonnées sont des double
, donc ce doit être
également le cas pour les différences de coordonnées quand on tire le
cercle.
Taille des lignes de code
Pensez à limiter la taille de vos lignes de code à (environ) 80 caractères, en alignant lorsque nécessaire (par exemple des tests à rallonge) de manière à en faciliter la relecture.
1.c. Classe Domino
Recopier demo1.cpp
sous le nom domino.cpp
dans le même répertoire.
Déclarer une classe Domino
, mémorisant en données membre les
points entiers m_a
et m_b
, les coordonnées en pixels (mais de type double
)
du centre du domino m_xc
et m_yc
, et un angle de rotation entier m_angle
valant 0 (horizontal), 90 (vertical), 180 (horizontal poids inversés) ou 270
(vertical poids inversés).
Le constructeur n'aura pour le moment aucun paramètre (on améliorera cela au TP n°3). Les 5 données membre d'un domino pourront être utilisées ou modifiées directement (nous verrons les setter et getter au cours n°4).
Comme au TP n°1, déclarer une constante DOMINO_SIDE
qui sera la largeur en
pixels du petit côté d'un domino, qui aura donc comme longueur 2*DOMINO_SIDE
,
et des constantes pour d'autres paramètre du dessin, en particulier pour
le rayon du rivet.
Ajouter une méthode void draw (const CairoContext& cr)
qui dessine le
domino selon ses données membre en utilisant le contexte Cairo cr
, en
particulier son angle (4 cas à traiter).
Mémoriser un Domino
nommé m_domino1
dans MyData
, donner des valeurs aux 5
membres de m_domino1
dans le constructeur de MyData
et le dessiner en
appelant sa méthode draw
dans darea_on_draw
. Tester les 4 cas pour l'angle.
1.d. Manipulation d'un Domino
Les gestes pour manipuler un domino seront les suivants : on pourra déplacer un domino en cliquant à l'intérieur ou au bord du domino puis en tirant la souris ; un clic sur le centre du domino (le rivet) provoquera sa rotation de 90 degrés.
Rajouter une méthode domino_is_clicked (ev_x, ev_y)
recevant en paramètre les
coordonnées réelles de la souris. La méthode renverra true
si
les coordonnées de la souris sont à l'intérieur ou au bord du domino,
false
sinon.
Rajouter une méthode rivet_is_clicked (ev_x, ev_y)
recevant en paramètre les
coordonnées réelles de la souris. La méthode renverra true
si les coordonnées
de la souris sont à l'intérieur ou au bord du rivet, false
sinon (il suffit de
comparer la distance de la souris au centre du domino, au rayon du rivet).
Rajouter un flag dans MyData
, initialisé à false
.
Appeler la méthode domino_is_clicked
lorsque la souris est cliquée et
mémoriser la valeur obtenue dans le flag.
Lorsque la souris est tirée et le flag est true
, déplacer le
domino en utilisant la même approche que dans la question 1.b. ci-dessus
avec le cercle.
Lorsque la souris est relâchée et le flag est true
, si la méthode
rivet_is_clicked
renvoie true
alors rajouter 90 degrés (modulo 360) à son
angle, et forcer le réaffichage.
Enfin, remettre systématiquement le flag à false
lorsque la souris est
relâchée.
Tester l'interface en déplaçant le domino et en lui faisant subir des rotations. Suite au prochain épisode !