Il primo programma in GTK+/GLib

La libreria GLib utilizza tipi propri oltre a quelli standard forniti dal linguaggio C.

Oltre ai tipi propri vi sono anche numerosi costruttori che sostituiscono quelli standard; in particolare modo le funzioni che gesticono l'allocazione della memoria sono più sicure.

Tipi propri e macro

Prima di introdurre i tipi propri, introduciamo le macro definite da GLib. Si consulti la tabella 1. Sono comunemente usate da tutti i programmatori.

MACRO

Risultato

MAX(a, b)

Restituisce il massimo tra 'a' e 'b'

MIN(a, b)

Restituisce il minimo tra 'a' e 'b'

CLAMP(x, low, high)

Restituisce 'x' se 'low' < 'x' < 'high'

ABS(a)

Restituisce il valore assoluto di 'a'

Tabella 1 - Macro di GLib

I tipi fondamentali definiti da GLib sono i seguenti:

I tipi e, si vedrà in seguito, anche le funzioni di GLib sono preceduti dalla lettera 'g', la quale è utile per indicare una sorta di namespace proprio della libreria.

Hello world con GLib

In Listato 1 un semplice "Hello, world!" che fa uso delle funzioni stringa. La funzione g_malloc() se fallisce (non vi è memoria libera disponibile) fa chiudere l'applicazione diversamente da malloc() dove il controllo precedente è compito del programmatore.

Prima di procedere con la compilazione del codice sorgente bisogna definire i parametri e le librerie da passare al compilatore (gcc).

Lo script di shell 'glib-config' (vi è uno script simile anche per le librerie GTK+) passa al compilatore il percorso dei file header (file con estensione .h) e quello delle librerie per generare il codice eseguibile.

linux:~ # gcc `glib-config --cflags` `glib-config --libs` -o hello_glib hello_glib.c

Viene generato un input esteso corrispondente al seguente comando (la posizione delle directory può variare in base alla distribuzione installata):

linux:~# gcc -I/usr/include/glib-1.2 -I/usr/lib/glib/include -L/usr/lib -lglib -o hello hello.c



/**
 * Listato 1
 * hello_glib.c
 **/

#include <glib-1.2/glib.h>

#define LEN 14

int main ()
{
        gchar* buf;

        buf = g_malloc (sizeof (gchar) * LEN);

        /** la funzione termina la stringa con
         * il carattere NULL
         **/
        g_snprintf (buf, LEN, “%s”, “Hello, world!”);

        g_print (“%s\n”, buf);

        /* libera la memoria allocata */
        g_free (buf);
}

GLib richiede un unico file header di nome 'glib.h' da inserire nei vostri progetti.

Il programma alloca LEN byte di memoria, nei quali viene scritta la stringa "Hello, world!". La funzione g_snprintf() è più sicura della funzione sprintf, in quanto richiede come secondo argomento il numero di byte da utilizare. Il puntatore [buf] viene terminato con il carattere NULL.

La funzione g_print() è molto usata per il debug delle applicazioni, è da preferire alla printf().

Per terminare g_free() libera la memoria occupata da g_malloc(). Le chiamate non sono equivalenti, per questo motivo g_free() va utilizzata con il puntatore restituito da g_malloc(), mentre free() con il puntatore restituito da malloc(). In tabella 2 la descrizione delle funzioni.

Funzione (argomento)

Tipo restituito

g_malloc (gulong)

gpointer

g_free (gpointer)

void

g_realloc (gpointer, gulong)

gpointer

Tabella 2 - Allocazione di memoria

Prima di allocare memoria è indispensabile calcolare lo spazio di memoria richiesto; per prevenire errori vi sono macro che che facilitano la stesura del codice. Esse (vedi tabella 3) ricevono come argomenti il tipo da allocare e lo spazio richiesto. Quindi:

buf = g_new (gchar, LEN);

è preferibile a:

buf = g_malloc (sizeof (gchar) * LEN);

Macro (argomento)

Tipo restituito

g_new (tipo, quantità)

gpointer

g_new0 (tipo, quantità)

gpointer

Tabella 3 - Macro per la allocazione di memoria.

Hello World in GTK+

Prima di entrare nel merito della programmazione in GTK+ inziamo con il primo esempio di "Helllo, World!". Compilato e lanciato il programma è sufficiente digitare all'interno della finestra perché la stringa cambi da scritta in minuscolo a scritta in maiuscolo. Anche per le librerie GTK+ è disponibile uno script di shell che passa al compilatore il percorso dei file e le librerie.

linux:~ # gcc `gtk-config --cflags` `gtk-config --libs` -o hello_gtk hello_gtk.c

Il programma è composto dalla funzione main principale e dalle due funzioni di callback che gesticono rispettivamente la chiusura della finestra e il "click" del pulsante del mouse.

Gli Oggetti

Per dichiarare un oggetto di GTK+ è possibile dichiararlo come tipo GtkWidget che, ricordiamo, è la classe base. Ogni funzione tipo gtk_<sottoclasse>_new() restiuisce un tipo GtkWidget, per questo è conveniente dichiarare sempre i puntatori agli oggetti delle sottoclassi (GtkLabel, GtkButton, ...) come puntatori alla classe base GtkWidget.

Ottenuto il puntatore all'oggetto questo può essere manipolato con le funzioni della sottoclasse di appartenenza (che in modo pomposo potremmo definire i loro metodi).

Nell'esempio del listato 2 la scritta nel pulsante viene cambiata chiamando la funzione gtk_label_set_text(). Ogni funzione o metodo, accetta sempre come primo argomento il puntatore all'oggetto da manipolare.

Le macro GTK_LABEL come la macro GTK_BIN eseguono la conversione di tipo, ossia il cast sull'argomento; in proposito per ogni sottoclasse di GTK+ è presente una macro tipo GTK_<SOTTOCLASE>.

Diversamente GTK_IS_LABEL è una macro che restituisce TRUE se il tipo che si controlla è (in questo caso) un oggetto appartenente alla classe GtkLabel. Per ogni sottoclasse di GTK+ è presente una macro tipo GTK_IS_<SOTTOCLASSE>. Questo controlli sono a mio avviso indispensabili per evitare spiacevoli errori nei software, spesso difficili da individuare.

La funzione gtk_set_locale() attiva il supporto per l'internazionalizzazione del programma e va inserita prima dalla gtk_init() che controlla i parametri passati in linea di comando e si incarica della connessione con il server grafico.

La funzione gtk_main() inizializza il loop principale e quindi gestisce gli eventi del programma; in un'applicazione GTK+ ci possono essere più chiamate alla funzione gtk_main(), tuttavia ognuna di esse va terminata con la chiamata alla funzione gtk_main_quit(). In seguito analizzeremo come iterare ricorsivamente più gtk_main(), un'eventualità, questa, non remota se ci sono calcoli lunghi e complessi o si attende un evento da parte dell'utente.

Nello scorso numero sono stati introdotti i contenitori in GTK+. Entrambi sono "figli" o sottoclassi di GtkContainer; GtkButton è un "figlio" di GtkBin che a sua volta è un "figlio" di GtkContainer. GtkBin può contenere solamente un "figlio".

Dichiarato (come oggetto GtkWidget) e creato il puntatore al tipo GtkButton (con la chiamata alla funzione gtk_button_new_with_label()), questo va inserito, attraverso la chiamata di funzione gtk_add_container(), alla finestra "padre". La funzione gtk_add_container() accetta come primo argomento l'oggetto tipo GtkContainer al puntatore "padre" (il contenitore), come secondo argomento l'oggetto al puntatore "figlio" (l'oggetto da inserire). La macro GTK_CONTAINER esegue il cast sul puntatore.

Il widget va quindi visualizzato con la funzione gtk_widget_show(). Il ciclo di vita di un widget è articolato tra i più passaggi: realizzazione->mappatura->visualizzazione. Verranno trattati solamente la creazione con la funzione gtk_<widget>_new() e la visualizzazione con il metodo gtk_widget_show().

Il tipo GtkButton poteva essere creato con la funzione gtk_button_new(). Il tipo GtkLabel poteva essere creato con la funzione gtk_label_new(). Il codice va così modificato:

...
GtkWidget* window;
GtkWidget* button;
GtkWidget* label;
...
button = gtk_button_new();
label = gtk_label_new("Hello, world!");
...
gtk_container_add(GTK_CONTAINER(button), label);
gtk_container_add(GTK_CONTAINER(window), button);
...
gtk_widget_show(label);
gtk_widget_show(button);
gtk_widget_show(window);
...

Come spiegato in precedenza l'oggetto GtkLabel va inserito nell'oggetto GtkButton con la funzione gtk_add_container().

Funzioni della finestra principale

La funzione gtk_window_set_title() assegna il titolo della finestra; accetta come primo argomento il puntatore all'oggetto GtkWindow, come secondo argomento la stringa contente il titolo. La funzione gtk_window_set_default_size() assegna le dimensioni in pixel della finestra alla sua creazione.

Altre funzioni impostano il tipo di finestra: modale o non modale; le policy, ossia, la possibilità di ridimensionare la finestra, utile con le finestre di dialogo; la classe di appartenza... Queste funzioni saranno riprese nei prossimi articoli.

Le finestre, come ogni altro widget, sono soggetti agli eventi di "configure" (ridimensionamento del widget) e di "expose" (aggiornamento di una parte del widget). Le applicazioni che usano finestre grafiche per disegno o per diagrammare grafici, dovranno sfruttare funzioni di callback che ricostruiscono la zona che è stata coperta o ridimensionata.

Segnali e Eventi

I segnali in GTK+ collegano gli eventi alle funzioni definite dall'utente. Ogni widget gestisce degli eventi; quelli più comuni sono la visualizzazione e la distruzione.

Altri eventi dipendono dal tipo di widget. Un tipo GtkButton ha un evento "clicked" che viene associato alla pressione del mouse. Un tipo GtkCList (lista con colonne multiple) ha un evento "click-column" che viene associato al click sull'etichetta di una colonna.

La funzione gtk_signal_connect() accetta come primo argomento l'oggetto sensibile all'evento. Il secondo argomento è l'evento da gestire. Il terzo argomento la funzione di callback da eseguire se si verifica l'evento. Il quarto argomento un puntatore tipo gpointer ad un dato utente.

La funzione on_window_destroy_event() viene eseguita quando l'utente preme sul bottone di chiusura della finestra: l'applicazione viene terminata.

La funzione on_button_clicked() cambia la scritta da minuscolo/maiuscolo e viceversa.

La funzione g_return_if_fail() controlla la consistenza dei dati passati, l'argomento è una condizione che deve restituire TRUE, onde evitare spiacevoli "segmentation fault".

Conclusioni

Nel prossimo articolo verrrano presentati gli altri tipi di contenitori che si possono sfruttare con le GTK+, come i box orizzontali, verticali e le tabelle.

Negli articoli successivi sarà poi introdotta la programmazione con GDK.

/**
 * Listato 2
 * hello_gtk.c
 **/

#include <gtk/gtk.h>

gboolean on_window_destroy_event(GtkWidget*, GdkEvent*, gpointer);

void on_button_clicked(GtkButton*, gpointer);

int main(int argc, char *argv[])
{
        GtkWidget* window;
        GtkWidget* button;

        gtk_set_locale();
        gtk_init(&argc, &argv);

        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

        gtk_window_set_title(GTK_WINDOW(window), "Hello, World!");
        gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);

        button = gtk_button_new_with_label("hello, world!");

        gtk_container_add(GTK_CONTAINER(window), button);

        /* collega le funzioni di callback */
        gtk_signal_connect(GTK_OBJECT(window), "destroy_event",
                        GTK_SIGNAL_FUNC(on_window_destroy_event), NULL);
        gtk_signal_connect(GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(on_button_clicked), NULL);

        /* visualizza il pulsante e la finestra
         * principale
         */
        gtk_widget_show (button);
        gtk_widget_show (window);

        gtk_main();
        return 0;
}

gboolean
on_window_destroy_event(GtkWidget* widget,
                               GdkEvent* event, gpointer user_data)
{
        /* chiude l'applicazione */
        gtk_main_quit();
        return FALSE;
}


void on_button_clicked(GtkButton* button, gpointer user_data)
{
        GtkWidget* label;
        gchar* text;
        gchar *tmp_text;

        static guint i = 0;

        g_return_if_fail(button);
        g_return_if_fail(GTK_IS_BUTTON(button));

        /* il pulsante [button] contiene l'etichetta [label]*/
        label = GTK_BIN(button)->child;

        /* label è un tipo GtkLabel? */
        if (GTK_IS_LABEL(label)) {
                /* assegna a [text] la stringa
                 * contenuta nell'etichetta [label] */
                gtk_label_get(GTK_LABEL(label), &text);

                /* duplica la variabile [text] e la assegna a [tmp_text] */
                tmp_text = g_strdup(text);
                i++;

                if (i % 2) {
                        /* converte la stringa in maiuscolo */
                        g_strup(tmp_text);
                } else {
                        /* converte la stringa in minuscolo */
                        g_strdown(tmp_text);
                }
                /* cambia la scritta sull'etichetta del pulsante */
                gtk_label_set_text(GTK_LABEL(label), tmp_text);

                /* libera la memoria occupata da [tmp_text] */
                g_free(tmp_text);
        }
}