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 ! 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!
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 ; |
Pourquoi la console reste-t-elle apparente lorsque je ferme la fenêtre ?
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é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 :
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 :
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) :
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
etPRIVATE
dérivé deGTK_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 ?
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 ?
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 :
- GNAT ne bronche pas. Pour lui, tout est syntaxiquement juste;
- votre fenêtre ne comporte toujours qu'un seul bouton;
- 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 parMain
. - 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 leMain
, pensez à les afficher avec les méthodesshow
oushow_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.