La programmation sous GNOME

Voici le troisième volet de notre dossier sur la programmation sous Gnome. Ce mois-ci, nous terminons notre survol des possibilités offertes par Gnome en terme d'interface graphique. Nous allons voir comment écrire des interfaces multidocuments (MDI), puis comment utiliser le puissant canevas.

Le mois dernier, nous avons vu comment agrémenter une application d'éléments détachables et comment utiliser le système d'aide. Ce mois-ci, nous allons aborder le sujet un peu plus complexe des interfaces multidocuments. Il est absolument indispensable de savoir créer une application multidocuments, car cette forme d'interface est devenue un standard dans les environnements graphiques Windows et MacOS. La plupart des utilisateurs y sont habitués et n'accepteraient pas de ne pas disposer d'une interface suffisamment conviviale pour leur permettre d'éditer plusieurs documents simultanément. Dans un deuxième temps, nous nous amuserons un peu plus, puisque nous apprendrons à utiliser le canevas de Gnome. Nous verrons comment créer un canevas, comment y ajouter des éléments graphiques et enfin, comment tirer partie des possibilités d'antialiasing et de transparence pour créer des applications graphiques de qualité irréprochable.

Créer des interfaces multidocuments

Une application MDI est un programme dans lequel l'utilisateur peut ouvrir plusieurs documents simultanément. Gimp est un bon exemple d'application MDI : l'utilisateur peut en effet y éditer plusieurs images à la fois. Vous pouvez tout à fait créer une application MDI en GTK, sans l'aide des librairies de Gnome, mais vous devrez gérer vous-même la totalité des étapes nécessaires à la création d'une interface de ce type. En particulier, vous devrez vous charger de tâches assez répétitives telles que la création des menus et des barres d'outils. Par ailleurs, dans une application MDI, il est souvent utile de pouvoir modifier la présentation des fenêtres sur l'écran. En effet, lorsque le nombre de documents édités simultanément devient important, le bureau peut devenir rapidement très chargé. Il est donc parfois souhaitable que les documents soient tous contenus dans une même fenêtre ou au contraire que chaque document possède sa propre fenêtre.

Gnome fournit une solution élégante à l'ensemble des problèmes soulevés ci-dessus. Du point de vue de l'utilisateur, une application MDI Gnome ressemble très fortement à une application normale. Il peut cependant choisir le mode d'affichage de ses documents. Comme dans Gimp, il peut y avoir une fenêtre différente pour chaque document : on appelle cela le mode "toplevel". Une autre possibilité consiste à n'avoir qu'une seule fenêtre et que seul un document soit affiché à la fois, l'utilisateur passant d'un document à l'autre via un menu : on appelle cela le mode "modal". Enfin, dans un style assez semblable, les documents peuvent être affichés dans un unique carnet et, pour passer d'un document à l'autre, l'utilisateur clique sur les onglets du carnet : c'est ce que l'on appelle le mode "notebook".


Un exemple de canevas antialiasé utilisant la transparence.
Notez l'absence d'effet de crénelage sur le contour des ellipses.

Un exemple de canevas GDK. le rendu est de moins bonne qualité que dans un canevas antialiasé.
En contrepartie, le rendu est beaucoup plus rapide.


L'utilisateur peut choisir le mode d'affichage par défaut de ses applications MDI dans le centre de contrôle de Gnome. Toutefois, il lui est possible, pour une application donnée, de passer outre le mode par défaut. Les applications MDI permettent souvent de spécifier leur mode d'affichage dans les préférences. L'application Gedit est un bon exemple d'utilisation du MDI.

Nous allons voir maintenant les principes de base de la création d'une application MDI sous Gnome. Comme vous pourrez le constater, c'est un tout petit peu plus compliqué que d'écrire une application Gnome normale.

Comme nous l'avons vu dans le premier volet de ce dossier, dans une application Gnome normale (monodocument), les fenêtres principales sont des objets GnomeApp. Dans le cas d'une application MDI, les objets contenant les documents sont des objets GnomeMDI. Leur utilisation est un peu différente de celle des objets GnomeApp, c'est pourquoi nous allons réécrire complètement notre programme d'exemple.

/************************************************/

/* tutoriel GNOME */

/* Bertrand Guiheneuf, 1999 */

/* 2/LFM9.c */

/* Pour compiler cet exemple, copiez/collez ces */

/* lignes dans une fenetre shell */

/*

gcc -o LFM9 `gnome-config --cflags gnomeui` \

LFM9.c `gnome-config --libs gnomeui`

*/

/************************************************/

#include <gnome.h>

static void quit_cb (GtkWidget *widget,

void *data);

static void new_child_cb (GtkWidget *widget,

gpointer data);

/************************************************/

/* Definition des menus */

/************************************************/

static GnomeUIInfo file_menu [] = {

GNOMEUIINFO_ITEM_STOCK ("Nouveau", NULL,

new_child_cb, GNOME_STOCK_MENU_NEW),

GNOMEUIINFO_ITEM_STOCK ("Sortir", NULL, quit_cb,

GNOME_STOCK_MENU_EXIT), GNOMEUIINFO_END

};

/* Un menu vide, pour ajouter la liste des */

/* fenetres enfants mdi */

GnomeUIInfo void_menu[] = {

{ GNOME_APP_UI_ENDOFINFO }

};

/* Definition de la barre de menu principale */

static GnomeUIInfo main_menu [] = {

GNOMEUIINFO_SUBTREE ("Fichier", &file_menu),

GNOMEUIINFO_SUBTREE ("Fenetres", &void_menu),

GNOMEUIINFO_END

};

/************************************************/

static GnomeMDI *mdi;

/* callback appele quand toutes les fenetres */

/* sont fermees */

static void

quit_cb (GtkWidget *widget, void *data)

{

gtk_main_quit ();

}

/* creer la barre de menu principale. En fait */

/* c'est un simple appel de fonction. Tout se */

/* fait dans la declaration des elements de */

/* menu au debut de ce fichier */

static void

create_toplevel_menu()

{

/* creer les elements de menu communs a toutes

les fenetres MDI */

gnome_mdi_set_menubar_template(mdi, main_menu);

}

static GtkWidget *

create_view (GnomeMDIChild *child, gpointer data)

{

GtkWidget *texte;

/* Ecrire un petit texte dans la fenetre enfant

... */

texte = gtk_label_new ("Un enfant");

gtk_widget_show (texte);

return GTK_WIDGET(texte);

}

static void

new_child_cb (GtkWidget *widget, gpointer data) {

static gint num_fenetre = 1;

gchar name[32];

GnomeMDIGenericChild *child;

sprintf(name, "Enfant %d", num_fenetre);

if((child = gnome_mdi_generic_child_new(name))

!= NULL) {

gnome_mdi_generic_child_set_view_creator

(child, create_view, NULL);

gtk_object_set_user_data(GTK_OBJECT(child),

GINT_TO_POINTER(num_fenetre));

/* ajouter l'enfant */

gnome_mdi_add_child(mdi,

GNOME_MDI_CHILD(child));

/* cette routine enregistre une vue de

l'enfant */

gnome_mdi_add_view(mdi,

GNOME_MDI_CHILD(child));

num_fenetre++;

}

}

int

main(int argc, char *argv[])

{

GtkWidget *main_widget;

gnome_init ("Bonjour", "1.0", argc, argv);

mdi = GNOME_MDI(gnome_mdi_new

("Linux France Magazine 8",

"Troisieme programme d'exemple"));

/* forcer le mode "toplevel". Chaque nouveau */

/* document s'affiche dans une fenetre de */

/* haut niveau */

/* Supprimer cette ligne pour utiliser le */

/* mode par defaut specifie dans le centre de */

/* controle */

gnome_mdi_set_mode(mdi, GNOME_MDI_TOPLEVEL);

/* creer la barre de menu des fenetres */

/* principales cette routine ne cree que les */

/* elements de menu communs a toutes les */

/* fenetres principales. */

create_toplevel_menu (mdi);

/* preciser dans quel menu sera inseree la */

/* liste des fenetres enfants mdi */

gnome_mdi_set_child_list_path(mdi, "Fenetres/");

gtk_signal_connect (GTK_OBJECT (mdi), "destroy",

GTK_SIGNAL_FUNC (quit_cb),

NULL);

gnome_mdi_open_toplevel (mdi);

gtk_main ();

return 0;

}

Comme vous pouvez le constater, le processus d'écriture d'applications MDI est un peu particulier. En effet, la routine de création des fenêtres de haut niveau est implémentée comme un callback. Dans notre cas, il s'agit de la routine create_view. C'est en fait la librairie Gnome qui prend en charge la création des fenêtres de haut niveau. En réalité, vous créez un objet GnomeMDIChild, qui représente un document de l'utilisateur. A chaque document créé correspond un unique objet GnomeMDIChild. L'utilisateur peut ensuite créer autant de vues de l'objet qu'il le souhaite. Par exemple, si l'application est un outil de rendu 3D, le document sera une scène 3D et les vues représenteront différentes positions de caméra. Pour créer une vue, il faut appeler la routine gnome_mdi_add_view. Dans le cas où le mode par défaut est le mode "toplevel", une nouvelle fenêtre est créée ainsi que sa barre de menu et d'outils. Le callback de création des vues de l'objet GnomeMDIChild est alors appelé. La barre de menu associée à chaque vue est spécifiée à la création de l'objet GnomeMDIChild par l'appel de la routine gnome_mdi_set_menu bar_tem plate. On peut, comme dans l'exemple ci-dessus, préciser un chemin de menu dans lequel s'affichera la liste des vues existantes ; cela se fait par l'appel de la fonction gnome_mdi_set_child_list_path(). Par un simple clic sur ces entrées de menu, l'utilisateur peut ainsi passer d'une vue à l'autre. Il est aussi possible de spécifier un chemin de menu dans lequel on peut ajouter un menu commun à toutes les vues d'un objet GnomeMDIChild. Le chemin de menu est spécifié par la routine gnome_mdi_set_chi ld_me nu_path() et le menu correspondant est créé via la routine gnome_mdi_child_set_me nu_template.

Voilà, nous avons vu les principes de base de la création d'une application MDI. C'est probablement une des parties de l'environnement de développement de Gnome la plus difficile à aborder, mais une fois que le processus de création des interfaces MDI est acquis, les détails sont relativement faciles à comprendre. Les interfaces MDI sont très répandues dans les mondes Windows ou MacOS et leur utilisation sous GNU/Linux est plus que fortement conseillée.

Nous allons maintenant voir une partie de la librairie graphique Gnome plus amusante et plus facile à utiliser : il s'agit du déjà fameux canevas.

Utiliser le canevas de Gnome.

Le canevas est simplement un objet dérivé de GtkWidget sur lequel vous pouvez placer des éléments graphiques appelés les items (texte, lignes, images... ). Il existe en fait deux canevas dans Gnome : l'un d'eux est très similaire au canevas de Tk et utilise les primitives graphiques de Gdk (la couche entre Gtk et Xwindow), tandis que l'autre utilise un moteur graphique très puissant intégré dans Gnome et portant le nom de libart. Le premier vise la rapidité et l'efficacité de l'affichage tandis que le second vise une qualité de rendu irréprochable. Le premier est souvent appelé le canevas Gdk tandis que le second est appelé le canevas antialiasé. Le canevas Gdk est utilisé dans le tableur gnumeric pour afficher la feuille de calcul. Le canevas antialiasé est utilisé dans Guppi, le composant de visualisation scientifique de Gnome. On sépare le canevas en deux entités séparées car les algorithmes sous-jacents sont totalement différents mais, en réalité, l'interface de programmation est exactement la même dans les deux cas. Dans un cas, l'instruction pour créer un nouveau canevas est gnome_canvas_new; dans l'autre, il faut utiliser gnome_can vas_new_aa. Vous pouvez utiliser les deux canevas de la même façon, quasiment sans modifier votre code. En réalité, il existe cependant quelques différences. Le canevas antialiasé, par exemple, supporte la transparence tandis que le canevas Gdk ne la supporte pas.

Les items sont des objets à part entière que vous pouvez dériver comme n'importe quel objet Gtk afin de modifier leur comportement. Les items peuvent être associés en groupes, ce qui permet, par exemple, de créer des couches au sein du canevas, exactement selon le même principe que les calques de Gimp. Chaque item appartient à un groupe et un groupe est lui-même un item. Ainsi, un groupe peut-il contenir d'autres groupes. Les items d'un canevas forment donc une arborescence dont le groupe de plus haut niveau est ce que l'on appelle le groupe racine. Tous les items doivent appartenir à un groupe, la seule exception étant évidemment le groupe racine qui est créé automatiquement lors de l'instanciation d'un nouveau canevas.

Les coordonnées d'un item sont toujours données relativement au groupe auquel il appartient. Ainsi, pour modifier la position d'un groupe d'items, vous n'avez qu'à modifier les coordonnées de l'item groupe auquel ils appartiennent.

Il faut aussi bien comprendre comment fonctionne le système de coordonnées du canevas. A chaque canevas est affecté un facteur d'échelle (de zoom en quelque sorte) entre les coordonnées des items dans le canevas et leurs coordonnées réelles sur l'écran. Ce facteur est modifiable dynamiquement et permet une visualisation très précise. Cette propriété du canevas est utilisée dans gnumeric pour modifier le facteur de zoom de la feuille de calcul. En réalité, le canevas antialiasé permet même des transformations affines entre les coordonnées du canevas et les coordonnées de l'écran, mais cette possibilité sort du cadre de cette introduction et ne sera pas abordée.

Voici maintenant un exemple de code créant un canevas. Nous allons réutiliser l'exemple de code MDI et créer un canevas dans chaque vue. Il faut pour cela modifier la routine create_view pour qu'elle crée un nouveau canevas.

static gboolean aa = FALSE;

static GtkWidget *

create_view (GnomeMDIChild *child, gpointer data)

{

GtkWidget *canvas;

/* creer un nouveau canvas */

canvas = create_canvas();

gtk_widget_show (canvas);

return GTK_WIDGET(canvas);

}

static GtkWidget *

create_canvas ()

{

GtkWidget *canvas;

GnomeCanvasGroup *root_group;

GnomeCanvasItem *ellipse;

gint i;

/* creer le canvas en lui meme */

if (aa) {

gtk_widget_push_visual (gdk_rgb_get_visual ());

gtk_widget_push_colormap (gdk_rgb

_get_cmap ());

canvas = gnome_canvas_new_aa ();

} else {

gtk_widget_push_visual (

gdk_imlib_get_visual ());

gtk_widget_push_colormap (

gdk_imlib_get_colormap ());

canvas = gnome_canvas_new ();

}

/* fixer les coordonnees d'affichage du canvas */

gnome_canvas_set_scroll_region (

GNOME_CANVAS (canvas), 0, 0, 400, 300);

/* fixer la taille de la fenetre */

gtk_widget_set_usize (canvas, 400, 300);

/* recuperer le groupe de plus haut niveau */

root_group = gnome_canvas_root (

GNOME_CANVAS (canvas));

/* ajouter des ellipses au canevas */

for (i=1; i<10 ; i++)

ellipse = gnome_canvas_item_new

(root_group,

gnome_canvas_ellipse_get_type(),

"x1", 200.0 * (double)rand()/RAND_MAX,

"x2", 200.0 * (1 +(dou ble)rand()/RAND_MAX),

"y1", 150.0 * (double)rand()/RAND_MAX,

"y2", 150.0 * (1 +(double)rand()/RAND_MAX),

/* "fill_color_rgba", 0x3c007140, */

"outline_color", "black",

"width_units", 2.0,

NULL);

return canvas;

}

Vous pouvez tester le rendu des deux canevas en changeant la valeur de la variable statique aa. Comme vous pouvez le constater, le code d'initialisation de la palette de couleurs est un peu différent dans les deux cas. Cela tient au fait que, pour des raisons historiques, le canevas Gdk utilise la librairie imlib tandis que le canevas antialiasé utilise la librairie GdkRGB, qui supporte le rendu avec canal alpha (l'opacité) et est par ailleurs beaucoup plus efficace. Il est d'ailleurs très probable que dans la prochaine version de Gnome, l'utilisation d'imlib soit partiellement abandonnée au profit de GdkRGB.

Comme nous l'avons vu, le groupe racine est créé automatiquement et peut être obtenu à l'aide de la commande gnome_canvas_root(). La création d'items est quant à elle un jeu d'enfant. La syntaxe générale de la routine de création des items est :

item = gnome_canvas_item_new (

groupe_parent, type_item,

parametre_1, valeur_1,

parametre_2, valeur_2,

....,

NULL);

où :

* groupe_parent est un pointeur sur un item de type groupe. Dans notre exemple, nous avons inséré les ellipses directement dans le groupe principal.

* type_item est le type gtk de la classe d'item que vous voulez créer.

* les paramètres parametre_1, parametre_2, etc. sont des chaînes de caractères indiquant les paramètres de l'item à modifier.

* valeur_1, valeur_2, etc. sont les valeurs à affecter aux dits paramètres. Leur type dépend évidemment des paramètres auxquels elles correspondent.

Si vous ne connaissez pas le modèle objet gtk, la description de type_item peut vous paraître un peu cryptique. Dans ce cas, il vous suffit de savoir que pour chaque item existe une routine qui renvoie la valeur dont vous avez besoin. Dans le cas de l'ellipse, il s'agit de la routine gnome_canvas_elli pse_get_type(). D'une manière générale, la routine permettant d'obtenir un item de type "nom_item" s'appelle gnome_canvas_"nom_item"_get_ty pe().

L'apparence des items se configure complètement via le mécanisme "parametre_x/valeur_x" décrit ci-dessus. Aussi faut-il connaître le nom des paramètres et les valeurs qu'ils peuvent prendre pour chaque item. La liste de ces paramètres est évidemment trop longue pour être décrite entièrement dans le cadre de ce dossier. Afin que vous puissiez tester par vous-même les capacités du canevas, voici toutefois une description exhaustive des paramètres de l'item "image".

 
Nom Type Description
image GdkImlibImage* Pointeur sur un objet GdkImlibImage
x double Abscisse du point d'ancrage
y double Ordonnée du point d'ancrage
width double Largeur à laquelle dimensionner l'image
height double Hauteur à laquelle dimensionner l'image
anchor GtkAnchorType Placement du point d'ancrage

 

Gardez bien à l'esprit que les coordonnées sont toujours données relativement au groupe auquel l'item appartient. Pour connaître les coordonnées d'un item dans le canevas (c'est-à-dire non plus par rapport à son groupe père mais par rapport au point d'origine du canevas), vous pouvez utiliser la routine gnome_canvas_item_i2w().

De même, la taille à laquelle dimensionner l'image est donnée en unités de canevas. Le passage des unités de canevas aux unités d'écran se fait via la fonction gnome_canvas_c2w().

Pour fixer le facteur d'échelle du canevas, vous utiliserez la routine gnome_canvas_set_pixels_per_unit().

Reportez-vous à la documentation de référence de Gnome pour plus de détails sur ces commandes.

Le point d'ancrage est le point de l'image qui sert de repère pour son positionnement. Vous fixez ce point grâce à l'argument "anchor" dont les valeurs possibles sont de la forme GTK_ANCHOR_x et où x peut être N, S, E, W, NW, NE, SE, SW ou CENTER, les lettres représentant les initiales des directions cardinales en anglais.

Concernant le paramètre "image", un pointeur sur une image peut être obtenu en chargeant celle-ci depuis un fichier à l'aide de la commande gdk_imlib_load_image(). Si votre image contient un canal alpha, il faudra plutôt utiliser la commande gnome_canvas_load_alpha(), mais la transparence ne sera rendue que si vous utilisez un canevas antialiasé.

Afin de tester la transparence du canevas antialiasé, reprenez l'exemple de code ci-dessus et décommentez la ligne

/* "fill_color_rgba", 0x3c007140, */

Mettez bien la variable statique aa à TRUE, recompilez le programme, relancez-le et... miracle, les contours des ellipses sont parfaitement dessinés, la couleur mauve qui les remplit n'est pas totalement opaque. Les ellipses se superposent sans se masquer. C'est le miracle de la technologie Gnome :)

Conclusion

Ce mois-ci, nous avons terminé notre rapide tour d'horizon des principaux éléments d'interface graphique de Gnome. Le panorama n'est évidemment pas complet et je vous laisse découvrir le reste par vous-même. Vous avez maintenant le bagage technique suffisant pour comprendre les sources de programmes écrits sous Gnome. La licence GNU vous permet de lire les sources de grands développeurs, profitez-en ! Le mois prochain, nous aborderons la gestion de session et verrons comment écrire des applets pour le panel.

 
News GNOME

INTERNATIONAL GNOME SUPPORT

Miguel de Icaza, coordinateur du projet GNOME, annonce la création d'une société commercialisant des services de support et de développement pour GNOME.

Le but de la société est d'adapter GNOME aux besoins des clients, d'aider au déploiement rapide d'applications et d'apporter un support à l'intégration de GNOME dans les systèmes déjà existants.

International GNOME Support est dédié au logiciel libre et au modèle OpenSource. En effet, tout le code produit par la société sera sous licence GPL ou LGPL. Ainsi, les développements effectués par cette société pour des tiers seront profitables à l'ensemble des utilisateurs GNOME.

Voici encore une preuve irréfutable que le logiciel libre n'est pas incompatible avec le commerce.

International GNOME Support peut être contacté aux coordonnées mentionnées ci-dessous :

International GNOME Support

3, Langdon Street

Cambridge, MA 02138-2532

U.S.A.

email : info@gnome-support.com

tél.: (00) 1-617-577-8333

fax : (00) 1-617-227-5351

web : www.gnome-support.com

 


© Copyright 2000 Diamond Editions/Linux magazine France. - Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1or any later version published by the Free Software Foundation; A copy of the license is included in the section entitled "GNU Free Documentation License".