Bases pour développer une application graphique GTK sous linux en Ada avec GPS (Gnat Programming studio) et Glade.

Ada est un langage rigoureux et fortement typé. Ses caractéristiques en font un langage de choix pour les applications ayant de fortes exigences de fiabilité. Attiré par cette réputation, l'auteur de cette page a souhaité le découvrir.

Quoique habitué à la programmation dans divers langages (sans être informaticien), il a trouvé ses premiers pas en Ada difficiles. Des ressources pour apprendre existent en ligne, en anglais (wikibooks 2017, e-book Ada-distilled pour la version 2005 de la norme) ou, plus rarement, en français (Cours Ada de l'IUT d'Aix-En-Provence (2002)). On peut aussi avoir besoin de se plonger dans le manuel de référence ; la dernière version est la norme est de 2012 mais ce n'est pas un ouvrage pédagogique !

Ces ressources sont utiles pour se former au langage mais ne font, au mieux, qu'effleurer la programmation d'applications graphiques. Celle-ci est possible en utilisant, par exemple, GTK, qui a été porté sous ada (voir le guide utilisateur de gtkada (anglais), des exemples sur rosetta.code). L'utilisation de Glade, par exemple, pour concevoir l'interface graphique est classique. Quoiqu'orientée vers Windows, le document Réalisation d’interfaces graphiques pour des programmes écrits en Ada avec le compilateur Gnat est intéressant à lire pour se familiariser avec les bases de la technique. Voir aussi Support for Glade à l'université de Brest

Cependant, dans la pratique, l'écriture des gestionnaires et, surtout, leur connexion aux signaux sont semées d'embûches. Il faut ajouter à cela que le compilateur est assez sourcilleux et produit des messages quelque peu ésotériques. Au total, on manque d'exemples complets compilables de l'usage combiné de Ada, GTK et Glade.

Cette page vise à apporter sa petite pierre au comblement de cette lacune en expliquant les bases nécessaires, récemment découvertes par un débutant, et en fournissant un exemple complet compilable commenté d'une application élémentaire.

Configuration utilisée

Le fonctionnement des outils considérés s'avère sensible aux versions mises en œuvre, des incompatibilités pouvant apparaître. Le travail présenté ici a été développé et testé sous la configuration suivante.

L'application

L'application élémentaire adoptée ici comme suppport de présentation s'appelle ppal (programme principal, c'est original). Elle utilise une seule fenêtre contenant trois composants :

La fenêtre initiale de l'application

Chaque clic sur le bouton place dans l'étiquette un nouveau texte indiquant le nombre de clics effectués.

La fenêtre de l'application après un clic
La fenêtre de l'application après deux clics

Le titre de la fenêtre est celui proposé par défaut par Glade : le même que le programme principal (il est possible de mettre un autre titre). La fenêtre se referme en cliquant sur la croix de sa barre de titre.

Approche de développement

Une approche commode pour la mise au point de l'application - et facile pour apprendre - consiste à concevoir graphiquement l'interface avec glade, à stocker sa description dans un fichier .glade (XML, au format gtkbuilder) et, en parallèle, à écrire l'application en utilisant un environnement de développement intégré (EDI) ; GPS (Gnat Programming Studio) est choisi ici à cet effet. Dans la pratique, Glade et GPS sont ouverts en même temps car le développement de l'application implique de fréquents allers et retours entre les deux. Toutefois, ils sont abordés successivement ci-dessous.

Conception de l'interface avec Glade

L'application Glade est utilisée pour concevoir l'interface indépendamment du langage. La fenêtre est élaborée de façon classique (pour une présentation plus détaillée voir Premier pas en programmation d'applications graphiques en python avec glade (sous GNU/Linux), page en relation avec python mais cela ne change rien pour glade). Comme illustré par l'image suivante, deux signaux sont activés :

Les deux gestionnaires de signaux seront programmés en Ada plus tard.
La fenêtre en construction dans Glade

Ce projet est enregistré en format gtkbuilder dans le fichier interface2.glade (XML) où la structure de la fenêtre est assez reconnaissable :

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="2.24"/>
  <!-- interface-naming-policy project-wide -->
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <property name="default_width">440</property>
    <property name="default_height">250</property>
    <signal name="delete-event" handler="SI_fenetre_ppale_close" swapped="no"/>
    <child>
      <object class="GtkVBox" id="vbox1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkLabel" id="Texte">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Texte initial</property>
            <attributes>
              <attribute name="underline" value="True"/>
            </attributes>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="boutonCLT">
            <property name="label" translatable="yes">Changer le texte</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_action_appearance">False</property>
            <signal name="clicked" handler="SI_appui_changer_le_texte" swapped="no"/>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Le développement du programme avec GPS

GPS travaille par projet. Il est capable de gérer des projets complexes, avec plusieurs langages de programmation. Chaque projet est décrit par un fichier gpr. Dans le cas présent, s'agissant de développer une application "squelettique", le projet est nommé "squelette" et le fichier descriptif correspondant est
squelette.gpr :

with "gtkada";

project Squelette is

   for Main use ("ppal.adb");
   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Source_Files use ("actions_specifiques.adb", "actions_specifiques.ads", "ppal.adb");

end Squelette;

Ce document très simple fait deux choses importantes : 1/ il incorpore au projet la bibliothèque gtkada, qui fait le lien entre le langage Ada et la bibliothèque graphique GTK ; 2/ il décrit la constitution du projet : son arborescence et ses fichiers :

>squelette.gpr
>src
   >actions_specifiques.adb
   >actions_specifiques.ads
   >ppal.adb
>obj
   >(initialement vide)

Les fichiers adb et ads sont des unités en langage Ada (b pour body et s pour specification). Le fichier ppal.adb contient le programme principal. Le dossier obj, initialement vide, va recevoir les fichiers produits par l'édition des liens et la compilation : fichiers de code objet *.o résultant de la compilation séparée des unités, fichiers *.ali (ada library information), d'autres fichiers auxiliaires éventuels et bien sûr l'exécutable. Dans le cas où l'on emploie glade avec un fichier externe, il faut encore placer ce fichier dans le même dossier que le fichier projet, à la racine du projet.

L'ouverture de squelette.gpr dans GPS charge le projet.
La fenêtre de GPS

Si les fichiers sources ne sont pas affichés, il suffit de double cliquer dessus dans le panneau de l'arborescence à gauche pour les ouvrir dans des onglets. On constate que GPS ne considère pas le fichier glade comme une source du projet (il est cependant possible de l'ouvrir dans un onglet par le menu File > Open [ou la touche F3]). Les boutons dans la partie droite de la barre d'outils permettent de compiler des fichiers individuellement, de construire l'exécutable du programme principal (ou l'ensemble du projet mais, dans le cas simple présent, c'est la même chose) puis de lancer l'exécution directement depuis l'EDI. Les sorties de l'application et les éventuelles erreurs s'affichent alors dans le panneau inférieur (onglet Run ou Messages).
La fenêtre de GPS en action

Après construction de l'exécutable, la constitution du projet est :

>squelette.gpr
>interface2.glade
>src
   >actions_specifiques.adb
   >actions_specifiques.ads
   >ppal.adb
>obj
   >actions_specifiques.ali
   >actions_specifiques.o
   >ppal    ........................................................< exécutable
   >ppal.ali
   >ppal.o

Les fichiers intermédiaires (*.o et *.ali et autres auxiliaires éventuels) peuvent être effacés.

Distribution de l'exécutable

Pour distribuer l'application utilisable en binaire, il faut fournir l'exécutable et le fichier .glade, par exemple réunis dans une boule tar ppal.tar.gz. Si l'environnement d'accueil est identique à celui de développement, l'installation est simplissime : il suffit de décompresser les fichiers dans un même dossier <rep> quelconque et de donner le droit d'exécution à ppal, qui se lance alors depuis ce dossier dans un terminal par :

<rep>$ ./ppal

Pendant l'utilisation de l'application, les messages d'information programmés apparaissent dans le terminal. Ci-dessous un exemple de sortie après fermeture de la fenêtre :

Initialisation effectuée.
Fenêtre principale créée.
Signal de fermeture connecté.
Bouton Changer_le_texte créé.
Signal de clic sur Changer_le_texte connecté.
Fenêtre affichée.
Lancement de la boucle de détetion d'événements.
Appui sur le bouton ^Changer le texte^.
Avant le clic, le texte de l'étiquette était: "Texte initial"
Appui sur le bouton ^Changer le texte^.
Avant le clic, le texte de l'étiquette était: "Nombre de clic(s) = 1"
Fermeture de la fenêtre.

Si l'environnement d'accueil est différent, l'application déclare une erreur de dépendance insatisfaite et ne s'exécute pas.

Distribution des sources

Pour pouvoir recompiler l'application avec GPS (dans un système de versions compatibles), outre squelette.gpr et interface2.glade, il faut disposer des fichiers
actions_specifiques.adb :

-- Ce fichier implémente les actions spécifiques à l'application.
-- Ce corps doit etre compilé avant la spécification actions_specifiques.ads (automatique dans GPS)

with Text_IO ; -- pour les écritures en console
with Gtk.Button ; -- parce qu'il y a un actionneur bouton
with Gtk.Label ; -- parce qu'il y a une étiquette ("label") sur laquelle on agit en lecture et en écriture

with Gtk.Builder ; -- POUR GLADE
with Glib.Error ; -- POUR GLADE

Package body Actions_specifiques is
   Compteur: Natural :=0 ;
   -- L'inclusion du présent paquet dans ppal fait de Compteur une VARIABLE GLOBALE conservée d'un appel de Si_appui_changer_le_texte
   -- au suivant, ce qui permet de l'incrémenter pour compter les clics.

   procedure Si_appui_changer_le_texte (Object : access Gtk.Button.Gtk_Button_Record'Class; Etiquette: Gtk.Label.Gtk_Label) is
      -- Cette procédure a besoin de deux arguments: l'actionneur bouton, toujours présent, et la cible des actions de lecture et d'écriture
      -- Dans le programme principal, la connexion doit donc faire appel à Gtk.Handlers.User_Callback, qui accepte deux types
   begin
      Text_IO.Put_Line("Appui sur le bouton ^Changer le texte^.") ;
      Text_IO.Put_Line("Avant le clic, le texte de l'étiquette était: """ & Etiquette.Get_Label & """") ;
      Compteur := Compteur + 1 ;
      Etiquette.Set_Text("Nombre de clic(s) = " & Compteur'Img) ;

   end Si_appui_changer_le_texte ;

end Actions_specifiques ;

NOTA : on a ici gardé les choses simples mais, dans une application réelle, le gestionnaire de signal doit gérer les erreurs (exceptions).

actions_specifiques.ads :

-- Ce fichier spécifie les actions spécifiques à l'application
-- Le corps actions_specifiques.adb doit être compilé avant cette spécification (automatique dans GPS)
with Gtk.Button ;
with Gtk.Label ;

Package Actions_specifiques is

   procedure Si_appui_changer_le_texte (Object : access Gtk.Button.Gtk_Button_Record'Class ; Etiquette: Gtk.Label.Gtk_Label) ;

end Actions_specifiques ;

ppal.adb :

-- =========================================================================
-- ======================== APPLICATION ELEMENTAIRE ========================
-- =========================================================================
-- Cette application est en réalité un modèle pour commencer à programmer
-- une application graphique sous linux en Ada.
-- Elle est conçue sous ubuntu en utilisant l'environnement de développement
-- intégré GNAT Programming Studio et Glade.
-- Plus précisément, la distribution est Ubuntu Trusty Tahr 14.04.5 LTS
-- 64 bits avec le bureau MATE Desktop Environment 1.8.2.
-- La configuration de l'environnement de programmation est installée depuis
-- les dépots de la distribution pour en garantir la cohérence.
-- Les versions disponibles installées à l'heure où ce commentaire est écrit
-- sont les suivantes
-- * GTK 2 et 3 sont présents sur le système, en versions respectives
-- 2.24.27-0ubuntu1~trusty1 et 3.10.8-0ubuntu1.4
-- * Glade 3.16.1 est présent et capable d'utiliser GTK 3.10 mais, pour
-- cette application, on a utilisé le module limité à
-- GTK2 (glade-gtk2 version 3.8.0) réglé pour utiliser GTK 2.24
-- avec le format gtkbuilder.
-- * Le compilateur est Gnat et, bien que la version 4.8 soit disponible,
-- c'est ici la version 4.6 qui a dû être employée
-- * La liaison avec Ada, gtkada, a été installée par Synaptic en version
-- 2.24.1-14
-- * L'EDI Gnat Programming studio est en version GPS 5.0-16 (pour la
-- famille Debian)
-- L'utilisation d'OpenGL n'est pas abordée ici.
-- =========================================================================
-- Ce programme principal contient d'abord les spécifications et implémentations
-- minimales requises pour toute application graphique utilisant une seule
-- fenêtre principale, conçue avec Glade. Les lignes spécifiques nécessaires
-- à l'emploi de Glade portent la mention "POUR GLADE". Le seul sous-programme
-- de rappel dont le corps est dans le programme principal est le gestionnaire
-- du signal "delete-event" car il doit toujours être présent pour fermer la
-- fenêtre. La majeure partie du programme principal est ainsi générique.
-- Pour une application particulière utilisant une fenêtre avec des éléments
-- spécifiques, la stratégie lisible est ici de n'ajouter au programme
-- principal que la déclaration des actionneurs (émetteurs de signaux) et des
-- paquets requis, puis la connexion de leurs signaux aux gestionnaires respectifs.
-- Les spécifications et implémentations de ces gestionnaires sont externalisées
-- dans un paquet Actions_specifiques.
-- La création d'une nouvelle application simple peut ainsi être circonscrite
-- aux sections spécifiques du programme principal et aux modules externes
-- Actions_specifiques.
-- =========================================================================
-- Cette version lit l'interface graphique dans le fichier interface2.glade
-- externe avec la fonction Gtk.Builder.Add_From_File.
-- C'est commode pour la mise au point du projet sous GPS mais sous-optimal
-- pour la distribution d'un exécutable autonome.
-- En effet, pour que l'exécutable compilé et lié fonctionne dans ces conditions,
-- il faut
-- a) placer le fichier interface2.glade dans le même dossier que le fichier
-- projet .gpr (son chemin dans Add_From_File se réduit alors à son nom) ;
-- b) après compilation et édition des liens, copier ^^l'exécutable et le
-- fichier .glade^^ dans un même dossier quelconque ;
-- c) ouvrir ce dossier dans un terminal et lancer l'exécutable
-- en ligne de commande classiquement : $ ./ppal
-- Ainsi, l'exécutable parvient à trouver le fichier de description de l'interface
-- et tout fonctionne.
-- Cependant, d'une part, cela oblige à distribuer les deux fichiers ensemble et,
-- d'autre part, rend le fonctionnement du programme éventuellement vulnérable à
-- une modification du fichier glade, qui est un simple fichier texte.
-- Pour éviter ces inconvénients, une fois le fonctionnement au point, il est
-- possible d'incorporer la description XML fournie par Glade dans
-- une variable String et d'utiliser Add_From_String pour embarquer l'interface
-- dans l'exécutable (voir la version correspondante de ce projet).
-- =========================================================================

with Gtk.Enums;       -- fournit les types de fenêtres comme Window_Toplevel
with Gtk.Main;          -- fournit la boucle de détection d'événement
with Gtk.Handlers;    -- fournit les gestionnaires de signaux dont le rappel de sortie
with Text_IO; -- nécessaire pour les écritures à la console
with Gtk.Window ; -- nécessaire pour la déclaration de la fenêtre principale

With Gtk.Builder ; -- POUR GLADE
With Gtk.Widget ; -- POUR GLADE
With Glib.Error ; -- POUR GLADE

-- ===========================================================
-- ====== INCLUSIONS SPECIFIQUES A L'APPLICATION: DEBUT ======
-- ===========================================================
with Actions_specifiques ; -- Pour inclure le paquet décrivant les actions spécifiques à l'application
with Gtk.Button ; -- Parce qu'il y a un actionneur bouton dans l'application
with Gtk.Label ; -- Parce qu'il y a une étiquette dans l'application et qu'on agit sur elle
-- ===========================================================
-- ====== INCLUSIONS SPECIFIQUES A L'APPLICATION: FIN ========
-- ===========================================================

procedure ppal is
   -- ON COMMENCE PAR INSTANCIER LE PAQUET QUI FOURNIT LES UTILITAIRES DE CONNEXION ET DE SIGNAUX POUR LA FENETRE PRINCIPALE
   -- le GS function Si_fenetre_ppale_close (Object : access Gtk.Window.Gtk_Window_Record'Class) return Boolean
    -- est une function => paquet de connecteurs *_Return_Calback
    -- n'a pas besoin d'un objet autre que la fenêtre => paquet Return_Callback (sans User_ donc), choix du connecteur Connect
   package Utilitaires_de_fenetre is new Gtk.Handlers.Return_Callback (Gtk.Window.Gtk_Window_Record, Boolean) ; -- dfn de Utilitaires_de_fenetre

   -- ============ rappel pour quitter =================
   -- PUIS ON ECRIT LA FONCTION QUI SERA APPELEE SUR DETECTION DE Delete_Event
   -- il serait plus lisible de lui donner un nom qui contient le nom du signal mais on peut la nommer librement, exemple ci-dessous.
   -- function Si_fenetre_ppale_close (Object : access Gtk.Window.Gtk_Window_Record'Class) return Boolean ; -- << specification facultative ici
   -- car le corps de la fonction est present dans le programme avec la même visibilité.

   function Si_fenetre_ppale_close (Object : access Gtk.Window.Gtk_Window_Record'Class) return Boolean is    -- implementation
      pragma Unreferenced (Object); -- directive apparemment inutile ? Pas d'avertissement si elle est désactivée...
   begin
      Text_IO.Put_Line("Fermeture de la fenêtre.") ;
      Gtk.Main.Gtk_Exit (0);
      return True ;
   end Si_fenetre_ppale_close ;
   -- ============ fin du rappel pour quitter =================

   -- ON CREE LE POINTEUR DE LA FENETRE PRINCIPALE (avant de lui associer l'objet correspondant)
   Fenetre_principale : Gtk.Window.Gtk_Window ;
   -- et on declare les variables et objets POUR GLADE
   Import_glade_OK: Glib.Error.GError ;     -- POUR GLADE : declaration de l'erreur pour recuperer le resultat de la fonction Add_From_File
   mon_interface: Gtk.Builder.Gtk_Builder ; -- POUR GLADE : declaration de l'objet pour recueillir la description de l'interface
   FP_widget: Gtk.Widget.Gtk_Widget ;     -- POUR GLADE: widget pour extraire la fenetre principale de l'interface ^^avec le type Gtk_Widget^^

begin
   -- L'implémentation comporte trois parties: la première et la dernière sont génériques. La deuxième est spécifique à l'application.
   -- Début de la partie générique initiale du corps du programme
   -- Initialisation
   Gtk.Main.Set_Locale ; -- Chargement des propriétés de localisation du système
   Gtk.Main.Init ; -- Initialisation du programme
   Text_IO.Put_Line("Initialisation effectuée.") ; -- Sortie à la console pour suivre l'avancement du programme et informer l'utilisateur

   -- ====== CREATION DE LA FENETRE PRINCIPALE - DEBUT DES INSTRUCTIONS POUR L'EMPLOI DE GLADE ===========
   -- Avec Gtk directement, on creerait la nouvelle fenetre associee au pointeur Fenetre_principale par
   -- Gtk.Window.Gtk_New (Fenetre_principale, Gtk.Enums.Window_Toplevel) ;
   -- et il faudrait ensuite lui ajouter par programmation tous ses composants. Ceci devient vite fastidieux pour une application réelle.
   -- Avec Glade, on crée l'interface graphiquement et Glade fournit sa description sous la forme XML. Il faut donc d'abord importer l'interface
   -- depuis cette description.
   -- Pour cela, en amont, il faut 3 with supplémentaires et il faut déclarer 2 objets en plus: une erreur et le conteneur pour l'interface
   -- Pour une meilleure lisibilité, on déclare aussi ici un objet intermédiaire FP_widget pour mieux mettre en évidence la conversion de type
   Gtk.Builder.Gtk_New(Builder => mon_interface) ; -- Création de l'objet pour recevoir l'interface
   Import_glade_OK := Gtk.Builder.Add_From_File(Builder => mon_interface, Filename => "interface2.glade"); -- Importation de l'interface
   -- Le mot "builder" peut se comprendre car ce paquet fournit un objet permettant de ^construire" les composants de l'interface.

   FP_widget := mon_interface.Get_Widget("window1") ; -- Récupération du widget de la fenêtre (!! Get_Widget disparait dans gtk3 !!)
   Fenetre_principale := Gtk.Window.Gtk_Window (FP_widget) ; -- Création de la fenêtre par conversion en type Gtk_Window (pointeur)
   -- ====== CREATION DE LA FENETRE PRINCIPALE - FIN DES INSTRUCTIONS POUR L'EMPLOI DE GLADE ===========
   Text_IO.Put_Line("Fenêtre principale créée.") ; -- Sortie à la console pour information

   -- Connexion du signal de fermeture de la fenêtre au rappel local pour quitter
   Utilitaires_de_fenetre.Connect     -- Connecteur
    (Fenetre_principale,     -- Objet actionneur
      "delete-event",     -- Nom du signal (lu dans Glade ou directement dans le fichier XML)
      --Utilitaires_de_fenetre.To_Marshaller (Si_fenetre_ppale_close'Access) -- Marsh: in Marshallers.Marshaller marche mais pas indispensable
      Si_fenetre_ppale_close'Access     -- gestionnaire de signal en direct
    );     -- After, booleen facultatif, omis ici
   Text_IO.Put_Line("Signal de fermeture connecté.") ;     -- Sortie à la console pour information
   -- Fin de la partie générique initiale du corps du programme

   -- =====================================================================
   -- ========== DEBUT DE LA PARTIE SPECIFIQUE A L'APPLICATION ============
   -- =====================================================================
   -- Pour faciliter l'adaptation, les éléments spécifiques (déclarations et implémentation) sont groupés dans un bloc declare.
   Actionneurs_specifiques :
   declare -- Dans les déclarations figurent les instances de Gtk.Handlers.**Callback** nécessaires et les objets actionneurs
      -- le GS procedure Si_appui_changer_le_texte (Object : access Gtk.Button.Gtk_Button_Record'Class ; Etiquette: Gtk.Label.Gtk_Label)
          -- ne renvoit pas de valeur => paquet *_Callback (sans Return)
          -- a besoin d'un objet utilisateur Label pour y écrire => paquet User_Callback choix Connect
      package Utilitaires_de_bouton is new Gtk.Handlers.User_Callback(Gtk.Button.Gtk_Button_Record, Gtk.Label.Gtk_Label) ;
      -- Le paquet Utilitaires_de_bouton est ici défini comme une instance de Gtk.Handlers.User_Callback afin de pouvoir passer 2 paramètres
      -- au gestionnaire de signal. Il pourrait servir à plusieurs couples "bouton-étiquette".
      Changer_le_texte: Gtk.Button.Gtk_Button ; -- Création de l'objet bouton, nommé d'après la légende du bouton, pour la lisibilité

   begin -- Dans l'implémentation, on traite les actionneurs un par un
         -- ===============================
         -- 1. Le bouton "Changer_le_texte"
         -- ===============================
      Changer_le_texte:= Gtk.Button.Gtk_Button(mon_interface.Get_Object("boutonCLT")) ; -- Extraction et conversion du bouton de l'interface
      Text_IO.Put_Line("Bouton Changer_le_texte créé.") ; -- Sortie à la console pour information
      Utilitaires_de_bouton.Connect -- Connecteur
       (Widget    => Changer_le_texte -- le widget actionneur
         , Name      => "clicked" -- le nom du signal
         , Cb    => Actions_specifiques.Si_appui_changer_le_texte'Access -- le gestionnaire de signal, sans marshaller
         , User_Data => Gtk.Label.Gtk_Label(mon_interface.Get_Widget("Texte")) -- l'objet "donnée utilisateur" pour y écrire
         --, After    => -- facultatif
       ) ;
         Text_IO.Put_Line("Signal de clic sur Changer_le_texte connecté.") ; -- Sortie à la console pour information

         -- ===============================
         -- 2. etc. (dans cette application, il y a un seul actionneur spécifique)
         -- ===============================

   end Actionneurs_specifiques ;

   -- =====================================================================
   -- ========== FIN DE LA PARTIE SPECIFIQUE A L'APPLICATION ==============
   -- =====================================================================

   -- Début de la partie générique finale du programme principal
   -- Rendre visible la fenêtre
   Gtk.Window.Show_All (Fenetre_principale); -- Affichage de la fenêtre principale
   Text_IO.Put_Line("Fenêtre affichée.") ; -- Sortie à la console pour information
   -- Lancer la boucle de détection d'événement
   Text_IO.Put_Line("Lancement de la boucle de détection d'événements.") ; -- Sortie à la console pour information
   Gtk.Main.Main;
   -- Fin de la partie générique finale du programme principal

end ppal ;

Ces fichiers sont regroupés, avec la structure de dossiers du projet, dans la boule tar suivante : ppal.src.tar.gz.

Version produisant un exécutable autonome

Comme indiqué dans les commentaires du programme principal, la version présentée ci-dessus a un inconvénient.

Elle lit l'interface graphique dans le fichier interface2.glade externe avec la fonction Gtk.Builder.Add_From_File. C'est commode pour la mise au point du projet sous GPS mais sous-optimal pour la distribution de l'exécutable. En effet, pour que l'exécutable compilé et lié fonctionne dans ces conditions, il faut :

Ainsi, l'exécutable parvient à trouver le fichier de description de l'interface et tout fonctionne. Cependant, d'une part, cela oblige à distribuer les deux fichiers ensemble et, d'autre part, cela rend le fonctionnement du programme éventuellement vulnérable à une modification du fichier glade, qui est un simple fichier texte. Pour éviter ces inconvénients, une fois le fonctionnement au point, il est possible d'incorporer la description XML fournie par Glade dans une variable String et d'utiliser Add_From_String, pour embarquer l'interface dans l'exécutable

Les modifications à apporter au programme principal pour ce faire sont les suivantes.

   mon_interface_texte: String(1..1477) := "<?xml version=""1.0"" encoding=""UTF-8""?>"      -- POUR GLADE
          & "<interface>"
          & "<requires lib=""gtk+"" version=""2.24""/>"
          & "<!-- interface-naming-policy project-wide -->"
          & "<object class=""GtkWindow"" id=""window1"">"
          & "<property name=""can_focus"">False</property>"
          & "<property name=""default_width"">440</property>"
          & "<property name=""default_height"">250</property>"
          & "<signal name=""delete-event"" handler=""SI_fenetre_ppale_close"" swapped=""no""/>"
          & "<child>"
          & "<object class=""GtkVBox"" id=""vbox1"">"
          & "<property name=""visible"">True</property>"
          & "<property name=""can_focus"">False</property>"
          & "<child>"
          & "<object class=""GtkLabel"" id=""Texte"">"
          & "<property name=""visible"">True</property>"
          & "<property name=""can_focus"">False</property>"
          & "<property name=""label"" translatable=""yes"">Texte initial</property>"
          & "<attributes>"
          & "<attribute name=""underline"" value=""True""/>"
          & "</attributes>"
          & "</object>"
          & "<packing>"
          & "<property name=""expand"">True</property>"
          & "<property name=""fill"">True</property>"
          & "<property name=""position"">0</property>"
          & "</packing>"
          & "</child>"
          & "<child>"
          & "<object class=""GtkButton"" id=""boutonCLT"">"
          & "<property name=""label"" translatable=""yes"">Changer le texte</property>"
          & "<property name=""visible"">True</property>"
          & "<property name=""can_focus"">True</property>"
          & "<property name=""receives_default"">True</property>"
          & "<property name=""use_action_appearance"">False</property>"
          & "<signal name=""clicked"" handler=""SI_appui_changer_le_texte"" swapped=""no""/>"
          & "</object>"
          & "<packing>"
          & "<property name=""expand"">True</property>"
          & "<property name=""fill"">True</property>"
          & "<property name=""position"">1</property>"
          & "</packing>"
          & "</child>"
          & "</object>"
          & "</child>"
          & "</object>"
          & "</interface>" ;
   -- NOTA: chaque double guillemet dans la chaine compte pour 1 seul caractère (mécanisme d'échappement de Ada).

begin
[…]
   -- Import_glade_OK := Gtk.Builder.Add_From_File(Builder => mon_interface, Filename => "interface2.glade");
   Import_glade_OK := Gtk.Builder.Add_From_String(Builder=> mon_interface, Buffer=> mon_interface_texte, Length => mon_interface_texte'Length) ;
[…]
end ppal ;

Pour aller (un peu) plus loin

Voici une application encore élémentaire mais un peu plus réaliste, qui prend le prétexte du calcul de la clef de contrôle du numéro de sécurité sociale pour explorer les bases du langage :
Fenêtre de l'application clef INSEE

Rappel : après compilation, pour que l'application fonctionne en étant lancée au terminal depuis son dossier par la commande

$ ./clef_insee

il faut que le fichier interface.glade (ou un lien vers lui) soit dans le même dossier que l'exécutable.

Conclusion

Pour s'initier à Ada, il y a deux difficultés :

Transporté sur un autre système Ubuntu, l'exécutable ppal (version autonome) se lance mais ne peut pas afficher la fenêtre. L'erreur signalée est une dépendance non satisfaite car la bibliothèque gtkada2-24.1 ne peut pas être trouvée (error while loading shared libraries: libgtkada.so.2.24.1: cannot open shared object file: no such file or directory). De fait, gtkada est bien installée sur ce système mais en version 2.24.4. Plus précisément, la configuration installée sur ce système est la suivante :

L'auteur n'a pas trouvé de solution pour embarquer dans l'exécutable les composants de gtkada 2.24.1 nécessaires, ce qui aurait permis de disposer d'un exécutable réellement autonome (ceci n'est d'ailleurs peut être pas souhaitable, pour bénéficier des mises à jour…).

En revanche, le projet s'ouvre sans erreur dans le GPS du nouveau système et la recompilation fournit, sans modification, une application qui fonctionne parfaitement. Si l'application devait être distribuée largement, il conviendrait de fabriquer un paquet deb, par exemple, pour automatiser le chargement de ses dépendances et son installation. Toutefois, il s'agit d'une tache complexe.


logo html 5  Validé avec le vérificateur expérimental du W3C