I Contenitori in GTK+
Il precedente articolo si è concluso anticpando i widget fondamentali per la costruzione di interfacce grafiche in GTK+: i contenitori. L'esempio scorso consisteva nel visualizzare la scritta "Hello, World!" e con la pressione di un tasto del mouse questa cambiava trasformando tutti i caratteri da maiuscolo in minuscolo e viceversa.
Il widget utilizzato era GtkButton, sottotipo di GtkBin. GtkBin può contenere un solo widget. I contenitori di tipo GtkBox e GtkTable invece consentono di contenere più widget contemporaneamente. Per essere più specifico un widget GtkTable con una dimensione di tre righe per tre colonne potrebbe contenere in totale 6 bottoni, oppure tre etichette e sei bottoni, oppure tre etichette e tre campi testo e così via.
In quest'aritcolo tratteremo un tipo GtkTable; tuttavia un contenitore GtkHBox e uno GtkVBox possono essere considerati come un sottoinsieme del tipo GtkTable, e quindi GtkTable una forma generalizzata di GtkBox, ma nella gerarchia degli oggetti GTK+ questo, comunque, non è vero.
Contenitori di tipo GtkTable
Costruiremo una finestra contenente un tipo GtkTable largo due colonne e alto tre righe. Nella prima colonna, quella di sinistra, inseriremo due tipi GtkLabel (etichette di testo), mentre nella seconda colonna, quella a destra, inseriremo due tipi GtkEntry (campi di testo). L'ultima riga due tipi GtkButton con due bottoni, uno con testo "Nascondi" e l'altro con testo "Cancella".
Il tipo GtkEntry è una sottotipo di GtkEditable, e consente di inserire una sola riga di testo. Se il testo supera la lunghezza predefinita del widget, esso scorre nei limiti consentiti.
Il codice sorgente autoesplicativo scritto in C è stampato in listato 1. La finestra grafica generata dal codice oggetto è in figura 1.
Costruire un GtkTable
La funzione gtk_table_new() restituisce un puntatore a un tipo GtkWidget. Come parametri formali accetta due interi senza segno che rappresentano il numero di righe ed il numero di colonne. L'ultimo parametro può valere TRUE o FALSE, nel caso sia TRUE la tabella avrà righe e colonne di larghezza ed altezza omogena in base alle dimensioni del widget più grande.
La funzione gtk_entry_new() restituisce un puntatore a un tipo GtkWidget e, non accetta parametri formali. Il puntatore restituito verrà collegato in una cella della tabella. Il tipo GtkLabel è già stato trattato nell'articolo precedente.
I widget di tipo GtkEntry e GtkLabel vengolo collegati alla tabella con la funzione gtk_table_attach(). Come un tipo GtkButton viene aggiunto ad un tipo GtkContainer con gtk_container_add() anche la tabella, infine, viene collegata nello stesso modo alla finestra principale.
Gli unici segnali presenti sono ancora una volta collegati alla pressione di uno qualsiasi dei tasti del mouse e alla chiusura della finestra.
Alcune funzioni di GtkTable
Le celle della tabella sono controllate in larghezza ed altezza; i widget all'interno possono acquisire la proprietà di occupare tutto lo spazio disponibile o solo una parte assegnata o, se possibile, fissata a priori.
Un widget viene collegato ad una cella della tabella per mezzo della funzione gtk_table_attach(). Il primo parametro formale è il tipo GtkTable, ossia il puntatore restituito da gtk_table_new(). Il cast viene effettuato con la macro GTK_TABLE. Il secondo parametro formale è il puntatore al widget da inserire, nel nostro esempio è un GtkEntry o un GtkLabel. I quattro parametri formali successivi danno le coordinate in numeri interi della griglia di contenimento. Quest'ultima viene espressa in numero di righe. La nostra tabella di tre righe e due colonne avrà quattro linee orizzontali (numerate da 0 a 3) e tre linee verticali (numerate da 0 a 2). La prima casella di testo è contenuta tra le linee verticali 1 e 2 e tra quelle orizzontali 0 e 1.
I parametri formali sei e sette che sono di tipo GtkAttachOptions e valgono rispettivamente per la dimensione in x e y, accettano tre opzioni differenti anche separate dall'operatore "|" (OR inclusivo).
GTK_FILL espande il widget contenuto fino ad occupare tutto lo spazio della cella.
GTK_EXPAND espande la cella della tabella fino ad occupare tutta la dimensione rimasta libera nella finestra.
GTK_SHRINK se viene impostato al widget verrà dato tutto lo spazio richiesto, tuttavia può causare problemi di sovrapposizione con la finestra principale o altri widget presenti.
Le azioni compiute sono di espandere (GTK_EXPAND) le celle GtkLabel e, per omogeneità, anche i le celle dei GtkEntry avranno le stesse dimensioni. Per garantire spazio sufficiente per scrivere saranno riempite al massimo le celle della seconda colonna della tabella con i tipi GtkEntry. Per esercizio provare tutte le combinazioni ed osservarne il comportamento.
Gli ultimi due parametri formali specificano la spaziatura in pixel tra righe e colonne per evitare che i widget contenuti siano troppo vicini. Consiglio di utilizzare una tecnica diversa ossia impostare la spaziatura (un contorno) intorno al widget. Il risultato viene ottenuto con la funzione gtk_misc_set_padding(). Un'altra funzione presente nel listato è gtk_misc_set_alignment() che accetta come secondo e terzo parametro formale un valore compreso tra 0 e 1 che indica l'allineamento orizzontale e verticale del widget. Osservare in listato 1 come è stato utilizzato questo accorgimento per le etichette.
Modificare le dimensioni dei widget
Le dimensioni e la posizione dei widget possono essere forzate. La funzione gtk_widget_set_usize() accetta tre parametri formali: un puntatore al tipo GtkWidget contente l'oggetto da modificare e le dimensioni in pixel lungo x e y. Queste ultime fissano la geometria che non può essere cambiata manualmente. La funzione gtk_widget_set_default_size() accetta gli stessi parametri formali, ma le dimensioni sono solo quelle iniziali; le dimensioni della finestra principale andrebbero sempre stabilite con questa funzione e non con la precedente. La funzione gtk_widget_set_uposition() accetta sempre tre parametri formali; il primo il puntatore al widget gli ultimi due la posizione, in pixel, in coordinate x e y.
GTK+ comprende una serie di funzioni per la gestione del tipo e la posizione delle finestre. Una finestra di dialogo quasi sempre è di tipo modale (blocca l'interatività con tutte le altre finestre attive) e dovrà apparire al centro della finestra principale. Le finestre di dialogo saranno affrontate nel prossimo articolo.
Esempio con GtkTable
Il programma costruisce la finestra princiaple ed inserisce una tabella di tre righe per due colonne. Se i campi GtkEntry non hanno almeno un carattere allora il programma non termina se viene premuto il tasto di chiusura della finestra. La funzione on_window_delete_event() in questo caso restituisce il valore TRUE. Questo comportamento è utile se state utilizzando file aperti e non salvati dopo una modifica. La funzione utilizzata per prelevare il valore dai campi GtkEntry è gtk_editable_get_chars(). Il primo parametro formale è un tipo GtkEditable ottenuto con la macro utilizzata per il cast sull'oggetto. Il secondo parametro è la posizione iniziale e il terzo la posizione finale, ossi i limiti della stringa. Se l'ultimo argomento è negativo preleva l'informazione fino all'ultimo carattere.
Per confrontare le due stringhe utilizziamo g_strncasecmp() (esiste anche g_strcasecmp() che considera diversi i caratteri maiuscoli e minuscoli) che accetta come parametri formali le due stringhe e come i terzo argomento quanti caratteri da confrontare. Restituisce il valore zero se sono uguali.
La pressione del tasto "Cancella" elimina il contenuto dei due campi testo. I parametri formali sono gli stessi di gtk_editable_get_chars(). La pressione del tasto "Nascondi" disattiva i campi testo e questo comportamento ha lo scopo di spiegare l'utilizzo della funzione gtk_widget_set_sensitive().
Spesso i programmatori non curano l'aspetto di rendere non operativo un pulsante od una voce di menù quando non devono interagire con l'utente. Il tasto "Cancella" potrebbe essere attivo solo quando c'è del testo, in modo da non indurre l'utente a premerlo (per eserecizio completate il programma con questo comportamento). La macro GTK_WIDGET_IS_SENSITIVE() controlla lo stato: se disattivato, il widget viene riportato allo stato normale.
Conclusioni
Con il prossimo articolo terminerà il ciclo sulla programmazione in GTK+/GLib. Non era possibile affrontare completamente gli innumerevoli aspetti della programmazione, però questi semplici esempi possono incoraggiare ad avvicinarsi a questo potente strumento. Gli argomenti che affronteremo saranno la gestione dei menu, le finestre di dialogo e una finestra di disegno come approccio a GDK.
/**
* Listato 1
* gtk_table.c
**/
#include <gtk/gtk.h>
/* definisce i prototipi delle funzioni */
GtkWidget* create_main_window(void);
void on_button_cancel_clicked(GtkButton*, gpointer);
void on_button_ok_clicked(GtkButton*, gpointer);
gboolean on_window_delete_event(GtkWidget*, GdkEvent*, gpointer);
/* definisce i puntatori GtkWidget */
GtkWidget* main_window;
GtkWidget* table;
GtkWidget* label_1;
GtkWidget* label_2;
GtkWidget* button_ok;
GtkWidget* button_cancel;
GtkWidget* text_1;
GtkWidget* text_2;
int main(int argc, char* argv[])
{
GtkWidget* main_window;
gtk_set_locale();
gtk_init(&argc, &argv);
main_window = create_main_window();
gtk_widget_show(main_window);
gtk_main();
return 0;
}
/* crea la finestra principale */
GtkWidget* create_main_window(void)
{
/* crea la finestra principale */
main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(main_window), "GtkTable");
gtk_window_set_default_size(GTK_WINDOW(main_window), 510, 300);
/* crea la tabella */
table = gtk_table_new(3, 2, TRUE);
gtk_widget_show(table);
gtk_container_add(GTK_CONTAINER(main_window), table);
/* crea le etichette */
label_1 = gtk_label_new("Testo 1");
gtk_widget_show(label_1);
gtk_table_attach(GTK_TABLE(table), label_1, 0, 1, 0, 1,
(GtkAttachOptions) (GTK_EXPAND),
(GtkAttachOptions) (0), 0, 0);
/* allinea il widget */
gtk_misc_set_alignment(GTK_MISC(label_1), 0, 0.5);
gtk_misc_set_padding(GTK_MISC(label_1), 5, 5);
label_2 = gtk_label_new("Testo 2");
gtk_widget_show(label_2);
gtk_table_attach(GTK_TABLE(table), label_2, 0, 1, 1, 2,
(GtkAttachOptions) (GTK_EXPAND),
(GtkAttachOptions) (0), 0, 0);
gtk_misc_set_alignment(GTK_MISC(label_2), 0, 0.5);
gtk_misc_set_padding(GTK_MISC(label_2), 5, 5);
/* crea i pulsanti "Nascondi" e "Cancella" */
button_ok = gtk_button_new_with_label("Nascondi");
gtk_widget_show(button_ok);
gtk_table_attach(GTK_TABLE(table), button_ok, 0, 1, 2, 3,
(GtkAttachOptions) (0),
(GtkAttachOptions) (GTK_EXPAND), 0, 0);
gtk_widget_set_usize(button_ok, 65, 25);
button_cancel = gtk_button_new_with_label("Cancella");
gtk_widget_show(button_cancel);
gtk_table_attach(GTK_TABLE(table), button_cancel, 1, 2, 2, 3,
(GtkAttachOptions) (0),
(GtkAttachOptions) (GTK_EXPAND), 0, 0);
gtk_widget_set_usize(button_cancel, 65, 25);
/* crea i campi testo */
text_1 = gtk_entry_new();
gtk_widget_show(text_1);
gtk_table_attach(GTK_TABLE(table), text_1, 1, 2, 0, 1,
(GtkAttachOptions) (GTK_FILL),
(GtkAttachOptions) (0), 5, 5);
text_2 = gtk_entry_new();
gtk_widget_show(text_2);
gtk_table_attach(GTK_TABLE(table), text_2, 1, 2, 1, 2,
(GtkAttachOptions) (GTK_FILL),
(GtkAttachOptions) (0), 5, 5);
/* collega i segnali alle funzioni */
gtk_signal_connect(GTK_OBJECT(button_ok), "clicked",
GTK_SIGNAL_FUNC(on_button_ok_clicked), NULL);
gtk_signal_connect(GTK_OBJECT(button_cancel), "clicked",
GTK_SIGNAL_FUNC(on_button_cancel_clicked), NULL);
gtk_signal_connect(GTK_OBJECT(main_window), "delete_event",
GTK_SIGNAL_FUNC(on_window_delete_event), NULL);
return main_window;
}
void on_button_cancel_clicked(GtkButton* button, gpointer user_data)
{
g_return_if_fail(button);
g_return_if_fail(GTK_IS_BUTTON(button));
gtk_editable_delete_text(GTK_EDITABLE(text_1), 0, -1);
gtk_editable_delete_text(GTK_EDITABLE(text_2), 0, -1);
}
void on_button_ok_clicked(GtkButton* button, gpointer user_data)
{
g_return_if_fail(button);
g_return_if_fail(GTK_IS_BUTTON(button));
/* inverti lo stato del widget */
if (GTK_WIDGET_IS_SENSITIVE(text_1)) {
gtk_widget_set_sensitive(text_1, FALSE);
} else {
gtk_widget_set_sensitive(text_1, TRUE);
}
}
gboolean
on_window_delete_event(GtkWidget* widget, GdkEvent* event,
gpointer user_data)
{
gchar *text1, *text2;
text1 = gtk_editable_get_chars(GTK_EDITABLE(text_1), 0, -1);
text2 = gtk_editable_get_chars(GTK_EDITABLE(text_2), 0, -1);
/* se il campo e' vuoto non esce */
if (!g_strncasecmp(text1, "", strlen(text1))) {
return TRUE;
}
if (!g_strncasecmp(text2, "", strlen(text2))) {
return TRUE;
}
/* libera le risorse */
g_free(text1);
g_free(text2);
gtk_main_quit();
return FALSE;
}