Nel momento in cui si crea un'applicazione, normalmente si avrà la
necessità di mettere più di un unico bottone all'interno di
una finestra. Il nostro primo esempio ``Hello World'' usava un solo oggetto,
cosicché abbiamo potuto usare semplicemente una chiamata a
gtk_container_add per impacchettare il widget nella finestra. Quando invece
si vuole inserire più di un unico widget in una finestra, come si fa
a controllare dove vengono posizionati i propri oggetti? E' qui che entra in
gioco il meccanismo dell'``impacchettamento''.
La maggior parte dell'impacchettamento viene effettuata creando delle scatole
come nell'esempio più sopra. Le scatole sono dei contenitori invisibili
di widget che possiamo usare per imballarci i nostri oggetti e che esistono in
due varietà: in particolare si possono avere scatole orizzontali (hbox)
e verticali (vbox).
Quando si impacchentano degli oggetti in una scatola orizzontale, gli oggetti
vengono inseriti orizzontalmente da sinistra a destra oppure da destra a sinistra
a seconda della chiamata di funzione che si usa. In una scatola verticale, gli
oggetti vengono inseriti dall'alto in basso o viceversa. Si può usare
qualsiasi combinazione di scatole all'interno o a fianco di altre scatole, fino
ad ottenere l'effetto desiderato.
Per creare una nuova scatola orizzontale, si usa una chiamata a gtk_hbox_new(),
mentre per le scatole verticali si usa gtk_vbox_new(). Per inserire i widget
all'interno di questi contenitori si usano le funzioni gtk_box_pack_start() e
gtk_box_pack_end(). La funzione gtk_box_pack_start() comincerà dall'alto
verso il basso in una vbox e da sinistra a destra in una hbox. gtk_box_pack_end()
fa l'opposto, impacchettando dal basso verso l'alto in una vbox e da destra a
sinistra in una hbox. Queste funzioni ci permettono di giustificare a destra o
a sinistra i nostri widget, e possono essere mescolate in qualsiasi modo per
ottenere l'effetto desiderato. Useremo gtk_box_pack_start() nella maggior parte
dei nostri esempi. Un oggetto può essere costituito da un altro contenitore
o da un oggetto grafico. Infatti, molti oggetti grafici sono a loro volta dei
contenitori, compreso il bottone, anche se tipicamente all'interno del bottone
mettiamo solo una etichetta.
Usando queste chiamate, GTK riesce a capire dove si vogliono piazzare i propri
widget, in modo di essere poi in grado di effettuare il ridimensionamento
automatico e altre cose interessanti. Esiste poi un insieme di opzioni che riguardano
il modo in cui i propri oggetti grafici dovrebbero essere impacchettati. Come
si può immaginare, questo metodo dà una buona flessibilità nella creazione e
nella disposizione dei propri widget.
A causa di questa flessibilità, le scatole per impacchettamento del GTK
possono, di primo acchito, creare un po' di disorientamento. Sono infatti disponibili
molte opzioni, e non è immediato il modo in cui si combinano l'una con l'altra.
Alla fine però, si possono ottenere essenzialmente cinque diversi stili.
Ogni linea contiene una scatola orizzontale (hbox) con diversi bottoni.
La chiamata a gtk_box_pack è una scorciatoia per la chiamata di
impacchettamento di ognuno dei bottoni nella hbox. Ognuno dei bottoni viene
impacchettato nella hbox nello stesso modo (cioè, con gli stessi
argomenti per la funzione gtk_box_pack_start ()).
Questa è la dichiarazione della funzione gtk_box_pack_start.
Il primo argomento è la scatola nella quale si stanno inscatolando i
widget, il secondo è il widget stesso. Gli oggetti per ora saranno
bottoni, quindi quello che faremo sarà impacchettare bottoni in scatole.
L'argomento ``expand'' in gtk_box_pack_start() o gtk_box_pack_end() controlla
se gli oggetti devono essere sistemati nella scatola in modo da riempire tutto
lo spazio in diponibile presente nella scatola, in modo che la scatola si espanda
fino ad occupare tutta l'area assegnatale (valore TRUE).
La scatola può anche essere rimpiciolita in modo da contenere esattamente i
widget (valore FALSE). Assegnare a expand il valore FALSE permette di giustificare
a destra o sinistra i propri oggetti. In caso contrario, tutti gli ogetti si
espandono fino ad adattarsi alla scatola, e il medesimo effetto si può
ottenere usando solo una delle funzioni gtk_box_pack_start o pack_end.
L'argomento ``fill'' delle funzioni gtk_box_pack stabilisce se lo spazio disponibile
nella scatola deve essere allocato agli oggetti (TRUE) o se deve essere mantenuto
come riempimento attorno a questi oggetti (FALSE). Questo argomento ha effetto
solo se a expand è assegnato il valore TRUE.
Quando si crea una nuova scatola, la funzione ha questo aspetto:
L'argomento homogeneous di gtk_hbox_new (la stesso per gtk_vbox_new)
determina se ogni oggetto nella scatola deve avere la stessa dimensione
(cioè la stessa ampiezza in una hbox o la stessa altezza in una vbox).
Se è settato, l'argomento expand delle routine gtk_box_pack è
sempre attivato.
Qual è la differenza fra la spaziatura (che è stabilita quando
la scatola viene creata) e il riempimento (che viene stabilito quando gli
elementi vengono impacchettati)? La spaziatura viene inserita fra gli oggetti,
mentre il riempimento viene aggiuno a ciascuno dei lati dell'oggetti. La seguente
figura dovrebbe chiarire meglio questo punto:
Di seguito è riportato il codice usato per creare le immagini precedenti.
L'ho commentato in modo piuttosto pesante, in modo che non dovreste avere
problemi nel seguirlo. Compilatelo voi stessi e provate a giocarci un po'.
/* packbox.c */
#include "gtk/gtk.h"
void
delete_event (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
/* Costruisco una nuova hbox riempita con bottoni-etichette. Gli
* argomenti per le varabili che ci interessano sono passati
* in questa funzione. Non mostriamo la scatola, ma mostriamo
* tutto quello che c'e' dentro. */
GtkWidget *make_box (gint homogeneous, gint spacing,
gint expand, gint fill, gint padding)
{
GtkWidget *box;
GtkWidget *button;
char padstr[80];
/* costruisco una nuova hbox con i valori appropriati di
* homogeneous e spacing */
box = gtk_hbox_new (homogeneous, spacing);
/* costruisco una serie di bottoni con i valori appropriati */
button = gtk_button_new_with_label ("gtk_box_pack");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
button = gtk_button_new_with_label ("(box,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
button = gtk_button_new_with_label ("button,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
/* costruisco un bottone con l'etichetta che dipende dal valore di
* expand. */
if (expand == TRUE)
button = gtk_button_new_with_label ("TRUE,");
else
button = gtk_button_new_with_label ("FALSE,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
/* Questo e' la stessa cosa della creazione del bottone per "expand"
* piu' sopra, ma usa la forma breve. */
button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
sprintf (padstr, "%d);", padding);
button = gtk_button_new_with_label (padstr);
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
return box;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
GtkWidget *box2;
GtkWidget *separator;
GtkWidget *label;
GtkWidget *quitbox;
int which;
/* La nostra inizializzazione, non dimenticatela! :) */
gtk_init (&argc, &argv);
if (argc != 2) {
fprintf (stderr, "uso: packbox num, dove num è 1, 2, o 3.\n");
/* questo fa solo un po' di pulizia in GTK, ed esce con un valore 1. */
gtk_exit (1);
}
which = atoi (argv[1]);
/* Creiamo la nostra finestra */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Ci si dovrebbe sempre ricordare di connettere il segnale di destroy
* alla finestra principale. Cio' e' molto importante per avere un funzionamento
* corretto dal punto di vista intuitivo */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Creiamo una scatola verticale (vbox) in cui impacchettare quelle
* orizzontali. Questo ci permette di impilare le scatole orizzontali
* piene di bottoni una sull'altra in questa vbox. */
box1 = gtk_vbox_new (FALSE, 0);
/* Decide quale esempio si deve mostrare. Corrispondono alle figure precedenti */
switch (which) {
case 1:
/* creare una nuova etichetta. */
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
/* allineare l'etichetta al lato sinistro. Discuteremo questa e altre
* funzioni nella sezione dedicata agli attributi degli oggetti grafici. */
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
/* Impacchettare l'etichetta nella scatola verticale (vbox box1).
* Ricordare che gli oggetti che vengono aggiunti in una vbox vengono
* impacchettati uno sopra all'altro in ordine. */
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
/* mostrare l'etichetta */
gtk_widget_show (label);
/* chiamare la nostra funzione make_box - homogeneous = FALSE,
* spacing = 0, expand = FALSE, fill = FALSE, padding = 0 */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* chiamare la nostra funzione make_box - homogeneous = FALSE, spacing = 0,
* expand = FALSE, fill = FALSE, padding = 0 */
box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Questo crea un separatore. Li conosceremo meglio in seguito,
* comunque sono piuttosto semplici. */
separator = gtk_hseparator_new ();
/* Impacchetta il separatore nella vbox. Ricordare che stiamo impacchettando
* ognuno di questi oggetti in una vbox, cosicché essi verranno
* impacchettati verticalmente. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
/* crea un'altra nuova etichetta e mostrala. */
label = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* ancora un nuovo separatore. */
separator = gtk_hseparator_new ();
/* Gli ultimi 3 argumenti per gtk_box_pack_start sono: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;
case 2:
/* creare una nuova etichetta, ricordare che box1 e' la vbox creata
* vicino all'inizio di main() */
label = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
separator = gtk_hseparator_new ();
/* Gli ultimi tre arcomenti di gtk_box_pack_start sono: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
separator = gtk_hseparator_new ();
/* Gli ultimi tre argomenti di gtk_box_pack_start sono: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;
case 3:
/* Questo dimostra la possibilita' di usare use gtk_box_pack_end() per
* giustificare gli oggetti a destra. Per prima cosa creiamo una
* nuova scatola come prima. */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
/* creiamo l'etichetta che sara' aggiunta alla fine. */
label = gtk_label_new ("end");
/* impacchettiamola usando gtk_box_pack_end(), cosa' che viene inserita
* sul lato destro della hbox creata nella chiamata a the make_box(). */
gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
/* mostriamo l'etichetta. */
gtk_widget_show (label);
/* impacchettiamo box2 in box1 (the vbox, ricordate? :) */
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* un separatore per il fondo */
separator = gtk_hseparator_new ();
/* Questo assegna esplicitamente al separatore l'ampiezza di 400 pixel
* e l'altezza di 5 pixel. Cio' fa si' che la hbox che abbiamo creato sia
* anche essa larga 400 pixel, e che l'etichetta finale sia separata dalle
* altre etichette nella hbox. In caso contrario, tutti gli oggetti nella
* hbox sarebbero impacchettati il piu' vicino possibile. */
gtk_widget_set_usize (separator, 400, 5);
/* impacchetta il separatore nella vbox (box1) creata vicino all'inizio
* di main() */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
}
/* Creare un'altra nuova hbox.. ricordate che ne possiamo usare quante ne vogliamo! */
quitbox = gtk_hbox_new (FALSE, 0);
/* Il nostro bottone di uscita. */
button = gtk_button_new_with_label ("Quit");
/* Configuriamo il segnale per distruggere la finestra. Ricordate che
* ciò manderà alla finestra il segnale "destroy", che verrà catturato
* dal nostro gestore di segnali che abbiamo definito in precedenza. */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_main_quit),
GTK_OBJECT (window));
/* impacchetta il bottone in quitbox.
* Gli ultimi tre argomenti di gtk_box_pack_start sono: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
/* impacchetta quitbox nella vbox (box1) */
gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
/* impacchetta la vbox (box1), che ora contiene tutti i nostri oggetti,
* nella finestra principale. */
gtk_container_add (GTK_CONTAINER (window), box1);
/* e mostra tutto quel che rimane */
gtk_widget_show (button);
gtk_widget_show (quitbox);
gtk_widget_show (box1);
/* Mostriamo la finestra alla fine in modo che tutto spunti fuori assieme. */
gtk_widget_show (window);
/* E, naturalmente, la nostra funzione main. */
gtk_main ();
/* Il controllo ritorna a questo punto quando viene chiamata gtk_main_quit(),
* ma non quando si usa gtk_exit. */
return 0;
}
Il primo argomento rappresenta il numero di righe da mettere nella tabella,
mentre il secondo è ovviamente il numero di colonne.
L'argomento homogeneous ha a che fare con il modo in cui le caselle della tabella
sono dimensionate. Se homogeneous ha il valore TRUE, le caselle sono ridimensionate
fino alla dimensione del più grande oggetto contenuto nella tabelle. Se è FALSE, la
dimensione delle caselleè decisa dal più alto oggetto in una certa riga e dal più
largo oggetto in una stessa colonna.
Le righe e le colonne sono disposte a partire da 0 fino a n, dove n è il numero
che era stato specificato nella chiamata a gtk_table_new. Così, se specificate
rows = 2 e columns = 2, lo schema avrà questo aspetto:
In cui il primo argomento (``table'') è la tabella che avete creato e il secondo
(``child'') è l'oggetto che volete piazzare nella tabella.
Gli argomenti ``attach'' (right, left, top, bottom) specificano dove mettere l'oggetto
e quante caselle adoperare. Se volete mettere un bottone nella casella in basso a destra
nella nostra tabella 2x2, e volete che esso riempia SOLO quella casella, dovete porre
left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2.
Se invece volete che un oggetto si prenda tutta la riga più in alto nella nostra tabella
2x2, dovreste usare left_attach = 0, right_attach =2, top_attach = 0,
bottom_attach = 1.
Gli argomenti ``xoptions'' e ``yoptions'' sono usati per specificare le opzioni di impacchettamento;
di essi si può fare l'OR in modo di ottenere opzioni multiple.
Le opzioni sono:
GTK_FILL - Se la parte di tabella in cui si vuole inserire il widget è più
grande dell'oggetto, e se si specifica GTK_FILL, l'oggetto viene espanso fino ad
occupare tutto lo spazio disponibile.
GTK_SHRINK - Se si alloca all'oggetto nella tabella meno spazio del necessario
(di solito succede quando l'utente ridimensiona la finestra), allora normalmente
l'oggetto verrebbe spinto fuori dal fondo della finestra fino a sparire.
Se invece si specifica GTK_SHRINK is specified, gli oggetti si rimpiccioliscono
assieme alla tabella.
GTK_EXPAND - Questo fa sì che la tabella si espanda fino ad occupare tutto lo
spazio che rimane nella finestra.
Il riempimento funziona come nelle scatole, con la creazione di un'area vuota
attorno all'oggetto la cui dimensione viene specificata in pixel.
La funzione gtk_table_attach() ha UN MUCCHIO di opzioni. Quindi, ecco una scorciatoia:
Le xoptions e yoptions vengono posti per difetto a GTK_FILL | GTK_EXPAND, e sia xpadding
che ypadding vengono posti a 0. Il resto degli argomenti sono identici a quelli della funzione
precedente.
Ci sono poi le funzioni gtk_table_set_row_spacing() and gtk_table_set_col_spacing().
Queste mettono dello spazio fra le righe (o colonne)in corrispondenza di una specifica
riga (o colonna).
In questo esempio creiamo una finestra avente tre bottoni disposti
in una tabella 2x2. I primi due bottoni li mettiamo nella riga superiore.
Un terzo bottone, quit, lo mettiamo nella riga inferioe, in modo da
comprendere entrambe le colonne. Ciò significa che dovremmo
avere qualcosa di questo tipo:
Ecco il codice sorgente:
/* table.c */
#include <gtk/gtk.h>
/* la nostra funzione di ritorno.
* i dati passati a questa funzione vengono stampati su stdout */
void callback (GtkWidget *widget, gpointer data)
{
g_print ("Hello again - %s was pressed\n", (char *) data);
}
/* questa funzione fa uscire dal programma */
void delete_event (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *table;
gtk_init (&argc, &argv);
/* creiamo una nova finestra */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* predisponiamo il titolo per la finestra */
gtk_window_set_title (GTK_WINDOW (window), "Table");
/* creiamo un gestore per delete_event che esca immediatamente
* da GTK. */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* regoliamo la larghezza del bordo della finestra. */
gtk_container_border_width (GTK_CONTAINER (window), 20);
/* creiamo una tabella 2x2 */
table = gtk_table_new (2, 2, TRUE);
/* mettiamo la tabella nella finesta principale */
gtk_container_add (GTK_CONTAINER (window), table);
/*creiamo il primo bottone */
button = gtk_button_new_with_label ("button 1");
/* quando viene premuto il bottone, chiamiamo la funzione di ritorno
* con un puntatore a "button 1"come argomento */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");
/* inseriamo il bottone 1 nel quadrante in alto a sinistra della tabella */
gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 1, 0, 1);
gtk_widget_show (button);
/* creiamo il secondo bottone */
button = gtk_button_new_with_label ("button 2");
/* quando si preme il bottone, chiamamo la funzione di ritorno
* con un puntatore a "button 2"come argomento */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");
/* inseriamo il secondo bottone nel quadrate in alto a destra della tbella */
gtk_table_attach_defaults (GTK_TABLE(table), button, 1, 2, 0, 1);
gtk_widget_show (button);
/* creiamo il botone "Quit" */
button = gtk_button_new_with_label ("Quit");
/* quando viene premuto questo bottone, chiamiamo la funzione "delete_event"
* e si esce dal programma */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* inseriamo il pulsante quit nelle due casele in basso della tabella */
gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 2, 1, 2);
gtk_widget_show (button);
gtk_widget_show (table);
gtk_widget_show (window);
gtk_main ();
return 0;
}