GTK Tutorial: Iniziamo

Avanti Indietro Indice

## 2. Iniziamo

La prima cosa da fare è certamente quella di scaricare il GTK e installarlo. Potete prendere l'ultima versione dal sito ftp.gtk.org in /pub/gtk. Un'altra possibile sorgente di informazioni è il sito http://www.gtk.org/.

GTK usa il comando GNU autoconf per autoconfigurarsi. Una volta estratti i file dall'archivio tar, eseguite configure --help per vedere una lista delle opzioni del comando configure.

Per iniziare la nostra introduzione a GTK, cominceremo con il più semplice programma possibile. Questo programma crea una finestra con dimensioni (in pixel) di 200x200 e l'unica possibilità di uscita è di ucciderlo usando la shell o il Window Manager.

> `

>     #include <gtk/gtk.h>

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

>         gtk_init (&argc, &argv);

>         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
>         gtk_widget_show (window);

>         gtk_main ();

>         return 0;
>     }

> `

Tutti i programmi GTK includeranno sicuramente <gtk/gtk.h> in cui vengono dichiarate le variabili, le funzioni, le strutture, etc. che saranno usate nella tua applicazione GTK.

La linea seguente:

> `

>     gtk_init (&argc, &argv);

> `

invoca la funzione gtk_init(gint *argc, gchar ***argv) che sarà usata in tutte le applicazioni GTK. Questa funzione sistema alcune cose al posto nostro, come la visuale predefinita e la mappa dei colori, e procede poi chiamando gdk_init(gint *argc, gchar ***argv). Questa funzione inizializza la libreria per l'uso, setta il gestore predefinito dei segnali e guarda negli argomenti, passati via linea di comando alla vostra applicazione, alla ricerca di uno di questi argomenti:

- `--display`
- `--debug-level`
- `--no-xshm`
- `--sync`
- `--show-events`
- `--no-show-events`

Rimuove poi questi argomenti dalla lista degli argomenti passati, lasciando quelli non riconosciuti a disposizione della vostra applicazione che potrà tenerne conto o ignorarli. In questo modo si crea un set di argomenti standard accettato da tutte le applicazioni GTK.

Le seguenti 2 linee di codice creano e mostrano la finestra.

> `

>       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
>       gtk_widget_show (window);

> `

L'argomento GTK_WINDOW_TOPLEVEL specifica che noi vogliamo che la nostra finestra si sottometta alle decorazioni del windows manager e alla posizione che quest'ultimo indicherà. Invece di creare una finestra avente dimensioni 0x0, la dimensione di una finestra senza figli (altri widget, come i bottoni, etc) è predefinita a 200x200 così che si possa manipolarla. La funzione gtk_widget_show() fa sì che GTK sappia che abbiamo finito di settare gli attributi di questo widget e che quindi quest'ultimo può essere visualizzato.

L'ultima linea ci fa entrare nel ciclo principale del GTK.

> `

>     gtk_main ();

> `

gtk_main() è un'altra chiamata che vedrete in tutte le applicazioni GTK. Quando il controllo raggiunge questo punto, l'applicazione si metterà a dormire aspettando che si verifichino eventi di X (come la pressione di un bottone o di un tasto), timeout o notifiche di Input/Output dai file Nel nostro esempio, comunque, tutti gli eventi vengono ignorati.

## 2.1 Hello World in GTK

Ok, ora un programma con un widget (un bottone). E' il classico ``Hello World'' alla GTK.

> `

>     /* helloworld.c */
>     #include <gtk/gtk.h>

>     /* E' una funzione di ritorno (callback). Gli argomenti passati sono ignorati in questo
>     * esempio.
>     * Piu' informazioni sulle callback in seguito. */

>     void hello (GtkWidget *widget, gpointer data)
>     {
>         g_print ("Hello World\n");
>     }

>     gint delete_event(GtkWidget *widget, gpointer data)
>       {
>           g_print ("delete event occured\n");
>           /* Se si dà FALSE al gestore del segnale ``delete_event'', GTK emettera' il segnale
>            * ``destroy''. Fornire TRUE significa non volere che la finestra sia distrutta.
>           * Questo e' utile per far uscire delle finestre di dialogo del tipo:
>           * 'sei sicuro di voler uscire ?'
>           * Cambia TRUE in FALSE e la finestra principale sara' distrutta con un "delete_event"
>           */
>         return (TRUE);

>     }

>     /* Un'altra callback */
>     void destroy (GtkWidget *widget, gpointer data)
>     {
>         gtk_main_quit ();
>     }

>     int main (int argc, char *argv[])
>     {
>         /* GtkWidget e' il tipo di dato per i Widget */
>         GtkWidget *window;
>         GtkWidget *button;

>         /* Questa e' una chiamata presente in tutte le applicazioni GTK. Gli argomenti della
>             linea di comando vengono scorsi e restituiti alla applicazione  */
>         gtk_init (&argc, &argv);

>         /* Crea una nuova finestra */
>         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

>         /* Quando alla finestra viene passato il segnale ``delete_event'' (questo
>          * segnale viene passato Windows Manager di solito con l'opzione 'close'
>          * o con la barra del titolo (title bar)) noi chiediamo che la funzione
>          * delete_event() (definita sopra) venga invocata.
>          * Il dato passato come argomento alla funzione di ritorno é NULL
>          * ed é ignorato dalla funzione stessa. */
>         gtk_signal_connect (GTK_OBJECT (window), "delete_event",
>                             GTK_SIGNAL_FUNC (delete_event), NULL);

>         /* Qui connettiamo l'evento ``destroy'' al gestore del segnale.
>          * Questo evento accade quando noi chiamimo la funzione gtk_widget_destroy()
>          * sulla finestra o se ritorniamo FALSE dalla callback ``delete_event''. */
>         gtk_signal_connect (GTK_OBJECT (window), "destroy",
>                             GTK_SIGNAL_FUNC (destroy), NULL);

>         /* Setta il bordo interno della finestra */
>         gtk_container_border_width (GTK_CONTAINER (window), 10);

>         /* Crea un nuovo bottone avente etichetta (label)  uguale a ``Hello World'' */
>         button = gtk_button_new_with_label ("Hello World");

>         /* Quando il bottone riceve il segnale ``clicked'', invochera' la funzione
>          * hello() passando NULL come argomento della funzione. La funzione
>          * hello() é definita sopra. */
>         gtk_signal_connect (GTK_OBJECT (button), "clicked",
>                             GTK_SIGNAL_FUNC (hello), NULL);

>         /* Questo farà sì che la finestra venga distrutta dalla chiamata
>          * gtk_widget_destroy(window) quando il bottone verrà premuto. Ancora,
>          * questo segnale (``destroy'') puo' arrivare da qui o dal windows
>          * manager */
>         gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
>                                    GTK_SIGNAL_FUNC (gtk_widget_destroy),
>                                    GTK_OBJECT (window));

>         /* Questo inserisce il bottone nella finestra
>          * (un contenitore GTK) */
>         gtk_container_add (GTK_CONTAINER (window), button);

>         /* Il passo finale é il mostrare questo nuovo widget appena creato */
>         gtk_widget_show (button);

>         /* e la finestra */
>         gtk_widget_show (window);

>         /* Tutte le applicazioni GTK devono avere la funzione gtk_main().
>          * Il controllo finisce qui e attende un evento (come la pressione
>          * di un tasto o l'evento di un mouse). */
>         gtk_main ();

>         return 0;
>     }

> `

## 2.2 Compilare hello World

Per compilare si utilizza :

> `

>     gcc -Wall -g helloworld.c -o hello_world `gtk-config --cflags` \
>         `gtk-config --libs`

> `

(N.d.T.: se lanciato da linea di comando, il precedente comando di compilazione va messo su di una unica linea eliminando il backslash)

In questo modo, si usa il progamma `gtk-config`, che viene distribuito con gtk. Questo programma 'sa' che opzioni di compilatore sono necessarie per compilare i programmi che usano gtk. `gtk-config --cflags` dà come risultato una lista di directory in cui i file di include devono essere cercati, e `gtk-config --libs` fornisce invece la lista delle librerie che devono essere linkate con le directory in cui devono essere cercate.

Le librerie che normalmente vengono linkate sono:

- la libreria glib (-lglib), contiene varie funzioni, ma solo g_print() é usato in questo esempio. GTK si appoggia a questa libreria, quindi essa viene sempre, comunque, linkata. Vedi comunque la sezione sulla glib per altri dettagli.
- La libreria GDK (-lgdk), la copertura della X11.
- La libreria GTK (-lgtk), la libreria dei widget, basata sulla GDK.
- La libreria Xlib(-lX11) la quale è usata dalla GDK.
- La libreria Xext(-lXext). Questa contiene il codice per le pixmap a memoria condivisa e altre estensioni di X.
- La libreria matematica (-lm). Questa é usata dalla GTK per vari scopi.

## 2.3 Teoria dei segnali e delle funzioni di ritorno (callback)

Prima di guardare in dettaglio ``Hello World'', parleremo un po' degli eventi e delle funzioni di ritorno. GTK è un toolkit guidato dagli eventi, il che significa che se ne starà a dorimire in gtk_main finché non succede un evento ed il controllo viene passato alla funzione appropriata.

Questo passaggio di controllo è basato sull'idea dei segnali. Quando si ha un evento, come la pressione di un bottone del mouse, verrà emesso il segnale appropriato, per esempio dal widget che é stato premuto. Questo è il modo in cui GTK fa molto del suo utile lavoro. Per far sì che un bottone esegua una azione, prepareremo un gestore del segnale che catturi questi segnali e chiami la funzione corretta. Questo viene fatto usando una funzione del tipo:

> `

>     gint gtk_signal_connect (GtkObject *object,
>                              gchar *name,
>                              GtkSignalFunc func,
>                              gpointer func_data);

> `

in cui il primo argomento è il widget che emetterà il segnale, il secondo è il nome del segnale che si vuole catturare, il terzo è la funzione che verrà invocata quando il segnale sarà catturato e il quarto è il dato che potrà essere passato a questa funzione.

La funzione specificata come terzo argomento è chiamata ``funzione di ritorno (callback)'', e dovrebbe essere della forma:

> `

>     void callback_func(GtkWidget *widget, gpointer callback_data);

> `

Dove il primo argomento sarà un puntatore al widget che emette il segnale e il secondo un puntatore al dato passato come ultimo argomento della funzione gtk_signal_connect() come descritto sopra.

Un'altra chiamata usata nell'esempio Hello World è:

> `

>     gint gtk_signal_connect_object (GtkObject *object,
>                                     gchar  *name,
>                                     GtkSignalFunc func,
>                                     GtkObject *slot_object);

> `

gtk_signal_connect_object() è uguale a gtk_signal_connect() eccetto che la funzione di callback usa solo un argomento, un puntatore ad un'oggetto GTK. Così quando si usa questa funzione per connettere i segnali, la callback dovrebbe essere della forma :

> `

>     void callback_func (GtkObject *object);

> `

dove object è normalmente un widget. Generalmente, non si assegna una callback per gtk_signal_connect_object. Queste sono invocate, usualmente, per chiamare una funzione GTK che accetta un widget singolo o un oggetto come argomento, come nel caso dell'esempio Hello World.

Lo scopo di avere due funzioni per connettere i segnali è semplicemente quello di permettere alla funzione di callback di avere un numero di argomenti diverso. Molte funzioni della libreria GTK accettano solo un singolo puntatore ad un widget GTK come argomento, così per queste si può usare la funzione gtk_signal_connect_object(), mentre per le vostre funzioni potreste aver bisogno di passare dati supplementari alle funzioni di ritorno.

## 2.4 Attraverso Hello World passo per passo

Ora che conosciamo la teoria che vi è dietro, iniziamo ad essere più chiari camminando attraverso il programma di Hello World.

Questa è la funzione di callback che sarà invocata quando il bottone viene cliccato. Noi, in questo esempio, ignoriamo sia il widget che i dati passati, ma non è difficile farci invece qualcosa. Il prossimo esempio userà l'argomento passato per dire quale bottone è stato premuto.

> `

>     void hello (GtkWidget *widget, gpointer data)
>     {
>         g_print ("Hello World\n");
>     }

> `

Questa callback è un po' speciale. L'evento ``delete'' avviene quanto il Window Manager manda questo evento all'applicazione. Qui abbiamo una scelta da fare: cosa fare di questo evento. Possiamo ignorarlo, creare qualche tipo di risposta, o semplicemente terminare l'applicazione.

Il valore che si restituisce in questa callback fa sì che la GTK sappia cosa fare. Restituire TRUE significa che non vogliamo che il segnale ``destroy'' sia emesso, quindi far sì che la nostra applicazione proceda normalmente. Ritornare FALSE vuole dire far emettere il segnale ``destroy'' il quale chiamerà la nostra funzione di callback che gestisce il segnale ``destroy''.

> `

>     gint delete_event(GtkWidget *widget, gpointer data)
>     {
>        g_print ("delete event occured\n");

>        return (TRUE);
>     }

> `

Questa è un'altra funzione di callback la quale fa uscire dal programma chiamando gtk_main_quit(). Questa funzione dice a GTK che deve uscire da gtk_main quando gli viene restituito il controllo.

> `

>     void destroy (GtkWidget *widget, gpointer data)
>     {
>         gtk_main_quit ();
>     }

> `

Ritengo che conosciate la funzione main()... si, come tutte le altre applicazioni anche le applicazioni GTK hanno questa funzione.

> `

>     int main (int argc, char *argv[])
>     {

> `

Questa parte dichiara un puntatore ad una struttura di tipo GtkWidget. Queste sono usate più sotto per creare una finestra ed un bottone.

> `

>         GtkWidget *window;
>         GtkWidget *button;

> `

Qui vi è ancora la nostra gtk_init. Come prima questa inizializza il toolkit e analizza gli argomenti trovati nella linea di comando. Tutti gli argomenti riconosciuti nella linea di comando sono rimossi dalla lista degli argomenti e vengono così modificati argc e argv per far sì che sembri che questi non siano mai esisiti e permettere alla vostra applicazione di analizzare gli argomenti rimasti.

> `

>         gtk_init (&argc, &argv);

> `

Crea una nuova finestra. Questo viene spiegato abbastanza approfonditamente più avanti. Viene allocata la memoria per la struttura GtkWidget *window così che si punti ad una struttura valida. In questo modo si predispone la nuova finestra, ma non la si visualizza fino a sotto dove, quasi alla fine del nostro programma, invochiamo gtk_widget_show(window).

> `

>         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

> `

Questo è un esempio di come connettere un gestore dei segnali con un oggetto, in questo caso la finestra. Qui viene catturato il segnale ``destroy''. Questo viene emesso quando usiamo il Window Manager per uccidere la finestra (e noi restituiamo TRUE dal gestore di ``delete_event'') o quando emettiamo la chiamata gtk_widget_destroy() passando l'oggetto finestra come oggetto da distruggere. Sistemando le cose così, trattiamo entrambi i casi con una singola chiamata. Qui è giusto invocare la funzione destroy() definita sopra con NULL come argomento, la quale termina l'applicazione GTK per noi. Questo ci permetterà di utilizzare il Window Manager per uccidere il programma.

GTK_OBJECT e GTK_SIGNAL_FUNC sono macro che interpretano il casting e il controllo di tipo per noi, così da rendere piu' leggibile il codice.

> `

>         gtk_signal_connect (GTK_OBJECT (window), "destroy",
>                             GTK_SIGNAL_FUNC (destroy), NULL);

> `

La prossima funzione è usata per settare un attributo di un oggetto contenitore. Questo sistema la finestra così da avere un'area vuota all'interno della finestrra larga 10 pixel dove non potrà andare nessun widget. Ci sono altre funzioni simili che vedremo nella sezione Settare gli attributi del Widget.

E ancora, GTK_CONTAINER è una macro per interpretare il casting di tipo.

> `

>         gtk_container_border_width (GTK_CONTAINER (window), 10);

> `

Questa chiamata crea un nuovo bottone. Alloca spazio in memoria per un nuovo GtkWidget, inizializzandolo e facendo sì che il puntatore a bottone punti ad esso. Quando sarà visualizzato, avrà etichetta ``Hello World''.

> `

>         button = gtk_button_new_with_label ("Hello World");

> `

Qui prendiamo il bottone e gli facciamo fare qualcosa di utile. Gli colleghiamo un gestore di segnale in modo che quando emetterà il segnale ``clicked'', verrà invocata la nostra funzione hello(). Il dato passato alla funzione è ignorato, cosicché alla funzione di callback hello() passiamo semplicemente NULL. Evidentemente il segnale ``clicked'' viene emesso quando premiamo il bottone con il mouse.

> `

>         gtk_signal_connect (GTK_OBJECT (button), "clicked",
>                             GTK_SIGNAL_FUNC (hello), NULL);

> `

Usiamo questo bottone anche per uscire dal programma. Questo illustrerà come il segnale ``destroy'' può arrivare sia dal Window Manager che dal nostro programma. Quando il bottone viene cliccato come descritto sopra, chiamerà la funzione di callback hello() e poi quest'ultima nell'ordine in cui sono definite. Si possono cioé avere tante funzioni di callback quante sono necessarie, e saranno eseguite nell'ordine in cui sono connesse. Visto che la funzione gtk_widget_destroy() accetta come argomento solo un GtkWidget *widget, usiamo la funzione gtk_signal_connect_object() al posto della normale gtk_signal_connect().

> `

>         gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
>                                    GTK_SIGNAL_FUNC (gtk_widget_destroy),
>                                    GTK_OBJECT (window));

> `

Questa é una chiamata di ``impacchettamento'' che sarà spiegata più avanti. Ma è molto facile da capire. Semplicemente dice alla libreria GTK che il bottone è da mettere nella finestra dove sarà visualizzato.

> `

>         gtk_container_add (GTK_CONTAINER (window), button);

> `

A questo punto abbiamo predisposto tutto quello che ci eravamo prefissati. Con tutti i gestori di segnale a posto e il bottone messo nella finestra in cui dovrebbe essere, possiamo dire a GTK di mostrare gli oggetti sullo schermo. L'oggetto finestra viene mostrato per ultimo così che la finestra completa di tutti i suoi oggetti verrà mostrata tutta in una volta, invece di vedere prima la finestra spoglia e poi la comparsa del bottone all'interno di essa. Per quanto, con questi semplici esempi, questo l'avrete già notato.

> `

>         gtk_widget_show (button);

>         gtk_widget_show (window);

> `

E naturalmente chiamiamo gtk_main(), la quale aspetta l'arrivo degli eventi dal server X e chiama l'oggetto interessato per fargli emettere il segnale corrispondente.

> `

>         gtk_main ();

> `

E il return finale. Il controllo ritorna qui dopo che viene invocata gtk_quit().

> `

>         return 0;

> `

Ora, quando premiamo il bottone del mouse su un bottone GTK, questo oggetto emette il segnale ``clicked''. Per poter utilizzare queste informazioni, il nostro programma predispone un gestore di segnale per catturare quel segnale, il quale avvia la funzione da noi scelta. Nel nostro esempio, quando il bottone creato viene cliccato, la funzione hello() viene invocata con argomento NULL, dopoodiché viene invocato il successivo gestore di questo segnale. Questo chiama la funziona gtk_widget_destroy(), passandole l'oggetto-finestra (window) come argomento, che distruggerà la finestra. Questo fa sì che la finestra emetta il segnale ``destroy'' che viene catturato e che fa invocare la funzione di ritorno destroy(), che semplicemente esce dal programma GTK.

Un'altro modo in cui possono andare le cose è l'uso del window manager per uccidere la finestra. Questo causera' l'emissione del segnale ``delete_event'' che automaticamente chiamerà il gestore del segnale ``delete_event''. Se qui noi restituiamo il valore TRUE, la finestra non verrà toccata e tutto procederà come se nulla fosse successo. Dare invece il valore FALSE causerà l'emissione da parte di GTK del segnale ``destroy'' il quale, a sua volta, invocherà la callback ``destroy'', uscendo dall'applicazione.

Nota che questi segnali non sono gli stessi del sistema Unix e che non sono implementati usando quei segnali, anche se la terminologia è praticamente identica.

Avanti Indietro Indice