GTK+ par l'exemple


précédentsommairesuivant

V. Afficher le contenue d'un fichier

V-A. Aperçu

Image non disponible
Cliquez pour agrandir

V-B. Saisir du texte

Les choses sérieuses vont commencer, il s'agit de mettre en place le widget qui va nous permettre d'afficher et d'éditer du texte. Il s'agit de la classe GtkTextView. Grâce à GTK+ la mise en place est toujours aussi simple :

main.c
Sélectionnez
  GtkWidget *p_text_view = NULL;
  /* ... */
  /* Creation de la zone de texte */
  p_text_view = gtk_text_view_new ();
  gtk_box_pack_start (GTK_BOX (p_main_box), p_text_view, TRUE, TRUE, 0);

Comme il s'agit de la partie centrale de notre application, nous demandons à ce qu'elle prenne le plus de place possible (grâce à la fonction gtk_box_pack_start).
Puisque nous voulons afficher les boutons en dessous de la zone de texte, il faut ajouter ce morceau de code avant celui créant le bouton.

V-C. Ouvrir un fichier

Maintenant nous avons une belle zone de texte, il faut la remplir avec le contenu d'un fichier. Pour ce faire, nous allons commencer par ajouter un bouton ouvrir :

main.c
Sélectionnez
  /* Creation du bouton "Ouvrir" */
  {
    GtkWidget *p_button = NULL;

    p_button = gtk_button_new_from_stock (GTK_STOCK_OPEN);
    g_signal_connect (G_OBJECT (p_button), "clicked", G_CALLBACK (cb_open), p_text_view);
    gtk_box_pack_start (GTK_BOX (p_main_box), p_button, FALSE, FALSE, 0);
  }

Si vous exécutez le programme maintenant (3), vous remarquerez que les boutons sont ajoutés les uns en dessous des autres, ce qui diminue la zone de saisie (avec deux boutons ce n'est pas gênant mais au bout d'une dizaine ça risque d'être problématique). Pour pallier ce problème, nous allons utiliser un nouveau style de GtkBox : les GtkButtonBox qui sont prévus pour contenir des boutons (ça tombe plutôt bien :D). Donc pour éviter de diminuer la zone de saisie et comme nous avons de la place en largueur, nous allons utiliser un GtkHButtonBox qui va être inclus dans la p_main_box. Voici les modifications à effectuer :

main.c
Sélectionnez
  GtkWidget *p_button_box = NULL;
  /* ... */
  /* Creation du conteneur pour les boutons */
  p_button_box = gtk_hbutton_box_new ();
  gtk_box_pack_start (GTK_BOX (p_main_box), p_button_box, FALSE, FALSE, 0);
  /* ... */
  gtk_box_pack_start (GTK_BOX (p_button_box), p_button, FALSE, FALSE, 0);
  /* ... */
  gtk_box_pack_start (GTK_BOX (p_button_box), p_button, FALSE, FALSE, 0);

Maintenant il nous reste plus qu'à créer la fonction cb_open dans le fichier callback.c, pour l'instant nous nous contenterons d'ouvrir toujours le même fichier, nous verrons plus tard comment laisser le choix à l'utilisateur :

callback.c
Sélectionnez
#define DEFAULT_FILE "main.c"

void cb_open (GtkWidget *p_widget, gpointer user_data)
{
  open_file (DEFAULT_FILE, GTK_TEXT_VIEW (user_data));

  /* Parametre inutilise */
  (void)p_widget;
}

Vous aurez compris que l'on va déléguer tout le travail à la fonction open_file, qui devra ouvrir le fichier dont le nom est passé en premier paramètre pour l'afficher dans le GktTextView.
J'ai fait ce choix pour deux raisons : le code d'une fonction callback peut être très conséquent (surtout si l'on souhaite qu'elle affiche une boîte de dialogue), et aussi parce que cb_open n'est peut être pas la seule fonction à copier un fichier dans un GtkTextView (par exemple lors de la création d'un nouveau fichier, l'on peut vouloir copier un squelette de fichier).
Pour copier notre fichier, plutôt que d'utiliser la fonction fgets, nous allons nous servir de la glib (4) qui propose la fonction :

 
Sélectionnez
gboolean g_file_get_contents (const gchar *filename,  gchar **contents, gsize *length, GError **error);

Qui nous renvoit le contenu du fichier nommé filename dans le tampon contents. Il est possible de récupérer le nombre de caractères copiés et retourne TRUE en cas de succès, sinon FALSE et dans ce cas une structure de type GError est crée pour en savoir plus sur le type d'erreur rencontré :

callback.c
Sélectionnez
static void open_file (const gchar *file_name, GtkTextView *p_text_view)
{
  g_return_if_fail (file_name && p_text_view);
  {
    gchar *contents = NULL;

    if (g_file_get_contents (file_name, &contents, NULL, NULL))
    {
      /* Copie de contents dans le GtkTextView */
    }
    else
    {
    	print_warning ("Impossible d'ouvrir le fichier %s\n", file_name);
    }
  }
}

Je pense qu'un certain nombre d'explications s'impose (surtout si je vais trop vite, n'hésitez pas à m'arrêter !) :

  • g_return_if_fail est une macro qui peut être comparée à assert sauf qu'elle se contente de sortir de la fonction si l'expression passée en paramètre est fausse. C'est une méthode pratique pour vérifier la validité des paramètres (si la fonction doit retourne une valeur, utilisé plutôt g_return_val_if_fail, qui prend un second paramètre la valeur à retourner)
  • Ensuite on essaie de récupérer le contenu de notre fichier
  • Si cela échoue, nous le signalons à l'utilisateur à l'aide de la fonction print_warning.
  • Le type gchar est une redéfinition du type char par la glib (il en va de même pour tous les autres types du C), privilégiez ces types lorsque vous utilisez des fonctions de cette bibliothèques.

Mais qu'est-ce donc cette fonction print_warning ? C'est une fonction que nous allons créer, semblable à printf qui signalera à l'utilisateur qu'une erreur non critique est survenue. Comme nous n'avons pas encore abordé les boîtes de dialogue pour afficher un message, il s'agira pour l'instant d'une simple redéfinition de printf. Pendant que nous sommes dans l'affichage des messages, nous allons aussi créer deux fonctions print_info et print_error qui vont respectivement afficher un message d'information et un message d'erreur critique (ce qui entraîne la terminaison du programme), tout ceci dans un nouveau fichier error.c :

error.h
Sélectionnez
#ifndef H_ERROR
#define H_ERROR

void print_info (char *, ...);
void print_warning (char *, ...);
void print_error (char *, ...);

#endif /* not H_ERROR */
error.c
Sélectionnez
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "error.h"

void print_info (char *format, ...)
{
  va_list va;

  va_start (va, format);
  printf ("Information : ");
  vprintf (format, va);
  printf ("\n");
}

void print_warning (char *format, ...)
{
  va_list va;

  va_start (va, format);
  fprintf (stderr, "Erreur : ");
  vfprintf (stderr, format, va);
  fprintf (stderr, "\n");
}

void print_error (char *format, ...)
{
  va_list va;

  va_start (va, format);
  fprintf (stderr, "Erreur fatale : ");
  vfprintf (stderr, format, va);
  fprintf (stderr, "\n");
  exit (EXIT_FAILURE);
}

Pour en finir avec l'ouverture d'un fichier, il nous reste à copier contents dans le GtkTextView. En fait le GtkTextView ne gère que la forme, pour modifier le contenu, il faut passer par les GtkTextBuffer. Commençons donc par récupérer ce fameux GtkTextBuffer :

callback.c
Sélectionnez
      GtkTextBuffer *p_text_buffer = NULL;

      p_text_buffer = gtk_text_view_get_buffer (p_text_view);

Et pour finir on utilise la fonction :

 
Sélectionnez
void gtk_text_buffer_insert (GtkTextBuffer *buffer, GtkTextIter *iter, const gchar *text, gint len);

Récapitulons : buffer est le GtkTextBuffer que l'on vient de récupérer, text c'est le texte à afficher et len la taille de ce dernier (ou -1 s'il s'agit d'une chaîne de caractères au sens du langage C).
Mais qu'est-ce donc ce GtkTextIter ? On peut voir cela comme un curseur virtuel qui indique la position à laquelle effectuer l'insertion de texte dans le GtkTextBuffer (5). Allons voir ce que la documentation peut nous apprendre à leur sujet (6) :

 
Sélectionnez
typedef struct {
  /* GtkTextIter is an opaque datatype; ignore all these fields.
   * Initialize the iter with gtk_text_buffer_get_iter_*
   * functions
   */
} GtkTextIter;

Voilà, on a notre solution : suffit de rechercher les fonctions commençant par gtk_text_buffer_get_iter_, voici la liste :

 
Sélectionnez
void gtk_text_buffer_get_iter_at_line_offset (GtkTextBuffer *buffer, GtkTextIter *iter, gint line_number, gint char_offset);
void gtk_text_buffer_get_iter_at_offset (GtkTextBuffer *buffer, GtkTextIter *iter, gint char_offset);
void gtk_text_buffer_get_iter_at_line (GtkTextBuffer *buffer, GtkTextIter *iter, gint line_number);
void gtk_text_buffer_get_iter_at_line_index (GtkTextBuffer *buffer, GtkTextIter *iter, gint line_number, gint byte_index);
void gtk_text_buffer_get_iter_at_mark (GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextMark *mark);
void gtk_text_buffer_get_iter_at_child_anchor (GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextChildAnchor *anchor);

La fonction gtk_text_buffer_get_iter_at_line fait parfaitement l'affaire :

callback.c
Sélectionnez
      GtkTextIter iter;

      /* ... */
      gtk_text_buffer_get_iter_at_line (p_text_buffer, &iter, 0);
      gtk_text_buffer_insert (p_text_buffer, &iter, contents, -1);

Cependant si vous ne voulez pas avoir de problèmes avec le codage des caractères, il vaut mieux convertir le codage local vers utf8. Voici le résultat final :

 
Sélectionnez
      gchar *utf8 = NULL;
      GtkTextIter iter;
      GtkTextBuffer *p_text_buffer = NULL;

      p_text_buffer = gtk_text_view_get_buffer (p_text_view);
      gtk_text_buffer_get_iter_at_line (p_text_buffer, &iter, 0);
      utf8 = g_locale_to_utf8 (contents, -1, NULL, NULL, NULL);
      g_free (contents), contents = NULL;
      gtk_text_buffer_insert (p_text_buffer, &iter, utf8, -1);
      g_free (utf8), utf8 = NULL;

V-D. La petite touche finale

Pour finir cette laborieuse partie sur une petite touche esthétique, nous allons demander à GTK+ de nous lancer l'application en plein écran. Pour se faire, il suffit d'ajouter, lors de la création de la fenêtre principale :

main.c
Sélectionnez
gtk_window_maximize (GTK_WINDOW (p_window));

On peut même se permettre une pointe d'excentricité en modifiant le titre de notre fenêtre :

main.c
Sélectionnez
gtk_window_set_title (GTK_WINDOW (p_window), "Editeur de texte en GTK+");

V-E. Code source


précédentsommairesuivant
ce que je vous conseille de faire pour voir le problème, n'oubliez pas de créer une méthode cb_open sur le même modèle que cb_quit pour pouvoir compiler
La glib contient de nombreuses fonctions fortes utiles, il m'est souvent arrivé de coder une fonction qui était en fait présente dans la glib, n'hésitez pas à perdre quelques minutes à passer sa documentation en revue pour éviter une perte de temps.
Oui ça ressemble, de loin, aux itérateurs du C# pour ceux qui connaissent.
Il va falloir vous y faire, à moins d'être un surdoué, il est impossible de connaître l'API par coeur alors prenez le réflexe d'avoir toujours la documentation à portée de main.

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006-2008 Nicolas Joseph. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.