V. Afficher le contenu d'un fichier▲
V-A. Aperçu▲
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 :
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 :
/* 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 :
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 :
#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 :
gboolean g_file_get_contents (
const
gchar *
filename, gchar **
contents, gsize *
length, GError **
error);
Qui nous renvoie 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é :
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 retourner une valeur, utilisez 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èque.
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 :
#ifndef H_ERROR
#define H_ERROR
void
print_info (
char
*
, ...);
void
print_warning (
char
*
, ...);
void
print_error (
char
*
, ...);
#endif /* not H_ERROR */
#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 :
GtkTextBuffer *
p_text_buffer =
NULL
;
p_text_buffer =
gtk_text_view_get_buffer (
p_text_view);
Et pour finir on utilise la fonction :
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) :
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 :
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 :
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 :
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 :
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 :
gtk_window_set_title (
GTK_WINDOW (
p_window), "
Editeur de texte en GTK+
"
);