<- PW: AVI - Indice Generale - Copertina - SL: Intro -> |
PlutoWare
L'articoloLo scorso giugno il desktop manager GNOME è finalmente giunto alla versione 2.0.0 (versione in verità rivolta soprattutto agli sviluppatori), rinnovando così la sfida volta ad ottenere un desktop semplice da utilizzare e bello da vedere. Come sappiamo, GNOME è basato sulle librerie GTK+, anch'esse giunte alla versione 2.0 da qualche tempo. Già nello scorso numero del Journal abbiamo effettuato una panoramica sulle novità introdotte dalle nuove GTK+; da questo inizieremo a conoscere i nuovi widget, iniziando da quelli destinati al trattamento delle liste e degli alberi. In webografia troverete gli indirizzi della documentazione delle nuove API di GTK+. |
Il nuovo widget di GTK+ per la gestione delle liste e degli alberi è composto in realtà da un insieme di widget. Per creare una lista o un albero con le nuove librerie, è necessario infatti utilizzare un oggetto GtkTreeModel e un oggetto GtkTreeView. Questo perché il nuovo widget è realizzato secondo il pattern Model/View/Controller.
In pratica il nostro oggetto è composto da tre parti: il modello, la vista ed il controllore. Il modello rappresenta i dati che dobbiamo trattare (la lista, l'albero); la vista rappresenta il modo in cui i dati (il modello) devono essere visualizzati; il controllore, che è in genere la nostra interfaccia grafica, ci permette di interagire con il modello e modificare, aggiungere, eliminare i dati in esso contenuti.
I widget che fanno parte del "View" sono GtkTreeView, GtkTreeViewColumn e GtkCellRenderer; i widget che generalmente sono usati per creare il modello sono GtkListStore (per le liste) e GtkTreeStore (per gli alberi), mentre se si ha la necessità di creare un particolare modello si ricorre a GtkTreeModel. Ma qual'è il vantaggio derivante da ciò, a cosa serve questa diversificazione? Una maggiore flessibilità, in primis: ad esempio è possibile avere più viste, o viste diverse, degli stessi dati. Pensiamo ad un file manager in stile Midnight Commander che visualizza lo stesso file system (i dati, il modello creato una sola volta) in due differenti finestre, ognuna delle quali mostra una differente directory, ma con impostazioni comuni, e comunque alterabili dall'utente in maniera indipendente e integrata nell'applicazione. Questo grazie alla separazione tra dati e visualizzazione.
In generale i dati saranno visualizzati in modo tabellare, disporremo quindi di righe, ognuna delle quali divise in una o più colonne (in funzione del modello che abbiamo creato) in cui disporre i dati. I dati appartenenti alla stessa colonna dovranno essere della stessa natura (quindi in una certa colonna si potranno inserire solo stringhe o interi o immagini, eccetera)
#include <gtk/gtk.h> #include <stdlib.h> GtkWidget *entry1; GtkWidget *entry2; enum { COLONNA_STRINGHE, COLONNA_NUMERI, COLONNE }; void aggiungi(GtkButton *button, gpointer data) { GtkTreeView *view = GTK_TREE_VIEW(data); GtkTreeModel *model; GtkTreeIter iter; gchar *str; gchar *num; model = gtk_tree_view_get_model(GTK_TREE_VIEW(view)); str = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry1))); num = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry2))); gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLONNA_STRINGHE, str, COLONNA_NUMERI, atoi(num), -1); gtk_entry_set_text(GTK_ENTRY(entry1),""); gtk_entry_set_text(GTK_ENTRY(entry2),""); } void modifica(GtkButton *button, gpointer data) { GtkTreeView *view = GTK_TREE_VIEW(data); GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *selection; gchar *str = NULL; gchar *num = NULL; model = gtk_tree_view_get_model(GTK_TREE_VIEW(view)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); str = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry1))); num = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry2))); if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { gint i; GtkTreePath *path; path = gtk_tree_model_get_path(model, &iter); i = gtk_tree_path_get_indices(path)[0]; gtk_list_store_remove(GTK_LIST_STORE(model), &iter); gtk_tree_path_free(path); gtk_list_store_insert(GTK_LIST_STORE(model), &iter, i); gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLONNA_STRINGHE, str, COLONNA_NUMERI, atoi(num), -1); gtk_entry_set_text(GTK_ENTRY(entry1),""); gtk_entry_set_text(GTK_ENTRY(entry2),""); } } void cancella(GtkButton *button, gpointer data) { GtkTreeView *view = GTK_TREE_VIEW(data); GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *selection; model = gtk_tree_view_get_model(GTK_TREE_VIEW(view)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { gtk_list_store_remove(GTK_LIST_STORE(model), &iter); gtk_entry_set_text(GTK_ENTRY(entry1),""); gtk_entry_set_text(GTK_ENTRY(entry2),""); } } void selection_changed(GtkTreeSelection *selection, gpointer data) { GtkTreeModel *model; GtkTreeIter iter; gchar *str; gint num; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, COLONNA_STRINGHE, &str, COLONNA_NUMERI, &num, -1); gtk_entry_set_text(GTK_ENTRY(entry1), str); g_free(str); str = g_strdup_printf("%d", num); gtk_entry_set_text(GTK_ENTRY(entry2), str); g_free(str); } } GtkWidget* create_window (void) { GtkWidget *window; GtkWidget *hbox1; GtkWidget *hbox2; GtkWidget *hbox3; GtkWidget *vbox; GtkWidget *button1; GtkWidget *button2; GtkWidget *button3; GtkWidget *button4; GtkWidget *label1; GtkWidget *label2; GtkWidget *hseparator; GtkWidget *scrolledwindow; GtkListStore *model; /* l'oggetto model */ GtkWidget *view; /* -\ */ GtkCellRenderer *renderer; /* --> l'oggetto view */ GtkTreeSelection *selection; /* -/ */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Gtk+2.0 Usiamo le liste"); hbox1 = gtk_hbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), hbox1); gtk_widget_show (hbox1); vbox = gtk_vbox_new (TRUE, 5); gtk_box_pack_start (GTK_BOX (hbox1), vbox, FALSE, FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); gtk_widget_show (vbox); button1 = gtk_button_new_with_label("Aggiungi"); gtk_box_pack_start (GTK_BOX (vbox), button1, FALSE, FALSE, 0); gtk_widget_show (button1); button2 = gtk_button_new_with_label("Modifica"); gtk_box_pack_start (GTK_BOX (vbox), button2, FALSE, FALSE, 0); gtk_widget_show (button2); button3 = gtk_button_new_with_label("Elimina"); gtk_box_pack_start (GTK_BOX (vbox), button3, FALSE, FALSE, 0); gtk_widget_show (button3); hbox2 = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox2, FALSE, FALSE, 0); gtk_widget_show (hbox2); label1 = gtk_label_new ("Stringa"); gtk_box_pack_start (GTK_BOX (hbox2), label1, FALSE, FALSE, 5); gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT); gtk_widget_show (label1); entry1 = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (hbox2), entry1, TRUE, TRUE, 0); gtk_widget_show (entry1); hbox3 = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox3, FALSE, FALSE, 0); gtk_widget_show (hbox3); label2 = gtk_label_new ("Numero"); gtk_box_pack_start (GTK_BOX (hbox3), label2, FALSE, FALSE, 5); gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT); gtk_widget_show (label2); entry2 = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (hbox3), entry2, TRUE, TRUE, 0); gtk_widget_show (entry2); hseparator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (vbox), hseparator, FALSE, FALSE, 0); gtk_widget_show (hseparator); button4 = gtk_button_new_with_label("Esci"); gtk_box_pack_start (GTK_BOX (vbox), button4, FALSE, FALSE, 0); gtk_widget_show (button4); scrolledwindow = gtk_scrolled_window_new (NULL, NULL); gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow), 10); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_show (scrolledwindow); model = gtk_list_store_new(COLONNE, G_TYPE_STRING, G_TYPE_INT); view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(model)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), /* vista */ -1, /* posizione della colonna */ "Colonna stringhe", /* titolo della colonna */ renderer, /* cella inserita nella colonna */ "text", /* attributo colonna */ COLONNA_STRINGHE, /* colonna inserita */ NULL); /* fine ;-) */ renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "Colonna numeri", renderer, "text", COLONNA_NUMERI, NULL); gtk_widget_show (view); g_object_unref(model); gtk_container_add (GTK_CONTAINER (scrolledwindow), view); gtk_container_set_border_width (GTK_CONTAINER (view), 5); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); g_signal_connect(G_OBJECT (button1), "clicked", G_CALLBACK (aggiungi), view); g_signal_connect(G_OBJECT (button2), "clicked", G_CALLBACK (modifica), view); g_signal_connect(G_OBJECT (button3), "clicked", G_CALLBACK (cancella), view); g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selection_changed), view); g_signal_connect(G_OBJECT (button4), "clicked", G_CALLBACK (gtk_main_quit), NULL); return window; } int main (int argc, char *argv[]) { GtkWidget *window; gtk_init(&argc, &argv); window = create_window(); gtk_widget_show(window); gtk_main (); return 0; } |
Salvate il programma di prova nel file gtk_list.c e compilate con:
gcc -Wall -g gtk_list.c -o gtk_list `pkg-config gtk+-2.0 --libs --cflags` |
Iniziamo immediatamente con una delle novità introdotte dalle nuove librerie,
lo script pkg-config, che sostituisce e generalizza i compiti svolti sia da gtk-config sia
da gnome-config. Richiamate lo script dalla riga di comando con la seguente sintassi:
pkg-config --help
, per avere l'elenco di tutte le possibili opzioni, e con la
sintassi: pkg-config --list-all
per avere l'elenco di tutte le librerie supportate
dallo script. Naturalmente il suo scopo è quello di fornire al compilatore e al linker i
percorsi per i file da includere e per le librerie da linkare. Il risultato della compilazione
e dell'esecuzione del programma di prova è osservabile nella figura 1.
Abbiamo visto che per creare una lista (o un albero) è necessario creare un modello e una vista; vediamo in dettaglio come fare.
enum { COLONNA_STRINGHE, COLONNA_NUMERI, COLONNE }; ... model = gtk_list_store_new(COLONNE, G_TYPE_STRING, G_TYPE_INT); |
Sono disponibili due tipi differenti di modelli "preconfezionati", uno per creare le liste (GtkListStore) e uno per creare gli alberi (GtkTreeStore); in questo numero del Pluto Journal vedremo come adoperare le liste. Alcune delle funzioni utilizzate per la creazione del modello e per il suo utilizzo sono le seguenti:
GtkListStore* gtk_list_store_new (gint n_columns, ...); void gtk_tree_model_get (GtkTreeModel *tree_model, GtkTreeIter *iter, ...); void gtk_list_store_set (GtkListStore *list_store, GtkTreeIter *iter, ...); void gtk_list_store_remove (GtkListStore *list_store, GtkTreeIter *iter); void gtk_list_store_clear (GtkListStore *list_store); void gtk_list_store_insert (GtkListStore *list_store, GtkTreeIter *iter, gint position); void gtk_list_store_prepend (GtkListStore *list_store, GtkTreeIter *iter); void gtk_list_store_append (GtkListStore *list_store, GtkTreeIter *iter); GtkTreePath* gtk_tree_path_new_from_string (const gchar *path); gchar* gtk_tree_path_to_string (GtkTreePath *path); gint* gtk_tree_path_get_indices (GtkTreePath *path); gboolean gtk_tree_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path); gboolean gtk_tree_model_get_iter_from_string (GtkTreeModel *tree_model, GtkTreeIter *iter, const gchar *path_string); void gtk_tree_path_next (GtkTreePath *path); gboolean gtk_tree_path_prev (GtkTreePath *path); gboolean gtk_tree_path_up (GtkTreePath *path); void gtk_tree_path_down (GtkTreePath *path); void gtk_tree_path_free (GtkTreePath *path); |
Osserviamo l'esempio proposto. Chiamando gtk_list_store_new (COLONNE, G_TYPE_STRING, G_TYPE_INT);
abbiamo creato il modello della nostra lista, che è formata da due
colonne, la prima contenente delle stringhe (G_TYPE_STRING
),
la seconda degli interi (G_TYPE_INT
). La funzione
ritorna un puntatore al nuovo oggetto "modello lista" creato. La funzione
prevede come primo argomento il numero di colonne della lista e come argomenti
successivi il tipo di dato che ogni colonna dovrà contenere. Avremmo potuto
creare il modello richiamando la funzione nel modo seguente:
gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
, ma in genere
si preferisce utilizzare un enumeratore. Questo sia per indicare il numero di colonne
della lista, sia per identificare le colonne medesime, perché molte
funzioni operano sia sulle singole colonne sia sul loro numero totale.
Ad esempio supponiamo di scrivere un piccolo programma per la gestione delle
spese della famiglia; è molto più semplice riferirsi alla colonna
CONSUMI_TELEFONICI
o SPESE_CINEMA
che alla colonna numero tre
o numero sette, semplicità che diventa ancor maggiore se il programma
gestisce più di una lista.
Ora che abbiamo creato il nostro modello, vediamo come inserirci qualche dato:
GtkTreeIter iter; ... str = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry1))); num = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry2))); gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLONNA_STRINGHE, str, COLONNA_NUMERI, atoi(num), -1); |
Nel nostro esempio recuperiamo i dati da inserire nella lista da due widget entry,
quindi utilizziamo la funzione gtk_list_store_append()
per aggiungere una
nuova riga al modello.
La riga può essere inserita in testa alla lista utilizzando la funzione
gtk_list_store_prepend();
, o in coda usando
gtk_list_store_append();
o, infine, in una generica posizione richiamando
gtk_list_store_insert()
(figura 2).
Le funzioni per appendere in testa e in coda alla lista necessitano del puntatore al modello e dell'indirizzo di una struttura GtkTreeIter, mentre per la funzione di inserimento in un generico punto occorre passare anche la posizione in cui si vuole inserire il dato.
Ma cos'è un GtkTreeIter? Per realizzare liste, alberi e quant'altro non è sufficiente disporre di un widget flessibile che ci consenta di creare diversi modelli; è necessario anche un meccanismo che ci permetta di accedere ai dati contenuti nel modello stesso. Pensiamo ad un foglio di calcolo ed alla sua struttura a griglia, e supponiamo di voler accedere alla cella che si trova all'intersezione della colonna XYZ e della riga 436. Come fare? La risposta di GTK+ è GtkTreeIter (GtkTreePath): è possibile accedere (riferirsi) ai dati del modello contenuti in una certa riga o in una certa colonna utilizzando la struttura GtkTreeIter (o GtkTreePath).
Una volta aggiunta la riga alla lista, occorre inserire i dati veri e propri. Questa
operazione sarà effettuata richiamando la funzione gtk_list_store_set()
.
La funzione vuole il puntatore al modello come primo argomento, l'iteratore come secondo e poi
un elenco variabile di coppie (numero_di_colonna, valore_da_inserire_in_colonna) terminato
da -1. Osserviamo il codice proposto; abbiamo le coppie
COLONNA_STRINGHE, str
e COLONNA_NUMERI, atoi(num)
, quindi nella
colonna COLONNA_STRINGHE inseriamo il valore della variabile str, e così via.
Possiamo osservare, inoltre, come le enumerazioni effettuate ci facilitino la vita;
pensate ad una lista con qualche decina di colonne, alla difficoltà di ricordare il
numero di una certa colonna, alla possibilità di commettere errori invertendo numeri
di colonna, ecc.
Abbiamo inserito dei dati nella lista, ora vogliamo recuperarli:
... gchar *str; gint num; ... gtk_tree_model_get(model, &iter, COLONNA_STRINGHE, &str, COLONNA_NUMERI, &num, -1); gtk_entry_set_text(GTK_ENTRY(entry1), str); g_free(str); str = g_strdup_printf("%d", num); gtk_entry_set_text(GTK_ENTRY(entry2), str); g_free(str); ... |
Richiamiamo la funzione gtk_tree_model_get()
, passandole il puntatore
al modello, il puntatore all'iteratore (cioè il puntatore alla riga che ci interessa),
e l'elenco delle coppie (numero_di_colonna_da_recuperare, variabile_in_cui_salvare_il_valore_recuperato)
terminato da -1. Osserviamo che non siamo tenuti a recuperare il contenuto di tutte le
colonne; se avessimo avuto bisogno del contenuto della colonna COLONNA_NUMERI, avremmo
richiamato la funzione nel modo seguente:
gtk_tree_model_get(model, &iter, COLONNA_NUMERI, &num, -1);
. Quindi con
il puntatore all'iteratore selezioniamo una riga del modello, con il numero di colonna
selezioniamo una delle colonne (o la colonna, se unica) della riga puntata, il valore
contenuto in questa cella verrà salvato nella variabile passata.
Per eliminare una riga dalla lista, è sufficiente chiamare gtk_list_store_remove()
passandogli il puntatore alla lista e l'iteratore relativo alla riga da eliminare
Per finire vogliamo pulire la lista, eliminando tutte le righe in essa contenute.
Otteniamo questo risultato utilizzando la funzione gtk_list_store_clear()
e
passandole il puntatore alla lista.
GtkTreeIter e GtkTreePath sono strettamente correlati: GtkTreeIter punta alla locazione in cui i dati del modello sono memorizzati; permette quindi di accedere ai dati contenuti nel modello. Il corretto valore dell'Iter ci sarà fornito da "un'interrogazione" al modello. GtkTreePath indica un potenziale nodo; è un "indirizzo" di un possibile nodo del modello. Se "l'indirizzo" è valido, cioè se a quell'indirizzo corrisponde nel modello un dato, allora potremo accedervi. Utilizzando GtkTreePath possiamo visitare un generico nodo del nostro modello, se quel nodo esiste.
E' possibile convertire la struttura GtkTreePath sia in una lista d'interi, utilizzando la
funzione gtk_tree_path_get_indices();
che prende un puntatore alla path e ritorna
un puntatore alla lista d'interi, sia in una stringa usando la funzione
gtk_tree_path_to_string();
, che prende un puntatore alla path e ritorna una stringa.
Utilizzeremo proprio la possibilità di trasformare una path in una stringa per meglio
comprendere il suo funzionamento.
Supponiamo che il nostro modello rappresenti un albero, la stringa rappresentativa della path sarà formata da una lista di numeri separati da ":" (due punti), ad esempio "0:4:7"; ogni numero si riferisce ad un offset del livello, in questo caso "0" si riferisce al nodo radice dell'albero, mentre "4:7" si riferisce all'ottavo elemento del quinto nodo della radice. Quindi, se volessimo accedere al secondo elemento del terzo nodo del nodo radice, la nostra stringa dovrebbe essere "0:2:1".
La possibilità di trasformare stringhe in path, utilizzando la funzione
gtk_tree_path_new_from_string();
, che prende una stringa nella forma
"x:y:z:k...:w" e ritorna un puntatore a GtkTreePath, ci permette di navigare con
molta semplicità attraverso la nostra lista o albero.
Come già detto, GtkTreeIter si riferisce ad un preciso nodo del modello, ed è il modello stesso che assegna un valore corretto all'iteratore. I GtkTreeIter sono il modo principale per accedere ai dati contenuti nel modello. GtkTreeModel definisce inoltre una serie di funzioni che permettono di navigare attraverso i dati utilizzando GtkTreeIter e/o GtkTreePath
E' possibile recuperare l'iteratore associato ad un determinato elemento della lista in vari modi; qui ne esamineremo un paio.
Si può utilizzare la funzione gtk_tree_model_get_iter_from_string()
,
che vuole il puntatore al modello, l'indirizzo di memoria della variabile iter e la
stringa che rappresenta la path dell'elemento che ci interessa. Ritorna TRUE
se l'iter è stato assegnato correttamente. Ad esempio con questo codice
if (gtk_tree_model_get_iter_from_string(model, &iter, "0:3:4:5"))
possiamo far puntare l'iter all'elemento indicato dalla stringa, e contemporaneamente
verificare che l'elemento esista (nel caso in cui l'elemento non esista o non fosse
raggiungibile, la funzione ritornerebbe FALSE
, inoltre il valore contenuto
in iter non sarebbe valido).
Un modo alternativo per accedere ad un elemento della lista è il seguente:
partiamo dalla stringa rappresentativa della path, richiamando
gtk_tree_path_new_from_string()
, che ci fornirà un puntatore a
GtkTreePath. Passeremo poi questo puntatore alla funzione gtk_tree_model_get_iter();
.
Quest'ultima funzione vuole il puntatore al modello, il puntatore della path
di cui vogliamo l'iter e l'indirizzo di memoria della variabile iter. La funzione
riempirà la variabile iter con i valori appropriati a far puntare l'iteratore
all'elemento che ci interessa. Anche questa funzione ritorna un valore booleano, che sarà
TRUE
se alla variabile iter è stato assegnato un valore corretto,
FALSE
in caso contrario.
Attenzione, la memoria al puntatore a GtkTreePath, allocata richiamando
gtk_tree_path_new_from_string()
deve essere liberata utilizzando
gtk_tree_path_free()
.
Gli iteratori possono essere considerati validi finché il modello non viene modificato o finché non emette un segnale. Non bisogna liberare la memoria occupata dagli iteratori, che sono di proprietà del modello. Si noti tra l'altro che possiamo sempre allocare gli iteratori sullo stack; in parole povere, dichiararli come oggetti (non puntatori) e passare alle funzioni il riferimento (con l'operatore &). Questo ha il vantaggio rispetto all'allocazione sull'heap di non richiedere di liberare la memoria. Anche la documentazione di GTK consiglia l'allocazione sullo stack, per l'uso comune.
Ricapitolando, la memoria che allochiamo utilizzando la funzione
gtk_tree_path_new_from_string()
va liberata chiamando
gtk_tree_path_free()
, mentre non bisogna liberare la memoria occupata
dagli iteratori.
Per navigare attraverso la lista o l'albero si ricorre alle seguenti funzioni (è necessario passare a tutte un puntatore a GtkTreePath):
gtk_tree_path_next()
: permette di spostare la path all'elemento successivo,
relativamente all'attuale profondità della path
gtk_tree_path_prev()
: permette di spostare la path all'elemento precedente,
se esiste, relativamente all'attuale profondità della path
gtk_tree_path_up()
: sposta la path all'elemento genitore, se esiste, di quello attuale
gtk_tree_path_down()
: sposta la path all'elemento figlio, se esiste, di quello attuale
... view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(model)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), /* vista */ -1, /* posizione della colonna */ "Colonna stringhe", /* titolo della colonna */ renderer, /* cella inserita nella colonna */ "text", /* attributo colonna */ COLONNA_STRINGHE, /* colonna inserita */ NULL); /* fine ;-) */ ... gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); ... g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selection_changed), view); |
GtkWidget* gtk_tree_view_new_with_model(GtkTreeModel *model); GtkTreeModel* gtk_tree_view_get_model(GtkTreeView *tree_view); GtkTreeSelection* gtk_tree_view_get_selection(GtkTreeView *tree_view); gint gtk_tree_view_insert_column_with_attributes(GtkTreeView *tree_view, gint position, const gchar *title, GtkCellRenderer *cell, ...); GtkTreeViewColumn* gtk_tree_view_get_column(GtkTreeView *tree_view, gint n); GtkTreeViewColumn* gtk_tree_view_column_new_with_attributes(const gchar *title, GtkCellRenderer *cell, ...); gint gtk_tree_view_append_column(GtkTreeView *tree_view, GtkTreeViewColumn *column); gint gtk_tree_view_insert_column(GtkTreeView *tree_view, GtkTreeViewColumn *column, gint position); gint gtk_tree_view_remove_column(GtkTreeView *tree_view, GtkTreeViewColumn *column); GtkTreeViewColumn* gtk_tree_view_get_column(GtkTreeView *tree_view, gint n); gboolean gtk_tree_view_get_headers_visible(GtkTreeView *tree_view); void gtk_tree_view_set_headers_visible(GtkTreeView *tree_view, gboolean headers_visible); void gtk_tree_view_column_set_title(GtkTreeViewColumn *tree_column, const gchar *title); G_CONST_RETURN gchar* gtk_tree_view_column_get_title (GtkTreeViewColumn *tree_column); void gtk_tree_view_columns_autosize(GtkTreeView *tree_view); void gtk_tree_view_column_set_resizable(GtkTreeViewColumn *tree_column, gboolean resizable); gboolean gtk_tree_view_column_get_resizable(GtkTreeViewColumn *tree_column); void gtk_tree_view_column_set_clickable(GtkTreeViewColumn *tree_column, gboolean clickable); gboolean gtk_tree_view_column_get_clickable(GtkTreeViewColumn *tree_column); void gtk_tree_view_column_set_visible(GtkTreeViewColumn *tree_column, gboolean visible); gboolean gtk_tree_view_column_get_visible(GtkTreeViewColumn *tree_column); |
Abbiamo creato il modello, ora occorre visualizzare i dati in esso contenuti.
Per far ciò si ricorre ad un oggetto GtkTreeView, che è creato richiamando
la funzione gtk_tree_view_new_with_model()
alla quale bisogna passare
il puntatore al modello della nostra lista; ci ritornerà il puntatore
all'oggetto GtkTreeView.
Dal puntatore al GtkTreeView è possibile ricavare il puntatore al modello e
un puntatore a GtkTreeSelection. Chiamando la funzione
gtk_tree_view_get_model()
, con in ingresso il puntatore a GtkTreeView,
avremo un puntatore a GtkTreeModel, che rappresenta il modello utilizzato da quella
particolare vista, mentre chiamando la funzione gtk_tree_view_get_selection()
con in ingresso il puntatore a GtkTreeView, otterremo il puntatore a GtkTreeSelection.
Creato l'oggetto GtkTreeView, è necessario inserire nella lista le varie
colonne, operazione che è possibile compiere in vari modi. Iniziamo dal più
semplice, la funzione gtk_tree_view_insert_column_with_attributes();
,
alla quale passeremo il puntatore a GtkTreeView, la posizione in cui inserire la
colonna (se si passa -1 la colonna è inserita in ultima posizione), il titolo
che apparirà sulla colonna, un puntatore ad un oggetto GtkCellRenderer,
che rappresenta la cella della colonna, ed infine un elenco d'attributi da assegnare
alla cella, terminato da NULL
. La funzione ritorna un intero che indica
il numero totale di colonne presenti. Gli attributi che è possibile assegnare alla
cella sono delle stringhe che descrivono le sue caratteristiche; nel programma
allegato si fa uso di un solo attributo, la stringa "text"
,
con la quale indichiamo che le celle della lista conterranno del testo.
Un modo alternativo per creare una colonna è quello di ricorrere alla funzione
gtk_tree_view_column_new_with_attributes()
, che accetta in ingresso
il titolo della colonna, il puntatore alla cella che si sta inserendo e l'elenco,
terminato da NULL
, degli attributi della cella. Quello che ci sarà
ritornato è un puntatore al nuovo oggetto colonna GtkTreeViewColumn. Sarà,
ora compito nostro inserire la colonna in GtkTreeView, utilizzando una tra le
seguenti funzioni:
gtk_tree_view_append_column
: appende una colonna in fondo alla
lista. Vuole un puntatore a GtkTreeView, il puntatore alla colonna che si sta
inserendo, e ritorna il numero totale di colonne.
gtk_tree_view_insert_column
: inserisce la colonna passata nella
posizione chiesta, se la posizione è -1, la colonna è inserita
come ultima. Anche questa funzione ritorna il numero totale di colonne inserite.
Per eliminare una colonna è sufficiente richiamare gtk_tree_view_remove_column
,
passando come primo argomento il puntatore all'oggetto vista e come secondo il
puntatore alla colonna da eliminare. L'intero restituito indica il numero di colonne
dopo l'eliminazione.
gtk_tree_view_get_column()
, che avuti in ingresso un
puntatore a GtkTreeView e un intero (la posizione della colonna) ci restituisce il
puntatore alla colonna. Attenzione: le colonne sono numerate a partire da zero,
la prima colonna sarà dunque in posizione zero.
Abbiamo creato la nostra lista, ed inserito anche qualche dato; adesso cerchiamo di modificarne l'aspetto esteriore.
Se vogliamo sapere se le intestazioni delle colonne dalla lista sono visibili per
nasconderle (o sono nascoste per mostrarle), utilizzeremo la funzione
gtk_tree_view_get_headers_visible()
, alla quale passeremo il puntatore
al GtkTreeView di cui vogliamo sapere se sta visualizzando gli header, e ci
ritornerà TRUE
nel caso in cui gli header siano visibili. A
questo punto possiamo modificare lo stato degli header richiamando la funzione
gtk_tree_view_set_headers_visible()
, alla quale passeremo il
puntatore a GtkTreeView ed un valore booleano, vero o falso, a seconda se vogliamo
visualizzare o meno le intestazioni.
Sappiamo come mostrare o nascondere gli header delle colonne, ma come fare a
modificare la stringa che contengono? Ricorreremo alle funzioni
gtk_tree_view_column_set_title()
e gtk_tree_view_column_get_title()
;
la prima necessita del puntatore alla specifica colonna (GtkTreeViewColumn) di
cui vogliamo modificare il titolo e del nuovo titolo, mentre alla seconda
passeremo sempre il puntatore alla colonna e ci restituirà il titolo della
stessa.
Utilizzando la funzione gtk_tree_view_columns_autosize()
facciamo in modo che le colonne si ridimensionino automaticamente. Supponiamo
di avere una colonna in cui l'elemento più lungo sia di 10 caratteri, e di
inserire la stringa "precipitevolissimevolmente"; se abbiamo stabilito che le colonne debbano
essere autoridimensionanti, allora la colonna in questione s'allargherà per
visualizzare completamente la nuova stringa.
Possiamo decidere che debba essere l'utente ad occuparsi del ridimensionamento delle colonne, posizionando il puntatore del mouse sul bordo della testata della colonna e trascinandolo.
Per sapere se una specifica colonna è ridimensionabile o meno, utilizzeremo
la funzione gtk_tree_view_column_get_resizable()
, alla quale passeremo un
puntatore a GtkTreeViewColum e che ci ritornerà un valore booleano. Utilizzeremo invece
la funzione gtk_tree_view_column_set_resizable()
, alla quale passeremo il puntatore
alla colonna e un valore booleano, per indicare se quella colonna è ridimensionabile
(TRUE
) o no (FALSE
).
Vogliamo che gli header delle colonne della lista siano cliccabili? Bene, chiamiamo
la funzione gtk_tree_view_column_set_clickable()
con un puntatore alla
colonna e un valore booleano (TRUE
per rendere l'header cliccabile,
FALSE
altrimenti), come parametri. Potremmo in seguito intercettare il
click sull'header e richiamare, ad esempio, una callback per riordinare la lista.
Poniamo che durante il funzionamento del nostro programma si verifichi una certa condizione,
e vogliamo che una colonna non sia più mostrata finché non se ne
verifichi un'altra. Utilizzeremo allora la funzione gtk_tree_view_column_set_visible()
per mostrare (TRUE
) o nascondere (FALSE
) la colonna individuata
dal puntatore a GtkTreeViewColumn passato come primo parametro. Per conoscere lo stato
di una colonna si chiamerà la funzione gtk_tree_view_column_get_visible
,
che ritornerà TRUE
o FALSE
a seconda che la colonna
passata come parametro sia o no visibile.
... g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selection_changed), view); ... void modifica(GtkButton *button, gpointer data) { ... /* questa istruzione è ridondante */ model = gtk_tree_view_get_model(GTK_TREE_VIEW(view)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); ... /* posso recuperare il modello da qui, sostituendo a NULL il ptr. al modello */ if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { gint i; GtkTreePath *path; path = gtk_tree_model_get_path(model, &iter); i = gtk_tree_path_get_indices(path)[0]; gtk_list_store_remove(GTK_LIST_STORE(model), &iter); gtk_tree_path_free(path); gtk_list_store_insert(GTK_LIST_STORE(model), &iter, i); ... void selection_changed(GtkTreeSelection *selection, gpointer data) { ... if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, COLONNA_STRINGHE, &str, COLONNA_NUMERI, &num, -1); gtk_entry_set_text(GTK_ENTRY(entry1), str); g_free(str); str = g_strdup_printf("%d", num); gtk_entry_set_text(GTK_ENTRY(entry2), str); g_free(str); } ... |
L'oggetto GtkTreeSelection è creato automaticamente durante la creazione dell'oggetto GtkTreeView, e non può esistere se non in funzione di GtkTreeView. Non esiste, quindi, una funzione gtk_tree_selection_new(); il solo modo per ricavare un puntatore all'oggetto GtkTreeSelection è quello di recuperarlo da un puntatore a GtkTreeView.
Lo scopo dell'oggetto GtkTreeSelection è quello di determinare lo stato della lista/albero e/o determinare se e quale nodo (elemento/riga) della lista/albero è stato selezionato/deselezionato.
Nell'esempio allegato, abbiamo riempito la lista con una serie di valori, ma nel caso avessimo commesso un errore, come potremmo modificare il dato errato? Possiamo selezionare il dato che ci interessa (molto banalmente lo clicchiamo) e intercettare il segnale "changed" che è emesso dall'oggetto GtkTreeSelection ogni qualvolta che un suo nodo è selezionato; quindi richiamiamo un'opportuna funzione di callback.
GtkTreeView* gtk_tree_selection_get_tree_view (GtkTreeSelection *selection); gboolean gtk_tree_selection_get_selected (GtkTreeSelection *selection, GtkTreeModel **model, GtkTreeIter *iter); void gtk_tree_selection_set_mode (GtkTreeSelection *selection, GtkSelectionMode type); void gtk_tree_selection_select_path (GtkTreeSelection *selection, GtkTreePath *path); void gtk_tree_selection_select_iter (GtkTreeSelection *selection, GtkTreeIter *iter); void gtk_tree_selection_select_all (GtkTreeSelection *selection); void gtk_tree_selection_unselect_all (GtkTreeSelection *selection); void gtk_tree_selection_select_range (GtkTreeSelection *selection, GtkTreePath *start_path, GtkTreePath *end_path); |
Vediamo come utilizzare l'oggetto GtkTreeSelection per compiere delle operazioni sugli elementi selezionati. Nota: nel programma d'esempio ho utilizzato qualche istruzione in eccesso, quindi quello che sto per dire non è il modo più breve per effettuare operazioni su GtkTreeSelection; in questo modo sarà però più chiara la flessibilità offerta da queste funzioni.
Abbiamo visto che un GtkTreeSelection non può esistere se non in funzione di un GtkTreeView, quindi la prima operazione che si deve fare è quella di recuperare il puntatore a GtkTreeView. Una volta in possesso del puntatore alla vista, si può, eventualmente, recuperare il puntatore al modello, ed utilizzando quest'ultimo stabilire quale elemento/elementi sono stati selezionati (utilizzando GtkTreeIter o GtkTreePath) e si effettuano le operazioni necessarie al nostro programma.
Se si è già in possesso del puntatore a GtkTreeSelection (ad esempio
passato all'interno di una funzione di callback), si può recuperare il
puntatore a GtkTreeView, chiamando la funzione gtk_tree_selection_get_tree_view()
,
alla quale si passa come parametro il puntatore a GtkTreeSelection e si ottiene il
puntatore a GtkTreeView.
Se si è in una funzione callback richiamata dall'intercettazione del
segnale "changed" emesso dall'oggetto GtkTreeSelection, si può ricorrere
alla funzione gtk_tree_selection_get_selected()
, alla quale
bisogna passare il puntatore al GtkTreeSelection, e gli indirizzi di memoria del
modello e dell'iteratore. La funzione assegnerà alle variabili del modello e
dell'iteratore dei valori opportuni. All'uscita dalla funzione, l'iter punterà
all'elemento selezionato, mentre se si passa NULL
come indirizzo dell'iter,
allora la funzione testerà se nella lista/albero è presente
qualche elemento selezionato.
Richiamando la funzione gtk_tree_selection_set_mode()
possiamo indicare
che tipo di selezione vogliamo effettuare. La funzione accetta come primo argomento un
puntatore a GtkTreeSelection, e come secondo una variabile di tipo GtkSelectionMode
,
alla quale si può assegnare uno tra i seguenti valori:
GTK_SELECTION_NONE
GTK_SELECTION_SINGLE
GTK_SELECTION_BROWSE
GTK_SELECTION_MULTIPLE
Possiamo selezionare un determinato elemento utilizzando una tra le funzioni
gtk_tree_selection_select_path()
e gtk_tree_selection_select_iter()
.
Ad entrambe dobbiamo passare come primo parametro il puntatore a GtkTreeSelection,
mentre il secondo parametro da passare è l'"indirizzo" dell'elemento che vogliamo che sia
selezionato. Per la prima funzione passeremo la path dell'elemento, alla seconda
forniremo l'iter dell'elemento.
Utilizzando gtk_tree_selection_select_path()
possiamo, ad esempio,
costruire una funzione che ci permetta di navigare attraverso una lista o un albero.
Costruiamo la path partendo da una stringa gtk_tree_path_new_from_string(...,"0")
,
selezioniamo il nodo "puntato" dalla path con gtk_tree_selection_select_path()
,
e poi navighiamo attraverso la lista/albero con gtk_tree_path_next()
,
gtk_tree_path_prev ()
, gtk_tree_path_up()
eccetera.
Gli elementi contenuti nella lista/albero possono essere selezionati o deselezionati
tutti insieme semplicemente chiamando le funzioni gtk_tree_selection_select_all()
o gtk_tree_selection_unselect_all()
. Se invece vogliamo selezionare i nodi
contenuti in un certo intervallo, useremo la funzione gtk_tree_selection_select_range
.
Alle prime due funzioni dobbiamo passare solo il puntatore a GtkTreeSelection, mentre all'ultima
dobbiamo passare anche le path al primo ed all'ultimo elemento da selezionare.
... renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), /* vista */ -1, /* posizione della colonna */ "Colonna stringhe", /* titolo della colonna */ renderer, /* cella inserita nella colonna */ "text", /* attributo colonna */ COLONNA_STRINGHE, /* colonna inserita */ NULL); /* fine ;-) */ ... |
Le celle contenute nelle colonne si creano utilizzando le funzioni della classe GtkCellRenderer. Con l'introduzione dell'oggetto GtkCellRenderer è diventato semplicissimo inserire nelle liste oggetti quali immagini, check button, radio button; per le celle contenenti stringhe è ora possibile editare il contenuto semplicemente facendo click sulla cella stessa.
In questo numero del Pluto Journal esamineremo solo le celle di tipo testo, nei prossimi numeri vedremo come inserire qualche immagine, check button, eccetera (mi raccomando non mancate ;)).
La cella "testuale" è creata dalla funzione gtk_cell_renderer_text_new()
.
La funzione non accetta nessun parametro, e ritorna il puntatore al nuovo oggetto. E' in pratica
la sola funzione che ci serve. Una volta che è stata creata dobbiamo inserire la cella
in una colonna, e le dobbiamo assegnare alcuni attributi; per compiere queste operazioni si
ricorre ad una funzione che abbiamo incontrato in precedenza,
gtk_tree_view_insert_column_with_attributes
.
Trattandosi di una cella che conterrà una stringa, il nostro attributo sarà
"text"
.
Gli attributi disponibili sono molti, vi rimando alla documentazione per l'elenco completo.
Bene, siamo giunti alla conclusione di questo articolo, che spero possa esservi utile. Per finire vi invito a provare a modificare il programma d'esempio, relativamente all'uso di path, iter e selector. Inserite nella finestra del programma due etichette, la prima con la frase "elemento selezionato" (o qualcosa di simile), e la seconda da impostare in funzione dell'elemento selezionato e contenente la path dell'elemento stesso.
Alla prossima! :)
Potete scaricare la documentazione relativa alle nuove API di GTK+-2.0 da qui:
http://developer.gnome.org/doc/API/2.0/glib/index.html
http://developer.gnome.org/doc/API/2.0/gobject/index.html
http://developer.gnome.org/doc/API/2.0/gdk/index.html
http://developer.gnome.org/doc/API/2.0/gtk/index.html
http://developer.gnome.org/doc/API/2.0/gdk-pixbuf/index.html
http://developer.gnome.org/doc/API/2.0/pango/index.html
http://developer.gnome.org/doc/API/2.0/atk/book1.html
L'autoreNicola Fragale. E' iscritto ad Ingegneria Informatica al Federico II di Napoli. I suoi ultimi interessi informatici sono rivolti all'XML e particolarmente a GConf. Attualmente sta scrivendo una rubrica per GTK+ e GNOME, naturalmente rilasciata sotto licenza GPL. |
<- PW: AVI - Indice Generale - Copertina - SL: Intro -> |