Les conteneurs II

Nous avons déjà vu les conteneurs, pourquoi y revenir ?

Nous avons effectivement abordé les conteneurs principaux, les plus utiles. Ce chapitre est fait pour que nous abordions quelques conteneurs plus spécifiques de par leur apparence : ils ne sont plus seulement là pour organiser discrètement vos widgets ; ces conteneurs sont faits pour être vus ! Parmi eux, nous verrons les barres d'onglets, les barres de défilement, les panneaux, les cadres, les extensions et les boîtes détachables. Cela complètera votre boîte à outils, même si je ne serai pas aussi exhaustif que pour le précédent chapitre (eh oui, il sera bientôt temps pour vous de voler de vos propres ailes et de lire le manuel :D ).

Les onglets

Fiche d'identité

Le premier conteneur de cette nouvelle série est donc l'onglet, ou plutôt la barre d'onglets. De quoi s'agit-il ? Eh bien c'est un widget que vous utilisez très certainement en ce moment même si vous lisez ce cours via internet :

La barre d'onglets

Eh oui, ce sont ces petites cases que vous ouvrez lorsque vous voulez visiter plusieurs sites web en même temps. Il est tout à fait possible d'en créer avec GTK, par exemple si vous souhaitez créer un navigateur internet ou un logiciel qui ouvrira tous ses fichiers dans une même fenêtre à l'aide de plusieurs onglets (c'est ce que fait par exemple le lecteur de fichier pdf Foxit Reader ou l'éditeur de code NotePad++). Voici donc la fiche d'identité des barres d'onglets :

  • Widget : GTK_Notebook
  • Package : Gtk.Notebook
  • Descendance : GTK_Widget >> GTK_Container
  • Description : Conteneur comportant plusieurs pages, chaque page permettant d'afficher un seul widget à la fois.

Méthodes

Créer des onglets

Le constructeur de Gtk_Notebook est simplissime, je n'en parlerai donc pas. Vous devez toutefois savoir qu'une barre d'onglet GTK_Notebook, seule, ne fait pas grand chose et ne contiendra pas vos widgets. En fait, elle contient (ou pas) des Gtk_Notebook_Page, c'est-à-dire des onglets ; et ce sont ces onglets qui contiendront un unique widget chacun. Il est donc important d'en créer et de les ajouter à votre barre. Tout cela se fait simplement avec les méthodes suivantes :

 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
procedure Append_Page(Notebook  : access Gtk_Notebook_Record;
                      Child     : access Gtk_Widget_Record'Class;
                      Tab_Label : access Gtk_Widget_Record'Class);
procedure Append_Page(Notebook  : access Gtk_Notebook_Record;
                      Child     : access Gtk_Widget_Record'Class);
procedure Append_Page_Menu(Notebook   : access Gtk_Notebook_Record;
                           Child      : access Gtk_Widget_Record'Class;
                           Tab_Label  : access Gtk_Widget_Record'Class;
                           Menu_Label : access Gtk_Widget_Record'Class);

procedure Prepend_Page(Notebook  : access Gtk_Notebook_Record;
                       Child     : access Gtk_Widget_Record'Class;
                       Tab_Label : access Gtk_Widget_Record'Class);
procedure Prepend_Page_Menu(Notebook   : access Gtk_Notebook_Record;
                            Child      : access Gtk_Widget_Record'Class;
                            Tab_Label  : access Gtk_Widget_Record'Class;
                            Menu_Label : access Gtk_Widget_Record'Class);

procedure Insert_Page(Notebook  : access Gtk_Notebook_Record;
                      Child     : access Gtk_Widget_Record'Class;
                      Tab_Label : access Gtk_Widget_Record'Class;
                      Position  : Gint);
procedure Insert_Page_Menu(Notebook   : access Gtk_Notebook_Record;
                           Child      : access Gtk_Widget_Record'Class;
                           Tab_Label  : access Gtk_Widget_Record'Class;
                           Menu_Label : access Gtk_Widget_Record'Class;
                           Position   : Gint);

procedure Remove_Page(Notebook : access Gtk_Notebook_Record; 
                      Page_Num : Gint);

Avec l'expérience acquise, vous devriez être capable de comprendre le sens de ces différentes méthodes sans mon aide. Toujours pas ? Rappelez vous : Append signifie Ajouter à la fin ; Prepend signifie Ajouter au début ; Insert signifie Insérer (à une position donnée) et Remove signifie Retirer, supprimer. Ces méthodes vont donc vous permettre d'ajouter, insérer ou supprimer des onglets (des GTK_Notebook_Page) à votre barre.

Que signifient les différentes paramètres ? Prenons un exemple : la première méthode Append_Page(). Le paramètre Notebook est, ai-je encore besoin de le préciser, votre barre d'onglets. Le paramètre Child indique le widget que vous souhaitez ajouter. L'onglet sera automatiquement généré, pas besoin de constructeur pour les GTK_Notebook_Page. Bien entendu, ce widget peut être un conteneur complexe, contenant lui-même divers boutons, étiquettes, zones de saisie… Enfin, le troisième paramètre Tab_Label correspond au nom de l'onglet. Celui-ci n'est pas un string comme on aurait pu le croire mais un GTK_Widget. Pourquoi ? Eh bien parce que le titre de votre onglet peut-être du texte (généralement un GTK_Label) mais aussi une GTK_Box afin d'afficher un image avec votre texte ! Si vous ne renseignez pas le paramètre Tab_Label, l'onglet sera nommé «Page 1», «Page 2»… par défaut.

Ah, d'accord ! Et je me doute que les paramètres Position correspondent à la position où l'on souhaite insérer un onglet. Par contre à quoi servent les paramètres Menu_Label ? :euh:

Nous reviendrons sur ce paramètre un peu plus tard. Retenez qu'il s'agit d'un nom alternatif pour votre onglet et qui sera affiché lorsque l'utilisateur passera par le menu. Voyons maintenant les méthodes pour modifier ou accéder aux paramètres de base de vos onglets.

Modifier ou lire les paramètres de base des onglets

Si vous souhaitez obtenir ou modifier l'étiquette servant de titre à votre onglet, vous pourrez utiliser les méthodes suivantes :

1
2
3
4
5
function Get_Tab_Label(Notebook  : access Gtk_Notebook_Record;
                       Child     : access Gtk_Widget_Record'Class) return Gtk_Widget;
procedure Set_Tab_Label(Notebook  : access Gtk_Notebook_Record;
                        Child     : access Gtk_Widget_Record'Class;
                        Tab_Label : access Gtk_Widget_Record'Class);

Vous devez renseigner la barre d'onglets (Notebook) bien entendu, mais également le widget contenu dans l'onglet visé. À noter que si vous modifier le titre (Tab_Label), vous devrez actualiser son affichage avec show() ou show_all(). Vous pouvez obtenir les mêmes résultats seulement en utilisant les string avec les méthodes suivantes :

1
2
3
4
5
procedure Set_Tab_Label_Text(Notebook : access Gtk_Notebook_Record;
                             Child    : access Gtk_Widget_Record'Class;
                             Tab_Text : UTF8_String);
function Get_Tab_Label_Text(Notebook : access Gtk_Notebook_Record;
                            Child    : access Gtk_Widget_Record'Class) return UTF8_String;

Vous pouvez également modifier un onglet via son numéro, sans connaître le widget qu'elle contient :

1
2
3
procedure Set_Tab(Notebook  : access Gtk_Notebook_Record;
                  Page_Num  : Gint;
                  Tab_Label : access Gtk_Widget_Record'Class);

Enfin, vous retrouvez les mêmes méthodes pour modifier le paramètre Menu_Label de votre onglet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function Get_Menu_Label (Notebook : access Gtk_Notebook_Record;
                         Child    : access Gtk_Widget_Record'Class) return Gtk_Widget;
procedure Set_Menu_Label(Notebook   : access Gtk_Notebook_Record;
                         Child      : access Gtk_Widget_Record'Class;
                         Menu_Label : access Gtk_Widget_Record'Class);
procedure Set_Menu_Label_Text(Notebook  : access Gtk_Notebook_Record;
                              Child     : access Gtk_Widget_Record'Class;
                              Menu_Text : UTF8_String);
function Get_Menu_Label_Text (Notebook  : access Gtk_Notebook_Record;
                              Child     : access Gtk_Widget_Record'Class) return UTF8_String;

Jouer avec les onglets

Ensuite, le nombre d'onglets pouvant augmenter ou diminuer au cours de l'utilisation de votre programme, il est important que vous puissiez connaître le nombre d'onglets ou lequel est actif. Ainsi pour connaître quel onglet est actif, utilisez la méthode Get_Current_Page() et si vous souhaitez changer d'onglet, utilisez la méthode Set_Current_Page() :

1
2
3
4
function  Get_Current_Page(Notebook : access Gtk_Notebook_Record) return Gint;
procedure Set_Current_Page(Notebook : access Gtk_Notebook_Record;
                           Page_Num : Gint := -1);
   --Si Page_Num vaut -1, cela renverra automatiquement à la dernière page.

La première page porte le numéro 0 et non 1 !

Vous pourrez obtenir le nombre total de pages grâce à Get_N_Pages() ainsi que le numéro correspondant à un widget avec Page_Num()

1
2
3
function Get_N_Pages(Notebook : access Gtk_Notebook_Record) return Gint;
function Page_Num   (Widget : access Gtk_Notebook_Record'Class;
                     Child  : access Gtk_Widget_Record'Class) return Gint;

Maintenant, si vous souhaitez pouvoir changer d'onglet en passant au suivant (next) ou au précédent (previous) :

1
2
procedure Next_Page (Notebook : access Gtk_Notebook_Record);
procedure Prev_Page (Notebook : access Gtk_Notebook_Record);

Modifier le comportement et le menu

Lorsque l'utilisateur a ouvert de très nombreux onglets, il peut être pratique d'accéder à n'importe quel onglet, non pas en cliquant dessus après l'avoir longuement cherché dans la liste mais plutôt en le sélectionnant via un menu accessible d'un simple clic droit :

Un menu d'onglets

Pour cela vous devrez autoriser (ou interdire) ces menus grâces aux méthodes suivantes :

1
2
procedure Popup_Enable  (Notebook : access Gtk_Notebook_Record); --autoriser
procedure Popup_Disable (Notebook : access Gtk_Notebook_Record); --interdire

Par défaut, le titre du menu est celui de l'onglet. Mais nous avons vu que vous pouviez modifier ce titre pour notamment indiquer l'action correspondante («enregistrez-vous», «Lire les conditions d'utilisations»…). Mais un autre problème se pose : lorsque l'utilisateur ouvrira beaucoup d'onglets, soit la fenêtre devra s'agrandir en conséquence, soit les onglets devront se rétrécir pour laisser de la place aux nouveaux venus. Pas très esthétique tout cela. :( Une solution pour pallier à ce problème est de ne pas tous les afficher et de permettre l'accès aux onglets cachés via des boutons :

Accès des onglets via des boutons

Pour cela il suffit d'utiliser la méthode Set_Scrollable() en indiquant TRUE comme paramètre.

1
2
3
procedure Set_Scrollable(Notebook   : access Gtk_Notebook_Record;
                         Scrollable : Boolean := True);
function Get_Scrollable (Notebook : access Gtk_Notebook_Record) return Boolean;

Déplacer les onglets

Pour organiser son travail, l'utilisateur souhaitera peut-être regrouper certains onglets entre eux. Si vous souhaitez qu'un onglet puisse être déplacé au sein d'une barre d'onglets, utilisez la méthode Set_Tab_Reorderable() :

1
2
3
4
5
6
function Get_Tab_Reorderable (Notebook : access Gtk_Notebook_Record;
                              Child    : access Gtk_Widget_Record'Class;
                              Position : Gint) return Boolean;
procedure Set_Tab_Reorderable(Notebook    : access Gtk_Notebook_Record;
                              Child       : access Gtk_Widget_Record'Class;
                              Reorderable : Boolean := True);

Mais il est possible de pousser le vice encore plus loin en permettant de déplacer un onglet d'une barre dans une autre, pour peu que ces deux barres fassent partie d'un même groupe. Voici les méthodes en jeu, je déroulerai un exemple tout de suite après :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function Get_Tab_Detachable (Notebook : access Gtk_Notebook_Record;
                             Child    : access Gtk_Widget_Record'Class;
                             Position : Gint) return Boolean;
procedure Set_Tab_Detachable(Notebook   : access Gtk_Notebook_Record;
                             Child      : access Gtk_Widget_Record'Class;
                             Detachable : Boolean := True);

procedure Set_Group_Id(Notebook : access Gtk_Notebook_Record; 
                       Group_Id : Gint);
function Get_Group_Id (Notebook : access Gtk_Notebook_Record) return Gint;

Vous devrez tout d'abord indiquer quel onglet sera détachable en indiquant le widget qu'il contient (child). Puis vous devrez lier les barres d'onglet entre elles en leur attribuant un même numéro de groupe avec Set_Group_Id(). Voici un exemple plus concret :

 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
WITH Gtk.Main ;         USE Gtk.Main ;
WITH Gtk.Window ;       USE Gtk.Window ;
WITH Gtk.Label ;        USE Gtk.Label ; 
WITH Gtk.Notebook ;     USE Gtk.Notebook ;
WITH Glib.Convert ;     USE Glib.Convert ; 
WITH Gtk.Box ;          USE Gtk.Box ;


PROCEDURE MesOnglets IS

   Win         : Gtk_Window ;
   Boite       : Gtk_HBox ; 
   Onglets1, 
   Onglets2    : Gtk_Notebook ; 
   Lbl1, Lbl2,
   Lbl3, Lbl4  : Gtk_Label ; 

BEGIN
   Init ;

      --Création de la fenêtre
   Gtk_New(Win) ;
   Win.Set_Default_Size(150,200) ;
   Win.Set_Title(locale_to_utf8("Une fenêtre à onglets")) ;

      --Création d'une Horizontal Box pour séparer les deux barres d'onglets
   Gtk_new_hbox(boite) ; 
   Win.Add(Boite) ; 

      --Création des deux barres d'onglets
   Gtk_New(Onglets1) ; 
   Gtk_New(Onglets2) ; 
   Boite.Pack_Start(Onglets1) ; 
   Boite.Pack_Start(Onglets2) ; 

      --Création de quatre étiquettes qui constitueront les quatre onglets
   Gtk_New(Lbl1, Locale_To_Utf8("Ceci est la première page")) ; 
   Gtk_New(Lbl2, Locale_To_Utf8("Ceci est la seconde page")) ;
   Gtk_New(Lbl3, Locale_To_Utf8("Ceci est la troisième page")) ; 
   Gtk_New(Lbl4, Locale_To_Utf8("Ceci est la quatrième page")) ; 

      --Création des onglets de la barre n°1 
   Onglets1.Append_Page(Lbl1) ; 
   Onglets1.Append_Page(Lbl2) ;
   Onglets1.Set_Tab_detachable(Lbl1) ; 
   Onglets1.Set_Tab_detachable(Lbl2) ; 

      --Création des onglets de la barre n°2
   Onglets2.Append_Page(Lbl3) ;
   Onglets2.Append_Page(Lbl4) ;
   Onglets2.Set_Tab_detachable(Lbl3) ; 
   Onglets2.Set_Tab_detachable(Lbl4) ; 

      --Création d'un même groupe pour les deux barres d'onglet
   Onglets1.Set_Group_Id(1) ; 
   Onglets2.Set_Group_Id(1) ; 

   Win.show_all ;
   Main ;
END MesOnglets ;

Le code ci-dessus nous donne la fenêtre sur la figure suivante. Remarquez que je n'ai pas donné de titre particulier à mes onglets et que GTK les renumérote automatiquement lors d'un déplacement, ce qui crée un décalage entre le titre de l'onglet et son contenu.

Déplacement d'un onglet d'une barre à l'autre

Les barres de défilement

Fiche d'identité

Si vous avez déjà effectué quelques tests des divers widgets abordés depuis le début de cette partie, vous avez du vous rendre compte que selon le dimensionnement de la fenêtre, tout n'était pas toujours visible ou bien, inversement, que la fenêtre ne pouvait pas toujours être redimensionnée afin que certains widgets restent affichés. Par exemple, lorsque l'on utilise un éditeur de texte, la fenêtre ne se redimensionne pas quand on écrit trop de texte ; et pourtant, c'est bien ce qui se passe actuellement. Nous devons donc ajouter des barres de défilement horizontales et verticales à nos fenêtres pour pouvoir tout afficher.

  • Widget : GTK_Scrolled_Window
  • Package : Gtk.Scrolled_Window
  • Descendance : GTK_Widget >> GTK_Container >> GTK_Bin
  • Description : Ce widget permet d'afficher des barres de défilement horizontal en bas de la fenêtre et vertical à gauche de la fenêtre (voir la figure suivante)

Barres de défilement

Exemples d'utilisation

Utilisation avec un GTK_Text_View

La construction d'une GTK_Scrolled_Window se fait très simplement. Il vous suffit ensuite d'y ajouter avec la méthode Add() n'importe quel widget. Les barres de défilement ne sont donc rien de plus qu'un conteneur bordé de deux ascenseurs. Pour réaliser le programme que vous avez pu admirer ci-dessus, il m'a suffi d'écrire le code suivant :

 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
WITH Gtk.Main ;                   USE Gtk.Main ; 
WITH Gtk.Window ;                 USE Gtk.Window ; 
WITH Gtk.Scrolled_Window ;        USE Gtk.Scrolled_Window ; 
WITH Gtk.Text_View ;              USE Gtk.Text_View ; 
WITH Glib.Convert ;               USE Glib.Convert ; 


PROCEDURE Ascenseur IS
   Win         : GTK_Window ; 
   Defilement  : GTK_Scrolled_Window ; 
   Texte       : GTK_Text_View ; 
BEGIN
   Init ; 

      --Création de la fenêtre principale
   Gtk_New(Win) ; 
   Win.Set_Default_Size(200,100) ; 
   Win.Set_Title(Locale_To_Utf8("Défilement")) ; 

      --Création des barres de défilement
   Gtk_New(Defilement) ; 
   Win.Add(Defilement) ; 

      --Création de la zone de texte et ajout aux barres de défilement
   Gtk_New(Texte) ; 
   Defilement.Add(Texte) ; 

   Win.Show_All ; 
   Main ; 
END Ascenseur ;

Utilisation avec un GTK_Fixed

Un problème se pose toutefois avec certains widgets. Tous ne supportent pas aussi bien les barres de défilement (aussi appelés ascenseurs). Les GTK_Text_View sont dits scrollable en langage GTK, c'est-à-dire qu'ils supportent très bien les ascenseurs. Mais les GTK_Fixed, par exemple, ne les supportent pas et sont dits non scrollables.

Pour mieux comprendre, faisons un petit exercice : vous allez réaliser un programme qui affiche une carte. Celle-ci est composée d'images simples : des cases vertes pour la terre, des cases bleues pour la mer. Celles-ci seront placées sur un GTK_Fixed auquel nous adjoindrons des ascenseurs pour pouvoir garder l'accès à toutes les cases même lorsque l'on redimensionnera la fenêtre. Comme pour le démineur, je vous conseille de créer un tableau de widget. Voici une solution :

 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
WITH Gtk.Main ;                   USE Gtk.Main ; 
WITH Gtk.Window ;                 USE Gtk.Window ; 
WITH Gtk.Scrolled_Window ;        USE Gtk.Scrolled_Window ; 
WITH Gtk.Image ;                  USE Gtk.Image ; 
WITH Gtk.Fixed ;                  USE Gtk.Fixed ; 
WITH Glib.Convert ;               USE Glib.Convert ; 
WITH Glib ;                       USE Glib ; 


PROCEDURE Carte IS
   Win         : GTK_Window ; 
   Defilement  : GTK_Scrolled_Window ; 
   Fond        : GTK_Fixed ; 
      --Création d'un type tableau pour savoir si les cases sont terrestres ou maritimes
   TYPE T_Array IS ARRAY(1..6,1..6) OF Boolean; 
   Terre       : CONSTANT T_Array := 
      ((True,True,True,False,False,False),
      (True,True,True,True,False,False),
      (True,False,False,False,True,True),
      (False,False,False,False,False,True),
      (True,False,False,False,False,False),
      (True,True,True,False,False,True)); 
      --Création d'un tableau contenant les images
   TYPE T_Image_Array IS ARRAY(1..6,1..6) OF GTK_Image ; 
   Image       : T_Image_Array ; 
   
BEGIN
   Init ; 

      --Création de la fenêtre
   Gtk_New(Win) ; 
   Win.Set_Default_Size(200,100) ; 
   Win.Set_Title(Locale_To_Utf8("Défilement")) ; 

      --Création des ascenseurs
   Gtk_New(Defilement) ; 
   Win.Add(Defilement) ; 

      --Création du GTK_Fixed
   Gtk_New(Fond) ; 
   Defilement.Add(Fond) ; 

      --Création et ajout des images (chaque image a une taille de 50 par 50 pixels
   FOR J IN 1..6 LOOP
      FOR I IN 1..6 LOOP
         IF Terre(I,J)
               THEN Gtk_New(Image(I,J),"terre.png") ;
         ELSE Gtk_New(Image(I,J),"mer.png") ; 
         END IF ; 
         fond.put(Image(i,j),Gint((j-1)*50),Gint((i-1)*50)) ; 
      END LOOP ; 
   END LOOP ; 

   Win.Show_All ; 
   Main ; 
END Carte ;

Malheureusement, le résultat n'est pas à la hauteur de nos attentes : les barres de défilement sont inutilisables et nous obtenons en sus une jolie erreur (voir la figure suivante) :

Gtk-WARNING **: gtk_scrolled_window_add() : cannot add non scrollable widget use gtk_scrolled_window_add_with_viewport() instead

Un résultat décevant

Nous allons devoir utiliser un GTK_Viewport, un conteneur dont le seul but est de jouer les intermédiaires entre notre GTK_Fixed et notre GTK_Scrolled_Window. Deux solutions s'offrent à nous :

  1. Créer nous-même un GTK_Viewport.
  2. Utiliser une méthode en remplacement de Add() qui créera ce GTK_Viewport pour nous.

Dans le premier cas, sachez que le package concerné est (rien d'original me direz-vous) GTK.Viewport. Le constructeur est simplissime, vous vous en tirerez tout seul. Une fois vos widgets initialisés, vous devrez ajouter le GTK_Fixed à votre GTK_Viewport qui à son tour devra être ajouté à votre GTK_Scrolled_Window. Long et fastidieux, non ?

Pour aller plus vite, nous opterons pour la seconde méthode en utilisant Add_With_Viewport(). Le GTK_Viewport est ainsi généré automatiquement sans encombrer votre code. Notre programme donne ainsi :

 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
WITH Gtk.Main ;                   USE Gtk.Main ; 
WITH Gtk.Window ;                 USE Gtk.Window ; 
WITH Gtk.Scrolled_Window ;        USE Gtk.Scrolled_Window ; 
WITH Gtk.Image ;                  USE Gtk.Image ; 
WITH Gtk.Fixed ;                  USE Gtk.Fixed ; 
WITH Glib.Convert ;               USE Glib.Convert ; 
WITH Glib ;                       USE Glib ; 


PROCEDURE Carte IS
   Win         : GTK_Window ; 
   Defilement  : GTK_Scrolled_Window ; 
   Fond        : GTK_Fixed ; 
   TYPE T_Array IS ARRAY(1..6,1..6) OF Boolean; 
   Terre       : CONSTANT T_Array := 
      ((True,True,True,False,False,False),
      (True,True,True,True,False,False),
      (True,False,False,False,True,True),
      (False,False,False,False,False,True),
      (True,False,False,False,False,False),
      (True,True,True,False,False,True)); 
   TYPE T_Image_Array IS ARRAY(1..6,1..6) OF GTK_Image ; 
   Image       : T_Image_Array ; 

BEGIN
   Init ; 

   Gtk_New(Win) ; 
   Win.Set_Default_Size(200,100) ; 
   Win.Set_Title(Locale_To_Utf8("Défilement")) ; 

   Gtk_New(Defilement) ; 
   Win.Add(Defilement) ; 

   Gtk_New(Fond) ; 
   Defilement.Add_With_Viewport(Fond) ; 

   FOR J IN 1..6 LOOP
      FOR I IN 1..6 LOOP
         IF Terre(I,J)
               THEN Gtk_New(Image(I,J),"terre.png") ;
         ELSE Gtk_New(Image(I,J),"mer.png") ; 
         END IF ; 
         fond.put(Image(i,j),Gint((j-1)*50),Gint((i-1)*50)) ; 
      END LOOP ; 
   END LOOP ; 

   Win.Show_All ; 
   Main ; 
END Carte ;

Affichage correct

Méthodes

Voici quelques méthodes supplémentaires. Tout d'abord, si vous souhaitez obtenir l'une des deux barres de défilement :

1
2
3
4
function Get_Hscrollbar(Scrolled_Window : access Gtk_Scrolled_Window_Record)
      return Gtk_Scrollbar;
function Get_Vscrollbar(Scrolled_Window : access Gtk_Scrolled_Window_Record)
      return Gtk_Scrollbar;

Chacune de ces méthodes renvoie une Gtk_Scrollbar, c'est-à-dire le widget servant de barre de défilement (je vous laisse regarder son package si vous souhaitez en savoir plus). Evidemment, Get_Hscrollbar() renvoie la barre horizontale et Get_Vscrollbar() la barre verticale. Enfin, voici quelques dernières méthodes utiles :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
procedure Set_Policy(Scrolled_Window    : access Gtk_Scrolled_Window_Record;
                     H_Scrollbar_Policy : Gtk_Policy_Type;
                     V_Scrollbar_Policy : Gtk_Policy_Type);
procedure Get_Policy(Scrolled_Window    : access Gtk_Scrolled_Window_Record;
                     H_Scrollbar_Policy : out Gtk_Policy_Type;
                     V_Scrollbar_Policy : out Gtk_Policy_Type);

procedure Set_Placement(Scrolled_Window  : access Gtk_Scrolled_Window_Record;
                        Window_Placement : Gtk_Corner_Type);
function Get_Placement(Scrolled_Window  : access Gtk_Scrolled_Window_Record) return Gtk_Corner_Type;

Set_Policy() permet de gérer l'affichage des barres. Pour l'heure, celles-ci s'affichent toujours car les paramètres H_Scrollbar_Policy et V_Scrollbar_Policy ont leur valeur par défaut : Policy_Always. En fouillant dans le package Gtk.Enums vous trouverez les autres valeurs :

  • Policy_Always : les barres sont toujours affichées.
  • Policy_Automatic : les barres ne s'affichent que si nécessaire.
  • Policy_Never : les barres ne sont jamais affichées.

Bien entendu, chaque barre peut avoir une politique d'affichage différente, comme toujours afficher la barre verticale mais jamais la barre horizontale. La méthode Set_Placement() indique l'emplacement des deux ascenseurs ou plus exactement, il indique dans quel coin de la fenêtre les ascenseurs ne sont pas présents. Par défaut, la barre verticale est à droite et la barre horizontale en bas. Le seul coin qui ne touche pas les ascenseurs est donc le coin supérieur gauche. Par défaut, le paramètre Window_Placement vaut donc Corner_Top_Left. Les autres valeurs sont : Corner_Top_Right, Corner_Bottom_Left et Corner_Bottom_Right.

Les panneaux

Fiche d'identité

  • Widget : GTK_Paned
  • Package : Gtk.Paned
  • Descendance : GTK_Widget >> GTK_Container
  • Description : Ce widget est une sorte de boîte, horizontale ou verticale, séparant deux widgets. À la différence des boîtes classiques, les panneaux peuvent être redimensionnés par l'utilisateur, à la façon des explorateurs de fichiers (voir la figure suivante).

Panneaux redimensionnés

Méthodes

Comme pour les boîtes, il existe deux sortes de GTK_Paned, les GTK_HPaned (panneau horizontal comme ci-dessus) et les GTK_VPaned (panneaux verticaux). Chacun dispose de son propre constructeur : GTK_New_HPaned() et GTK_New_VPaned().

Pour ajouter des widgets à vos panneaux, deux méthodes existent : Add1() et Add2(). La première ajoute le widget à gauche pour un panneau horizontal ou en haut pour un panneau vertical. Le second ajoutera donc le widget à droite ou en bas :

1
2
3
4
procedure Add1(Paned : access Gtk_Paned_Record;
               Child : access Gtk_Widget_Record'Class);
procedure Add2(Paned : access Gtk_Paned_Record;
               Child : access Gtk_Widget_Record'Class);

Vous pouvez également paramétrer la répartition des widgets au sein du panneau avec les méthodes ci-dessous :

1
2
3
4
5
6
7
8
procedure Pack1(Paned  : access Gtk_Paned_Record;
                Child  : access Gtk_Widget_Record'Class;
                Resize : Boolean := False;
                Shrink : Boolean := True);
procedure Pack2(Paned  : access Gtk_Paned_Record;
                Child  : access Gtk_Widget_Record'Class;
                Resize : Boolean := False;
                Shrink : Boolean := False);

L'attribut Resize indique si l'emplacement réservé au widget peut être redimensionné pour qu'il occupe le plus de place possible au démarrage. Bien sûr, cela n'est visible que si les widgets de gauche et de droite ne sont pas paramétrés de la même façon. L'attribut Shrink indique s'il est possible que l'utilisateur puisse redimensionné le panneau au point que le widget soit partiellement ou entièrement caché. Il est également possible de récupérer ces widgets avec les méthodes Get_Child1() et Get_Child2() :

1
2
function Get_Child1(Paned : access Gtk_Paned_Record) return Gtk_Widget;
function Get_Child2(Paned : access Gtk_Paned_Record) return Gtk_Widget;

Enfin, dernière méthode. Si vous souhaitez positionner vous-même la séparation entre les deux widgets, utilisez la méthode Set_Position() en indiquant la distance voulue en pixels :

1
2
function  Get_Position (Paned : access Gtk_Paned_Record) return Gint;
procedure Set_Position (Paned : access Gtk_Paned_Record; Position : Gint);

Les cadres

Les cadres simples

Fiche d'identité

  • Widget : GTK_Frame
  • Package : GTK.Frame
  • Descendance : GTK_Widget >> GTK_Container >> GTK_Bin
  • Description : C'est un conteneur pour un seul widget. Son but est en général de grouper de façon esthétique plusieurs widgets. De plus, il peut être doté d'un titre (voir la figure suivante).

Un cadre simple

Méthodes

Nous aurons très vite effectué le tour des méthodes car ce conteneur est simple d'utilisation. Commençons par le constructeur qui comporte un paramètre supplémentaire pour le titre du cadre (dans l'exemple ci-dessus, ce titre est "Cadre pour le bouton"). Notez que le titre est optionnel :

1
procedure Gtk_New (Frame : out Gtk_Frame; Label : UTF8_String := "");

Vous disposez également de deux méthodes pour lire ou (re)définir directement le titre (Get_Label() et Set_Label()) mais aussi pour éditer l'étiquette elle-même (Get_Label_Widget() et Set_Label_Widget()) :

1
2
3
4
5
6
function  Get_Label (Frame : access Gtk_Frame_Record) return UTF8_String;
procedure Set_Label (Frame : access Gtk_Frame_Record;
                     Label : UTF8_String);
function  Get_Label_Widget (Frame : access Gtk_Frame_Record) return Gtk_Widget;
procedure Set_Label_Widget (Frame        : access Gtk_Frame_Record;
                            Label_Widget : access Gtk_Widget_Record'Class);

Enfin, vous pouvez obtenir ou modifier l'alignement du titre avec les méthodes suivantes :

1
2
3
4
5
6
procedure Get_Label_Align(Frame  : access Gtk_Frame_Record;
                          Xalign : out Gfloat;
                          Yalign : out Gfloat);
procedure Set_Label_Align(Frame  : access Gtk_Frame_Record;
                          Xalign : Gfloat;
                          Yalign : Gfloat);

Les paramètres Xalign et Yalign définissent bien entendu les alignements horizontaux et verticaux mais surtout, ont des valeurs comprises entre 0.0 et 1.0. Pour Xalign, la valeur 0.0 indique un titre aligné à gauche et 1.0 à droite. Une valeur de 0.25 indiquera que le titre est placé à un quart de la longueur. Pour Yalign, 0.0 indiquera que la bordure du cadre sera en bas par rapport au titre, une valeur de 1.0 indiquera une bordure en haut du titre. Généralement, cette valeur est laissée à 0.5 pour obtenir une bordure partant du «milieu du titre».

Les cadres d'aspect

Fiche d'identité

  • Widget : GTK_Aspect_Frame
  • Package : GTK.Aspect_Frame
  • Descendance : GTK_Widget >> GTK_Container >> GTK_Bin >> GTK_Frame
  • Description : Ce conteneur dérive des GTK_Frame et a donc les mêmes attributs. Il permet toutefois de placer de façon harmonieuse vos widgets en plaçant des espaces vides tout autour.

Méthodes

Le constructeur est ici la seule méthode vraiment complexe, il mérite donc que l'on s'y arrête :

1
2
3
4
5
6
procedure Gtk_New (Aspect_Frame : out Gtk_Aspect_Frame;
                   Label        : UTF8_String := "";
                   Xalign       : Gfloat;
                   Yalign       : Gfloat;
                   Ratio        : Gfloat;
                   Obey_Child   : Boolean);

Les paramètres Xalign et Yalign indiquent ici non plus l'alignement du titre mais celui du cadre tout entier. Imaginons que votre fenêtre soit bien plus large que nécessaire, si Xalign vaut 0.0, votre cadre s'alignera alors à gauche. S'il vaut 1.0, il s'alignera à droite. Et ainsi de suite, le fonctionnement est similaire à ce que nous venons de voir.

Le paramètre Ratio est un peu plus complexe à comprendre. Il s'agit du rapport $largeur \over hauteur$ du widget contenu dans votre cadre. Si vous placez un bouton dans votre cadre avec un ratio de 0.5, cela indiquera que la largeur du bouton doit valoir la moitié de sa hauteur. Avec un ratio de 2.0, c'est l'inverse : la largeur est deux fois plus grande que la hauteur.

Mais je n'en sais rien moi, du ratio dont j'ai besoin ! :(

Alors dans ce cas, le dernier paramètre devrait vous satisfaire : si Obey_Child vaut TRUE, alors le ratio sera ignoré et le cadre devra «obéir au fils», c'est-à-dire prendre les dimensions voulues par le widget qu'il contient. Enfin, si vous souhaitez connaître ces paramètres ou les modifier, voici ce dont vous aurez besoin :

1
2
3
4
5
6
7
8
procedure Set(Aspect_Frame : access Gtk_Aspect_Frame_Record;
              Xalign       : Gfloat;
              Yalign       : Gfloat;
              Ratio        : Gfloat;
              Obey_Child   : Boolean);
function Get_Xalign(Aspect_Frame : access Gtk_Aspect_Frame_Record) return Gfloat;
function Get_Yalign(Aspect_Frame : access Gtk_Aspect_Frame_Record) return Gfloat;
function Get_Ratio (Aspect_Frame : access Gtk_Aspect_Frame_Record) return Gfloat;

Les extenseurs

Fiche d'identité

  • Widget : GTK_Expander
  • Package : GTK.Expander
  • Descendance : GTK_Widget >> GTK_Container >> GTK_Bin
  • Description : C'est un conteneur pour un seul widget. Son but est d'afficher ou de masquer le widget qu'il contient (voir la figure suivante).

Les extenseurs

Méthodes

Comme pour les cadres simples, le constructeur des extenseurs exige un titre. Il vous est possible également de souligner certaines lettres comme pour les étiquettes avec GTK_New_With_Mnemonic() :

1
2
3
4
procedure Gtk_New (Expander : out Gtk_Expander; 
                   Label    : UTF8_String);
procedure Gtk_New_With_Mnemonic(Expander : out Gtk_Expander;
                                Label    : UTF8_String);

On retrouve également de nombreuses méthodes déjà vues avec les étiquettes ou les boutons, comme Set_Use_Markup() et Set_Use_Underline(). Je ne réexplique pas leur fonctionnement, mais je vous invite à relire les chapitres 2 et 5 de la partie V si vous avez tout oublié.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function  Get_Use_Markup(Expander : access Gtk_Expander_Record) return Boolean;
procedure Set_Use_Markup(Expander   : access Gtk_Expander_Record;
                         Use_Markup : Boolean);
function Get_Use_Underline(Expander : access Gtk_Expander_Record) return Boolean;
procedure Set_Use_Underline(Expander      : access Gtk_Expander_Record;
                            Use_Underline : Boolean);
function  Get_Label_Fill(Expander : access Gtk_Expander_Record) return Boolean;
procedure Set_Label_Fill(Expander   : access Gtk_Expander_Record;
                         Label_Fill : Boolean);
function  Get_Label(Expander : access Gtk_Expander_Record) return UTF8_String;
procedure Set_Label(Expander : access Gtk_Expander_Record;
                    Label    : UTF8_String);
function  Get_Label_Widget(Expander : access Gtk_Expander_Record) return Gtk_Widget;
procedure Set_Label_Widget(Expander     : access Gtk_Expander_Record;
                           Label_Widget : access Gtk_Widget_Record'Class);

La méthode Set_Label_Fill() vous permet d'indiquer si vous souhaitez que le titre «remplisse» tout l'espace horizontal qui lui est attribué. Plus simplement, si le paramètre Label_Fill vaut TRUE, votre titre apparaîtra centré et non aligné à gauche. Enfin, vous pouvez obtenir ou modifier le titre soit sous forme de String (avec Get_Label() et Set_Label()), soit sous forme de widget (avec Set_Label_Widget() et Get_Label_Widget()).

Rien de bien complexe dans tout cela. Voici maintenant les dernières méthodes (eh oui, ce conteneur est simple d'emploi) :

1
2
3
4
5
6
7
function Get_Spacing (Expander : access Gtk_Expander_Record) return Gint;
procedure Set_Spacing(Expander : access Gtk_Expander_Record;
                      Spacing  : Gint);

function Get_Expanded(Expander : access Gtk_Expander_Record) return Boolean;
procedure Set_Expanded(Expander : access Gtk_Expander_Record;
                       Expanded : Boolean);

La méthode Set_Spacing() permet de spécifier le nombre de pixels séparant le titre et le widget fils lorsque l'extenseur est déroulé. Quand à Set_Expanded(), il permet d'indiquer si l'extenseur est déroulé (TRUE) ou pas (FALSE).

Les boîtes détachables

Fiche d'identité

  • Widget : GTK_Handle_Box
  • Package : GTK.Handle_Box
  • Descendance : GTK_Widget >> GTK_Container >> GTK_Bin
  • Description : Ce conteneur est conçu pour un unique widget. Mais il est possible de le «détacher» de la fenêtre, à la façon de certaines barres d'outils (voir la figure suivante).

La boîte détachable

Déplacer la boîte détachable

Méthodes

Malgré ce que l'on pourrait croire, les boîtes détachables sont très simples à créer (mais peut-être pas toujours à utiliser). Je ne m'attarde pas sur le constructeur et j'en viens tout de suite aux trois méthodes utiles (oui c'est peu mais je n'y peux rien) :

1
2
3
4
5
6
7
8
9
procedure Set_Handle_Position(Handle_Box : access  Gtk_Handle_Box_Record;
                              Position   : Gtk_Position_Type);
function  Get_Handle_Position(Handle_Box : access  Gtk_Handle_Box_Record) return Gtk_Position_Type;

procedure Set_Snap_Edge(Handle_Box : access  Gtk_Handle_Box_Record;
                        Edge       : Gtk_Position_Type);
function  Get_Snap_Edge(Handle_Box : access  Gtk_Handle_Box_Record) return Gtk_Position_Type;

function  Get_Child_Detached(Handle_Box : access Gtk_Handle_Box_Record) return Boolean;

Tout d'abord, la méthode Set_Handle_Position() permet de positionner la zone de capture (vous savez, cette barre qui ressemble à une tôle gaufrée et qui permet de saisir le conteneur pour le déplacer). Par défaut, cette zone de capture est placée à gauche, mais vous pouvez bien entendu modifier cette valeur en jetant un œil au package Gtk.Enums. Les valeurs possibles sont :

  • Pos_Left : Zone de saisie à gauche.
  • Pos_Right : Zone de saisie à droite.
  • Pos_Top : Zone de saisie en haut.
  • Pos_Bottom : Zone de saisie en bas.

Vous devez également savoir que la GTK_Handle_Box ne bouge pas. Lorsque vous croyez la détacher, en réalité, GTK crée une sorte de conteneur fantôme pour vous donner l'illusion du mouvement. Mais votre GTK_Handle_Box est toujours en place ! Mais lorsque vous voudrez replacer le fantôme dans la GTK_Handle_Box, où devrez vous le lâcher ? Il faut en fait faire coïncider un bord du fantôme avec un bord de la GTK_Handle_Box. Oui, mais lequel ? Pour le spécifier, utiliser la méthode Set_Snap_Edge().

Enfin, la dernière méthode (Get_Child_Detached()), vous permettra de savoir si le widget à été détaché ou pas.


En résumé :

  • Ces conteneurs ne peuvent afficher qu'un seul widget (à l'exception du panneau qui peut en afficher deux), vous devrez donc combiner ces conteneurs avec ceux vus précédemment (conteneurs à positions fixes, boîtes, tables… ).
  • Ces conteneurs ont un intérêt principalement esthétique. Leur rôle est minime en ce qui concerne l'organisation des widgets dans votre fenêtre, mais énorme pour ce qui est de sa lisibilité. Ne les négligez pas.