Index   Table   Précédent   Suivant

2. Premiers pas

Dans ce chapitre on apprend les notions essentielles de « widget », de « callback » et de boucle d'évènement.

Tous les exemples du tutorial sont sous forme de sources et d'exécutables dans le repertoire examples/. Pour télécharger un fichier source depuis votre navigateur il suffit de maintenir enfoncé le bouton [Shift] et de cliquer sur le lien.

Index   Table   Précédent   Suivant

2.1. Ouvrir une fenêtre

Voici un programme qui ouvre une fenêtre "Hello World".

examples/hello/hello.c
    /* examples/hello/hello.c */
    
    #include <helium.h>
    
    He_node *princ;
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame ();
        HeSetFrameLabel (princ, "Hello World");
        
        return HeMainLoop (princ);
    }

La ligne #include <helium.h> inclut tous les fichiers .h standards (stdio.h, stdlib.h, etc, mais pas math.h) et les fichiers .h de X11, puis définit tous les types et prototypes de fonctions de Helium.

Un « widget » est un objet tel qu'une fenêtre, un bouton, un champ de saisie, etc. Tous les widgets d'Helium ont le type He_node*. Ici on déclare le widget princ, qui sera la fenêtre principale.

La fonction main reçoit les arguments de la ligne de commande du système, et renvoie le code de sortie entier (prototype ANSI).

On commence par appeler HeInit, qui initialise Helium, ouvre le display X11 (ou échoue). HeInit reçoit les arguments de la ligne de commande, les analyse et retire de argv toutes les options reconnues (telles que -display, -geom, -color, etc). C'est pourquoi on passe argc et argv avec des &.

On crée ensuite la fenêtre principale, le nom de la classe fenêtre étant « Frame » dans Helium. La fonction HeCreateFrame() renvoie l'adresse du widget créé. On ne manipule jamais directement les champs d'un He_node, qui est un type interne à Helium, mais on utilise les nombreuses fonctions fournies par Helium. Par exemple ici, on donne un titre à la fenêtre avec HeSetFrameLabel().

Une fois que tous les widgets on été créés, on appelle HeMainLoop en lui donnant la fenêtre principale. HeMainLoop fait les choses suivantes : elle affiche la fenêtre principale, applique éventuellement l'option -geom sur la taille et la position de la fenêtre, puis lance la boucle d'évènements infinie, qui attend puis répartit chaque évènement (souris, clavier, etc).

La fonction HeMainLoop sort lorsqu'on ferme la fenêtre par le Window Manager, et donne un code de sortie que l'on renvoie à main() ; c'est le code de sortie du programme.

Remarque : il ne doit y avoir qu'un seul appel à HeInit et à HeMainLoop dans tout le programme.

Index   Table   Précédent   Suivant

2.2. Compilation

Dans cette section, on montre 4 façons différentes de compiler un programme utilisant Helium : en ligne, avec un script shell, avec un Makefile simple, enfin avec un Makefile sophistiqué.

Pour compiler l'exemple précédent, il suffit de taper :

    gcc hello.c -o hello `/chemin-helium/helium-cfg --cflags --libs`
la commande entre backquotes ` ` permet de retrouver les options dépendant de l'installation d'Helium ; il suffit de remplacer /chemin-helium par le bon chemin absolu.

Lancer le programme avec les options suivantes et observer :

    hello &
    hello -help
    hello -res
    hello -geom 600x500+200-100
Pour faciliter la compilation des exemples de ce tutorial, voici un petit script en sh (remplacer /chemin-helium par le chemin absolu) :

    #! /bin/sh
    p=/chemin-helium
    f=`basename $1 .c`
    gcc $f.c -o $f `$p/helium-cfg --cflags --libs`
Appeler ce script "hcomp", sauver, taper "chmod +x hcomp". Pour compiler un fichier "ex.c", on tape "./hcomp ex.c" ou "./hcomp ex". Pour compiler un exemple et lancer automatiquement l'exécution si la compilation a réussie, on tape "./hcomp ex.c && ex".

Voici un Makefile simple ; on inclut le fichier de configuration de Helium /chemin-helium/.config, où les variables nécessaires sont déclarées, en particulier $(CC), $(HE_CFLAGS) et $(HE_LIBS).
Attention, vérifiez que les lignes décalées commencent bien par un [TAB].

    include /chemin-helium/.config

    .c.o :
        $(CC) -c $(HE_CFLAGS) $*.c

    bouton : bouton.o
        $(CC) -o $@ $@.o $(HE_LIBS)
Enfin, voici un Makefile sophistiqué, qui affiche un Message d'aide, permet de compiler plusieurs programmes et de nettoyer le répertoire.
    include /chemin-helium/.config

    .c.o :
        $(CC) -c $(HE_CFLAGS) $*.c

    # Rajoutez ici le nom de votre_prog
    EXECS = bouton bouton2 bouton3

    help ::
        @echo "Options du make : help all clean distclean $(EXECS)"

    # Rajoutez ici votre_prog : votre_prog.o
    bouton : bouton.o
    bouton2 : bouton2.o
    bouton3 : bouton3.o

    all :: $(EXECS)

    $(EXECS) :
        $(CC) -o $@ $@.o $(HE_LIBS)

    clean ::
        \rm -f *.o core

    distclean :: clean
        \rm -f $(EXECS)
Index   Table   Précédent   Suivant

2.3. Premier bouton

Voici un programme qui ouvre une fenêtre avec un bouton "Press me".

examples/button/pressme.c
    /* examples/button/pressme.c */
    
    #include <helium.h>
    
    He_node *princ, *panel, *butt;
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame ();
        HeSetFrameLabel (princ, "Premier bouton");
        
        panel = HeCreatePanel (princ);
        
        butt = HeCreateButton (panel);
        HeSetButtonLabel (butt, "Press me");
        
        return HeMainLoop (princ);
    }

On ne peut pas attacher directement un bouton à une fenêtre ; il faut créer un objet intermédiaire, le widget Panel : c'est un conteneur de boutons, chargé de leur transmettre les évènements X11.

On crée donc la fenêtre princ ; ensuite on crée le Panel avec HeCreatePanel(propriétaire), le propriétaire étant princ. Enfin on crée le bouton butt, dont le propriétaire est le Panel.

Index   Table   Précédent   Suivant

2.4. Principe général

D'une façon générale, on crée un widget de la catégorie Class par
    hn = HeCreateClass(owner);
(remplacer Class par Frame, Panel, Button, Text, etc). Un Frame n'a pas de propriétaire owner ; le propriétaire d'un Panel est en général un Frame ; le propriétaire d'un bouton, d'un message, d'un champ de saisie, etc est toujours un Panel.

Tous les widgets ont des propriétés communes, telles que taille, position, visible, actif, etc ; on peut changer une propriété Prop par

    HeSetProp (hn, value);
(remplacer Prop par X, Y, Width, Height, Show, Active, etc). On peut récupérer la valeur de cette propriété par
    value = HeGetProp (hn);
Les widgets d'une classe Class ont aussi des propriétés spécifiques à leur classe, par exemple le nom d'un bouton, la valeur d'un champ de saisie, etc ; on peut changer une telle propriété Prop par
    HeSetClassProp (hn, value);
(remplacer ClassProp par ButtonLabel, TextValue, MessageBold, etc). On peut récupérer la valeur de cette propriété par
    value = HeGetClassProp (hn);
Pour détruire un widget, on appelle
    HeDestroy (hn);
Cet appel détruit le widget et tous ses fils. Tout widget détruit est masqué, c'est-à-dire effacé de l'écran.

L'affichage est entièrement automatique ; lorsqu'on crée un widget, modifie ses propriété, ou détruit le widget, Helium reporte les changements nécessaires à l'écran (en une seule fois).

Index   Table   Précédent   Suivant

2.5. Action liée au bouton

Le programme pressme.c ne déclenche aucune action lorsqu'on clique dessus. On va modifier le programme pour qu'il affiche le nom du bouton.

examples/button/callback.c
    /* examples/button/callback.c */
    
    #include <helium.h>
    
    He_node *princ, *panel, *butt;
    
    void butt_proc (He_node *hn)
    {
        char *nom = HeGetButtonLabel (hn);
        
        printf ("butt_proc: %s\n", nom);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame ();
        HeSetFrameLabel (princ, "Action liée au bouton");
        
        panel = HeCreatePanel (princ);
        
        butt = HeCreateButton (panel);
        HeSetButtonLabel (butt, "Press me");
        HeSetButtonNotifyProc (butt, butt_proc);
        
        return HeMainLoop (princ);
    }

On appelle « callback » une fonction de votre programme, qui est appelée automatiquement par Helium lorsqu'un certain évènement se produit.

Il faut dire à Helium quelle callback doit être appelée dans quelle circonstance. Cela s'appelle « attacher » une callback.

Par exemple ici, on attache la callback butt_proc au bouton butt. Cette fonction sera appelée automatiquement par Helium chaque fois que le bouton sera pressé.

Une callback a toujours un prototype précis, imposé par Helium (puisque c'est Helium qui appelle votre fonction).

Ici, la fonction reçoit un seul paramètre, qui est le bouton pressé. Ainsi, plusieurs boutons peuvent avoir la même callback NotifyProc et déclencher une action dépendant par exemple du nom du bouton.

Index   Table   Précédent   Suivant

2.6. Raccourci pour le bouton

On peut remplacer ces lignes
    He_node *butt;
    butt = HeCreateButton (panel);
    HeSetButtonLabel (butt, "Press me");
    HeSetButtonNotifyProc (butt, butt_proc);
par
    He_node *butt;
    butt = HeCreateButtonP (panel, "Press me", butt_proc, NULL);
Si on n'a pas besoin de mémoriser butt, on peut écrire
    HeCreateButtonP (panel, "Press me", butt_proc, NULL);
L'exemple callback.c devient donc

examples/button/raccourci.c
    /* examples/button/raccourci.c */
    
    #include <helium.h>
    
    He_node *princ, *panel;
    
    void butt_proc (He_node *hn)
    {
        char *nom = HeGetButtonLabel (hn);
        printf ("butt_proc: %s\n", nom);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame ();
        HeSetFrameLabel (princ, "Raccourci du bouton");
        
        panel = HeCreatePanel (princ);
        
        HeCreateButtonP (panel, "Press me", butt_proc, NULL);
        
        return HeMainLoop (princ);
    }

Le quatrième paramètre de HeCreateButtonP permet d'attacher un ClientData au bouton (voir la section « 9.1. Une donnée ClientData »).

Index   Table   Précédent   Suivant

2.7. Quitter un programme

Dans l'exemple suivant on crée deux boutons : le premier est appelé "Press me", et le second "Quit". Les deux boutons ont chacun leur propre callback.

examples/button/quit.c
    /* examples/button/quit.c */
    
    #include <helium.h>
    
    He_node *princ, *panel;
    
    void butt_proc (He_node *hn)
    {
        char *nom = HeGetButtonLabel (hn);
        printf ("butt_proc: %s\n", nom);
    }
    
    void quit_proc (He_node *hn)
    {
        HeQuit (0);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame ();
        HeSetFrameLabel (princ, "Quitter un programme");
        
        panel = HeCreatePanel (princ);
        
        HeCreateButtonP (panel, "Press me", butt_proc, NULL);
        HeCreateButtonP (panel, "Quit", quit_proc, NULL);
        
        return HeMainLoop (princ);
    }

Pour quitter le programme, l'utilisateur presse le bouton "Quit". À ce moment, Helium appelle la callback du bouton, qui est quit_proc. Dans quit_proc, on appelle HeQuit(0), ce qui demande à Helium de quitter proprement le programme.

Le paramètre de HeQuit est le code de sortie de programme ; typiquement 0 signifie pas d'erreur, et 1 signifie erreur.

On peut appeler HeQuit dans n'importe quelle partie du programme, que ce soit avant, pendant ou après HeInit ou HeMainLoop : HeQuit réagit différemment en s'adaptant à la situation.

Lorsque HeQuit est appelé dans une callback, c'est à dire pendant la durée de vie de HeMainLoop, Hequit détruit récursivement tous les widgets puis ferme le display X11.


Index   Table   Début   Suivant