IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Cours GTK 2

Date de publication : 20 Février 2007



Les menus

Nous allons cette fois voir quelles sont les solutions que GTK+ met à notre disposition pour ajouter un menu à une fenêtre. Nous parlons de "solutions" au pluriel car il existe en fait deux méthodes possibles à la création d'un menu, l'une étant plus rapide que l'autre.

Ce chapitre est composé de trois parties distinctes :

  • une première partie traitant de la création d'un menu classique par la méthode la plus longue ;
  • une deuxième partie dans laquelle nous verrons des éléments spéciaux d'un menu ;
  • la troisième partie abordera la création d'un menu par la méthode rapide.

1. Le menu - Méthode longue.

Pour construire un tel menu, nous allons utiliser pas moins de trois widgets différents qui serviront tous à différentes étapes de la création. Ces widgets sont GtkMenuBar, GtkMenu, GtkMenuItem dont voici leurs positions dans la hiérarchie GTK+ :

1.1 Les éléments d'un menu.

Dans un premier temps, le plus simple est de voir comment seront disposés ces trois widgets dans notre futur menu.

Nous avons tous d'abord le widget GtkMenuBar (en rouge) qui est l'élément principal de notre menu. Il contient des éléments de type GtkMenuItem (en bleu) qui peuvent ouvrir des éléments GtkMenu (en vert) contenant aussi des GtkMenuItem.

Du point de vue du fonctionnement, les éléments GtkMenuItem peuvent soit ouvrir un sous-menu (élément GtkMenu) soit exécuter l'action qui lui est associée par l'intermédiaire d'une fonction callback. Il ne reste maintenant plus qu'à créer notre menu.

1.2 Création d'un menu

La création d'un menu doit passer par au moins six étapes différentes :

  • Etape 1 : création de l'élément GtkMenuBar qui sera la barre de menu;
  • Etape 2 : création d'un élément GtkMenu qui sera un menu ;
  • Etape 3 : création des éléments GtkMenuItem à insérer dans le menu ;
  • Etape 4 : création de l'élément GtkMenuItem qui ira dans l'élément GtkMenuBar ;
  • Etape 5 : association de cet élément avec le menu créer précédemment ;
  • Etape 6 : ajout de l'élément GtkMenuItem dans la barre GtkMenuBar.

Si par la suite, vous souhaitez ajouter d'autres menu, il suffit de recommencer à partir de l'étape 2. Etudions maintenant les fonctions qui vont nous permettre de coder le menu.

Etape 1 : création de l'élément GtkMenuBar

Cette étape, rapide et simple, se résume en l'utilisation d'une seule fonction :

GtkWidget* gtk_menu_bar_new(void);

Etape 2 : création d'un élément GtkMenu qui sera un menu

Cette fois aussi, une seule fonction est utilisée :

GtkWidget* gtk_menu_new(void);

Etape 3 : création des éléments GtkMenuItem à insérer dans le menu

Dans un premier temps, il faut créer le GtkMenuItem grâce à l'une de ces trois fonctions :

GtkWidget* gtk_menu_item_new(void);
GtkWidget* gtk_menu_item_new_with_label(const gchar* label);
GtkWidget* gtk_menu_item_new_with_mnemonic(const gchar* label);

Nous reconnaissons la syntaxe de ces constructeurs qui est semblable à celles des boutons. La première fonction créer un élément vide, la deuxième un élément avec un texte (label), et la troisième un élément avec un texte associé à un raccourci.

Maintenant que l'élément est créé, il faut l'insérer dans le menu. Pour cela, il existe plusieurs fonctions possibles, mais nous n'allons en voir que deux :

void gtk_menu_shell_append(GtkMenuShell *menu_shell, GtkWidget *child);
void gtk_menu_shell_prepend(GtkMenuShell *menu_shell, GtkWidget *child);

Ces fonctions ne font pas partie du widget GtkMenu mais de GtkMenuShell qui est le widget dont GtkMenu dérive. La première fonction ajoute les éléments de haut en bas alors que la seconde les ajoute de bas en haut. Le paramètre menu_shell est le menu dans lequel nous voulons ajouter l'élément et le paramètre child est l'élément à ajouter. Pour le premier paramètre, il faudra utiliser la macro de conversion GTK_MENU_SHELL().

Cette étape peut s'avérer longue à coder, car il faudra la répéter pour chaque élément que nous voulons ajouter au menu.

Etape 4 : création de l'élément GtkMenuItem qui ira dans l'élément GtkMenuBar

Il suffit d'utiliser une des trois fonctions de création du widget GtkMenuItem.

Etape 5 : association de cet élément avec le menu créer précédemment

Il faut maintenant dire que lorsque l'utilisateur cliquera sur l'élément créer pendant l'étape 4, il faudra ouvrir le menu qui a été créé précédemment. La fonction adéquate est celle-ci :

void gtk_menu_item_set_submenu(GtkMenuItem *menu_item, GtkWidget *submenu);

Cette fonction va donc associer le GtkMenuItem menu_item au GtkMenu submenu. Comme d'habitude, il faudra convertir le premier paramètre, cette fois-ci à l'aide de la macro GTK_MENU_ITEM().

Etape 6 : ajout de l'élément GtkMenuItem dans la barre GtkMenuBar

GtkMenuBar dérivant aussi de GtkMenuShell, nous allons utiliser la même fonction que lors de l'étape 3 (gtk_menu_shell_append) pour ajouter le sous-menu au menu principal.

1.3 Exemple

Notre exemple comportera deux menu. Le premier "Fichier", proposera des fonctions inactives ("Nouveau", "Ouvrir", "Enregistrer", "Fermer") et une fonction active ("Quitter"), alors que le second "?" proposera la fonction "A propos de...".

1.4 Programme exemple

#include <stdlib.h>
#include <gtk/gtk.h>

void OnQuitter(GtkWidget* widget, gpointer data);
void OnAbout(GtkWidget* widget, gpointer data);

int main(int argc, char **argv)
{
    GtkWidget *pWindow;
    GtkWidget *pVBox;
    GtkWidget *pMenuBar;
    GtkWidget *pMenu;
    GtkWidget *pMenuItem;

    gtk_init(&argc, &argv);

    /* Creation de la fenetre */
    pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(pWindow), "GtkMenu");
    gtk_window_set_default_size(GTK_WINDOW(pWindow), 320, 200);
    g_signal_connect(G_OBJECT(pWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    /* Creation de la GtkVBox */
    pVBox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(pWindow), pVBox);

    /**** Creation du menu ****/

    /* ETAPE 1 */
    pMenuBar = gtk_menu_bar_new();
    /** Premier sous-menu **/
    /* ETAPE 2 */
    pMenu = gtk_menu_new();
    /* ETAPE 3 */
    pMenuItem = gtk_menu_item_new_with_label("Nouveau");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_menu_item_new_with_label("Ouvrir");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_menu_item_new_with_label("Enregistrer");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_menu_item_new_with_label("Fermer");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_menu_item_new_with_label("Quitter");
    g_signal_connect(G_OBJECT(pMenuItem), "activate", G_CALLBACK(OnQuitter), (GtkWidget*) pWindow);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    /* ETAPE 4 */
    pMenuItem = gtk_menu_item_new_with_label("Fichier");
    /* ETAPE 5 */
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(pMenuItem), pMenu);
    /* ETAPE 6 */
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenuBar), pMenuItem);

    /** Second sous-menu **/
    /* ETAPE 2 */

    pMenu = gtk_menu_new();
    /* ETAPE 3 */
    pMenuItem = gtk_menu_item_new_with_label("A propos de...");
    g_signal_connect(G_OBJECT(pMenuItem), "activate", G_CALLBACK(OnAbout), (GtkWidget*) pWindow);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    /* ETAPE 4 */
    pMenuItem = gtk_menu_item_new_with_label("?");
    /* ETAPE 5 */
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(pMenuItem), pMenu);
    /* ETAPE 6 */
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenuBar), pMenuItem);

    /* Ajout du menu a la fenetre */
    gtk_box_pack_start(GTK_BOX(pVBox), pMenuBar, FALSE, FALSE, 0);

    gtk_widget_show_all(pWindow);

    gtk_main();

    return EXIT_SUCCESS;
}

void OnQuitter(GtkWidget* widget, gpointer data)
{
    GtkWidget *pQuestion;

    pQuestion = gtk_message_dialog_new(GTK_WINDOW(data),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_QUESTION,
        GTK_BUTTONS_YES_NO,
        "Voulez vous vraiment\n"
        "quitter le programme?");

    switch(gtk_dialog_run(GTK_DIALOG(pQuestion)))
    {
        case GTK_RESPONSE_YES:
            gtk_main_quit();
            break;
        case GTK_RESPONSE_NONE:
        case GTK_RESPONSE_NO:
            gtk_widget_destroy(pQuestion);
            break;
    }
}

void OnAbout(GtkWidget* widget, gpointer data)
{
    GtkWidget *pAbout;

    pAbout = gtk_message_dialog_new (GTK_WINDOW(data),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_INFO,
        GTK_BUTTONS_OK,
        "Cours GTK+ 2.0\n"
        "http://gtk.developpez.com");

    gtk_dialog_run(GTK_DIALOG(pAbout));

    gtk_widget_destroy(pAbout);
}

Résultat :

2. Eléments "avancés" de menu.

En plus des GtkMenuItem, GTK+ offre cinq éléments de menu additionnels prêts à l'emploi : GtkImageMenuItem, GtkRadioMenuItem, GtkCheckMenuItem, GtkSeparatorMenuItem et GtkTearoffMenuItem. Bien entendu toutes les fonctions s'appliquant sur des GtkMenuItem s'appliquent aussi à ces items (héritage oblige).

  • GtkImageMenuItem est un widget qui permet de créer une entrée de menu textuelle avec une icône juste devant, comme par exemple :

  • GtkRadioMenuItem est un widget qui marche en groupe, un seul GtkRadioMenuItem d'un groupe peut être activé comme dans l'exemple ci dessous. "Groupe 1 : choix 1" et "Groupe 1 : choix 2" font partie d'un premier groupe, "Groupe 2 : choix 1", "Groupe 2 : choix 2" et "Groupe 2 : choix 3" font partie d'un deuxième. Seule une entrée du premier groupe peut être activée (ici c'est "Groupe 1 : choix 1") et pareil pour le deuxième groupe (où l'entrée activée est "Groupe 2 : choix 1").

  • GtkCheckMenuItem est un widget à trois états que l'on peut cocher.

  • GtkSeparatorMenuItem est un widget qui sert simplement à séparer des parties de menu gràce à une ligne horizontale. Leur but est uniquement décoratif :

  • GtkTearoffMenuItem est un widget qui permet de détacher le menu de sa barre et d'en faire une fenêtre à part entière.

Le menu encore attaché (le GtkTearoffMenuItem c'est les pointillés en haut du menu)

Le menu une fois détaché

2.1 Les GtkImageMenuItem

Pour créer un GtkImageMenuItem, on a à disposition quatre fonctions :

GtkWidget* gtk_image_menu_item_new(void);
GtkWidget* gtk_image_menu_item_new_from_stock(const gchar *stock_id, GtkAccelGroup *accel_group);
GtkWidget* gtk_image_menu_item_new_with_label(const gchar *label);
GtkWidget* gtk_image_menu_item_new_with_mnemonic(const gchar *label);

On retrouve encore les mêmes types de fonctions de création, aussi je passe sur l'explication des paramètres. La seule nouveautée est peut être le GtkAccelGroup *accel_group de gtk_image_menu_item_new_from_stock, qui sert à ajouter l'entrée à un GtkAccelGroup précédemment créé en utilisant le raccourci par défaut de l'icône stock.

Maintenant, il s'agit de définir une icône pour notre entrée, pour cela, on a :

void gtk_image_menu_item_set_image(GtkImageMenuItem *image_menu_item, GtkWidget *image);

Cette fonction permet de définir l'icône (généralement on utilisera un GtkImage) qui sera affichée par l'entrée passée en paramètre. Cette fonction peut aussi servir à remplacer l'icône que gtk_image_menu_item_new_from_stock définit pour l'entrée lors de sa création.

La dernière fonction spécifique des GtkImageMenuItem est :

GtkWidget* gtk_image_menu_item_get_image(GtkImageMenuItem *image_menu_item);

Elle sert, comme vous l'aurez deviné, à récupérer ce qui sert d'icône à l'entrée.

2.2 Les GtkCheckMenuItem

Les GtkCheckMenuItem émettent un signal lorsqu'on les (dé)coche, il s'agit du signal "toggled" et dont la fonction de rappel est de la forme void user_function(GtkCheckMenuItem *checkmenuitem, gpointer user_data);

Les fonctions de création d'un GtkCheckMenuItem sont :

GtkWidget* gtk_check_menu_item_new(void);
GtkWidget* gtk_check_menu_item_new_with_label(const gchar *label);
GtkWidget* gtk_check_menu_item_new_with_mnemonic(const gchar *label);

Rien de nouveau à l'horizon, alors nous continuons.

Nous avons plusieurs fonctions qui permettent de changer l'état de notre GtkCheckMenuItem par programme :

void gtk_check_menu_item_set_active(GtkCheckMenuItem *check_menu_item, gboolean is_active);
void gtk_check_menu_item_set_inconsistent(GtkCheckMenuItem *check_menu_item, gboolean setting);
void gtk_check_menu_item_toggled(GtkCheckMenuItem *check_menu_item);

La première fonction pemet de passer le GtkCheckMenuItem dans l'état "coché" si le paramètre is_active est TRUE ou dans l'état "non coché" si is_active est FALSE. Cette fonction ne permet pas de mettre notre GtkCheckMenuItem dans le troisième état "demi coché", pour cela, il faut utiliser la deuxième fonction, et grâce au paramètre setting, on active ou non cet état. La troisième fonction, elle, permet d'alterner entre état "coché" et "non coché" car elle émet en fait le signal "toggled", ce qui a pour effet d'inverser l'état du GtkCheckMenuItem.

Nous disposons également des fonctions associées pour récupérer l'état d'un GtkCheckMenuItem :

gboolean gtk_check_menu_item_get_active(GtkCheckMenuItem *check_menu_item);
gboolean gtk_check_menu_item_get_inconsistent(GtkCheckMenuItem *check_menu_item);

La première permet de connaître l'état d'un GtkCheckMenuItem, elle renvoie TRUE si il est "coché" ou FALSE sinon.
La deuxième permet juste de savoir si le GtkCheckMenuItem est dans le troisième état "demi coché".

2.3 Les GtkRadioMenuItem

Les GtkRadioMenuItem héritent des GtkCheckMenuItem, donc tout ce qui a été dit juste avant s'applique aussi ici.

Pour créer un GtkRadioMenuItem, nous pouvons nous servir de :

GtkWidget* gtk_radio_menu_item_new(GSList *group);
GtkWidget* gtk_radio_menu_item_new_with_label(GSList *group, const gchar *label);
GtkWidget* gtk_radio_menu_item_new_with_mnemonic(GSList *group, const gchar *label);

Ce widget fonctionne de la même manière que le widget GtkRadioButton, et donc le paramètre group de ces fonctions, sert à dire au GtkRadioMenuItem à quel groupe il va appartenir.
Le premier GtkRadioMenuItem d'un groupe prendra toujours NULL comme paramètre group, de cette façon il va en créer un nouveau. Ensuite, il suffira de récupérer ce paramètre group grâce à la fonction suivante et de le passer à un autre GtkRadioMenuItem pour que celui ci fasse partie du même groupe que le premier.

GSList* gtk_radio_menu_item_get_group(GtkRadioMenuItem *radio_menu_item);

Tout comme pour le widget GtkRadioButton, quand on crée un groupe de GtkRadioMenuItem, il faut toujours récupérer le groupe du GtkRadioMenuItem précédemment créé (sauf pour le premier bien entendu).

On peut aussi définir ou remplacer le groupe d'un GtkRadioMenuItem aprés coup grâce à :

void gtk_radio_menu_item_set_group(GtkRadioMenuItem *radio_menu_item, GSList *group);

2.4 Les GtkSeparatorMenuItem

Ahh, en voilà un widget qu'il est bien !
Une seule fonction pour sa création, et c'est tout :

GtkWidget* gtk_separator_menu_item_new(void);

Ca c'est des fonctions qu'on aime ;-)

2.5 Les GtkTearoffMenuItem

Pour le créer faites :

GtkWidget* gtk_tearoff_menu_item_new(void);

Et c'est tout ! Ensuite, un premier clic sur l'élément GtkTearoffMenuItem créera une copie du menu dans une fenêtre, et un second clic sur l'élément (que cela soit dans le menu ou dans la fenêtre) supprimera la fenêtre créée.

Pour savoir si le menu est attaché ou détaché, il faut utiliser la fonction :

gboolean gtk_menu_get_tearoff_state(GtkMenu *menu);

Cette dernière renverra TRUE si le menu est détaché et FALSE sinon.

Lorsque nous fermons la fenêtre du menu détaché à la main (c'est à dire lorsque nous cliquons sur la croix), le menu est automatiquement réattaché, mais gtk_menu_get_tearoff_state renverra tout de même TRUE.

2.6 Programme exemple

Nous avons repris l'exemple précédent en modifiant le menu pour y mettre nos nouveaux items et en y ajoutant trois labels pour indiquer l'état des différents items. Il y a aussi trois callbacks supplémentaires pour la mise à jour de nos labels.

#include <stdlib.h>
#include <gtk/gtk.h>

void OnQuitter(GtkWidget* widget, gpointer data);
void OnAbout(GtkWidget* widget, gpointer data);
void OnRadio(GtkWidget* widget, gpointer data);
void OnTearoff(GtkWidget* widget, gpointer data);
void OnCheck(GtkWidget* widget, gpointer data);

static GtkWidget *pRadioLabel;
static GtkWidget *pCheckLabel;
static GtkWidget *pTearoffLabel;

int main(int argc, char **argv)
{
    GtkWidget *pWindow;
    GtkWidget *pVBox;
    GtkWidget *pVBox2;
    GtkWidget *pMenuBar;
    GtkWidget *pMenu;
    GtkWidget *pMenuItem;
    GSList *pList;
    gchar *sTempLabel;

    gtk_init(&argc, &argv);

    /* Creation de la fenetre */
    pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(pWindow), "GtkMenu");
    gtk_window_set_default_size(GTK_WINDOW(pWindow), 320, 200);
    g_signal_connect(G_OBJECT(pWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    /* Creation de la GtkVBox */
    pVBox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(pWindow), pVBox);

    /**** Creation du menu ****/

    /* ETAPE 1 */
    pMenuBar = gtk_menu_bar_new();
    /** Premier sous-menu **/
    /* ETAPE 2 */
    pMenu = gtk_menu_new();
    /* ETAPE 3 */

    /* GtkTearoffMenuItem */
    pMenuItem = gtk_tearoff_menu_item_new();
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    g_signal_connect(G_OBJECT(pMenuItem),"activate",G_CALLBACK(OnTearoff),(gpointer)pMenu);

    /* GtkImageMenuItem */
    pMenuItem = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW,NULL);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN,NULL);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE,NULL);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE,NULL);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    /* GtkSeparatorItem */
    pMenuItem = gtk_separator_menu_item_new();
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    /* GtkRadioMenuItem */
    pMenuItem = gtk_radio_menu_item_new_with_label(NULL, "Radio 1");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    pList = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(pMenuItem));
    /* Il est inutile ici d'utiliser le signal "toggled" */
    g_signal_connect(G_OBJECT(pMenuItem), "activate", G_CALLBACK(OnRadio), NULL);

    pMenuItem = gtk_radio_menu_item_new_with_label(pList, "Radio 2");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    pList = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(pMenuItem));
    g_signal_connect(G_OBJECT(pMenuItem) ,"activate", G_CALLBACK(OnRadio), NULL);

    pMenuItem = gtk_radio_menu_item_new_with_label(pList, "Radio 3");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    pList = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(pMenuItem));
    g_signal_connect(G_OBJECT(pMenuItem) ,"activate", G_CALLBACK(OnRadio), NULL);

    /* GtkSeparatorItem */
    pMenuItem = gtk_separator_menu_item_new();
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    /* GtkCheckMenuItem */
    pMenuItem = gtk_check_menu_item_new_with_label("Check");
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    g_signal_connect(G_OBJECT(pMenuItem),"toggled",G_CALLBACK(OnCheck),(gpointer)pMenu);

    /* GtkSeparatorItem */
    pMenuItem = gtk_separator_menu_item_new();
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    pMenuItem = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT,NULL);
    g_signal_connect(G_OBJECT(pMenuItem), "activate", G_CALLBACK(OnQuitter), (GtkWidget*) pWindow);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);

    /* ETAPE 4 */
    pMenuItem = gtk_menu_item_new_with_label("Fichier");
    /* ETAPE 5 */
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(pMenuItem), pMenu);
    /* ETAPE 6 */
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenuBar), pMenuItem);

    /** Deuxieme sous-menu **/
    /* ETAPE 2 */

    pMenu = gtk_menu_new();
    /* ETAPE 3 */
    pMenuItem = gtk_menu_item_new_with_label("A propos de...");
    g_signal_connect(G_OBJECT(pMenuItem), "activate", G_CALLBACK(OnAbout), (GtkWidget*) pWindow);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
    /* ETAPE 4 */
    pMenuItem = gtk_menu_item_new_with_label("?");
    /* ETAPE 5 */
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(pMenuItem), pMenu);
    /* ETAPE 6 */
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenuBar), pMenuItem);

    /* Creation de la deuxieme GtkVBox (pour les labels) */
    pVBox2 = gtk_vbox_new(FALSE, 0);

    pRadioLabel = gtk_label_new("Radio 1 est actif");
    gtk_box_pack_start(GTK_BOX(pVBox2), pRadioLabel, TRUE, TRUE, 0);

    sTempLabel = g_locale_to_utf8("Check est décoché", -1, NULL, NULL, NULL);
    pCheckLabel = gtk_label_new(sTempLabel);
    g_free(sTempLabel);
    gtk_box_pack_start(GTK_BOX(pVBox2), pCheckLabel, TRUE, TRUE, 0);

    sTempLabel = g_locale_to_utf8("Menu attaché", -1, NULL, NULL, NULL);
    pTearoffLabel = gtk_label_new(sTempLabel);
    g_free(sTempLabel);
    gtk_box_pack_start(GTK_BOX(pVBox2), pTearoffLabel, TRUE, TRUE, 0);

    /* Ajout du menu a la fenetre */
    gtk_box_pack_start(GTK_BOX(pVBox), pMenuBar, FALSE, FALSE, 0);
    /* Ajout des labels a la fenetre */
    gtk_box_pack_start(GTK_BOX(pVBox), pVBox2, TRUE, TRUE, 0);

    gtk_widget_show_all(pWindow);

    gtk_main();

    return EXIT_SUCCESS;
}


void OnRadio(GtkWidget* widget, gpointer data)
{
    const gchar *sRadioName;
    gchar *sLabel;

    /* Recuperer le label du bouton radio active */
    sRadioName = gtk_label_get_label(GTK_LABEL(GTK_BIN(widget)->child));

    sLabel = g_strdup_printf("%s est actif",sRadioName);
    gtk_label_set_label(GTK_LABEL(pRadioLabel), sLabel);
    g_free(sLabel);
}

void OnCheck(GtkWidget* widget, gpointer data)
{
    gboolean bCoche;
    gchar *sLabel;
    gchar *sLabelUtf8;

    /* Savoir si le GtkCheckMenuItem est coche ou non */
    bCoche = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));

    if(bCoche)
        sLabel = g_strdup("Check est coché");
    else
        sLabel = g_strdup("Check est décoché");

    sLabelUtf8 = g_locale_to_utf8(sLabel, -1, NULL, NULL, NULL);

    gtk_label_set_label(GTK_LABEL(pCheckLabel), sLabelUtf8);
    g_free(sLabel);
    g_free(sLabelUtf8);
}

void OnTearoff(GtkWidget* widget, gpointer data)
{
    gboolean bDetache;
    gchar *sLabel;
    gchar *sLabelUtf8;

    /* Savoir si le menu est detache ou non */
     bDetache = gtk_menu_get_tearoff_state(GTK_MENU(data));

    if(bDetache)
        sLabel = g_strdup("Menu détaché");
    else
        sLabel = g_strdup("Menu attaché");

    sLabelUtf8 = g_locale_to_utf8(sLabel, -1, NULL, NULL, NULL);

    gtk_label_set_label(GTK_LABEL(pTearoffLabel), sLabelUtf8);
    g_free(sLabel);
    g_free(sLabelUtf8);
}

void OnQuitter(GtkWidget* widget, gpointer data)
{
    GtkWidget *pQuestion;

    pQuestion = gtk_message_dialog_new(GTK_WINDOW(data),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_QUESTION,
        GTK_BUTTONS_YES_NO,
        "Voulez vous vraiment\n"
        "quitter le programme?");

    switch(gtk_dialog_run(GTK_DIALOG(pQuestion)))
    {
        case GTK_RESPONSE_YES:
            gtk_main_quit();
            break;
        case GTK_RESPONSE_NONE:
        case GTK_RESPONSE_NO:
            gtk_widget_destroy(pQuestion);
            break;
    }
}

void OnAbout(GtkWidget* widget, gpointer data)
{
    GtkWidget *pAbout;

    pAbout = gtk_message_dialog_new (GTK_WINDOW(data),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_INFO,
        GTK_BUTTONS_OK,
        "Cours GTK+ 2.0\n"
        "http://gtk.developpez.com");

    gtk_dialog_run(GTK_DIALOG(pAbout));

    gtk_widget_destroy(pAbout);
}

Résultat :

3. Le menu - Méthode rapide

warning Cette méthode est obsolète depuis GTK+ 2.4, il est fortement conseillé d'utiliser GtkUIManager en remplacement.

Grâce aux deux premières parties du chapitre, nous avons appris à créer un menu. Vous avez pu remarquer que cette méthode peut s'avérer très fastidieuse si le menu comporte beaucoup d'éléments. Nous allons maintenant voir comment simplifier tout cela pour le biais de l'objet GtkItemFactory.

3.1 Fonctionnement de GtkItemFactory

L'objet GtkItemFactory n'est en fait qu'une grosse machine nous fournissant les fonctions nécessaires à la création d'un menu. Pour cela, GtkItemFactory utilise la structure GtkItemFactoryEntry qui elle nous permet de définir les différents éléments qui composent le menu ainsi que les actions qui leurs sont associés. Voici la définition de la structure GtkItemFactoryEntry :

struct GtkItemFactoryEntry {
    gchar *path;
    gchar *accelerator;
    GtkItemFactoryCallback callback;
    guint callback_action;
    gchar *item_type;
    gconstpointer extra_data;
};

3.2 Création des éléments du menu

Pour cela, la seule à faire est de déclarer et de remplir un tableau de GtkItemFactoryEntry. Nous allons donc maintenant étudier comment remplir ce tableau d'après la définition de la structure GtkItemFactoryEntry.

Le paramètre path correspond au chemin de l'élément dans l'arborescence du menu. Il faut pour cela, voir l'arborescence d'un menu comme celle des fichiers, c'est-à-dire organisée en répertoire (les branches du menu) et en fichier (les éléments du menu). Ce paramètre permet aussi de définir le texte de l'élément. Par exemple, nous avons un menu "Fichier" qui possède un élément "Nouveau", alors le path de la branche "Fichier" sera "/Fichier" et celui de l'élément "Nouveau" sera "/Fichier/Nouveau". Pour souligner une lettre (pour indiquer un raccourci clavier), il faut précéder cette lettre de "_" comme pour les mnémonics. Cependant, cela ne crée pas un raccourci, il s'agit juste d'une indication.

Le second paramètre accelerator définit la combinaison de touche qui activera l'élément du menu, et cela, même si le menu n'est pas ouvert. La combinaison de touche allouée à l'élément est affichée après le label de l'élément du menu. La syntaxe de ce paramètre est "<touche_speciale>Lettre", touche_spéciale pouvant être Ctrl, Alt ou Shift. Voici donc le récapitulatif des solutions possibles :

Valeur Touche
<ALT> Alt
<CTRL> <CONTROL> <CTL> Control
<SHFT> <SHIFT> Shift

Il est bien sûr possible d'utiliser une combinaison de ces touches (exemple : "<CTRL><SHIFT>O").

Le paramètre callback est la fonction callback qui sera appelée lorsque l'élément sera activé à l'aide de la souris ou d'un raccourci clavier.

Le quatrième paramètre, callback_action, a plusieurs incidences sur la fonction callback. Tout d'abord, si callback_action est égal à 0, la fonction callback devra avoir ce prototype :

void fonction_callback(void);

Dans tous les autres cas, le prototype sera :

void fonction_callback(gpointer callback_data, gint callback_action, GtkWidget *widget);

Dans ce cas, callback_data est la donnée supplémentaire envoyée à la fonction callback, callback_action et identique à la valeur donnée à l'élément et widget est l'élément lui-même.

Le cinquième paramètre, item_type, définit de quoi est constitué l'élément du menu. Ce paramètre doit être une des valeurs suivantes :

Valeur Type d'élément
"<Branch>" Elément ouvrant un autre menu.
NULL, "", "<Item>" Elément classique avec du texte.
"<StockItem>" Elément de type GtkStockItem.
"<ImageItem>" Elément de type GtkImageMenuItem.
"<CheckItem>" Elément de type GtkCheckMenuItem.
"<ToggleItem>" Elément de type GtkToggleBtn.
"<RadioItem>" Elément de type GtkRadioMenuItem.
"path" Elément de type GtkRadioMenuItem.
path correspond au path de l'élément principal du groupe de GtkRadioMenuItem.
"<Title>" Affiche seulement le texte (grisé) de l'élément. Ce dernier ne peut être sélectionné.
"<Separator>" Une ligne de séparation.
"<Tearoff>" Elément pour détacher une partie du menu
"<LastBranch>" Elément ouvrant un autre menu. Met l'élément à droite de la barre de menu.

La seule subtilité ici, est pour les éléments de type GtkRadioMenuItem. Le premier bouton radio du groupe (ex : path = "/Choix/Pour") aura item_type égal à "<RadioItem>" alors que les autres auront item_type égal à "/Choix/Pour".

Enfin pour terminer, le dernier paramètreextra_data devra être défini dans deux cas :

  • l'élément est de type "<StockItem>", alors extra_data sera l'identifiant du GtkStockItem à afficher dans le menu ;
  • l'élément est de type "<ImageItem>", alors extra_data sera un pointeur sur le GdkPixmap (pas encore étudié à ce stade du cours) à afficher.

Nous savons donc maintenant comment déclarer le menu et il ne reste plus qu'à le créer.

3.3 Création du menu.

La première chose à faire est de créer un objet pour récupérer les raccourcis clavier du menu. Pour cela nous allons utiliser l'objet GtkAccelGroup dont nous n'allons pas parler ici. La création de cet objet se fait à l'aide de cette fonction :

GtkAccelGroup* gtk_accel_group_new (void);

Ensuite il faut créer l'objet GtkItemFactory avec cette fonction :

GtkItemFactory* gtk_item_factory_new(GType container_type, const gchar *path, GtkAccelGroup *accel_group);

Le premier paramètre container_type, définit le type de menu que nous voulons créer. Il existe trois valeurs possibles :

  • GTK_TYPE_MENU_BAR, qui créera une barre de menu tout ce qu'il y a de plus normal ;
  • GTK_TYPE_MENU, qui créera un menu qui servira pour faire un menu popup ;
  • GTK_TYPE_OPTION_MENU, qui créera une sorte de bouton qui ouvrira le menu un fois cliqué.

Le paramètre path est le nom que nous donnons au menu. Il représente la racine du menu et doit avoir la syntaxe suivante "<nom_du_menu>".

Le dernier paramètre permet de récupérer les raccourcis clavier dans le GtkAccelGroup. Ce paramètre peut être égal à NULL.

Une fois le menu créer, il faut y insérer tous ses éléments avec la fonction :

void gtk_item_factory_create_items(GtkItemFactory *ifactory, guint n_entries, GtkItemFactoryEntry *entries, gpointer callback_data);

Voici le détail des différents paramètres :

  • ifactory est le GtkItemFactory que nous venons de créer ;
  • n_entries est le nombre d'éléments que nous allons ajouter ;
  • entries est le tableau de GtkItemFactoryEntry qui a été crée au début ;
  • callback_data est la donnée supplémentaire qui sera envoyé aux différentes fonctions callback. Cette donnée est unique et sera envoyée pour tous les éléments de entries qui ont le paramètre callback_action différents de zéro.

Il faut ensuite récupérer de tout cela un GtkWidget pour pouvoir l'ajouter à notre fenêtre. Pour cela, il faut utiliser cette fonction :

GtkWidget* gtk_item_factory_get_widget(GtkItemFactory *ifactory, const gchar *path);

Les paramètres sont respectivement le GtkItemFactory créer précédemment (ifactory) avec le nom qui lui est associé (path).

Et pour terminer tout cela, il faut associer les raccourcis à la fenêtre contenant le menu. Cela est possible par le biais d'une fonction du widget GtkWindow :

void gtk_window_add_accel_group(GtkWindow *window, GtkAccelGroup *accel);

Et voila, le menu est terminé.

3.4 Exemple.

Nous allons reprendre l'exemple de la première partie, mais cette en utilisant des éléments de menu de type "<StockItem>". En plus de cela, nous rajouterons en bas de la fenêtre un menu de type GTK_TYPE_OPTION_MENU.

3.5 Programme exemple.

#include <stdlib.h>
#include <gtk/gtk.h>

void OnQuitter(gpointer data, guint callback_action,GtkWidget *widget);
void OnAbout(gpointer data, guint callback_action,GtkWidget *widget);

/* Definition des elements du menu */
static GtkItemFactoryEntry MenuItem[] = {
    { "/_Fichier", NULL, NULL, 0, "<Branch>" },
    { "/Fichier/_Nouveau", NULL, NULL, 0, "<StockItem>", GTK_STOCK_NEW },
    { "/Fichier/_Ouvrir", NULL, NULL, 0, "<StockItem>", GTK_STOCK_OPEN },
    { "/Fichier/Enregi_strer", "<ctrl>S", NULL, 0, "<StockItem>", GTK_STOCK_SAVE },
    { "/Fichier/_Fermer", "<ctrl>F", NULL, 0, "<StockItem>", GTK_STOCK_CLOSE },
    { "/Fichier/Sep1", NULL, NULL, 0, "<Separator>" },
    { "/Fichier/_Quitter", NULL, OnQuitter, 1, "<StockItem>", GTK_STOCK_QUIT},
    { "/_?", NULL, NULL, 0, "<Branch>" },
    { "/?/_A propos de...", "<CTRL>A", OnAbout, 1, "<StockItem>", GTK_STOCK_HELP}
};

/* Nombre d elements du menu */
static gint iNbMenuItem = sizeof(MenuItem) / sizeof(MenuItem[0]);

int main(int argc, char **argv)
{
    GtkWidget *pWindow;
    GtkWidget *pVBox;
    GtkWidget *pMenuBar;
    GtkItemFactory *pItemFactory;
    GtkAccelGroup *pAccel;

    gtk_init(&argc, &argv);

    /* Creation de la fenetre */
    pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(pWindow), "Les menus");
    gtk_window_set_default_size(GTK_WINDOW(pWindow), 320, 200);
    g_signal_connect(G_OBJECT(pWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    pVBox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(pWindow), pVBox);

    /* Creation de la table d acceleration */
    pAccel = gtk_accel_group_new ();

    /* Creation du menu */
    pItemFactory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", pAccel);
    /* Recuperation des elements du menu */
    gtk_item_factory_create_items(pItemFactory, iNbMenuItem, MenuItem, (GtkWidget*)pWindow);
    /* Recuperation du widget pour l affichage du menu */
    pMenuBar = gtk_item_factory_get_widget(pItemFactory, "<main>");
    /* Ajout du menu en haut de la fenetre */
    gtk_box_pack_start(GTK_BOX(pVBox), pMenuBar, FALSE, FALSE, 0);
    /* Association des raccourcis avec la fenetre */
    gtk_window_add_accel_group(GTK_WINDOW(pWindow), pAccel);

    /* Creation d un second menu de type GTK_TYPE_OPTION_MENU */
    pItemFactory = gtk_item_factory_new(GTK_TYPE_OPTION_MENU, "<main>", NULL);
    /* Recuperation des elements du menu */
    gtk_item_factory_create_items(pItemFactory, iNbMenuItem, MenuItem, (GtkWidget*)pWindow);
    /* Recuperation du widget pour l affichage du menu */
    pMenuBar = gtk_item_factory_get_widget(pItemFactory, "<main>");
    /* Ajout du menu en bas de la fenetre */
    gtk_box_pack_end(GTK_BOX(pVBox), pMenuBar, FALSE, FALSE, 0);

    gtk_widget_show_all(pWindow);
    gtk_main();

    return EXIT_SUCCESS;
}

void OnQuitter(gpointer data, guint callback_action,GtkWidget *widget)
{
    GtkWidget *pQuestion;

    pQuestion = gtk_message_dialog_new(GTK_WINDOW(data),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_QUESTION,
        GTK_BUTTONS_YES_NO,
        "Voulez vous vraiment\n"
        "quitter le programme?");

    switch(gtk_dialog_run(GTK_DIALOG(pQuestion)))
    {
    case GTK_RESPONSE_YES:
        gtk_main_quit();
        break;
    case GTK_RESPONSE_NONE:
    case GTK_RESPONSE_NO:
        gtk_widget_destroy(pQuestion);
        break;
    }
}

void OnAbout(gpointer data, guint callback_action,GtkWidget *widget)
{
    GtkWidget *pAbout;

    pAbout = gtk_message_dialog_new (GTK_WINDOW(data),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_INFO,
        GTK_BUTTONS_OK,
        "Cours GTK+ 2.0\n"
        "http://gtk.developpez.com");

    gtk_dialog_run(GTK_DIALOG(pAbout));

    gtk_widget_destroy(pAbout);
}

Résultat :

4. En savoir plus :

4.1 Les signaux

GtkMenuShell
 
activate-current
Prototype fonction callback : void user_function(GtkMenuShell *menushell, gboolean force_hide, gpointer user_data);
cancel
Prototype fonction callback : void user_function (GtkMenuShell *menushell, gpointer user_data);
cycle-focus
Prototype fonction callback : void user_function (GtkMenuShell *menushell, GtkDirectionType arg1, gpointer user_data);
deactivate
Prototype fonction callback : void user_function (GtkMenuShell *menushell, gpointer user_data);
move-current
Prototype fonction callback : void user_function (GtkMenuShell *menushell, GtkMenuDirectionType direction, gpointer user_data);
selection-done
Prototype fonction callback : void user_function (GtkMenuShell *menushell, gpointer user_data);

GtkMenuItem
 
activate
Prototype fonction callback : void user_function(GtkMenuItem *menuitem, gpointer user_data);
activate-item
Prototype fonction callback : void user_function(GtkMenuItem *menuitem, gpointer user_data);
toggle-size-allocate
Prototype fonction callback : void user_function(GtkMenuItem *menuitem, gint arg1, gpointer user_data);
toggle-size-request
Prototype fonction callback : void user_function(GtkMenuItem *menuitem, gint arg1, gpointer user_data);

4.2 Fonctions non documentées

GtkMenuShell

void gtk_menu_shell_insert (GtkMenuShell *menu_shell, GtkWidget *child, gint position);
void gtk_menu_shell_deactivate (GtkMenuShell *menu_shell);
void gtk_menu_shell_select_item (GtkMenuShell *menu_shell, GtkWidget *menu_item);
void gtk_menu_shell_deselect (GtkMenuShell *menu_shell);
void gtk_menu_shell_activate_item (GtkMenuShell *menu_shell, GtkWidget *menu_item, gboolean force_deactivate);

GtkMenu

void gtk_menu_reorder_child (GtkMenu *menu, GtkWidget *child, gint position);
void gtk_menu_popup (GtkMenu *menu, GtkWidget *parent_menu_shell, GtkWidget *parent_menu_item, GtkMenuPositionFunc func, gpointer data, guint button, guint32 activate_time);
void gtk_menu_set_accel_group (GtkMenu *menu, GtkAccelGroup *accel_group);
GtkAccelGroup* gtk_menu_get_accel_group (GtkMenu *menu);
void gtk_menu_set_accel_path (GtkMenu *menu, const gchar *accel_path);
void gtk_menu_set_title (GtkMenu *menu, const gchar *title);
G_CONST_RETURN gchar* gtk_menu_get_title (GtkMenu *menu);
GtkWidget* gtk_menu_get_active (GtkMenu *menu);
void gtk_menu_set_active (GtkMenu *menu, guint index);
void gtk_menu_set_tearoff_state (GtkMenu *menu, gboolean torn_off);
void gtk_menu_attach_to_widget (GtkMenu *menu, GtkWidget *attach_widget, GtkMenuDetachFunc detacher);
void gtk_menu_detach (GtkMenu *menu);
GtkWidget* gtk_menu_get_attach_widget (GtkMenu *menu);

GtkMenuItem

void gtk_menu_item_set_right_justified(GtkMenuItem *menu_item, gboolean right_justified);
void gtk_menu_item_set_accel_path (GtkMenuItem *menu_item, const gchar *accel_path);
void gtk_menu_item_remove_submenu (GtkMenuItem *menu_item);
void gtk_menu_item_select (GtkMenuItem *menu_item);
void gtk_menu_item_deselect (GtkMenuItem *menu_item);
void gtk_menu_item_activate (GtkMenuItem *menu_item);
void gtk_menu_item_toggle_size_request(GtkMenuItem *menu_item, gint *requisition);
void gtk_menu_item_toggle_size_allocate(GtkMenuItem *menu_item, gint allocation);
gboolean gtk_menu_item_get_right_justified(GtkMenuItem *menu_item);
GtkWidget* gtk_menu_item_get_submenu (GtkMenuItem *menu_item);

Date de mise à jour : 03 mars 2003