GNU/Linux Magazine - France

Ada95 et GTK+, le duo gagnant ?

Simon Descarpentries -

Afin de poursuivre le travail de Xavier Garreau sur les couples langages/toolkit graphique, je vous propose aujourd’hui le couple Ada/GtkAda. Ce couple marié d’office à l’avantage d’être robuste et homogène. Il permet de produire de puissantes applications Gtk sur plusieurs plate-formes de développement et pour de nombreux environnements d’exécution (x86(s), sparc, Tru64, etc.).

Ada, la solution idéale pour programmer sous GNU/Linux ?

Peut-être bien oui. Ada95 est l’un des langages de programmation les plus puissants disponibles pour Linux. Il allie les fonctionnalités avancées de Java à la rapidité d’exécution du C, qu’il dépasse même parfois. Si l’on ajoute à ça qu’un compilateur libre, est carrément intégré dans GCC (devenu Gnu Compiler Collection pour l’occasion), on obtient une combinaison que les développeurs GNU/Linux auront du mal à ignorer.

Bref historique

GtkAda

GtkAda2.2.0, c’est l’implémentation GPL intégrale d’une interface entre la bibliothèque de fonction Gtk 2.2 écrite en C et Ada. Par une remarquable équipe de bénévoles dont la nombre et la qualité du travail croissant conjointement depuis presque 10 ans.

GtkAda offre une intégration complète avec Glade, pour des développements rapides efficaces, Glade se chargeant d’écrire le code de votre interface.

GtkAda offre également un support complet de la librairie OpenGL, vous permettant d’intégrer des objets en 3D au milieux de vos fenêtre Gtk.

Installation

Alors ça y est, vous voilà impatient d’arpenter le chemin vers un monde de rapidité d’exécution, de qualité logicielle et d’interfaçage graphique pointu ?

Installons ce qu’il vous faut.

Pour compiler du Ada, il vous suffit de reprendre votre installateur de distribution et de cocher la case « Support of Ada language » dans les options d’installation de GCC. Personnellement, ayant une Knoppix 3.4 des débuts, installée à la va-vite sur le disque dur, je soigne mon hybride Debian à coup d’apt-get, et je me souviens avoir lancé un apt-get gna3.15pt un certain jeudi soir, tard après un First Jeudi mémorable…

Concernant GtkAda2.2.0, si vous n’avez pas d’outil de la simplicité d’apt-get, ou si vous êtes comme moi un collectionneur de version, les dernières version stables seront toujours disponibles sur :

http://www.gtk.org

http://glade.gnome.org

http://libre.act-europe.fr/GtkAda

Enfin, si pour une obscure raison, forcément temporaire (rassurez-vous), vous vous trouvez privé de votre système d’exploitation favoris, vous ne serez pas pour autant privé de développement Gtk libre. Puisque des installateurs simplissimes sont disponibles pour d’autre systèmes d’exploitation répendus.

Premier programme

Le code

with Gtk.Main;
with Gtk.Window;
procedure Coucou_alpha is
	window	: Gtk.Window.Gtk_Window;
begin
	Gtk.Main.init;
	Gtk.Window.gtk_New(window);
	Gtk.Window.set_Title(window,"Coucou tout le monde !");
	Gtk.Window.show(window);
	Gtk.Main.main;
end Coucou_alpha;

Notre premier programme en Ada est simple, profitons-en pour bien le comprendre.

Il se divise tout d'abord en deux éléments : les directives de compilation, with, aussi nommées clauses de contexte, et la définition de la méthode donnant corps à notre programme.

Ada distingue les méthodes qui retournent une valeur, de celles qui ne s'occupent que de leurs arguments. Ainsi, il y a respectivement les fonctions introduites par le mot clé function et les procédures introduites par le mot clé procedure. Nous avons donc ici une procedure.

Toutes les méthodes en Ada comportent deux parties (au moins) : une première partie pour les déclarations de variables (ou tout autre élément local à la méthode) et une seconde partie pour les instructions de la méthode. Ainsi, les déclarations sont faites entre la ligne du nom de la méthode et la balise begin, puis les instructions sont rangées entre les balises begin et end nomDeLaMéthode;. La "convention" en C qui enjoint à placer ses déclarations en début de programme à tellement plu, qu'elle est intégrée à Ada. Cela permet également au compilateur de générer du code propre et performant.

Mais revenons à nos moutons, maintenant qu'on sait ce qu'on regarde, voyons ce qu'il fait ce programme.

En se donnant accès aux éléments déclarés dans Gtk.Main et Gtk.Window notre programme défini une procédure.

Cette procédure est dotée d'une variable window du type Gtk_Window défini dans le paquetage Gtk.Window.

Puis les instructions commencent et la procédure Init du paquetage Gtk.Main est appelée. Cette ligne du programme sera présente au début de chaque application GtkAda que nous allons écrire. Notez également qu'en Ada, une méthode qu'on appel sans argument n'a pas besoin de parenthèses vides, son nom suffit.

Ensuite, la procédure Gtk_New se charge d'initialiser notre variable window qu'on lui passe en paramètre.

On défini ensuite le titre de la fenêtre.

Puis on demande à Gtk de faire apparaître la fenêtre. Enfin, on lance la fameuse Gtk Main Loop (boucle principale Gtk) qui est normalement chargée d'attendre qu'il se passe quelque chose.

Compilation et exécution

Une fois avoir enregistré ce programme dans un fichier portant le nom de l'unique unité de traduction admise par fichier par le compilateur (ici le nom de la méthode, mais c'est généralement le nom du paquetage dans lequel on a mis toutes nos méthodes), et se terminant par .adb pour Ada Body, on lance la commande suivante :

gcc -gnat -I<prefix>/include/gtkada Coucou_alpha

(où <prefix> correspond à l'éventuel préfix que vous avez spécifié lors de l'installation de GtkAda)

Et on obtient un fichier Coucou_alpha executable :-)

Un petit : ./Coucou_alpha ouvre votre première fenêtre GtkAda.

Une fenêtre GTK vide.

Tada !

Ah bah oui elle est vide, et oui il faut taper [CTRL]+[C] pour récupérer la main dans la console...

Mais quand même, c'était une vrai fenêtre graphique.

Merci Gtk, merci Ada, merci GtkAda !

Bon, l'euphorie de l'évènemet commençant à retomber, je le vois dans vos yeux, nous allons passer au deuxième programme.

Ce coup-ci on va aller plus vite, on reprend notre programme, et on rempli la fenêtre.

Des éléments dans la fenêtre

with Gtk.Main;	use Gtk.Main;
with Gtk.Window;	use Gtk.Window;
with Gtk.Box;	use Gtk.Box;
with Gtk.Label;	use Gtk.Label;
with Gtk.Button;	use Gtk.Button;

procedure Coucou_1 is
	window_1	: Gtk_Window;
	container_1	: Gtk_Box;
	label_1	: Gtk_Label;
	button_1	: Gtk_Button;
begin
	init;
	gtk_New(window_1);
	set_Title(window_1,"Coucou_1"); 

	gtk_New_Vbox(container_1);
	gtk_New(label_1, "Coucou tout le monde !");
	gtk_New(button_1, "Ok");

	add(window_1, container_1);
	pack_Start(container_1, label_1);
	pack_Start(container_1, button_1);

	show_All(window_1);
	main;
end Coucou_1;

Petit coup d'œil sur les directives de compilation, cette fois des use accompagnent les with. Pour faire simple, les with permettent de voir dans notre programme ce qui est défini dans d'autre fichier, mais il faut préfixer les éléments qu'on utilise par le nom de la librairie en question. La clause use permet en plus, de se passer de cette notation pointée.

Notre procédure est cette fois dotée de 4 variables : une fenêtre, une conteneur, un label et un bouton.

Comme l'avait expliqué Xavier Garreau dans l'article sur Gtkmm, une fenêtre Gtk ne peut contenir qu'un seul widget, qu'un seul élément. Pour pallier à cet inconvénient, il existe des éléments dont le rôle est d'en contenir d'autre. On les appel des conteneurs. Chaque conteneur a sa propre politique d'agencement des éléments qu'il va accueillir. Pour les besoins de l'exemple, nous utilisons une VBox, un conteneur comparable à une étagère, dans laquelle nous allons ranger notre label en haut, puis notre bouton à l'étage du dessous.

En lisant le code, on retrouve les trois premières lignes du premier programme, mais sans les nom des librairies en préfix, les use nous en dispensent désormais.

Les trois lignes suivantes initialisent respectivement le conteneur, le label puis le bouton.

Les trois lignes suivantes imbriquent les éléments comme nous l'avons énoncé : la VBox dans la fenêtre, le label dans la VBox au premier étage, le bouton au second.

Enfin, la méthode show_all est invoquée au lieu d'un simple show car chaque composants Gtk à besoin d'être révélé. On peut le faire individuellement, en appelant la méthode show à chaque composant une fois ce dernier positionné dans la fenêtre, soit tout d'un coup, avec show_all.

Le dernier main lance la boucle principale Gtk.

Compilation, exécution, et cette fois notre fenêtre est garnie.

Une fenêtre GTK comportant un label ainsi qu'un bouton OK.

Tout y est, ou presque.

Seulement on ne s'amuse pas longtemps à cliquer sur un bouton qui ne réagit pas, je le sais bien...

Alors voici pour vous, le troisième programme.

Une fenêtre fonctionnelle cette fois

Cette fois le but est de faire réagir notre programme quand on clique sur le bouton "Ok". Pour bien comprendre ce qui se passe, il faut avoir en tête que lorsque l'utilisateur que nous sommes clique sur un bouton dans une fenêtre, son action se transforme en signal et l'objet qui le reçoit transmet ce signal au reste du programme.

Il nous suffit donc juste de récupérer ce signal, et d'y attacher un traitement particulier, une réponse.

Pour ce faire, nous avons besoin de deux nouvelles librairies, une concernant les Widgets en général et une autre contenant les outils nécessaires pour attacher une réponse à un signal.

with Gtk.Widget;	use Gtk.Widget;
with Gtk.Handlers;	use Gtk.Handlers;

Ensuite, il nous faut une réponse proprement dîtes :

procedure réponse(élément_Emetteur : access Gtk_Widget_Record'Class) is
begin
	Gtk.Main.main_quit;
end réponse;

Cette petite procédure ne contient qu'une seule instruction : main_quit. La réaction de notre fenêtre en réponse à un clique sur le bouton "Ok" sera donc de se fermer et de quitter le programme.

Signalons ensuite que notre procédure prends un paramètre en argument. Il s'agit d'un objet de type accès sur l'ensemble de la classe des types Gtk_Widget_Record. Heureusement, c'est GtkAda qui se chargera d'appeler cette méthode au bon moment, nous n'allons donc pas manipuler ce genre d'objet. Retenons donc simplement que le type access en Ada correspond aux pointeurs du langage C, et que notre accesseur pointe sur un objet Gtk générique, nous n'avons donc pas encore choisi à quel type de Widget cette réponse est attachable.

Mais où mettre cette définition de procédure ? On ne peut pas la mettre à la suite de la procédure principale puisqu'on a le droit qu'a une seule unité de traduction par fichier, c'est à dire un seul élément compilable autonome par fichier.

On pourrait la placer dans un fichier séparé, et s'en donner l'accès par un with, ou mieux, la ranger dans un paquetage indépendant ! Mais nous n'avons pas encore vu comment faire. Pour le moment, et pour faire simple, je vous propose de placer cette petite définition aux côtés des déclarations de variables. Ainsi, notre méthode sera locale au bloc d'exécution begin ... end de la procédure principale, et nous nous évitons d'avoir à la déclarer quelque part. Sa simple définition suffira.

Il nous faut maintenant ajouter cette ligne :

package Traitement is new Callback(Gtk_Widget_Record);

A la suite de la procédure réponse dans la partie déclarative du notre procédure principale.

Qu'est-ce donc que ce nouveau barbarisme ?

Il s'agit de la déclaration d'un paquetage Traitement comme étant dérivé du paquetage Callback ayant la particularité de gérer des Gtk_Widget_Record.

Nous y sommes presque ! Nous avons la réponse, et les outils pour la mettre en place, il ne reste plus que le coup de baguette magique final : connecter la réponse au signal.

Traitement.Connect
(
	button_1,
	"clicked",
	Traitement.to_Marshaller(réponse'Access)
);

Une fois avoir placé le bouton dans la fenêtre, cette ligne permet de connecter le signal « cliqued » du bouton_1 à la méthode réponse.

Compilation, exécution, et cette fois notre fenêtre disparaît quand on clique sur "Ok".

Une fenêtre GTK comportant un label ainsi qu'un bouton OK.

C'est pas très visible sur une image le changement, il faut y mettre un peu du votre aussi.

Maintenant nous sommes près à attaquer la Zazou fenêtre... (voir GNU/Linux Mag. numéro 57...)

La Zazou fenêtre

Petit rappel des faits, nous cherchons cette fois à programmer une fenêtre comportant un message nous invitant à saisir notre nom dans la case de texte juste en dessous, puis à cliquer sur un bouton juste en dessous, pour voir apparaître un nouveau message nous saluant personnellement. Le summum de la politesse, la Rolls-Royce des HelloWorlds.

Nous allons en profiter pour organiser un peu le code.

Nous disposons en Ada des paquetages et des types de données pour faire de la conception orientée objet, un paquetage encapsulant des données et les méthodes qui s'y rattachent.

Un paquetage est composé de deux fichiers, un premier fichier portant le nom du paquetage et se terminant pas .ads et un second classique en .adb. Le premier fichier contient les déclarations des types et méthodes du paquetage(à l'image des fichiers .h en C), et le second les définitions.

Dans notre fichier .ads nous allons donc déclarer un nouveau type de donnée, qui sera composé des anciennes variables de la procédure Coucou_2, plus les petites nouvelles : un second label et une entry. On obtient une structure de donnée composite. (à l'image d'une struct en C)

Comme vous pouvez le constater, ce nouveau type de donnée est, en plus de contenir nos objets Gtk, un type dérivé du type Controlled défini dans le paquetage Ada.Finalization. Cette astuce nous permet, en Ada, de bénéficier d'un mécanisme comparable aux constructeurs C++. Ici, nous allons utiliser la méthode Initialize. Si elle est définie avec un type Controlled en paramètre, cette méthode à la particularité d'être implicitement appelée à chaque déclaration d'une variable du type ainsi contrôlé (Les méthodes Finalize et Adjust sont également disponibles pour intervenir à la destruction et/ou à chaque ré-attribution de la variable).

Pour initialiser notre fenêtre, on récupère les instructions de l'ancien Coucou_2 et on les place dans le corps de la procédure Initialize, vous remarquerez au passage les quelques ajouts qui prennent en charge nos nouveaux besoins.

On retrouve ainsi réparti dans notre paquetage : d'un côté le corps de nos deux procédures et, de l'autre, notre nouveau type de donnée, les déclarations, et notre instantiation de paquetage Callback devenu User_Callback pour l'occasion.

Et qu'est-ce qui justifie ce changement de nom ? Notre nouvelle variable, qui est désormais propagée d'un bout à l'autre du traitement du signal et permet ainsi à la fenêtre de changer dynamiquement le texte de son label.

Enfin, un dernier fichier est nécessaire afin de lancer le tout. Après avoir donné la visibilité sur notre paquetage, on définit une procédure minimaliste (à l'image du main() en C++), avec une déclaration et une instruction, dans un fichier à son nom, Coucou_3 par exemple. On déclare une variable du type ma_fenêtre (et implicitement la procédure Initialize sera appelée) et dans le corps, on lance la boucle principale Gtk par : Gtk.Main.main

Après la compilation, si tout se passe bien, on obtient un fichier exécutable du nom de la procédure du lancement, et l'exécution de ce dernier déclenche les applaudissement de toute l'audience jusque là médusée lorsqu'on appuie sur le bouton "Ok".

Une fenêtre GTK comportant un label, une case de texte ainsi qu'un bouton OK.

La Zazou fenêtre.

Une fenêtre GTK comportant un label, une case de texte ainsi qu'un bouton OK.

Impressionnant, non ?

------------------------------------------------------------------
-- Fichier	: PQT_Coucou.ads
-- Description	: Entêtes du paquetage PQT_Coucou
-- Auteur	: Simon Descarpentries
-- Licence	: Licence Publique Générale (GPL)
------------------------------------------------------------------
with Gtk.Main;	use Gtk.Main;
with Gtk.Window;	use Gtk.Window;
with Gtk.Box;	use Gtk.Box;
with Gtk.Label;	use Gtk.Label;
with Gtk.Button;	use Gtk.Button;
with Gtk.GEntry;	use Gtk.GEntry;
with Gtk.Widget;	use Gtk.Widget;
with Gtk.Handlers; use Gtk.Handlers;

with Ada.Finalization;

package PQT_Coucou is

	type ma_fenêtre is new Ada.Finalization.Controlled with record
		window_1	: Gtk_Window;
		container_1	: Gtk_Box;
		label_1	: Gtk_Label;
		label_2	: Gtk_Label;
		entry_1	: Gtk_Entry;
		button_1	: Gtk_Button;
	end record;

	procedure Initialize(une_fenêtre : in out ma_fenêtre);

	procedure réponse
	(
		élément_Emetteur : access Gtk_Widget_Record'Class;
		la_fenêtre : ma_fenêtre
	);
	
	package Traitement is new User_Callback(Gtk_Widget_Record, ma_fenêtre);

end PQT_Coucou;

------------------------------------------------------------------
-- Fichier	: PQT_Coucou.adb
-- Description	: Corps des procédures du paquetage PQT_Coucou
-- Auteur	: Simon Descarpentries
-- Licence	: Licence Publique Générale (GPL)
------------------------------------------------------------------

package body PQT_Coucou is

	procedure réponse(
		élément_Emetteur : access Gtk_Widget_Record'Class;
		la_fenêtre : ma_fenêtre) is
	begin
		set_Text(la_fenêtre.label_2, "Bonjour a toi "&get_Text(la_fenêtre.entry_1)&" !");
	end réponse;
	 
	procedure Initialize(une_fenêtre : in out ma_fenêtre) is
	begin
		init;
		gtk_New(une_fenêtre.window_1);
		set_Title(une_fenêtre.window_1, "Coucou_3"); 

		gtk_New_Vbox(une_fenêtre.container_1);
		gtk_New(une_fenêtre.label_1, "Entrez votre nom et cliquez sur Ok");
		gtk_New(une_fenêtre.entry_1);
		gtk_New(une_fenêtre.button_1, "Ok");
		gtk_New(une_fenêtre.label_2, "Qui dois-je saluer ?");
		
		add(une_fenêtre.window_1, une_fenêtre.container_1);
		pack_Start(une_fenêtre.container_1, une_fenêtre.label_1);
		pack_Start(une_fenêtre.container_1, une_fenêtre.entry_1);
		pack_Start(une_fenêtre.container_1, une_fenêtre.button_1);
		pack_Start(une_fenêtre.container_1, une_fenêtre.label_2);
		
		Traitement.Connect
		(
			une_fenêtre.button_1,
			"clicked",
			Traitement.to_Marshaller(réponse'Access),
			une_fenêtre
		);

		show_All(une_fenêtre.window_1);
	end Initialize;
end PQT_Coucou;

------------------------------------------------------------------
-- Fichier	: PQT_Coucou.adb
-- Description : Procédure ouvrant une véritable Zazou fenêtre programmée en Ada
-- Auteur	: Simon Descarpentries
-- Licence	: Licence Publique Générale (GPL)
------------------------------------------------------------------

with Gtk.Main;
with PQT_Coucou; use PQT_Coucou;

procedure Coucou_3 is
	ZazouFenêtre : ma_fenêtre;
begin
	Gtk.Main.main;
end Coucou_3;

Conclusion

Après avoir développé en C, C++, Bash, Python, puis Java, ce fut une sacré révélation pour moi de découvrir, le langage Ada. J’ai retrouvé dans ce langage beaucoup des éléments qui font la force des autres langages, individuellement.

A travers cet article, je crois sincèrement vous avoir ouvert les portes d’un pays des merveilles de la programmation, et j’espère vous avoir donné envie de le parcourir par vous même.

Un point de départ ?

http://www.ada-france.org

Bon voyage, et à bientôt.