Questa guida si prefigge di fornire una chiave semplice e veloce per la realizzazione di liste lineari o ad albero con Glade .
Con le GTK2 e’ stato introdotto un unico widget per la visualizzazione di liste o di alberi ovvero l’ oggetto GtkTreeView anche detto “Vista” .
Concettualmente i passi da seguire per visualizzare i nostri dati sono due:
Creare un GtkTreeView e settare le proprietà
Organizzare i nostri dati in un “Modello” ( lista o albero ) da visualizzare nel GtkTreeView.
La “Vista” può ospitare una o più colonne, ma le sue proprietà come ad esempio il numero di colonne o la loro natura ( tipo Testo, Intero, Icona, Booleano ) per esempio non sono settabili da Glade e pertanto vedremo come farlo manualmente.
Mentre il View e’ creato da Glade, I tipi di dato che useremo per settare le sue proprietà sono :
“GtkTreeViewColumn” e “GtkCellRenderer”
che si riferiscono rispettivamente alla “Colonna” ed alla “Cella” di ogni colonna.
Creazione del View
Creare una vista con Glade è estremamente semplice, all’ interno di una finestra che chiameremo “window1” mettiamo una finestra di scorrimento ed al suo interno la nostra vista treeview1
Assumendo dunque che “treeview1” sia la nostra vista, possiamo vedere come Glade crei una sua nuova istanza nel file “interface.c” posto nella “src” del progetto, usando la funzione gtk_tree_view_new () che restituisce un puntatore al tipo GtkWidget.
GtkWidget *treeview1;
treeview1 = gtk_tree_view_new ();
Per utilizzare treeview1 dobbiamo prendere il suo puntatore:
treeview1 = lookup_widget(window1,”treeview1″);
Impostazione delle proprietà:
Come detto, creato il Treeview abbiamo la necessita’ di definirne le proprieta’ come il titolo di ogni colonna ed il tipo di cella che ogni colonna deve contenere, nell’ esempio che seguira’ avremo due colonne, una ti tipo icona ed una di tipo testo, il tipo GdkPixbuf servira’ ad ospitare la nostra icona.
GdkPixbuf *pixbuf;
CtkCellRenderer *renderer;
GtkCellViewColumn *column;
La funzione gtk_tree_view_column_new_with_attributes() accetta in ingresso il titolo della colonna, il puntatore alla cella che si sta settando, il tipo di dato e la colonna che lo ospitera’ terminati da NULL.
Quello che ci sara’ ritornato e’ un puntatore al nuovo oggetto colonna GtkTreeViewColumn. che ci permettera’ di “appendere” la colonna in GtkTreeView, attraverso la funzione gtk_tree_view_append_column().
//Prima colonna:
renderer = gtk_cell_renderer_pixbuf_new();
pixbuf = gdk_pixbuf_new_from_file(“mt_star.png”,NULL);
column = gtk_tree_view_column_new_with_attributes(“Titolo”,renderer, “pixbuf”,0,NULL); /* 0 e’ la prima colonna di tipo icona */
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview1), column);
//Seconda colonna:
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(
“Titolo”,renderer,”text”,1,NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview1), column);
Il questo modo abbiamo definito le caratteristiche del view, ma è assolutamente necessario tenere separato il codice che riguarda le proprietà della vista, da quello che riguarda il caricamento dei suoi dati, in caso contrario ad ogni reload della lista vedremo moltiplicare il numero di colonne all’ interno del treeview essendo queste nuovamente definite nel reload stesso.
Esempio: supponiamo che “make_view_setup” sia la funzione che raggruppa le chiamate che riguardano il view, window1 sia la finestra che lo ospita e load_list() la funzione che carica i nostri dati
Eseguiremo:
window1 = create_window1();
make_view_setup(…. )
load_list( … )
gtk_widget_show(window1)
al reload della lista chiameremo solamente “load_list” e non piu’ il setup.
La vista viene distrutta ogni volta che facciamo il destroy di “window1” quindi il setup va chiamato ogni volta che si apre la stessa finestra e cioe’ dopo ogni chiamata a “create”.
Oltre a quelle descritte ci sono altre proprieta’ che possono essere settate, come ad esempio la possibilita’ che una colonna sia ridimensionabile o che assuma automaticamente la dimensione del testo che la occupa o ancora che il titolo della colonna sia cliccabile, alcune funzioni necessarie allo scopo sono le seguenti:
void gtk_tree_view_column_set_resizable(GtkTreeViewColumn *tree_column,
gboolean resizable);
void gtk_tree_view_columns_autosize(GtkTreeView *tree_view);
void gtk_tree_view_column_set_clickable(GtkTreeColumn *tree_column,
gboolean clickable);
Se vogliamo sapere se queste proprieta’ sono settate, esistono le funzioni omologhe:
gtk_tree_view_column_get_resizable( … )
gtk_tree_view_column_get_clickable( … )
Creazione del Modello:
Come accennato in precedenza, i dati che vogliamo visualizzare nel TreeView, devono essere organizzati in un “Modello”, cioe’ devono avere una struttura coerente con la vista creata prima, il tipo di dato che ci consente di ospitarli e’ GtkListStore:
GtkListStore *lista;
lista = gtk_list_store_new(2,GDK_TYPE_PIXBUF,G_TYPE_STRING);
Questa funzione richiede come primo argomento il numero di colonne della lista, e come argomenti successivi i tipi di dato che stiamo ospitando, cioe’ un tipo icona ed un tipo stringa, se avessimo avuto un tipo intero avremmo scritto “G_TYPE_INT”.
Di seguito settiamo il modello nella vista:
gtk_tree_view_set_model(GTK_TREE_VIEW(vista),GTK_TREE_MODEL(lista));
Queste ultime due funzioni, possono essere incluse nella sezione setup in modo che i titoli delle colonne vengano visualizzati anche con una lista vuota, ovviamente “lista” deve essere dichiarato statico.
Infine dobbiamo stabilire in quale punto della lista posizionare i nostri dati e caricarli.
La funzione per appendere in coda alla lista necessita del puntatore al modello (lista) e dell’indirizzo di una struttura GtkTreeIter.
GtkTreeIter insieme a GtkTreePath rappresenta il meccanismo che ci permette di accedere ai dati contenuti nel modello stesso, e’ possibile riferirsi ai dati del modello contenuti in una certa riga o in una certa colonna utilizzando la struttura GtkTreeIter o GtkTreePath.
GtkTreeIter iter;
gtk_list_store_append(lista, &iter);
Una volta aggiunta la riga alla lista, occorre inserire i dati veri e propri nella riga stessa.
Questa operazione sara’ effettuata richiamando la funzione gtk_list_store_set().
gtk_list_store_set(lista, &iter,0,pixbuf,1,”Prova”, -1 );
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”.
Quindi nella colonna 0 abbiamo inserito l’ icona “pixbuf” e nella colonna 1 il testo “Prova”.
“gtk_list_store_append(..)” e “gtk_list_store_set(..)” vanno richiamati all’ interno di un ciclo iterativo tante volte quanti sono i dati da inserire.
Assembliamo tutte queste funzioni e vediamo quale potrebbe essere allora il codice di un caricatore di liste a due colonne di cui la prima un icona:
Nel nostro caso il list_loader viene chiamato dal “main.c ” in questo modo:
/* Initial main.c file generated by Glade. Edit as required. Glade will not overwrite this file. */
#ifdef HAVE_CONFIG_H
# include
#endif
#include <gtk/gtk.h>
#include “interface.h”
#include “callbacks.h”
#include “support.h”
#include “lists_loader.c”
int
main (int argc, char *argv[])
{ gchar *elenco[] = { “Uno”, “Due”, “Tre” };
gtk_set_locale ();
gtk_init (&argc, &argv);
gint i;
add_pixmap_directory (PACKAGE_DATA_DIR “/” PACKAGE “/pixmaps”);
add_pixmap_directory (“../pixmaps”);
Icon_path = g_strdup(“../pixmaps/mini/”); // dichiarato globale in callbacks.h
window1 = create_window1 ();
/* Anche treeview1 e’ dichiarato globale in callbacks.h */
treeview1 = lookup_widget(GTK_WIDGET(window1),”treeview1″);
make_view(treeview1,”mt_star.png”);
load_list(treeview1, elenco);
gtk_widget_show (window1);
gtk_main ();
return 0;
}
/* ================< list_loader.c > =================*/
#include <gtk/gtk.h>
#include <stdio.h>
static GtkListStore *lista;
static GtkTreeIter iter;
static GdkPixbuf *pixbuf;
static GtkCellRenderer *renderer;
static GtkTreeViewColumn *column;
static gchar pixmap_file[100];
static gchar icona[60];
GdkPixbuf *create_new_pixbuf(gchar *filename)
{ GdkPixbuf *pix = NULL;
g_return_val_if_fail(filename != NULL, NULL);
pix = gdk_pixbuf_new_from_file(filename, NULL);
if (!pix)
g_print(“Impossibile leggere il file di immagine %s\n”, filename);
return pix;
}
/* Se l’ icona passata a list_loader e’ nulla ( “” ) uso l’ icona settata come default */
const gchar *get_icon(void)
{ static gchar icon[60] ;
/* Icona di default */
sprintf(icon,”default.png”);
return icon;
}
void make_view(GtkWidget *vista,const gchar *pix)
{
if (*pix)
sprintf(icona,”%s”,pix);
else sprintf(icona,”%s”,get_icon());
sprintf(pixmap_file,”%s%s”,Icon_path,icona);
pixbuf = create_new_pixbuf(pixmap_file);
/* Impostazioni delle colonne della vista */
// Prima colonna:
renderer = gtk_cell_renderer_pixbuf_new();
column = gtk_tree_view_column_new_with_attributes(
“image”,renderer,
“pixbuf”,0,
NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(vista), column);
// Seconda colonna:
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(
“Titolo”,renderer,”text”,1,NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(vista), column);
lista = gtk_list_store_new(2,GDK_TYPE_PIXBUF,G_TYPE_STRING);
gtk_tree_view_set_model(GTK_TREE_VIEW(vista),GTK_TREE_MODEL (lista));
}
void load_list(GtkWidget *vista, gchar *elenco[])
{ gint i;
GtkTreePath *path = NULL;
gchar **p_elenc = NULL;
p_elenc = elenco;
enum {
COLONNA_PIX,
COLONNA_STRINGHE,
COLONNE
};
/* Puliamo il precedente loading */
gtk_list_store_clear(GTK_LIST_STORE(lista));
for (; *p_elenc ; p_elenc++)
{ gtk_list_store_append(lista, &iter);
gtk_list_store_set(lista, &iter,
COLONNA_PIX,pixbuf,
COLONNA_STRINGHE, *p_elenc,-1);
}
g_object_unref (lista);
/* Sposta il focus sulla seconda colonna “1” della prima riga “0” */
path = gtk_tree_path_new_from_indices(0, -1);
column = gtk_tree_view_get_column(GTK_TREE_VIEW(vista), 1);
gtk_tree_view_set_cursor(GTK_TREE_VIEW(vista), path, column, TRUE);
/* ================< Fine list_loader.c > ====================*/
Benche’ meno pratica, esiste una maniera alternativa per gestire la lista con Glade e la riporto per completezza di informazione.
Piuttosto che chiamare di volta in volta “make_view”, e’ possibile compilare la sezione che riguarda la vista direttamente nell’ interfaccia e ricavare il “Modello” cioe’ la lista dal treeview1 nel loader con un apposita funzione, vediamo come.
Abbiamo detto che Glade crea una nuova istanza del treeview con la funzione
gtk_treeview_new();
in realta’ esiste una funzione alternativa che la crea gia’ con il modello:
gtk_tree_view_new_with_model(GTK_TREE_MODEL(lista));
Concettualmente dobbiamo sostituire nel file che contiene l’ interfaccia del nostro progetto, la vista standard ( treeview1 nel nostro caso ) con una che contiene gia’ il modello, l’ idea puo’ sembrare alquanto bizzarra ma conoscendo i ferri del mestiere non lo e’ poi tanto.
Per prima cosa dobbiamo creare il file “modifica_alberi.c” fatto in questo modo:
GtkWidget *tree2c(GtkWidget *tree)
{ static GtkListStore *lista;
static GtkCellRenderer *renderer;
/* Qui’ ho creato il modello di lista che voglio visualizzare */
lista = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(lista));
renderer = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),
-1,
“Pix”,
renderer,
“pixbuf”,
0,
NULL);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),
-1,
“Testo”,
renderer,
“text”,
1,
NULL);
return tree;
Adesso devo sostituire in “interface.c” il gtk_tree_new(), con tre2c( … ) passando come argomento il treeview che voglio modificare, so cosa state pensando, devo editare a mano “interface.c” … no 🙂 basta usare sed e awk:
create un file di nome my_make.sh con questo codice:
sost() {
S1=”#include”
S2=’\”modifica_alberi.c\”‘
awk -v n=$S1 -v z=$S2 ‘/#include “support.h”/ {
print n,z
}
{ print }’ ./interface.c
}
if grep mdf_tree2.c interface.c >/dev/null
then echo -e “\033[33;1mskipped the replacement in the heading\033[0m\n”
else sost >interface.tmp
cp interface.tmp interface.c
rm interface.tmp
fi
sed s”/treeview1 = gtk_tree_view_new ();/treeview1 = tree2c(treeview1);/”g ./interface.c >interface.tmp
cp interface.tmp interface.c
rm interface.tmp
make
Da questo momento, ogni volta che modifico l’ interfaccia con glade eseguo my_make.sh e rimetto le cose a posto, in questo modo il treeview viene compilato con il modello, ma adesso come faccio a ricavare la lista per utilizzarla nel loader ?, esiste un tipo di dati di cui non abbiamo ancora parlato cioe’ :
GtkTreeModel che permette appunto di memorizzare il modello ricavato dalla vista:
GtkTreeModel *model;
model = gtk_tree_view_get_model(GTK_TREE_VIEW(vista));
La vista in questo caso e’ “treeview1” di consegueza nel list_loader sostituiremo la lista con model:
gtk_list_store_clear(GTK_LIST_STORE(model));
gtk_list_store_append(GTK_LIST_STORE(model), &iter);
gtk_list_store_set(GTK_LIST_STORE(model), &iter,
COLONNA_PIX,image,
COLONNA_STRINGHE, elemento, -1); // Ecco la nostra lista:
Tutto cio’ che concettualmente abbiamo visto per le liste, vale anche per gli alberi dove si usano i tipi e le funzioni omologhe, per esempio, invece del tipo GtkLIstStore useremo GtkTreeStore ed invece che gtk_list_store_new( … ), gtk_tree_store_new( … ); ma un esempio chiarira’ meglio il tutto.
Modifichiamo il codice del precedente list_loader in modo che diventi un tree_loader; si vuole caricare un albero a due colonne dove ogni nodo sia associato ad un icona:
Ed ecco come appare anche l’albero:
Infine un particolare tipo di vista è rappresentato da GtkIconView, questo infatti fornisce una “Vista” alternativa sul modello di lista.
GtkIconView, visualizza il modello come una griglia di icone con etichette nel modo in cui si conoscono già da un file manager, inoltre, permette di selezionare una o più voci (a seconda delle modalità di selezione, vedi set_selection_mode ()) sia con i tasti freccia, e sia con la selezione rubberband, cioè la selezione multipla che è controllata dal trascinamento del mouse sulle icone.
Nell’ esempio di seguito proposto, abbiamo una coppia “Testo/Icona” definito come visto in precedenza attraverso l’ istruzione:
gtk_list_store_new(COLONNE, G_TYPE_STRING,GDK_TYPE_PIXBUF);
che torna un puntatore a GtkListStore.
La notra “vista”, si chiama “iconview” e la coppia “Testo/Icona” viene definita attraverso le chiamate a :
gtk_icon_view_set_text_column (GTK_ICON_VIEW (iconview), COLONNA_STRINGHE);
gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (iconview), COLONNA_PIX);
inoltre è possibile settare la distanza fra le icone con:
gtk_icon_view_set_spacing(GTK_ICON_VIEW (iconview),19)
per comodità ho numerato il nome delle immagini che vengono poi caricate nel ciclo for della sezione load_list.
include <gtk/gtk.h>
#include “callbacks.h”
static GtkListStore *icon_list;
static GtkWidget *iconview;
static GtkTreeIter iter;
static GdkPixbuf *icon = NULL;
gchar *testo_icone[] = { “”, “Uno”,”Due”,”Tre”,”Quattro”,”Cinque”,”Sei”,’\0′ };
enum { COLONNA_STRINGHE,
COLONNA_PIX,
COLONNE
};
void make_icon_view(GtkWidget *container,const gchar *child)
{ iconview = lookup_widget(GTK_WIDGET(container), child);
// COLONNE equivale a 2
icon_list = gtk_list_store_new(COLONNE, G_TYPE_STRING,GDK_TYPE_PIXBUF);
// spazio fra le icone
gtk_icon_view_set_spacing(GTK_ICON_VIEW (iconview),19);
// 0 e’ la prima colonna di tipo testo, ospita il testo dell’ icona
gtk_icon_view_set_text_column (GTK_ICON_VIEW (iconview), COLONNA_STRINGHE);
// 1 e’ la seconda colonna di tipo immagine, ospita l’ icona
gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (iconview), COLONNA_PIX);
// modello di lista
gtk_icon_view_set_model(GTK_ICON_VIEW(iconview),GTK_TREE_MODEL (icon_list));
}
void load_icons(void)
{ gint i;
gchar buf[30];
/* Puliamo il precedente loading */
gtk_list_store_clear(GTK_LIST_STORE(icon_list));
for (i = 1; i < 7 ; i++)
{ sprintf(buf,”%s%d.png”,Icon_Path,i);
icon = gdk_pixbuf_new_from_file(buf, NULL);
gtk_list_store_append(icon_list, &iter);
gtk_list_store_set(icon_list, &iter,
0,testo_icone[i],
1,icon,-1);
}
g_object_unref (icon_list);
}
Ed ecco la nostra Iconview:
Infine vediamo come recuperare il contenuto di una riga o il numero di selezione nella lista, abbiamo parlato dei tipi “GtkTreeIter” e “GtkTreePath”, ecco come usarli per recuperare i dati che ci servono:
Scegliamo il segnale “cursor_changed” per la lista lineare in callbacks.c avremo:
void
on_tree_menu_cursor_changed (GtkTreeView *treeview,
gpointer user_data)
{ GtkTreeSelection *selection = NULL;
GtkWidget *view;
GtkTreeIter iter;
GtkTreeModel *model = NULL;
gchar *str;
gint num;
enum { COLONNA_PIX,
COLONNA_STRINGHE
};
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
if (gtk_tree_selection_get_selected(selection, &model, &iter))
{ //Numero di selezione
str = gtk_tree_model_get_string_from_iter(model,&iter);
num = atoi(str);
//Contenuto della selezione
//gtk_tree_model_get(model, &iter,COLONNA_STRINGHE,&str,-1);
g_print(“Hai selezionato %s\n”,str);
g_print (“Hai selezionato l’ intero %d\n”,num);
}
}
Per la iconview scegliamo il segnale “item_activated”:
void
on_iconview1_item_activated (GtkIconView *iconview,
GtkTreePath *path,
gpointer user_data)
{ GtkTreeIter iter;
GtkTreeModel *model;
gchar *text;
gint num;
model = gtk_icon_view_get_model (iconview);
gtk_tree_model_get_iter (model, &iter, path);
/* 0 e’ la colonna con il testo */
gtk_tree_model_get (model, &iter, 0, &text, -1);
// Se remmo questa riga, text contiene il testo della selezione
text = gtk_tree_model_get_string_from_iter(model,&iter);
// Numero della selezione
num = atoi(text);
g_print (“Item activated, num is %d\n”,num);
g_print (“Item activated, text is %s\n”, text);
g_free (text);
}
Scarica i programmi della guida:
List_test
Tree_test
Iconview