Licence CC BY-NC-SA

Les signaux

Maintenant que vous savez créer des fenêtres, les organiser, y ajouter des boutons ou des étiquettes, il est temps que tout cela agisse un petit peu. À quoi bon cliquer sur des boutons si rien ne se passe ? Nous allons devoir tout d'abord nous intéresser à la gestion bas niveau des évènements pour comprendre le fonctionnement de GTK. Puis nous retournerons au haut niveau et à la programmation évènementielle à proprement parler.

Ce chapitre est fondamental pour la suite, mais il est ardu. N'hésitez pas à le relire ou à revenir piocher dedans à l'avenir car il faut être précis sur les types ou sous-packages employés (et ils sont nombreux). GNAT sera impitoyable si vous ne respectez pas scrupuleusement mes conseils.

Le principe

Les problèmes de la programmation évènementielle

Le titre de cette partie commençait par «la programmation évènementielle», je crois. Eh bien on ne peut pas dire qu'il y ait beaucoup d'évènements dans nos programmes dernièrement. Comment je fais pour lancer mes procédures et mes fonctions ?

On serait tenté d'écrire ce genre de chose :

1
2
3
IF Bouton1.Is_clicked 
   THEN Ma_Procedure ;
END IF ;

Le souci, c'est que tout notre code consiste à créer et paramétrer des widgets, les afficher et enfin lancer une boucle infinie (Main). Où écrire ces quelques lignes ? Le programme ne met que quelques millisecondes à créer les widgets et à les afficher, cela ne laisse pas vraiment le temps à l'utilisateur d'aller cliquer sur un bouton. Et puis un clic ne dure pas plus d'une seconde, comment faire pour que le programme exécute cette ligne de test durant cette fraction de seconde ? C'est impossible.

Le problème est même plus profond que cela. Nous codions jusqu'ici de manière linéaire. Certes, nous avions les conditions pour exécuter telle ou telle instruction, les boucles pour répéter ou pas certaines portions de code, les tâches pour exécuter plusieurs programmes en même temps. Mais tout cela restait très prévisible : l'utilisateur ne pouvait sortir du cadre très fermé que vous lui aviez concocté à grands coups de IF, CASE, LOOP, TASK… Avec nos programmes fenêtrés, nous ne pouvons savoir quand l'utilisateur cliquera sur notre bouton, ni si il le fera, ni même s'il aura cliqué sur d'autres widgets auparavant. Vous êtes en réalité dans l'inconnu.

Et si on créait une grande boucle infinie qui testerait en continu les différentes actions possibles, comme nous l'avions fait au TP sur la bataille navale ?

Une boucle infinie ? C'est une idée. Mais nous disposons déjà de ce genre de boucle : Main ! Main contrôle en permanence les différents widgets créés et vérifie s'ils ne sont pas cliqués, déplacés, survolés… Bref, Main attend le moindre évènement pour réagir.

Le principe Signal-Procédure de rappel

Pour comprendre comment faire agir nos widgets, vous devez comprendre le principe de fonctionnement bas-niveau de GTK. Lorsqu'un évènement se produit, par exemple lorsque l'utilisateur clique sur un bouton, un signal est émis.

Émission d'un signal

Aussitôt, ce signal est intercepté par le Main qui va associer le widget et le type de signal émis à une procédure particulière, appelée procédure de rappel ou callback.

GTK associe un callback au signal émis

Mais comment Main peut-il savoir quelle procédure il doit appeler lorsque l'on clique sur un bouton ?

Pour que votre programme puisse faire le lien, il faut que vous ayez préalablement connecté le widget en question et son signal à cette fameuse procédure de rappel. C'est ce à quoi nous allons nous attacher maintenant.

Connecter un signal à un callback

Fermer proprement le programme

Code de base et package

Notre premier projet est simple : créer une fenêtre avec un unique bouton. Lorsque l'utilisateur cliquera sur le bouton ou sur le bouton de fermeture de la fenêtre, cela mettra fin au programme. Pour cela nous n'aurons pas à créer de procédure de rappel, nous ferons appel à des procédures préexistantes. Voici donc notre code de base (que vous devriez pouvoir déchiffrer seul désormais) :

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


PROCEDURE MaFenetre IS
   Win     : Gtk_Window ;
   Btn1    : Gtk_Button ;
BEGIN
   Init ;                                 --Initialisation de GTK
   Gtk_New(Win,Window_Toplevel) ;         --Création et paramétrage de la fenêtre
   Win.Set_Title("Fenetre") ;
   win.set_default_size(200,150) ;

   Gtk_New_With_Mnemonic(Btn1, "_Exit") ; --Création d'un bouton et ajout à la fenêtre
   win.add(Btn1) ;

   Win.Show_all ;                         --Affichage et lancement de la boucle d'évènements
   Main ;
END MaFenetre ;

Pour manipuler nos divers widget, nous aurons besoin du package Gtk.Handlers (pour rappel, le verbe anglais «to handle» signifie «manipuler»). Attention ce package en lui-même ne contient presque rien d'utilisable. Si vous ouvrez ses spécifications, vous vous rendrez compte qu'il contient en fait tout un ensemble d'autres sous-packages génériques. Je vous expliquerai les détails plus tard. Sachez pour l'instant que nous avons besoin du sous-package générique Gtk.Handlers.Callback. Il n'a qu'un seul paramètre de généricité : le type Widget_Type dont le nom me semble assez clair.

Nous devons donc l'instancier en créant un nouveau package P_Callback. Pour l'instant, nous n'allons fournir en paramètre que le type GTK_Window_Record et nous verrons pour le bouton un peu plus tard.

1
2
PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Window_Record) ; 
USE P_Callback ;

Attention, le type fourni est bien GTK_Window_Record et pas GTK_Window qui est, rappelons-le, un type pointeur !

La procédure de connexion

Vous devriez trouver à la ligne 847 de Gtk.Handlers les spécifications d'une procédure essentielle :

1
2
3
4
5
procedure Connect 
(   Widget  : access Widget_Type'Class;
    Name    : Glib.Signal_Name;
    Cb  : Simple_Handler;
    After   : Boolean := False);

C'est cette procédure qui va connecter le widget et le signal qu'il émet avec une procédure de votre choix. Il est important de bien regarder sa spécification si vous ne voulez pas que GNAT soit désagréable avec vous. Tout d'abord le widget : on attend un pointeur sur une classe de widget. Autrement dit, dans notre cas présent, un objet de type GTK_Window (et pas GTK_Window_Record). Ensuite, le paramètre Name correspond au nom du signal émis. Ce nom est en fait un simple string que je vous donnerai. Le dernier paramètre (After) ne nous servira que si vous voulez imposer que la connexion ne se fasse qu'une fois toutes les connexions par défaut effectuées. Bref, aucun intérêt à notre niveau. Enfin, nous arrivons au paramètre Cb de type Simple_Handler. Ce type correspond à un pointeur sur une procédure prenant en paramètre un pointeur sur une classe de widget (le widget grâce auquel vous avez instancié Gtk.Handlers.Callback). C'est donc ce paramètre qui enregistrera la procédure à connecter.

Vous devrez être le plus rigoureux possible lorsque vous utiliserez la procédure de connexion. Le moindre écart posera soucis à la compilation ! Alors n'hésitez par à revenir lire cette partie plusieurs fois.

La procédure de rappel

Nous allons créer une procédure pour sortir de la boucle d'évènements : Stop_Program. Cette procédure utilisera l'instruction Main_Quit qui met fin au Main. La logique voudrait que cette procédure n'ait aucun paramètre. Pourtant, si j'ai préalablement attiré votre attention sur la procédure connect(), c'est pour que vous compreniez comment définir votre callback. connect() va non seulement connecter un signal à une procédure, mais il transmettra également le widget appelant à ladite procédure si ce signal est détecté. Nous voilà donc obligés d'ajouter un paramètre à notre callback. Quant à son type, il suffit de regarder les spécifications de connect() pour le connaître : « ACCESS Widget_Type'Class » ! Ce qui nous donnera :

1
2
3
4
PROCEDURE Stop_Program(Emetteur : access Gtk_Window_Record'class) IS
BEGIN
   Main_Quit;
END Stop_Program ;

Si vous compiler, GNAT ne cessera de vous dire : "warning : formal parameter "Emetteur" is not referenced". Ce n'est pas une erreur rédhibitoire mais c'est toujours gênant de laisser traîner ces avertissements. Alors pour indiquer à GNAT que le paramètre Emetteur est sciemment inutilisé, vous pourrez ajouter le PRAGMA unreferenced :

1
2
3
4
5
PROCEDURE Stop_Program(Emetteur : access Gtk_Window_Record'class) IS
   PRAGMA Unreferenced (Emetteur);
BEGIN
   Main_Quit;
END Stop_Program ;

La connexion, enfin !

Le package est instancié et le callback rédigé. Il ne reste plus qu'à effectuer la connexion à l'aide de la procédure vue plus haut. Pour cela, vous devez connaître le nom du signal de fermeture de la fenêtre : "destroy". Ce signal est envoyé par n'importe quel widget lorsqu'il est détruit. Notre code donne donc :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
WITH Gtk.Main ;          USE Gtk.Main ;
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gtk.Button ;        USE Gtk.Button ;
WITH Gtk.Handlers ; 


PROCEDURE MaFenetre IS
      --Instanciation du package pour la connexion
   PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Window_Record) ; 
   USE P_Callback ; 
      --Notre callback
   PROCEDURE Stop_Program(Emetteur : access Gtk_Window_Record'class) IS
      PRAGMA Unreferenced (Emetteur);
   BEGIN
      Main_Quit;
   END Stop_Program ;

   Win     : Gtk_Window ;
   Btn1    : Gtk_Button ;
BEGIN
   Init ;
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Fenetre") ;
   win.set_default_size(200,150) ;

   Gtk_New_With_Mnemonic(Btn1, "_Exit") ; 
   win.add(Btn1) ;

      --Connexion du signal "destroy" avec le callback Stop_program
      --Pensez que le signal est un string, non un type énuméré
      --N'oubliez pas que le 3° paramètre doit être un pointeur
   connect(win, "destroy", Stop_Program'access) ;
      --en cas de souci, essayez ceci : 
      --connect(win, "destroy", to_Marshaller(Stop_Program'access)) ;
   Win.Show_all ;
   Main ;
END MaFenetre ;

Utiliser le bouton

Nous voudrions maintenant pouvoir faire de même avec notre bouton. Le souci, c'est que la procédure connect() a pour nom complet P_Callback.connect() et que le package P_Callback a été instancié pour les objets de la classe GTK_Window_Record'class. Deux solutions s'offrent à nous : soit créer un second package P_Callback_Button, soit améliorer le premier.

J'opte pour la deuxième méthode. Il suffit d'utiliser une classe commune aux GTK_Window_Record et aux GTK_Button_Record. Vous savez désormais que fenêtres et boutons sont deux widgets. Nous pourrions donc utiliser la classe GTK_Widget_Record'class et citer le package Gtk.Widget. Mais nous savons également que ce sont deux conteneurs (GTK_Container_Record'class) et même des conteneurs pour un seul élément (GTK_Bin_Record'class). Nous pouvons donc généraliser notre package de trois façons différentes. Les voici, de la plus restrictive à la plus large :

 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.Bin ;           USE Gtk.Bin ; 
 ...
PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Bin_Record) ; 
USE P_Callback ; 

PROCEDURE Stop_Program(Emetteur : access Gtk_Bin_Record'class) IS ...


                        -- OU


WITH Gtk.Container ;           USE Gtk.Container ; 
 ...
PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Container_Record) ; 
USE P_Callback ; 

PROCEDURE Stop_Program(Emetteur : access Gtk_Container_Record'class) IS ...


                        -- OU


WITH Gtk.Widget ;           USE Gtk.Widget ; 
 ...
PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; 
USE P_Callback ; 

PROCEDURE Stop_Program(Emetteur : access Gtk_Widget_Record'class) IS ...

Ne reste plus qu'à effectuer la connexion. Si peu de signaux sont intéressants pour l'instant avec les fenêtres, avec les boutons, nous avons davantage de choix. Voici quelques signaux :

  • "clicked" : le bouton a été cliqué, c'est-à-dire enfoncé puis relâché.
  • "pressed" : le bouton a été enfoncé
  • "released" : le bouton a été relâché
  • "enter" : la souris survole le bouton
  • "leave" : la souris ne survole plus le bouton

Voici ce que donnerait notre programme :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
WITH Gtk.Main ;          USE Gtk.Main ;
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gtk.Button ;        USE Gtk.Button ;
WITH Gtk.Bin ;           USE Gtk.Bin ; 
WITH Gtk.Handlers ; 


PROCEDURE MaFenetre IS

   PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Bin_Record) ; 
   USE P_Callback ; 

   PROCEDURE Stop_Program(Emetteur : access Gtk_Bin_Record'class) IS
      PRAGMA Unreferenced (Emetteur);
   BEGIN
      Main_Quit;
   END Stop_Program ;

   Win     : Gtk_Window ;
   Btn1    : Gtk_Button ;
BEGIN
   Init ;
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Fenetre") ;
   win.set_default_size(200,150) ;

   Gtk_New_With_Mnemonic(Btn1, "_Exit") ; 
   Win.Add(Btn1) ;

   Connect(Win, "destroy", To_Marshaller(Stop_Program'ACCESS)) ;
   Connect(Btn1, "clicked", To_Marshaller(Stop_Program'ACCESS)) ;


   Win.Show_all ;
   Main ;
END MaFenetre ;

Interagir avec les widgets

Un callback à deux paramètres

Fermer le programme, c'est facile : il suffit d'une seule instruction. Mais comment je fais si je veux créer des callbacks modifiant d'autres widgets ?

Pour y voir clair, nous allons créer un programme «Hello world». L'idée est simple : notre fenêtre affichera une étiquette nous saluant uniquement si nous appuyons sur un bouton. Nous avons donc besoin de créer une fenêtre contenant une étiquette, un bouton et bien-sûr un conteneur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
WITH Gtk.Main ;          USE Gtk.Main ;
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gtk.Button ;        USE Gtk.Button ;
WITH Gtk.Label ;         USE Gtk.Label ; 
WITH Gtk.Box ;           USE Gtk.Box ;
WITH Gtk.widget ;        USE Gtk.widget ; 
WITH Gtk.Handlers ; 


PROCEDURE MaFenetre IS
      --SOUS PACKAGES POUR MANIPULER LES CALLBACKS
   PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; 
   USE P_Callback ;

      --CALLBACKS
   PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) IS
      PRAGMA Unreferenced (Emetteur );
   BEGIN
      Main_Quit;
   END Stop_Program ;

      --WIDGETS
   Win     : Gtk_Window ;
   Btn     : Gtk_Button ;
   Lbl     : Gtk_Label ;
   Box     : Gtk_Vbox ;
BEGIN
   Init ;

      --CREATION DE LA FENETRE
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Fenetre") ;
   win.set_default_size(200,150) ;

      --CREATION DU BOUTON ET DE L'ETIQUETTE
   Gtk_New_With_Mnemonic(Btn, "Dis _bonjour !") ; 
   Gtk_New(Lbl,"") ; 

      --CREATION DE LA BOITE
   Gtk_New_Vbox(Box) ; 
   Box.Pack_Start(Lbl) ; 
   Box.Pack_Start(Btn) ; 
   Win.Add(Box) ; 

      --CONNEXION AVEC LE CALLBACK DE FERMETURE
   Connect(Win, "destroy", Stop_Program'ACCESS) ;

   Win.Show_all ;
   Main ;
END MaFenetre ;

Ce code n'est pas très compliqué, vous devriez pouvoir le comprendre seul. Maintenant, nous allons créer un callback supplémentaire appelé Dis_Bonjour() qui modifiera une étiquette (ne nous préoccupons pas de la connexion pour l'instant) :

1
2
3
4
5
6
7
8
9
PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ;
                      Label : Gtk_Label) IS
   PRAGMA Unreferenced(Emetteur) ;
BEGIN
   IF Label.Get_Text'Length = 0
         THEN Label.Set_Text("Hello world !") ;
         ELSE Label.Set_Text("Ouais, ouais ... salut !") ;
   END IF ;
END Dis_Bonjour;

J'utilise deux méthodes de la classe GTK_Label_Record : Set_text() qui modifie le texte de l'étiquette et Get_Text() qui renvoie le contenu. Rien de bien compliqué mais un souci toutefois : nous avons besoin de deux paramètres !

Un nouveau package de callbacks

Or, pour l'instant notre package P_Callback n'est instancié qu'avec un seul type : la classe GTK_Widget_Record du widget émetteur. Cela signifie que nos callbacks ne peuvent avoir qu'un unique paramètre de ce même type. Comment faire pour accéder à un second widget ? Plusieurs solutions s'offrent à vous. L'une d'elles consiste à placer nos widgets dans un package spécifique pour en faire des variables globales et ainsi ne plus avoir à les déclarer comme paramètre formel de nos méthodes. Vous pourrez ainsi facilement accéder à votre GTK_Label. Toutefois, vous savez que je n'aime pas abuser des variables globales. D'autant plus que les GTK_Widget, GTK_Button ou GTK_Label sont, vous le savez des pointeurs généralisés : nous multiplions les prises de risque.

Pour résoudre ce dilemne, GtkAda fournit toute une série de package pour nos callbacks. L'un d'entre eux, Gtk.Handlers.User_Callback permet de passer un paramètre supplémentaire à nos procédures de connexion (il faut donc l'instancier à l'aide de deux types). Ces deux paramètres sont simplement ceux fournis à notre callback : GTK_Widget_Record pour l'émetteur (le premier paramètre de notre callback est un pointeur vers ce type) ; GTK_Label pour le second paramètre. D'où la définition :

1
2
PACKAGE P_UCallback IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record,Gtk_Label) ;
USE P_Ucallback ;

La connexion

La connexion se fait de la même manière que précédemment : en utilisant la procédure connect(). Toutefois celle-ci admettra un paramètre supplémentaire :

1
2
3
4
5
6
procedure Connect(
        Widget    : access Widget_Type'Class;
    Name      : Glib.Signal_Name;
    Marsh     : Marshallers.Marshaller;
    User_Data : User_Type;
    After     : Boolean := False);

On retrouve les paramètres habituels, sauf User_Data qui ici correspond au type GTK_Label employé par votre callback. Voici ce que donne notre code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
WITH Gtk.Main ;          USE Gtk.Main ;
WITH Gtk.Window ;        USE Gtk.Window ;
WITH Gtk.Enums ;         USE Gtk.Enums ;
WITH Gtk.Button ;        USE Gtk.Button ;
WITH Gtk.Label ;         USE Gtk.Label ; 
WITH Gtk.Box ;           USE Gtk.Box ;
WITH Gtk.widget ;        USE Gtk.widget ; 
WITH Gtk.Handlers ; 


PROCEDURE MaFenetre IS
      --SOUS PACKAGES POUR MANIPULER LES CALLBACKS
   PACKAGE P_Callback IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ; 
   USE P_Callback ;
   PACKAGE P_UCallback IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record, Gtk_Label) ;
   USE P_Ucallback ; 

      --CALLBACKS
   PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) IS
      PRAGMA Unreferenced (Emetteur );
   BEGIN
      Main_Quit;
   END Stop_Program ;

   PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ;
                         Label  : GTK_Label) IS
      PRAGMA Unreferenced(Emetteur ) ; 
   BEGIN
      IF String(Label.Get_Text)'Length = 0
            THEN Label.Set_Text("Hello world !") ; 
            else Label.Set_Text("Ouais, ouais ... salut !") ; 
      END IF ; 
   END Dis_Bonjour;

      --WIDGETS
   Win     : Gtk_Window ;
   Btn     : Gtk_Button ;
   Lbl     : Gtk_Label ;
   Box     : Gtk_Vbox ;
BEGIN
   Init ;

      --CREATION DE LA FENETRE
   Gtk_New(Win,Window_Toplevel) ;
   Win.Set_Title("Fenetre") ;
   win.set_default_size(200,150) ;

      --CREATION DU BOUTON ET DE L'ETIQUETTE
   Gtk_New_With_Mnemonic(Btn, "Dis _bonjour !") ; 
   Gtk_New(Lbl,"") ; 

      --CREATION DE LA BOITE
   Gtk_New_Vbox(Box) ; 
   Box.Pack_Start(Lbl) ; 
   Box.Pack_Start(Btn) ; 
   Win.Add(Box) ; 

      --CONNEXION AVEC LES CALLBACKS
   Connect(Win, "destroy", Stop_Program'ACCESS) ;
   Connect(Btn, "clicked", Dis_Bonjour'ACCESS, Lbl); 

   Win.Show_all ;
   Main ;
END MaFenetre ;

Mes fenêtres Hello World

Perfectionner encore notre code

Mais ça veut dire que si je voulais ajouter un deuxième bouton qui modifierait un autre widget (une image par exemple), il faudrait créer un nouveau sous package du genre : PACKAGE P_UCallback2 IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record, Un_Autre_Type_De_Widget) ; ! Ça va devenir encombrant à la longue tous ces packages, non? :(

Vous avez raison, nous devons optimiser notre code pour ne pas créer plusieurs instances d'un même package. Et pour cela, nous allons réutiliser les types contrôlés ! Si, comme je vous l'avais conseillé, vous placez tous vos widgets dans un type contrôlé, accompagné d'une méthode Initialize(), vous n'aurez dès lors plus qu'une seule instanciation à effectuer :

 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 Gtk.Button ;        USE Gtk.Button ;
WITH Gtk.Label ;         USE Gtk.Label ;
WITH Gtk.Box ;           USE Gtk.Box ;
WITH Gtk.Widget ;        USE Gtk.Widget ;
WITH Ada.Finalization ;  USE Ada.Finalization ;
WITH Gtk.Handlers ;

PACKAGE P_Fenetre IS

   TYPE T_Fenetre IS NEW Controlled WITH RECORD
      Win     : Gtk_Window ;
      Btn     : Gtk_Button ;
      Lbl     : Gtk_Label ;
      Box     : Gtk_Vbox ;
   END RECORD ;

   PROCEDURE Initialize(F : IN OUT T_Fenetre) ;

   PACKAGE P_Handlers IS NEW Gtk.Handlers.Callback(Gtk_Widget_Record) ;
   USE P_Handlers ;
   PACKAGE P_UHandlers IS NEW Gtk.Handlers.User_Callback(Gtk_Widget_Record,T_Fenetre) ;
   use P_UHandlers ;

END P_Fenetre ;

P_Fenetre.ads

 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
WITH Gtk.Main ;          USE Gtk.Main ;
WITH P_Callbacks ;       USE P_Callbacks ;

PACKAGE BODY P_Fenetre IS

   PROCEDURE Initialize(F : IN OUT T_Fenetre) IS
   BEGIN
      Init ;
      Gtk_New(GTK_Window(F.Win),Window_Toplevel) ;
      F.Win.Set_Title("Fenetre") ;
      F.win.set_default_size(200,150) ;

      Gtk_New_With_Mnemonic(F.Btn, "Dis _bonjour !") ;
      Gtk_New(F.Lbl,"") ;

      Gtk_New_Vbox(F.Box) ;
      F.Box.Pack_Start(F.Lbl) ;
      F.Box.Pack_Start(F.Btn) ;
      F.Win.Add(F.Box) ;

      Connect(F.Win, "destroy", Stop_Program'ACCESS) ;
      Connect(F.Btn, "clicked", Dis_Bonjour'ACCESS, F) ;

      F.Win.Show_all ;
   END Initialize ;

END P_Fenetre ;

P_Fenetre.adb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
WITH Gtk.Widget ;                USE Gtk.Widget ;
with P_Fenetre ;                 use P_Fenetre ;


PACKAGE P_Callbacks IS

   PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) ;
   PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ;
                         F : T_Fenetre) ;
END P_Callbacks ;

P_Callbacks.ads

 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
WITH Gtk.Main ;                USE Gtk.Main ;
WITH Gtk.Label ;                 USE Gtk.Label ;


PACKAGE BODY P_Callbacks IS


   PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) IS
      PRAGMA Unreferenced (Emetteur );
   BEGIN
      Main_Quit;
   END Stop_Program ;


   PROCEDURE Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ;
                         F : T_Fenetre) IS
      PRAGMA Unreferenced(Emetteur ) ;
   BEGIN
      IF F.Lbl.Get_Text'Length = 0
            THEN F.Lbl.Set_Text("Hello world !") ;
            else F.Lbl.Set_Text("Ouais, ouais ... salut !") ;
      END IF ;
   END Dis_Bonjour;

END P_Callbacks ;

P_Callbacks.adb

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


PROCEDURE MaFenetre IS
   F     : T_Fenetre ;
   pragma Unreferenced(F) ;
BEGIN
   Main ;
END MaFenetre ;

MaFenetre.adb

Autres packages de callback

À quoi servent les autres sous-packages de Gtk.Handlers ?

Le package Gtk.Handlers comprend 6 sous-packages de callback :

  • Return_Callback : ce package permet d'employer des fonctions comme callbacks et pas des procédures. Elle prend en paramètre le type de widget émetteur, bien entendu, ainsi que le type des résultats retournés par les fonctions.
  • User_Return_Callback : similaire au package User_Callback utilisé dans cette sous-partie, mais pour des fonctions de rappel, il prend en paramètre la classe du widget émetteur, le type des résultats retournés par les fonctions de rappel, ainsi que le type User_Type du paramètre supplémentaire (notre GTK_Label dans l'exemple ci-dessus).
  • User_Return_Callback_With_Setup : similaire au package précédent, il permet toutefois de fournir une procédure d'initialisation des objets de type User_Type.
  • Callback : vu dans la sous-partie n°2.
  • User_Callback : vu dans la sous-partie n°3.
  • User_Callback_With_Setup : similaire au package User_Callback mais fournissant une procédure d'initialisation des objets de type User_Type.

GDK et les événements

Le clic droit

Les signaux de GTK.Widget

Tu ne nous as pas dit comment gérer les clic droits sur un bouton?

Les signaux dont je vous ai parlé jusque là sont ceux liés aux boutons, on les trouve dans le package Gtk.Button. Mais aucun signal lié aux GTK_Button ne prend en compte le clic-droit. Il faut donc chercher un peu plus dans les tréfonds de GTK pour y parvenir. Tout d'abord, nous savons que les boutons ne sont rien d'autre que des widgets, ou pour parler le langage de GTK, ce sont des GTK_Widget. Grâce aux principes de la POO, nous savons que les GTK_Button héritent par dérivation des méthodes des GTK_Widget. Nous n'avons donc qu'à fouiner dans le package GTK.Widget pour y trouver ce que nous cherchons. Malheureusement, les seuls signaux que nous y trouvons et qui soient liés à la souris sont les suivants :

1
2
Signal_Button_Press_Event      : constant Glib.Signal_Name := "button_press_event";
Signal_Button_Release_Event    : constant Glib.Signal_Name := "button_release_event";

Le premier est émis lorsqu'un bouton de la souris est enfoncé, le second lorsqu'il est relâché. Mais ça ne nous avance pas sur la question : est-ce un clic gauche ou droit ? Sans compter que certaines souris ont plus de 2 boutons ! C'est pourtant un début. Nous allons donc modifier la connexion entre notre bouton et la procédure Dis_Bonjour en utilisant ce signal :

1
2
3
Connect(Btn, "button_press_event",      Dis_Bonjour'ACCESS, F); 
         -- OU
Connect(Btn, Signal_Button_Press_Event, Dis_Bonjour'ACCESS, F);

P_Fenetre.adb

User_Return_Callback, nous voilà !

GNAT compile sans sourciller mais lorsque nous lançons notre programme, une jolie exception est levée :

raised SYSTEM.ASSERTIONS.ASSERT_FAILURE : Handlers for "button_press_event" on a GtkButton should be functions

Autrement dit, pour les non anglophones, avec ce signal et ce type de widget nous ne pouvons utiliser comme callback que des fonctions ! Et pour être plus précis, des fonctions renvoyant un booléen. En effet, GTK souhaite savoir s'il doit jouer ou non la petite animation du bouton qui s'enfonce lors du clic sur le GTK_Button. Cela remet en cause une bonne partie de notre code et notamment le package utilisé. Nous allons devoir abandonné le package Gtk.Handlers.User_Callback pour le package Gtk.Handlers.User_Return_Callback :

1
2
PACKAGE P_UHandlers IS NEW Gtk.Handlers.User_Return_Callback(Gtk_Widget_Record, Boolean, T_Fenetre) ;
USE P_UHandlers ;

P_Fenetre.ads

Le premier paramètre pour instancier notre package est toujours le type du widget émetteur, le second correspond au type du résultat retournée par les callbacks, enfin le dernier type correspond au type du paramètre de la fonction de callback. Il nous faut désormais en venir à notre procédure et la transformer en fonction adéquate :

1
2
FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ;
                     F        : T_Fenetre) RETURN Boolean ;

P_Callback.ads

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FUNCTION Dis_Bonjour(Emetteur : ACCESS GTK_Widget_Record'Class ;
                     F        : T_Fenetre) RETURN Boolean IS
   PRAGMA Unreferenced(Emetteur ) ;
BEGIN
   IF F.Lbl.Get_Text'Length = 0
         THEN F.Lbl.Set_Text("Hello world !") ;
         ELSE F.Lbl.Set_Text("Ouais, ouais... salut !") ;
   END IF ;
   RETURN True ; 
END Dis_Bonjour;

P_Callback.adb

Compilez et lancez votre programme : tout fonctionne de nouveau, à un détail près. Regardez attentivement le bouton «Dis bonjour !» et cliquez dessus. Certes le texte s'affiche convenablement au-dessus, mais le bouton reste immobile ! Comme si ce n'était qu'une simple image. La raison vient de la ligne 20 du fichier P_Callback.adb : nous avons renvoyé TRUE. Gtk considère que tout s'est bien passé durant notre callback et que celui-ci s'occupe de tout, notamment de l'aspect du bouton. Remplacez cette ligne par «RETURN FALSE ;» et vous verrez que ce détail s'arrangera.

Distinguer gauche et droite

Si je comprends bien, j'ai tout modifié pour en arriver au même point qu'avant ?

Pas tout à fait. Si vous testez votre programme, vous vous apercevrez que désormais, un clic droit activera le callback Dis_Bonjour() tout aussi bien qu'un clic gauche ou qu'un clic avec un autre bouton de la souris (la molette par exemple). Un clic droit active donc notre callback, il ne nous reste plus qu'à distinguer la gauche et la droite.

Pour ce faire, nous allons continuer notre exploration de GTK et même parcourir les bibliothèques de GDK ! Plus exactement, nous allons avoir besoin du package GDK-Event.ads que je vous conseille d'ouvrir pour suivre la fin de ce chapitre. À la ligne 229 de ce même package, vous trouverez le type suivant :

1
type Gdk_Event is new Gdk.C_Proxy;

Les GDK_Event sont des événements. Pour simplifier, disons qu'il s'agit de la version bas niveau des signaux. Lorsqu'un GDK_Event est émis, la boucle Main l'interprète et émet le signal correspondant. L'idée serait donc d'analyser l'événement qui a émis le Signal_Button_Press_Event à défaut de l'intercepter nous-même. Nous allons donc ajouter un nouveau paramètre à notre callback : Evenement de type GDK_Event.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
WITH Gtk.Widget ;                USE Gtk.Widget ;
WITH P_Fenetre ;                 USE P_Fenetre ;
WITH GDK.Event ;                 USE GDK.Event ; 


PACKAGE P_Callbacks IS

   PROCEDURE Stop_Program(Emetteur : access Gtk_widget_Record'class) ;
   FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                        Evenement : GDK_Event ;
                        F         : T_Fenetre) RETURN Boolean ;
END P_Callbacks ;

P_Callbacks.ads

Vous vous demandez comment nous allons pouvoir récupérer cet événement ? Soyez patients, faites comme si ce problème n'en était pas un et fouillons un peu dans nos packages. Nous souhaiterions connaître quel bouton a été cliqué ? Une fonction Get_Button() existe à la ligne 418 de Gdk-event.ads. Celle-ci analyse un événement et renvoie le numéro du bouton qui a été cliqué. Venons-en au corps de notre callback :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                     Evenement : GDK_Event ;
                     F         : T_Fenetre) RETURN Boolean IS
   PRAGMA Unreferenced(Emetteur ) ;
BEGIN
   CASE Get_Button(Evenement) IS
      WHEN 1 => F.Lbl.Set_Text("Hello world !") ;
      WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ;
      WHEN OTHERS => F.Lbl.Set_Text("") ;
   END CASE ;
   RETURN False ; 
END Dis_Bonjour;

p_callbacks.adb

Désormais, notre programme affichera "Hello world !" lors d'un clic gauche et "Ouais, ouais... salut !" lors d'un clic droit. Un clic avec tout autre bouton, comme la molette, effacera le contenu de l'étiquette.

Connaître l'événement grâce aux Marshallers

Mais ?!? GNAT n'accepte plus la connexion entre mon GTK_Button et mon callback ? Comment lui dire qu'il y a un paramètre en plus pour Dis_Bonjour ? Et quand est-ce que l'on peut obtenir ce GDK_Event pour de vrai ?

Pas de panique. On ne va pas chercher à obtenir cet événement puisqu'il a déjà été capturé afin d'émettre le signal Signal_Button_Press_Event. Lorsque notre callback sera déclenché, il sera trop tard pour obtenir l'événement et si nous cherchons à le capturer, cela empêchera l'exécution du callback. La seule solution est de faire appel aux fonctions To_Marshaller(). Elles vont nous permettre de transformer un simple pointeur sur fonction en un Marshaller, type qui permet d'embarquer davantage d'informations. Vous n'y comprenez rien, alors voyez l'exemple suivant :

1
Connect(F.Btn, "button_press_event", to_Marshaller(Dis_Bonjour'ACCESS), F) ;

P_Fenetre.adb

Vous remarquerez que je n'ai ajouté aucun paramètre pour le GDK_Event. J'ai simplement converti mon pointeur sur Dis_Bonjour en Marshaller. Testez ce code et dites m'en des nouvelles.

Le double clic

Le GDK_Event_Type

Et pour un double clic, on fait comment ?

Bonne question. Jetez un œil aux toutes premières lignes du package GDK-Event.ads. Vous allez y trouver un type très intéressant, Gdk_Event_Type, dont voici la liste des valeurs :

1
2
3
4
5
6
7
8
type Gdk_Event_Type is (Nothing, Delete, Destroy, Expose, Motion_ Notify, Button_Press, 
                        Gdk_2button_Press, Gdk_3button_Press, Button_Release, Key_Press, 
                        Key_Release, Enter_Notify, Leave_Notify, Focus_Change, Configure, 
                        Map, Unmap, Property_Notify, Selection_Clear, Selection_Request, 
                        Selection_Notify, Proximity_In, Proximity_Out, Drag_Enter,
                        Drag_Leave, Drag_Motion, Drag_Status, Drop_Start, Drop_Finished, 
                        Client_Event, Visibility_Notify, No_Expose, Scroll, Window_State, 
                        Setting, Owner_Change, Grab_Broken );

Il s'agit des différents types d'événements de bases qu'un GDK_Event peut prendre. Nous n'allons pas tous les voir mais si vous êtes attentif vous y trouverez button_Press, l'événement pour un simple clic de souris, Gdk_2button_Press, l'événement pour un double clic de souris, Gdk_3button_Press, pour un triple clic, Button_Release, pour un bouton de souris relâché, Key_Press, pour une touche de clavier enfoncée ou Key_Release, pour une touche de clavier relâchée. Nous verrons les deux dernières juste après ; concentrons-nous sur Gdk_2button_Press et Gdk_3button_Press.

Nous souhaiterions que notre callback affiche une phrase en espagnol lors d'un double clic. Nous devons donc distinguer les clics simples des clics doubles (le principe sera le même pour les triples clics) et pour cela connaître le Gdk_Event_Type de notre paramètre Evenement. Nous utiliserons pour cela une méthode de Gdk.Event :

1
function Get_Event_Type (Event : Gdk_Event) return Gdk_Event_Type;

Voici ce que pourrait donner notre callback, revu et corrigé «para hablar Español» :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                     Evenement : GDK_Event ;
                     F         : T_Fenetre) RETURN Boolean IS
   PRAGMA Unreferenced(Emetteur ) ;
   Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ;
BEGIN
   IF Type_Evenement = Button_Press
         THEN CASE Get_Button(Evenement) IS
                 WHEN 1 => F.Lbl.Set_Text("Hello world !") ;
                 WHEN 3 => F.Lbl.Set_Text("Ouais, ouais... salut !") ;
                 WHEN OTHERS => F.Lbl.Set_Text("") ;
              END CASE ;
   ELSIF Type_Evenement = Gdk_2button_Press
         THEN F.Lbl.Set_Text("Ola ! Como esta ?") ; --Désolé pour les accents manquants.
   END IF ; 
   RETURN False ; 
END Dis_Bonjour;

P_Callbacks.adb

Creusons un peu

Vous savez maintenant gérer les double-clics. Alors, heureux ? Je tiens toutefois à vous montrer quelque-chose. Complétons le code précédent :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                     Evenement : GDK_Event ;
                     F         : T_Fenetre) RETURN Boolean IS
   PRAGMA Unreferenced(Emetteur ) ;
   Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ;
BEGIN
   IF Type_Evenement = Button_Press
         THEN put_line("clic") ; 
              CASE Get_Button(Evenement) IS
                 WHEN 1 => F.Lbl.Set_Text("Hello world !") ;
                 WHEN 3 => F.Lbl.Set_Text("Ouais, ouais... salut !") ;
                 WHEN OTHERS => F.Lbl.Set_Text("") ;
              END CASE ;
   ELSIF Type_Evenement = Gdk_2button_Press
         THEN Put_Line("double clic") ; 
              F.Lbl.Set_Text("Ola ! Como esta ?") ;
   END IF ; 
   RETURN False ; 
END Dis_Bonjour;

P_Callbacks.adb

Compilez, exécutez votre programme et faites un double-clic sur le GTK_Button. Regardez maintenant la console. Elle affiche :

1
2
3
clic
clic
double clic

Autrement dit, le callback a été appelé 3 fois ! Soit une fois pour chaque clic puis une troisième fois pour le double clic. Ce n'est pas bien grave me direz-vous, mais cela doit vous amenez à prendre quelques précautions : si vous souhaitez qu'un double-clic réalise une action, assurez vous qu'un simple clic ne l'en empêche pas.

Et si on fouinait ?

Tant que nous avons le nez dans le package GDK.Event, profitons-en pour regarder quelques autres fonctions entourant Get_Event_Type() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function Get_Time      (Event : Gdk_Event) return Guint32;
   --  Renvoie l'heure à laquelle a eu lieu l'événement

function Get_X         (Event : Gdk_Event) return Gdouble;
function Get_Y         (Event : Gdk_Event) return Gdouble;
   --  Renvoient les coordonnées de la souris au moment de l'événement.
   --  Les coordonnées sont calculées par rapport à la fenêtre mère

function Get_X_Root    (Event : Gdk_Event) return Gdouble;
function Get_Y_Root    (Event : Gdk_Event) return Gdouble;
   --  Comme précédemment, sauf que les coordonnées sont calculées par rapport
   -- à la fenêtre dont est issu l'événement

function Get_Count (Event : Gdk_Event) return Gint;
   -- Renvoie le nombre d'événements en attente.

Le clavier

Reconnaître le clavier

Et maintenant : le clavier, le clavier, le clavier !!!

Vous êtes bien impatients ! Mais il est grand temps de nous en occuper. Nous allons faire appel aux GDK_Event_Type vus précédemment : Key_press et Key_release. Par exemple, si l'utilisateur clique sur une touche du clavier, le programme affichera un message en allemand :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                     Evenement : GDK_Event ;
                     F         : T_Fenetre) RETURN Boolean IS
   PRAGMA Unreferenced(Emetteur ) ;
   Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ;
BEGIN
   IF Type_Evenement = Button_Press
         THEN CASE Get_Button(Evenement) IS
                 WHEN 1 => F.Lbl.Set_Text("Hello world !") ;
                 WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ;
                 WHEN OTHERS => F.Lbl.Set_Text("") ;
              END CASE ;
   ELSIF Type_Evenement = Gdk_2button_Press
         THEN F.Lbl.Set_Text("Ola ! Como esta ?") ;
   ELSIF Type_Evenement = Key_Press
         THEN F.Lbl.Set_Text("Guten tag lieber Freund.") ;
   END IF ;
   RETURN False ;
END Dis_Bonjour;

P_Callbacks.adb

Bien sûr, il est important que le GTK_Button soit sélectionné pour que cela arrive, qu'il ait le focus. Cela se traduit par un cadre en pointillé autour du texte du bouton. Notre programme ne contient qu'un seul bouton, donc le problème ne se pose pas mais pensez-y à l'avenir.

Reconnaître la lettre

Cherchons maintenant à connaître la touche utilisée. Le message sera en allemand seulement si l'utilisateur appuie sur une lettre minuscule. Nous allons utiliser la fonction get_string() qui renvoie le caractère obtenu par la touche du clavier mais sous forme de chaîne de caractère :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                        Evenement : GDK_Event ;
                        F         : T_Fenetre) RETURN Boolean IS
      PRAGMA Unreferenced(Emetteur ) ;
      Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ;
   BEGIN
      IF Type_Evenement = Button_Press
            THEN CASE Get_Button(Evenement) IS
                    WHEN 1 => F.Lbl.Set_Text("Hello world !") ;
                    WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ;
                    WHEN OTHERS => F.Lbl.Set_Text("") ;
                 END CASE ;
      ELSIF Type_Evenement = Gdk_2button_Press
            THEN F.Lbl.Set_Text("Ola ! Como esta ?") ;
      ELSIF Type_Evenement = Key_Press
            THEN IF Get_String(Evenement)(1) in 'a'..'z'
                       THEN F.Lbl.Set_Text("Guten tag lieber Freund.") ;
                       ELSE F.Lbl.Set_Text("") ;
                 END IF ;
      END IF ;
      RETURN False ;
   END Dis_Bonjour;

P_Callbacks.adb

Reconnaître la touche

Cela fonctionne mais un souci se pose : tous les caractères ne sont pas imprimables ! Si l'utilisateur appuie sur Tabulation ou la touche , le programme plante. Mieux vaudrait connaître le code de la touche activée que le caractère correspondant. Cela peut se faire avec la fonction Get_Key_Val() qui renvoie une valeur de type Gdk_Key_Type. Ce type est défini dans le package Gdk.Types, mais la plupart des constantes nécessaires se trouvent dans le package Gdk.Types.keysyms que je vous conseille d'ouvrir à son tour. Vous y trouverez de nombreuses constantes. Par exemple, GDK_Tab (Tabulation), GDK_Return (Entrée), GDK_Escape (Echap), GDK_Delete (Supprimer), GDK_Left, GDK_Right, GDK_Down, GDK_Up (, , et ), GDK_Shift_L ou GDK_Shift_R (Majuscule gauche et Majuscule droite).

Les touches 'A' à 'Z' se nomment GDK_A à GDK_Z. Leurs valeurs, puisque ce ne sont que des nombres, sont comprises entre 16#0041# et 16#005a# (valeurs hexadécimales, vous l'aurez reconnu). Les minuscules, quant à elles se nomment GDK_LC_a, GDK_LC_b… (LC signifiant «lower case»). Leurs valeurs hexadécimales sont comprises entre 16#0061# et 16#007a#.

À nous de jouer maintenant ! Si l'utilisateur appuie sur une lettre minuscule, le programme lui parlera allemand, s'il appuie sur la tabulation, il lui parlera hollandais et sinon il lui parlera danois :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FUNCTION Dis_Bonjour(Emetteur  : ACCESS GTK_Widget_Record'Class ;
                     Evenement : GDK_Event ;
                     F         : T_Fenetre) RETURN Boolean IS
   PRAGMA Unreferenced(Emetteur ) ;
   Type_Evenement : CONSTANT Gdk_Event_Type := Get_Event_Type(Evenement) ;
BEGIN
   IF Type_Evenement = Button_Press
         THEN CASE Get_Button(Evenement) IS
                 WHEN 1 => F.Lbl.Set_Text("Hello world !") ;
                 WHEN 3 => F.Lbl.Set_Text("Ouais, ouais ... salut !") ;
                 WHEN OTHERS => F.Lbl.Set_Text("") ;
              END CASE ;
   ELSIF Type_Evenement = Gdk_2button_Press
         THEN F.Lbl.Set_Text("Ola ! Como esta ?") ;
   ELSIF Type_Evenement = Key_Press
         THEN IF Get_Key_Val(Evenement) in 16#0061#..16#007a#
                 THEN F.Lbl.Set_Text("Guten tag lieber Freund.") ;
              ELSIF Get_Key_Val(Evenement) = GDK_Tab
                 THEN F.Lbl.Set_Text("Goede dag lieve vriend.") ;
                 ELSE F.Lbl.Set_Text("God dag kaere ven.") ;
              END IF ;
   END IF ;
   RETURN False ;
END Dis_Bonjour;

P_Callbacks.adb

Bien sûr, pour que ce code fonctionne vous devrez penser à ajouter les lignes suivantes :

1
2
WITH Gdk.Types ;               USE Gdk.Types; 
WITH Gdk.Types.Keysyms ;       USE Gdk.Types.Keysyms ;

En résumé :

  • Tout évènement sur un widget, aussi anodin soit-il, déclenche l'émission d'un signal. C'est la capture de ce signal qui va permettre le déclenchement d'une procédure de rappel ou callback.
  • Tout callback doit être connecté à un widget et un signal. Cela se fait grâce aux procédures connect() disponibles dans les sous-packages de Gtk.Handlers.
  • Pour obtenir des interactions entre les différents widgets, vous pouvez utiliser le package Gtk.Handlers.User_Callback.
  • Si vous ne trouvez pas votre bonheur dans les signaux proposés, cherchez parmi les signaux des widgets parents ou utilisez les GDK_Event.
  • Faites appel aux types contrôlés et à vos connaissances en POO pour simplifier votre développement.