Panoramica sulle GTK+/GLib

Con questo articolo intendo svolgere una introduzione delle librerie GTK+ come strumento di sviluppo per interfacce utente. Non mi sono volutamente concentrato sui dettagli della programmazione, per lasciarla a successivi articoli, che con facilità guideranno anche coloro che non hanno mai programmato con queste librerie.

GTK+, ovvero Gimp Tool Kit, sono delle librerie con licenza LGPL utlizzate per creare interfacce grafiche utente. In origine è stata sviluppata per per sostituirla alle librerie proprietarie Motif base di sviluppo per GIMP (GNU Image Manipulation Program).

Dopo che gli sviluppatori di GIMP lo abbandonarono, lo sviluppo fu ripreso dalla comunità Open Source che partì proprio dallo sviluppo delle GTK+.

Introduzione

Per facilitare il porting verso altri sistemi operativi (le GTK+ sono disponbili anche per piattaforma Win32 e BeOS) è stato creato uno strato di astrazione delle librerie a basso livello di nome GDK (Gimp Drawing Kit). Quest'ultimo comunica con il sottosistema grafico ospite: X Window, quindi con Xlib, sotto Linux oppure la Win32 GDI sotto Windows.

Per portare GTK+ su un altro sistema operativo è sufficiente effettuare il porting del solo GDK. GTK+ come GDK fa uso di GLib; in quest'ultima vi sono funzioni per creare e manipolare dati quali array, liste, nodi stringhe, nuove funzioni per allocare memoria, uno scanner lessicale ecc.

Le GTK+ sono ancora oggi l'infrastruttura di GNOME (GNU Network Object Model Environment). GNOME 1.4 si basa sulle GTK+ V1.2, la nuova versione, la 2.0, sulle GTK+ 2.0. Prima di sviluppare applicazioni per GNOME è propedeutico un minino approccio alle GTK+/GLib.

Come il codice sorgente di Linux anche le GTK+ sono scritte in linguaggio C. Tuttavia esistono wrapper per programmare in C++, Objective C, Perl, Python, Ada, Eiffel, Pascal (compilatore FreePascal) ed altri.

Il C, a mio avviso, è stata la scelta vincente. Un linguaggio efficiente, veloce da compilare, rapido per analizzare il debug e scovare errori di programmazione. E gli oggetti? Per chi non vuole assolutamente rinunciare alla programmazione Object Oriented può utilizzare i wrapper scritti per gli altri linguaggi, oppure potrà scoprire la comodità del sistema a oggetti completo di ereditarietà fondamento di queste librerie.

Le GTK+ come le GDK e ovviamente le GLib usano un prefisso che precede i rispettivi identificatori. Per fare un esempio g_malloc() richiama e, volendo, rimpiazza la malloc() standard.

Sviluppare in GTK+

La costruzione di interfacce grafiche richiede molto sforzo iniziale, frequentemente è facile perdersi in mezzo ai numerosi costruttori, tipi propri, enumerazioni, macro ecc. Anche una semplice finestra di dialogo può scoraggiare chiunque.

Bisogna, perciò, trovare efficaci strumenti di sviluppo. Trovare un buon editor, un buon debugger e perché no un buon compilatore è quasi sempre un amletico problema. Anche se credo sia duro far cambiare le abitudini altrui, mi riferisco a chi programma con Emacs o VI, posso però suggerire di utilizzare come editor 'anjuta' e come costruttore dell'interfaccia grafica 'glade'.

Entrambi girano in ambiente grafico e sono integrati con GNOME/GTK+ .

Con essi si possono costruire in modo rapido applicazioni grafiche, un po' pomposamente potremmo definirlo un sistema RAD (Rapid Application Development).

Sia 'anjuta', scaricabile dal sito http://anjuta.sourceforge.net, che 'glade', scaricabile dal sito http://glade.gnome.org; sono, tuttora, in continuo sviluppo. In figura 1 ed in figura 2 vi sono i due software in azione.







Struttura di GLib

GLib definisce propri tipi che sostituiscono quelli standard del linguaggio C.

Per esempio un tipo gint8 è un intero ad 8 bit con segno, mentre un tipo gint32 rappresenta un intero a 32 bit con segno. Esistono anche i tipi gboolean, gfloat, gint ed altri. Questi tipi, come le funzioni, si comportano allo stesso modo in tutte le piattaforme. Con i tipi base vengono anche definiti i limiti associati come G_MAXFLOAT, G_MAXINT.

Sono poi definite altre macro come TRUE, FALSE, MAX() ed altre.

Il codice sorgente di GTK+ fa molto uso di funzioni per scovare più velocemente gli errori nel vostro codice. Queste funzioni controllano le precondizioni come g_return_if_fail() e le asserzioni come g_assert(), molto simile alla assert() tradizionale. Sono macro che verificano il codice e i dati runtime. Una funzione non viene eseguita se non è soddisfatta la condizione, per evitare gli spiacevoli e spesso introvabili "segmentation fault".

In cosa consiste la differenza tra una asserzione e una precondizione? Se fallisce la prima gli errori vanno ricercati internamente alla funzione, se fallisce la seconda vanno ricercati al di fuori di questa.

Personalmente ho trovato nelle funzioni che manipolano le stringhe un buon sostituto alle funzioni del C standard che, spesso, sono insicure e responsabili di molti buffer overflow sfruttati dagli hacker per infiltrarsi nei sistemi altrui. La più usata snprintf() (spesso sostituita con sprintf() ancora meno sicura) può essere abbandonata in favore di g_snprintf().

Oltre alle utility per le stringhe, timer, scanner lessicali ed altre con GLib, sono disponibili, tipi di dati per costruire array a dimensione variabile, liste uni o bidirezionali, tabelle di hash, alberi binari bilanciati ed altri ancora.

Ciò che accomuna queste funzioni è l'utilizzo esclusivo di robuste funzioni per manipolare la memoria come: g_malloc(), g_realloc() e g_free(). Oltre a garantire che non vi siano idiosincrasie tra i vari porting per le diverse piattaforme, occupano e liberano perfettamente lo spazio di memoria richiesto.

Per i messaggi di errore, di warning e in genere per i messaggi di output vi sono funzioni come g_print(), g_message(), g_warning() e g_error().

GLib dispone inoltre di un'astrazione portabile dei thread con utilizzo di condizioni e mutex. Gli eventi principali di una applicazione GTK+ sono gestiti dal Main Event Loop di GLib, che esegue questi ultimi in base alla loro priorità.

Le caratteristiche appenda descritte non sono che una parte di quelle disponibili.

Struttura di GTK+

GTK+ utilizza GDK per la gestione della grafica come le finestre, la palette dei colori e i caratteri.

Alla base dei componenti di GTK+ c'è la struttura GtkObject, da essa tutti i gli altri GTK+ ereditano come primo elemento un GtkObject. La struttura GtkWidget segue la GtkObject, e spesso viene preferita a quest'ultima.

Una struttura GtkButton eredita le informazioni dalla GtkWidget (primo elemento della struttura), tuttavia aggiunge tutte i dati in più necessari. Di seguito le strutture nidificate confermano quanto detto:

struct _GtkButton

{

GtkBin bin;


GtkWidget *child /* deprecapted field,

* use GTK_BIN (button)->child instead

*/;


guint in_button : 1;

guint button_down : 1;

guint relief : 2;

};

La struttura GtkBin a sua volta è così definita:

struct _GtkBin

{

GtkContainer container;


GtkWidget *child;

};

Per terminare infine con la struttura GtkContainer:

struct GtkContainer

{

GtkWidget widget;

GtkWidget *focus_child;


guint border_width : 16;

guint need_resize : 1;

guint resize_mode : 2;

guint reallocate_redraws : 1;


/* The list of children that requested a resize

*/

GSList *resize_widgets;

};

La gerarchia degli oggetti in questo esempio è così formata:

GtkObject

+--GtkWidget

+--GtkContainer

+--GtkBin

+--GtkButton

E' possibile utilizzare GtkWidget come puntatore a qualsiasi tipo di widget presente in GTK+ e attraverso l'uso delle macro accedere alle informazioni ivi associate. Le macro sono indispensabile per eseguire tutti i controlli e le conversioni di tipo (cast) a runtime. Con questo sistema, molto elegante, pur essendo il linguaggio C non a oggetti, rende GTK+ orientato comunque agli oggetti.

Ogni widget viene creato con una funzione simile ai costruttori del C++. Il widget GtkButton, per esempio, viene creato con la funzione gtk_button_new(). Le funzioni che manipolano i widget sono equivalenti ai metodi delle classi nei linguaggi orientati agli oggetti.

Per interagire con i widget disponiamo di segnali i quali, a loro volta, possono essere associati a funzioni di callback (i gestori del segnali). Un pulsante, o un bottone inserito in una finestra può rispondere all'evento di click del tasto sinistro del mouse. Questi gestori di segnali vengono registrati attraverso la funzione gtk_signal_connect(), la quale a sua volta viene attivata dalla gtk_main().

La funzione che registra il segnale accetta come argomento oltre all'oggetto, l'evento e la funzione di callback anche un puntatore (di tipo gpointer) al dato utente (spesso puntatore nullo).

Le funzioni di callback, tuttavia, non sono tutte uguali. Alcune ricevono come argomento solo due parametri: il puntatore all'oggetto che l'ha generato e il puntatore al dato utente associato; le altre aggiungono un puntatore all'evento che ha generato il segnale. Il click del mouse associa all'evento il numero del tasto del premuto.

Il nostro widget infine dovrà essere attaccato ad un contenitore. I contenitore in GTK+ sono di due tipi: i primi derivano da un GtkBin e contengono un solo widget al loro interno (come GtkButton); i secondi direttamente da GtkContainer e possono avere più widget figli al loro interno. Esempi di questi ultimi sono i box verticali (GtkVBox) e quelli orizzontali (GtkHBox) che sono sottoclassi di GtkBox. Anche le tabelle (GtkTable) rientrano in questo tipo.

Conclusioni

Dopo questa panoramica sulle caratteristiche delle GTK+ nei prossimi articoli passeremo anche ai programmi.

Cominciando con un semplice "Hello, world!", terminando con esempi di routine per lo sviluppo di progetti completi.