Licence CC BY-NC-SA

Votre première fenêtre

Vous avez créé votre premier programme fenêtré lors du précédent chapitre… mais sans rien comprendre à ce que vous faisiez. Nous allons donc prendre le temps cette fois de décortiquer et de comprendre ce premier bout de code GtkAda.

Analysons notre code

Code GtkAda minimal

Pour commencer, nous allons édulcorer encore notre code. Voici donc le minimum requis pour tout programme GTK :

1
2
3
4
5
6
7
WITH Gtk.Main ;       USE Gtk.Main ; 

PROCEDURE MaFenetre IS
BEGIN
   Init ; 
   Main ; 
END MaFenetre ;

Vous pouvez compiler, vous vous apercevrez qu'il ne se passe pas grand chose : la console s'ouvre comme d'habitude mais plus de jolie fenêtre ! :'( Et pourtant, GTK a bien été lancé. En fait, comme bon nombre de bibliothèques, GTK nécessite d'être initialisée avant toute utilisation avec la procédure Init ou plus exactement Gtk.Main.Init. Je vous précise le chemin complet car vous serez très certainement amenés à un moment ou à un autre à le préciser.

Puis, votre code doit se terminer par l'instruction Main (ou Gtk.Main.Main). Pourquoi ? Nous expliquerons cela en détail lors du chapitre sur les signaux, mais sachez pour l'instant que cette instruction équivaut à ceci :

1
2
3
LOOP
   Traiter_Les_Actions_De_L_Utilisateur ; 
END LOOP ;

L'instruction Main empêche ainsi que votre fenêtre se ferme aussitôt après avoir été créée et se chargera de réagir aux différentes actions de l'utilisateur (comme cliquer sur un bouton, déplacer la souris, appuyer sur une touche du clavier… ). Main n'est donc rien d'autre qu'une boucle infinie, laquelle peut toutefois être stoppée grâce à la procédure Main_Quit que nous verrons à la fin du chapitre et qui fait elle aussi partie du package Gtk.Main.

Créer une fenêtre

Une fenêtre sous GTK est un objet PRIVATE de la classe GTK_Window_Record. Cette classe et les méthodes qui y sont associées sont accessibles via le package GTK.Window. Cependant, vous ne manipulerez jamais les objets de classe GTK_Window_Record, vous ne manipulerez que des pointeurs sur cette classe.

Argh ! :waw: Des pointeurs sur des classes privées. Il va falloir que je relise toute la partie IV pour comprendre la suite ?

Ne vous inquiétez pas, tout se fera le plus simplement du monde. Plutôt que de manipuler directement des objets GTK_Window_Record, vous utiliserez des GTK_Window. Le fait qu'il s'agisse d'un pointeur sur classe ne compliquera pas votre développement, mais il est bon que vous sachiez qu'une fenêtre GTK se définit ainsi :

1
type Gtk_Window is access all Gtk_Window_Record'Class;

À quoi bon savoir cela ? Eh bien si vous savez qu'il s'agit d'un pointeur, vous savez dès lors que déclarer une GTK_Window ne fait que réserver un espace mémoire pour y stocker des adresses. Il faudra en plus absolument créer un espace mémoire sur lequel pointera notre GTK_Window. Cela se faisait habituellement avec l'instruction NEW, mais avec GtkAda vous utiliserez la procédure Gtk_New() dont voici la spécification :

1
2
procedure Gtk_New(Window   : out Gtk_Window;
                  The_Type : Gtk.Enums.Gtk_Window_Type := Gtk.Enums.Window_Toplevel);

Oublions pour l'instant le paramètre The_Type sur lequel nous reviendrons dans la prochaine partie. Lançons-nous ! Déclarons une Gtk_window et créons sa Gtk_Window_Record correspondante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
WITH Gtk.Main ;          USE Gtk.Main ; 
WITH Gtk.Window ;        USE Gtk.Window ;


PROCEDURE MaFenetre IS
   win : Gtk_window ;
BEGIN
   Init ; 
   gtk_new(win) ; 
   Main ; 
END MaFenetre ;

Euh… y se passe toujours rien lorsque je lance le programme! :euh:

Normal, vous avez créer une Gtk_Window_Record… en mémoire ! Elle existe donc bien, mais elle est tranquillement au chaud dans vos barettes de RAM. Il va falloir spécifier que vous souhaitez l'afficher en ajoutant l'instruction gtk.window.show() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
WITH Gtk.Main ;          USE Gtk.Main ; 
WITH Gtk.Window ;        USE Gtk.Window ;


PROCEDURE MaFenetre IS
   win : Gtk_window ;
BEGIN
   Init ; 
   Gtk_New(Win) ; 
   show(win) ; 
   Main ; 
END MaFenetre ;

Une fenêtre GTK

Pourquoi la console reste-t-elle apparente lorsque je ferme la fenêtre ? o_O

Nous avons déjà en partie répondu à cette question : fermer la fenêtre revient à ne plus l'afficher, mais cela n'implique pas que vous sortiez de la boucle infinie du Main. Pour en sortir, il faut associer l'action «fermer la fenêtre» à la procédure Main_quit. Nous verrons cela plus tard, pour l'instant contentez-vous de fermer la fenêtre et la console.

La console vous permettra, lors de la phase de développement, de connaître les exceptions levées par votre programme.

Personnaliser la fenêtre

C'est bien d'avoir créé une fenêtre mais elle est petite, vide et son titre n'est pas terrible. :(

C'est pourquoi nous allons désormais nous attacher à modifier notre fenêtre puis nous verrons dans la partie suivante comment la remplir. Je vous conseille pour mieux comprendre (ou pour aller plus loin) d'ouvrir le fichier gtk-window.ads situé dans le répertoire …/Gtkada/include/gtkada.

Changer de type de fenêtre

Souvenez-vous, nous avons négligé un paramètre lors de la création de notre fenêtre : le paramètre The_Type. Sa valeur par défaut était Gtk.Enums.window_toplevel. Plus clairement, sa valeur était window_toplevel mais les valeurs de ce paramètre sont listés dans le package Gtk.Enums (un package énumérant diverses valeurs comme les divers types d'ancrages, les tailles d'icônes, les positions dans une fenêtre…).

Deux valeurs sont possibles :

  • Window_Toplevel : fenêtre classique comme vous avez pu le voir tout à l'heure. Nous n'utiliserons quasiment que ce type de fenêtre.
  • Window_Popup : fenêtre sans barre d'état ni bords, ni possibilité d'être fermée, déplacée ou redimensionnée. Vous savez, c'est le genre de fenêtre qui s'ouvre au démarrage d'un logiciel le temps que celui-ci soit chargé (Libre Office par exemple).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
WITH Gtk.Main ;          USE Gtk.Main ; 
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ; 

PROCEDURE MaFenetre IS
   win : Gtk_window ;
BEGIN
   Init ; 
   Gtk_New(win,Window_Popup) ; 
   win.show ; 
   Main ; 
END MaFenetre ;

Si vous testez le code ci-dessus, vous devriez voir apparaître un magnifique rectangle gris sans intérêt à l'écran. Génial non ? :D

Définir les paramètres avec Set_

Définir le titre

Avant toute chose, un peu d'anglais : dans la langue de Shakespeare, le verbe « to set » (prononcez « tout cette ») a de nombreux sens tels que « fixer », « installer » ou « imposer ». Donc dès que vous aurez envie de fixer quelque chose (la taille, la position, l'apparence…), vous devrez utiliser des méthodes commençant par le terme «set». Ainsi, pour fixer le titre de votre fenêtre, nous utiliserons la méthode set_title() :

1
2
3
4
5
6
7
BEGIN
   Init ; 
   Gtk_New(win,Window_Popup) ; 
   win.set_title("Super programme !") ; 
   win.show ; 
   Main ; 
END MaFenetre ;

À l'inverse, si vous souhaitez connaître un paramètre, l'obtenir, il vous faudra utiliser des méthodes (plus exactement des fonctions) dont le nom commencera par le mot «get». Ainsi, Win.Get_Title() vous renverra le titre de la fenêtre Win. Exemple (regardez la console) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
WITH Ada.Text_IO ;       USE Ada.Text_IO ; 
WITH Gtk.Main ;          USE Gtk.Main ; 
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ; 


PROCEDURE MaFenetre IS
   win : Gtk_window ;
BEGIN
   Init ; 
   Gtk_New(Win,Window_Toplevel) ; 
   win.set_title("Super programme !") ; 
   Win.Show ; 
   put_line(win.get_title) ; 
   Main ; 
END MaFenetre ;

J'insiste sur cette syntaxe : Set pour fixer, Get pour obtenir. Elle sera reprise pour toutes les méthodes de tous les éléments, que ce soient les fenêtres, les boutons, les menus, les barres de progression… Il en sera de même pour la procédure Gtk_New.

Si l'écriture pointée vous gêne, vous pouvez remplacer win.set_title("Super programme !"), Win.Show et win.get_title par set_title(win,"super programme !"), show(win) et get_title(win). Mais puisque Gtk est orienté objet, autant en profiter.

Fixer la taille de la fenêtre

Nous allons désormais nous focaliser sur les méthodes «set», les méthodes «get» ne nous servirons que lorsque nous pourrons interagir avec la fenêtre (et nous en sommes encore loin). Si vous avez compris ce qui a été dit juste avant, vous devriez avoir une idée du nom de la méthode permettant de fixer la taille par défaut de notre fenêtre : Set_default_size, bien sûr ! Elle prend deux paramètres : Width pour la largeur et Height pour la hauteur de la fenêtre. Exemple :

1
2
3
4
5
6
7
8
BEGIN
   Init ; 
   Gtk_New(win,Window_Popup) ; 
   win.set_title("Super programme !") ; 
   Win.Set_Default_Size(600,400) ; 
   win.show ; 
   Main ; 
END MaFenetre ;

Si vous observez bien les spécifications du package, comme je vous l'avais conseillé, vous devriez avoir remarqué que les deux paramètres de Set_Default_Size ne sont pas des Integer, mais des Gint ! Il faut comprendre par là Glib Integer. Donc si vous voulez définir la taille par défaut à l'aide de deux variables, vous devrez soit les déclarer de type Gint soit de type Integer et les convertir en Gint le moment venu. Il faudra alors utiliser le package Glib. Vous vous souvenez ? La surcouche pour le C dont je vous parlais au premier chapitre.

Remarquez également que vous ne faites que définir une taille par défaut. Rien n'empêchera l'utilisateur de modifier cette taille à l'envie. Si vous souhaitez que votre fenêtre ne puisse pas être redimensionnée, vous allez devoir utiliser la méthode set_resizable().

1
win.set_resizable(false) ;

Cette ligne indiquera que votre fenêtre n'est pas «redimensionnable». En revanche, elle va automatiquement se contracter pour ne prendre que la place dont elle a besoin ; et vu que votre fenêtre ne contient rien pour l'instant, elle devient minuscule. Voici également quelques méthodes supplémentaires qui pourraient vous servir à l'avenir :

1
2
3
4
5
win.fullscreen ;                --met la fenêtre en plein écran
win.unfullscreen ;              --annule le plein écran
win.resize(60,40) :             --redimensionne la fenêtre à la taille 60x40
win.Reshow_With_Initial_Size ;  --fait disparaître la fenêtre pour la réafficher 
                                --à la taille initiale

Pour l'instant, hormis fullscreen, les autres méthodes ne devraient pas encore vous concerner. Mais attention, si vous mettez votre fenêtre en plein écran, vous n'aurez plus accès à l'icône pour la fermer. Vous devrez appuyer sur Alt + F4 pour la fermer ou bien sur Alt + Tab pour accéder à une autre fenêtre.

Fixer la position

Il y a deux façons de positionner votre fenêtre. La première méthode est la plus simple et la plus intuitive : set_position(). Cette méthode ne prend qu'un seul paramètre de type Gtk.Enums.Gtk_Window_Position (encore le package Gtk.Enums). Voici les différentes valeurs :

  • Win_Pos_None : aucune position particulière ;
  • Win_Pos_Center : la fenêtre est centrée à l'écran ;
  • Win_Pos_Mouse : la fenêtre est centrée autour de la souris ;
  • Win_Pos_Center_Always : la fenêtre est centrée à l'écran et le restera malgré les redimensionnements opérés par le programme ;
  • Win_Pos_Center_On_Parent : la fenêtre est centrée par rapport à la fenêtre qui l'a créée.

Pour centrer la fenêtre à l'écran, il suffira d'écrire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
WITH Gtk.Main ;          USE Gtk.Main ; 
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ; 


PROCEDURE MaFenetre IS
   Win : Gtk_Window ;
BEGIN
   Init ; 
   Gtk_New(Win,Window_Toplevel) ; 
   Win.Set_Title("Super programme !") ; 
   Win.Set_Default_Size(600,400) ; 
   win.set_position(win_pos_center) ; 
   Win.Show ; 
   Main ; 
END MaFenetre ;

La seconde méthode pour positionner votre fenêtre est plus compliquée et consiste à la placer en comptant les pixels :

1
win.move(0,0) ;

Cette instruction aura pour conséquence de placer votre fenêtre en haut à gauche de l'écran, ou plus exactement de placer le pixel supérieur gauche de votre fenêtre (le pixel de la fenêtre de coordonnées $(0 ; 0)$) dans le coin supérieur gauche de l'écran (sur le pixel de coordonnées $(0 ; 0)$ de votre écran). Par défaut c'est le point de la fenêtre de coordonnées $(0 ; 0)$ qui est le point de référence, appelé centre de gravité de la fenêtre.

Vous devez d'ailleurs savoir que chaque pixel de votre écran est repéré par des coordonnées $(x ; y)$. Plus $x$ est grand, plus le pixel est situé vers la droite. Plus $y$ est grand, plus le pixel est situé vers le bas :

Repérer les pixels sur l'écran

Bonus : grâce à la bibliothèque GDK et plus précisément au package gdk.screen, vous pouvez connaître la taille de votre écran. Les fonctions get_width() et get_height() renverront la largeur et la hauteur de votre écran. Elles prennent en paramètre des objets de type Gdk_Screen. Ainsi, en tapant get_width(get_default), vous obtiendrez la largeur de l'écran par défaut. Ceux qui ont plusieurs écran fouilleront dans le fichier gdk-screen.ads.

Enfin, il est possible de faire en sorte que votre fenêtre reste au-dessus de toutes les autres, quoi que vous fassiez. Il vous suffit pour cela d'utiliser la méthode win.set_keep_above(TRUE).

Fixer l'apparence

Venons-en maintenant à l'apparence de notre fenêtre. La première modification que nous pouvons apporter porte sur le bouton de fermeture de la fenêtre : actif ou non ? Par défaut, GTK crée des fenêtres supprimables, mais il est possible d'empêcher leur fermeture grâce à la méthode win.set_deletable(FALSE). Pour rappel, «to delete» signifie «supprimer» en anglais, donc «deletable» signifie «supprimable».

Seconde modification possible : l'opacité de la fenêtre. Il est possible avec Gtk de rendre une fenêtre complètement ou partiellement transparente :

Une fenêtre partiellement transparente

Cela se fait le plus simplement du monde avec la méthode win.set_opacity(). Cette méthode prend en paramètre un Gdouble compris entre 0.0 et 1.0, c'est à dire un Glib Double ce qui correspond à un nombre en virgule flottante (double précision) disponible dans le package Glib. Comme pour les Gint, vous serez sûrement amener à effectuer quelques conversions entre les float d'Ada et les Gdouble de Gdk.

1
2
3
win.set_opacity(0.0) ;      --Fenêtre complètement transparente (invisible)
win.set_opacity(0.5) ;      --Fenêtre semi-transparente comme ci-dessus
win.set_opacity(1.0) ;      --Fenêtre complètement opaque

Enfin, troisième modification (la plus attendue, je pense) : l'icône du programme. Changeons ce vilain icône de programme par un icône bien à vous. Enregistrez l'image ci-dessous dans le répertoire de votre programme (prenez soin d'avoir une image carrée pour ne pas avoir de surprises à l'affichage) :

L'icône de programme

Nous allons utiliser la méthode set_icon_from_file() qui permet de fixer l'icône à partir d'un fichier spécifique. Cependant, contrairement aux autres méthodes utilisées jusqu'ici, il s'agit d'une fonction et non d'une procédure. Elle renvoie un booléen : si le chargement de l'icône s'est bien passé, il vaudra TRUE, si le fichier n'existe pas, est introuvable ou n'est pas dans un format compatible, il vaudra FALSE. Cela vous permet de gérer ce genre d'erreur sans faire planter votre programme ou bien de lever une exception (ce sera un excellent rappel) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
...
   LOADING_ERROR : EXCEPTION ; 
BEGIN
   Init ;
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Super programme !") ;
   Win.Set_Default_Size(160,180) ;
   Win.Set_Position(Win_Pos_Center) ;
   Win.Set_Keep_Above(True) ;
   Win.Set_Opacity(0.8) ; 
   IF NOT win.Set_Icon_From_File("bug-icone.png")
         THEN RAISE LOADING_ERROR ;
   END IF ; 
   Win.Show ;
   Main ;
EXCEPTION
   WHEN LOADING_ERROR => Put_line("L'icone n'a pas pu etre charge") ;
END MaFenetre ;

La levée de l'exception empêchera l'affichage de la fenêtre et l'exécution du Main. A vous de voir si le non chargement de l'icône mérite l'arrêt du programme.

Vous pouvez également utiliser la fonction similaire Set_Default_Icon_From_File(). Celle-ci ne s'applique pas à une fenêtre en particulier, elle définit un icône par défaut pour toutes les fenêtres qui n'en auraient pas. Utile pour un projet multi-fenêtré.

Ajout d'un widget

Qu'est-ce qu'un widget ?

Un Widget est l'acronyme de Windows, Icons, Dialog box, Graphics Extensions, Track ball, mais on considère plus souvent que c'est la contraction de Window's gadget. Cela ne vous avance pas davantage ? Eh bien un widget, est simplement un élément graphique composant votre interface. Les boutons, les cases à cocher, les barres de défilement, les images, les lignes de texte, les zones de frappe mais aussi les menus, les icônes ou votre fenêtre elle-même constituent autant de widgets.

Pour GTK tous ces widgets constituent des classes qui dérivent en plus ou moins droite ligne de la classe GTK_Widget_Record. Imaginez que vous ayez un widget appelé buzzer, alors la norme d'écriture de GTK imposera que :

  • il porte le nom de GTK_Buzzer_Record ;
  • ce soit un type TAGGED et PRIVATE dérivé de GTK_Widget_Record ;
  • il soit définit, lui et ses méthodes, dans le package GTK.Buzzer et que les fichiers correspondant portent les noms gtk-buzzer.ads et gtk-buzzer.adb ;
  • un type GTK_Buzzer soit défini de la manière suivante : « type GTK_Buzzer is access all GTK_Buzzer'class ; »

Ces quelques remarques devraient vous rappeler quelque chose non ? C'est exactement ce que nous avons vu avec les fenêtres ! Ces conventions d'écriture et la conception orientée objet de la bibliothèque vont grandement nous simplifier la tâche, vous verrez.

Ajouter un bouton

Cessons ces généralités et ajoutons un bouton à notre fenêtre. Le widget-bouton se nomme tout simplement GTK_Button ! Enfin, GTK_Button_Record plus précisément mais vous aurez compris que c'est le pointeur GTK_Button qui nous intéresse réellement. Et il se trouve évidemment dans le package Gtk.Button. Vous commencez à comprendre le principe ? Très bien. ;)

Bouton s'écrit avec un seul T en français. En revanche, en anglais Button prend deux T.

Reprenons notre programme (que j'ai édulcoré pour ne nous concentrer que sur l'essentiel) :

 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
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gdk.Window ;        USE Gdk.Window ;
WITH Gtk.Button ;        USE Gtk.Button ; 


PROCEDURE MaFenetre IS
   Win    : Gtk_Window ;
   Bouton : Gtk_Button ; 
BEGIN
   Init ;
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Super programme !") ;
   win.set_default_size(150,180) ;
   Win.Set_Position(Win_Pos_Center) ;
   IF NOT Win.Set_Icon_From_File("bug-icone.png") 
         THEN NULL ; 
   END IF ;

   Gtk_New(Bouton) ; 
   win.add(Bouton) ; 

   Win.Show ;
   Bouton.show ;
   Main ;
END MaFenetre ;

Vous remarquerez que la procédure Gtk_New est toujours nécessaire pour créer notre bouton. Puis nous devons l'ajouter à notre fenêtre avec l'instruction win.add(Bouton) sans quoi nous aurons un bouton sans trop savoir quoi en faire. Enfin, dernière étape, on affiche le bouton avec la méthode Bouton.show.

Dis-moi : c'est vraiment nécessaire d'utiliser show autant de fois qu'il y a de widgets ? Parce que ça risque d'être un peu long quand on aura plein de bouton et de menus.

En effet, c'est pourquoi à partir de maintenant nous n'utiliserons plus win.show mais win.show_all. Cette méthode affichera non seulement la fenêtre Win mais également tout ce qu'elle contient.

Personnaliser le bouton

Bon c'est pas tout ça mais mon bouton est particulièrement moche. On peut pas faire mieux que ça ?

Si si, la fenêtre contient un bouton

Nous pourrions commencer par lui donner un nom ou plus exactement une étiquette («label» en anglais) :

1
2
3
4
5
6
...
   Gtk_New(Bouton) ; 
   Bouton.set_label("Ok !") ; 
   Win.add(Bouton) ; 

   Win.Show_all ;

À noter que les lignes 20 et 21 peuvent être remplacées par une seule : « GTK_New(Bouton,"Ok !") ; ». Bien plus rapide non ? Un autre possibilité, pour l'affichage du texte, est de souligner l'une des lettres afin d'indiquer le raccourci clavier. Mais ne vous emballez pas, nous n'allons pour l'instant souligner qu'une seule lettre. Deux méthodes s'offrent à vous :

1
2
3
4
5
6
GTK_New(Bouton,"_Ok !") ; 
Bouton.set_use_underline(true) ;        --on indique que l'on utilise le soulignage

      -- OU PLUS DIRECTEMENT

GTK_New_With_Mnemonic(Bouton,"_Ok !") ; --on crée directement le bouton comme tel

L'underscore devant la lettre O indiquera qu'il faut souligner cette lettre. Si jamais vous aviez besoin d'écrire un underscore, il vous suffirait d'en écrire deux d'affilée.

On peut placer une image dans le bouton ? :p

Bien sûr, mais pour cela il faudrait utiliser des GTK_Image et je garde cela pour le chapitre 4. En revanche, nous pourrions changer le relief du bouton, son épaisseur. On utilise alors la méthode Set_Relief() qui prend comme paramètre un Gtk.Enums.Gtk_Relief_Style (eh oui, encore ce package Gtk.Enums, il liste divers styles, donc mieux vaut garder un œil dessus) :

1
2
3
Bouton.Set_Relief(Relief_Normal) ; --le relief auquel vous étiez habitué
Bouton.Set_Relief(Relief_Half) ;   --un bouton un peu plus plat
Bouton.Set_Relief(Relief_None) ;   --un bouton complètement plat

Ajouter un second bouton ?

Et comment on fait pour ajouter un second bouton à ma fenêtre ?

Voici les éléments de réponses:

 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
WITH Gtk.Main ;          USE Gtk.Main ;
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gdk.Window ;        USE Gdk.Window ;
WITH Gtk.Button ;        USE Gtk.Button ; 


PROCEDURE MaFenetre IS
   Win    : Gtk_Window ;
   Btn1, Btn2 : Gtk_Button ; 
BEGIN
   Init ;
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Super programme !") ;
   win.set_default_size(150,180) ;
   Win.Set_Position(Win_Pos_Center) ;
   IF NOT Win.Set_Icon_From_File("bug-icone.png") 
         THEN NULL ; 
   END IF ;

   Gtk_New_With_Mnemonic(Btn1, "_Ok !") ; 
   Win.Add(Btn1) ; 
   Gtk_New_With_Mnemonic(Btn2, "_Annuler !") ; 
   Win.add(Btn2) ; 

   Win.Show_all ;
   Main ;
END MaFenetre ;

Si jamais vous compilez ce code, vous vous apercevrez que :

  1. GNAT ne bronche pas. Pour lui, tout est syntaxiquement juste;
  2. votre fenêtre ne comporte toujours qu'un seul bouton;
  3. une belle exception semble avoir été levée car votre console vous affiche : Gtk-WARNING **: Attempting to add a widget with type GtkButton to a GtkWindow, but as a GtkBin subclass a GtkWindow can only contain one widget at a time ; it already contains a widget of type GtkButton

Tout est dit dans la console (d'où son utilité) : les fenêtres ne peuvent contenir qu'un seul widget à la fois. Alors si vous voulez absolument placer de nouveaux boutons (car oui c'est possible), vous n'avez plus qu'à lire le chapitre 3.

Retour sur la POO

Eh bien ! Tout ce code pour seulement une fenêtre et un bouton !

C'est effectivement le problème des interfaces graphiques : elles génèrent énormément de code. Mais heureusement, nous avons déjà vu une solution idéale pour limiter tout ce code. Une solution orientée objet, adaptée aux pointeurs (et notamment aux pointeurs sur classe), qui permet d'initialiser automatiquement nos objets… Ça ne vous rappelle rien ? Les types contrôlés bien sûr ! C'est vrai que nous ne les avons pas beaucoup utilisés, même durant le TP car je savais pertinemment que nous serions amenés à les revoir.

Méthode brutale

Nous allons donc créer un package P_Fenetre, dans lequel nous déclarerons un type contrôlé T_Fenetre ainsi que sa méthode Initialize. Pas besoin de Adjust et Finalize. Notre type T_Fenetre aura deux composantes : win de type GTK_Window et btn de type GTK_Button :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
WITH Gtk.Window ;              USE Gtk.Window ;
WITH Gtk.Button ;              USE Gtk.Button ; 
WITH Ada.Finalization ;        USE Ada.Finalization ;

PACKAGE P_Fenetre IS

   TYPE T_Fenetre IS NEW Controlled WITH RECORD
      Win : GTK_Window ; 
      Btn : GTK_Button ;
   END RECORD ;

   PROCEDURE Initialize(F : IN OUT T_Fenetre) ;

end P_Fenetre ;

Que fera Initialize ? Eh bien c'est simple : tout ce que faisait notre programme précédemment. Initialize va initialiser GTK, créer et paramétrer notre fenêtre et notre bouton et lancer le Main :

 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
WITH Gtk.Main ;          USE Gtk.Main ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gdk.Window ;        USE Gdk.Window ;

PACKAGE BODY P_Fenetre IS

   PROCEDURE Initialize(F : IN OUT T_Fenetre) IS
   BEGIN
      Init ; 
         --Création de la fenêtre
      Gtk_New(F.Win,Window_Toplevel) ;
      F.Win.Set_Title("Super programme !") ;
      F.win.set_default_size(150,180) ;
      F.Win.Set_Position(Win_Pos_Center) ;
      IF NOT F.Win.Set_Icon_From_File("bug-icone.png") 
            THEN NULL ; 
      END IF ;
         --Création du bouton
      Gtk_New_With_Mnemonic(F.Btn, "_Ok !") ; 
      F.Win.Add(F.Btn) ; 
         --Affichage
      F.Win.Show_All ;
      Main ; 
   END Initialize ; 

END P_Fenetre ;

Notre procédure principale devient alors extrêmement simple puisqu'il ne reste plus qu'à déclarer un objet de type T_Fenetre. Génial, non ?

1
2
3
4
5
6
7
WITH P_Fenetre ;        USE P_Fenetre ;

PROCEDURE MaFenetre IS
   Mon_Programme : T_Fenetre ; 
BEGIN
   NULL ;
END MaFenetre ;

Méthode subtile

Une seconde possibilité existe, plus nuancée : créer deux packages (P_Window et P_Button) pour deux types contrôlés (T_Window et T_Button). Cette méthode évite de créer un package fourre-tout et laisse la main au programmeur de la procédure principale. Il n'aura simplement plus à se soucier de la mise en forme. Ce qui nous donnerait les spécifications suivantes :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
WITH Gtk.Button ;              USE Gtk.Button ; 
WITH Ada.Finalization ;        USE Ada.Finalization ;


PACKAGE P_Button IS

   TYPE T_Button IS NEW Controlled WITH RECORD
      Btn : GTK_Button ;
   END RECORD ;

   PROCEDURE Initialize(B : IN OUT T_Button) ;

end P_Button ;

P_Button.ads

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
WITH Gtk.Window ;              USE Gtk.Window ;
WITH Ada.Finalization ;        USE Ada.Finalization ;
WITH P_Button ;                USE P_Button ; 


PACKAGE P_Window IS

   TYPE T_Window IS NEW Controlled WITH RECORD
      Win : GTK_Window ; 
   END RECORD ;

   PROCEDURE Initialize(F : IN OUT T_Window) ;
   PROCEDURE Add(W : IN OUT T_Window ; B : IN T_Button) ; 
   PROCEDURE Show_All(W : IN T_Window) ;

end P_Window ;

P_Window.ads

Et voici le corps de nos packages :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gdk.Window ;        USE Gdk.Window ;


PACKAGE BODY P_Button IS

   PROCEDURE Initialize(B : IN OUT T_Button ) IS
   BEGIN
      Gtk_New_With_Mnemonic(B.Btn, "_Ok !") ; 
      B.Btn.set_relief(Relief_None) ; 
   END Initialize ; 

end P_Button ;

P_Button.adb

 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
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gdk.Window ;        USE Gdk.Window ;


PACKAGE BODY P_Window IS

   PROCEDURE Initialize(F : IN OUT T_Window) IS
   BEGIN
         --Création de la fenêtre
      Gtk_New(F.Win,Window_Toplevel) ;
      F.Win.Set_Title("Super programme !") ;
      F.win.set_default_size(150,180) ;
      F.Win.Set_Position(Win_Pos_Center) ;
      IF NOT F.Win.Set_Icon_From_File("bug-icone.png")
            THEN NULL ;
      END IF ;
   END Initialize ;

   PROCEDURE Add(W : IN OUT T_Window ; B : IN T_Button) IS
   BEGIN
      W.Win.Add(B.Btn) ; 
   END Add ; 

   PROCEDURE Show_all(W : IN T_Window) IS
   BEGIN
      W.Win.Show_all ; 
   END Show_all ; 

end P_Window ;

P_Window.adb

Et enfin notre procédure principale :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
WITH Gtk.Main ;         USE Gtk.Main ; 
WITH P_Window ;         USE P_Window ;
WITH P_Button ;         USE P_Button ;

PROCEDURE MaFenetre IS
BEGIN
   Init ;
   DECLARE
      Win : T_Window ; 
      Btn : T_Button ; 
   BEGIN
      Win.Add(Btn) ; 
      Win.Show_all ; 
      Main ; 
   END ;
END MaFenetre ;

MaFenetre.adb

Pensez à initialiser GTK avant de déclarer votre fenêtre ou votre bouton !


En résumé :

  • Un programme GTK commence par Init et se termine par Main.
  • Un widget GTK commence par «GTK_» suivi de son nom. Le package correspondant commence «Gtk.» suivi lui aussi du nom du widget.
  • Avant de manipuler les widgets, pensez à les initialiser avec Gtk_New(). Avant le Main, pensez à les afficher avec les méthodes show ou show_all.
  • Pour définir les paramètres d'un widget, on utilise les méthodes dont le nom commence par «set_» suivi de l'élément à paramétrer.
  • Pour clarifier le code de la procédure principale, pensez à créer des types contrôlés.