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

Cours GTK 2

Date de publication : 20 Février 2007



Les boutons (partie 2)

Nous allons étudier cette fois-ci trois nouveaux types de boutons qui dérivent du widget GtkButton (chapitre V). L'étude de ces widgets sera rapide car ils ne comportent que très peu de fonctions.

1. Le widget GtkToggleButton

Il s'agit ici d'un bouton poussoir qui ne peut prendre que deux états : enfoncé ou relâché. Ce widget dérive de GtkButton.

1.1 Création du bouton

Vous allez voir ici, il n'y a rien de bien compliqué vu que c'est toujours le même principe :

GtkWidget* gtk_toggle_button_new(void);
GtkWidget* gtk_toggle_button_new_with_label(const gchar* label);
GtkWidget* gtk_toggle_button_new_with_mnemonics(const gchar* label);

La première fonction crée un nouveau bouton vide, alors que la seconde ajoute du texte à l'intérieur et la troisième ajoute en plus un raccourci clavier.

1.2 Etats du bouton

Il peut être intéressant de connaître l'état du bouton pour agir en conséquence. Une fois encore, rien de plus simple on utilise la fonction :

gboolean gtk_toggle_button_get_active (GtkToggleButton *toggle_button);

Cette dernière nous renvoie TRUE si le bouton est enfoncé et FALSE sinon. Afin de pouvoir utiliser le paramètre toggle_button qui est le bouton dont on veut connaître l'état, il faut utiliser la macro GTK_TOGGLE_BUTTON().

Pour modifier l'état du bouton, c'est aussi simple :

void gtk_toggle_button_set_active (GtkToggleButton *toggle_button, gboolean is_active);

Il suffit de mettre le paramètre is_active à TRUE si l'on veut enfoncer le bouton ou à FALSE pour le relâcher.

Il existe cependant un troisième état qui n'est pas accessible en cliquant sur le bouton mais par une fonction spécifique. Ce troisième état, vous le connaissez sûrement. Le meilleur exemple est celui des éditeurs de texte :

Vous avez une phrase dans laquelle il y a du texte normal et du texte en gras. Si vous sélectionnez le texte en gras, le bouton permettant justement de le mettre en gras s'enfonce (en général B). Par contre si vous sélectionnez le texte normal, ce même bouton repasse à son état relâché. Venons en au troisième état, qui apparaît lorsque vous sélectionnez la phrase entière. Le bouton ne change pas d'état mais seulement d'aspect, il donne l'impression d'être inactif.

Ce changement d'aspect doit se faire manuellement, et s'effectue avec cette fonction :

void gtk_toggle_button_set_inconsistent(GtkToggleButton *toggle_button, gboolean setting);

Il suffit de mettre le paramètre setting à TRUE pour donner au bouton l'aspect inactif.

Et pour connaître l'aspect du bouton, il y a tout naturellement la fonction :

gboolean gtk_toggle_button_get_inconsistent(GtkToggleButton *toggle_button);

Evidemment, toutes les fonctions du widget GtkButton sont utilisables avec ce type de bouton.

1.3 Exemple

Nous allons construire une application contenant un GtkToggleButton qui changera d'état (bien sûr) lorsque nous cliquerons dessus, mais aussi lorsque nous cliquerons sur un deuxième bouton (le changement d'état entraînera la modification du label). De plus, il y aura un troisième bouton pour changer l'aspect du bouton (idem, on change aussi le label).

La création des widgets étant classique, nous n'allons donc pas revenir dessus.

Il va donc nous falloir trois fonctions callback. La première pour changer le texte lorsque l'on clique sur le GtkToggleButton, la deuxième pour changer son état lorsque l'on clique sur le deuxième bouton, et la troisième pour changer l'aspect.

Pour la première fonction callback, il faut intercepter le signal "toggled" qui est émis lorsque le bouton change d'état :

g_signal_connect(G_OBJECT(pToggleBtn), "toggled", G_CALLBACK(OnToggle), NULL);

Dans cette fonction nous allons récupérer l'état du bouton ainsi que son aspect afin de changer le label en fonction des valeurs de retour.

Pour la deuxième, on interceptera le signal "clicked" :

g_signal_connect(G_OBJECT(pEtatBtn), "clicked", G_CALLBACK(OnEtatBtn), pToggleBtn);

Lorsque l'on changera l'état du bouton toggle, le signal "toggled" sera émis pour celui-ci et le texte se changera automatiquement.

Pour la troisième, le signal à intercepter et le même ("clicked") :

g_signal_connect(G_OBJECT(pAspectBtn), "clicked", G_CALLBACK(OnAspectBtn), pToggleBtn);

Cette fois, lorsque l'on change l'aspect du bouton, le signal "toggled" n'est pas émis et le texte ne changera donc pas. Il faut donc émettre soit même ce signal avec la fonction :

void gtk_toggle_button_toggled (GtkToggleButton *toggle_button);

Cette fonction ne change en aucun cas l'état du bouton, elle ne fait qu'émettre le signal.

Mais le plus simple est de regarder le code source suivant.

1.4 Programme exemple

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

void OnToggle(GtkWidget *pToggle, gpointer data);
void OnEtatBtn(GtkWidget *pWidget, gpointer pToggle);
void OnAspectBtn(GtkWidget *pWidget, gpointer pToggle);

int main(int argc, char **argv)
{
    GtkWidget *pWindow;
    GtkWidget *pToggleBtn;
    GtkWidget *pEtatBtn;
    GtkWidget *pAspectBtn;
    GtkWidget *pVBox;
    gchar *sLabel;

    gtk_init(&argc,&argv);

    pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(pWindow), "GtkToggleButton");
    gtk_window_set_default_size(GTK_WINDOW(pWindow), 320, 200);

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

    /* Creation du label du bouton */
    sLabel = g_locale_to_utf8("Etat : Relâché - Aspect : Normal", -1, NULL, NULL, NULL);
    /* Creation du bouton GtkToggleButton */
    pToggleBtn = gtk_toggle_button_new_with_label(sLabel);
    /* Le label sLabel n'est plus utile */
    g_free(sLabel);

    gtk_box_pack_start(GTK_BOX(pVBox), pToggleBtn, FALSE, FALSE, 0);

    pEtatBtn = gtk_button_new_with_label("CHANGER ETAT");
    gtk_box_pack_start(GTK_BOX(pVBox), pEtatBtn, FALSE, FALSE, 0);

    pAspectBtn = gtk_button_new_with_label("CHANGER ASPECT");
    gtk_box_pack_start(GTK_BOX(pVBox), pAspectBtn, FALSE, FALSE, 0);

    gtk_widget_show_all(pWindow);

    /* Connexion des signaux */
    g_signal_connect(G_OBJECT(pWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(pToggleBtn), "toggled", G_CALLBACK(OnToggle), NULL);
    g_signal_connect(G_OBJECT(pEtatBtn), "clicked", G_CALLBACK(OnEtatBtn), pToggleBtn);
    g_signal_connect(G_OBJECT(pAspectBtn), "clicked", G_CALLBACK(OnAspectBtn), pToggleBtn);

    gtk_main();

    return EXIT_SUCCESS;
}

void OnEtatBtn(GtkWidget *pWidget, gpointer pToggle)
{
    gboolean bEtat;

    /* Recuperation de l etat du bouton */
    bEtat = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pToggle));

    /* Modification de l etat du bouton */
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pToggle), (bEtat ^ TRUE));
}

void OnAspectBtn(GtkWidget *pEtatBtn, gpointer pToggle)
{
    gboolean bInconsistent;

    /* Recuperation de l aspect du bouton */
    bInconsistent = gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(pToggle));

    /* Modification de l aspect du bouton */
    gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(pToggle), (bInconsistent ^ TRUE));

    /* On emet le signal "toggle" pour changer le texte du bouton */
    gtk_toggle_button_toggled(GTK_TOGGLE_BUTTON(pToggle));
}

void OnToggle(GtkWidget *pToggle, gpointer data)
{
    gboolean bEtat;
    gboolean bInconsistent;
    gchar *sLabel;
    gchar *sLabelUtf8;

    /* Recuperation de l etat du bouton */
    bEtat = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pToggle));
    /* Recuperation de l aspect du bouton */
    bInconsistent = gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(pToggle));

    /* Construction du label du bouton */
    sLabel = g_strdup_printf("Etat : %s - Aspect : %s",
        bEtat ? "Enfoncé" : "Relâché",
        bInconsistent ? "Modifié" : "Normal");
    /* Encodage du label en UTF8 */
    sLabelUtf8 = g_locale_to_utf8(sLabel, -1, NULL, NULL, NULL);

    /* Modification du label du bouton */
    gtk_button_set_label(GTK_BUTTON(pToggle), sLabelUtf8);

    /* Les chaines sLabel et sLabelUtf8 n'ont plus d'utilite */
    g_free(sLabel);
    g_free(sLabelUtf8);
}

Résultat :

2. Le widget GtkCheckButton

Sous ce nom se cachent tout simplement les cases à cocher que tout le monde connaît. Il s'agit là aussi d'un type de bouton binaire, d'ailleurs ce widget dérive de GtkToggleButton :


2.1 Création d'un GtkCheckButton

Une fois encore, la syntaxe et l'utilisation des fonctions de création restent classiques :

GtkWidget* gtk_check_button_new (void);
GtkWidget* gtk_check_button_new_with_label (const gchar *label);
GtkWidget* gtk_check_button_new_with_mnemonic(const gchar *label);

2.2 Utilisation d'un GtkCheckButton

Il n'existe pas d'autres fonctions pour ce widget, il faut donc utiliser les fonctions des widgets GtkToggleButton et GtkButton pour récupérer les propriétés du widget (état, aspect, label, ...).

Nous n'allons pas ici créer de programme exemple pour ce widget car il suffit juste de remplacer gtk_toggle_button_new_with_label par gtk_check_button_new_with_label dans l'exemple précédent pour obtenir ceci :

3. Le widget GtkRadioButton

Nous passons maintenant au widget GtkRadioButton qui se différencie par la possibilité d'en grouper plusieurs. De ce fait, lors que par exemple nous avons un groupe de trois boutons, il n'y en a qu'un seul qui peut être actif. On pourrait très bien faire cela avec le widget GtkCheckButton mais cela serait beaucoup plus long à programmer. Dans la hiérarchie des widgets, GtkRadioButton dérive de GtkCheckButton.

3.1 Création d'un groupe de GtkRadioButton.

Afin de grouper les boutons radio, GTK+ utilise les listes simplement chaînées de GLib. Pour créer le premier bouton radio du groupe, il faut obligatoirement passer par une de ces fonctions :

GtkWidget* gtk_radio_button_new (GSList *group);
GtkWidget* gtk_radio_button_new_with_label (GSList *group, const gchar *label);
GtkWidget* gtk_radio_button_new_with_mnemonic(GSList *group, const gchar *label);

Au moment de la création, le bouton radio est automatiquement ajouté à la liste group. Cependant, ce paramètre n'est pas obligatoire. Nous pouvons très bien mettre NULL comme valeur et cela marchera de la même manière, sauf que nous n'aurons pas de pointeur sur la liste. La valeur de retour de cette fonction sera aussi copiée dans la variable data de la liste chaînée.

Ensuite pour rajouter les autres boutons au groupe, il y a plusieurs possibilités. La première est d'utiliser une des trois fonctions précédentes mais ce n'est pas tout, car autant pour le premier bouton du groupe, il n'est pas nécessaire d'avoir une liste, autant pour les autres boutons cela devient obligatoire. Pour cela, GTK+ nous fournit une fonction qui permet d'obtenir la liste dans laquelle les boutons du groupe sont ajoutés :

GSList* gtk_radio_button_get_group(GtkRadioButton *radio_button);

Avec cette fonction, nous pouvons donc connaître la liste à laquelle appartient le bouton radio_button, ce qui va nous permettre d'ajouter de nouveau bouton au groupe. Mais là où cela se complique, c'est qu'il faut récupérer la liste avant chaque ajout de bouton avec le dernier bouton ajouté comme paramètre. Voici ce que cela donnerai pour un groupe de trois boutons :

pRadio1 = gtk_radio_button_new_with_label(NULL, "Radio 1");
pGroup = gtk_radio_button_get_group(GTK_RADIO_BUTTON(pRadio1));
pRadio2 = gtk_radio_button_new_with_label(pGroup, "Radio 2");
pGroup = gtk_radio_button_get_group(GTK_RADIO_BUTTON(pRadio2));
pRadio3 = gtk_radio_button_new_with_label(pGroup, "Radio 3");

Ce système peut donc s'avérer lourd lors de la création du groupe, surtout s'il contient un grand nombre de boutons. Heureusement, les concepteurs de GTK+ ont pensé à nous simplifier la vie en ajoutant ces trois fonctions :

GtkWidget* gtk_radio_button_new_from_widget(GtkRadioButton *group);
GtkWidget* gtk_radio_button_new_with_label_from_widget(GtkRadioButton *group, const gchar *label);
GtkWidget* gtk_radio_button_new_with_mnemonic_from_widget(GtkRadioButton *group, const gchar *label);

Cette fois group ne correspond pas à la liste mais à un des boutons du groupe. A chaque appel d'une de ces fonctions GTK+ va s'occuper de récupérer correctement la liste à laquelle appartient le bouton group et ajouter le nouveau bouton. Cela aura pour conséquence, dans le cas d'un groupe de trois boutons, de réduire le nombre de lignes de code de 5 à 3 (et oui, une ligne de code c'est une ligne de code).

Nous savons maintenant tout ce qu'il faut pour créer un groupe de GtkRadioButton.

3.2 Exemple

Le programme exemple se présente sous la forme d'un mini sondage à trois choix :

  • Pour ;
  • Contre ;
  • Sans opinion.

La possibilité de choisir parmi une de ces valeurs est rendue possible par l'utilisation des GtkRadioButton. Un autre bouton (tout ce qu'il y a de plus normal) permet de valider le choix fait par l'utilisateur ainsi que d'afficher le résultat dans une boîte de dialogue.

3.3 Programme exemple

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

void OnValider(GtkWidget *pBtn, gpointer data);

int main(int argc, char **argv)
{
    GtkWidget *pWindow;
    GtkWidget *pVBox;
    GtkWidget *pRadio1;
    GtkWidget *pRadio2;
    GtkWidget *pRadio3;
    GtkWidget *pValider;
    GtkWidget *pLabel;

    gtk_init(&argc, &argv);

    pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(pWindow), "GtkRadioButton");
    gtk_window_set_default_size(GTK_WINDOW(pWindow), 320, 200);

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

    pLabel = gtk_label_new("Votre choix :");
    gtk_box_pack_start(GTK_BOX(pVBox), pLabel, FALSE, FALSE, 0);

    /* Creation du premier bouton radio */
    pRadio1 = gtk_radio_button_new_with_label(NULL, "Pour");
    gtk_box_pack_start(GTK_BOX (pVBox), pRadio1, FALSE, FALSE, 0);
    /* Ajout du deuxieme */
    pRadio2 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON (pRadio1), "Contre");
    gtk_box_pack_start(GTK_BOX (pVBox), pRadio2, FALSE, FALSE, 0);
    /* Ajout du troisieme */
    pRadio3 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON (pRadio1), "Sans opinion");
    gtk_box_pack_start(GTK_BOX (pVBox), pRadio3, FALSE, FALSE, 0);

    pValider = gtk_button_new_from_stock(GTK_STOCK_OK);
    gtk_box_pack_start(GTK_BOX (pVBox), pValider, FALSE, FALSE, 0);

    gtk_widget_show_all(pWindow);

    /* Connexion des signaux */
    g_signal_connect(G_OBJECT(pWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(pValider), "clicked", G_CALLBACK(OnValider), pRadio1);

    gtk_main();

    return EXIT_SUCCESS;
}

void OnValider(GtkWidget *pBtn, gpointer data)
{
    GtkWidget *pInfo;
    GtkWidget *pWindow;
    GSList *pList;
    const gchar *sLabel;

    /* Recuperation de la liste des boutons */
    pList = gtk_radio_button_get_group(GTK_RADIO_BUTTON(data));

    /* Parcours de la liste */
    while(pList)
    {
        /* Le bouton est il selectionne */
        if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pList->data)))
        {
            /* OUI -> on copie le label du bouton */
            sLabel = gtk_button_get_label(GTK_BUTTON(pList->data));
            /* On met la liste a NULL pour sortir de la boucle */
            pList = NULL;
        }
        else
        {
            /* NON -> on passe au bouton suivant */
            pList = g_slist_next(pList);
        }
    }

    /* On recupere la fenetre principale */
    /*
    /* A partir d'un widget, gtk_widget_get_toplevel
    /* remonte jusqu'a la fenetre mere qui nous est
    /* utile pour l'affichage de la boite de dialogue
    */

    pWindow = gtk_widget_get_toplevel(GTK_WIDGET(data));

    pInfo = gtk_message_dialog_new (GTK_WINDOW(pWindow),
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_INFO,
        GTK_BUTTONS_OK,
        "Vous avez choisi : %s", sLabel);

    gtk_dialog_run(GTK_DIALOG(pInfo));

    gtk_widget_destroy(pInfo);
}

Résultat :

4. En savoir plus

4.1 Fonctions documentées

GtkToggleButton
 
void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button, gboolean draw_indicator);
Définit si le bouton est visible ou pas.
Entrée(s) :
toggle_button : le bouton.
draw_indicator : TRUE pour visible, FALSE pour invisible.
Sortie : rien
gboolean gtk_toggle_button_get_mode (GtkToggleButton *toggle_button);
Pour savoir si le bouton est visible ou pas.
Entrée(s) :
toggle_button : le bouton.
Sortie : TRUE si le bouton est visible, FALSE sinon.

GtkRadioButton

void gtk_radio_button_set_group (GtkRadioButton *radio_button, GSList *group);
Pour changer un bouton radio de groupe.
Entrée(s) :
toggle_button : le bouton.
group : le nouveau groupe du bouton.
Sortie : rien

Date de mise à jour : 18 septembre 2002