<- PW - La stabilità dei personal computer - Indice Generale - Copertina - SL - Intro ->

PlutoWare


Le liste in GTK+-2, seconda parte

L'articolo

Nello scorso numero abbiamo introdotto le nuove liste di Gtk+, focalizzando la nostra attenzione soprattutto ai concetti di vista e modello. In questo numero vedremo come sfruttare alcune delle potenzialità offerte dalle nuove liste.
Impareremo come inserire nelle liste immagini, check button e campi di testo editabili. In webografia, come sempre, troverete gli indirizzi della documentazione delle nuove API di GTK+.



Introduzione

    Come già detto nella presentazione, in questo numero completeremo la trattazione delle liste di Gtk+. Il programma di prova presentato non fa nulla, assolutamente nulla, se non mostrare le potenzialità del nuovo widget lista. Inseriremo dei check button, delle icone e degli entry di testo editabili in una lista. Per il programma di esempio, spero mi perdonerete, ho fatto un po' di auto celebrazione, citando in rigoroso ordine alfabetico ;) i membri della redazione del Pluto Journal. Per il buon funzionamento del programma occorre scaricare il seguente file star.png, contenente la piccola stella che potete ammirare in figura 1, o rinominare una qualsiasi immagine che potete trovare sul vostro hard disk.

Il programma di prova

#include <gtk/gtk.h>

typedef struct {
  gboolean edit;	
  gchar *pixmap_file;	
  gchar *redattore;
  gchar *rubrica;	
} RedazionePlutoJournal;

RedazionePlutoJournal redazione[] = {
  {TRUE, "star.png", "Giampaolo Podda",   "Coordinatore Pluto Journal"},
  {TRUE, "star.png", "Tommaso Di Donato", "Coordinatore Pluto Journal"},
  {TRUE, "star.png", "Beppe Lucente",     "Hardware in Liberta'"},
  {TRUE, "star.png", "Germano Rizzo",     "PlutoWare"}, 
  {TRUE, "star.png", "Marina Sturino",    "Agora'"},
  {TRUE, "star.png", "Nicola Fragale",    "Igloo"},
  {TRUE, "star.png", "Pietro Leone",      "Traduzioni"},
  {TRUE, "star.png", "Tommaso Di Donato", "Sistemi Aperti"},
  {TRUE, "star.png", "Il Journal vuole te!!", "Collabora con noi ;)"},
  {TRUE, "star.png", "Il Journal vuole te!!", "Collabora con noi ;)"},
  {FALSE, NULL, NULL, NULL}
};

enum {
  COLONNA_BOOLEAN,
  COLONNA_PIXMAP,
  COLONNA_REDATTORE,
  COLONNA_RUBRICA, 
  COLONNE
}; 


void cell_toggled_cb(GtkCellRendererToggle *cell,  
                     const gchar *path_string,
                     gpointer data)
{
  GtkTreeModel *model;
  GtkTreePath *path; 
  GtkTreeIter iter;
  gboolean bool;

  model = (GtkTreeModel *) data;
  path  = gtk_tree_path_new_from_string(path_string);	
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_tree_path_free(path);  
  
  gtk_tree_model_get(model, &iter, 
		                  COLONNA_BOOLEAN, &bool, -1);
  
  bool ^= 1;
  gtk_list_store_set(GTK_LIST_STORE(model), &iter, 
                     COLONNA_BOOLEAN, bool, -1);
}

void cell_edit_cb(GtkCellRendererText *cell, 
                  const gchar *path_string,
                  const gchar *new_text, 
                  gpointer data)
{
  GtkTreeModel *model; 
  GtkTreePath  *path; 
  GtkTreeIter iter;
  gint *column;
  gchar *old_text;

  model  = (GtkTreeModel *) data;
  path   = gtk_tree_path_new_from_string(path_string);
  column = g_object_get_data(G_OBJECT(cell), "column");

  gtk_tree_model_get_iter(model, &iter, path);

  gtk_tree_model_get(model, &iter, column, &old_text, -1);
  g_free (old_text);
  
  gtk_list_store_set(GTK_LIST_STORE(model), &iter, column, 
                     new_text, -1);
}


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", filename);
  
  return  pix;
}


GtkWidget*
create_window (void)
{
  GtkWidget *window;
  GtkWidget *vbox;
  GtkWidget *esci;
  GtkWidget *scrolledwindow;
  GtkListStore *model;              /* l'oggetto model    */
  
  GtkWidget *view;                  /* -\                 */ 
  GtkCellRenderer *renderer;        /* -/ l'oggetto view  */

  GtkTreeIter iter;  
  GdkPixbuf *image;
  RedazionePlutoJournal *r = redazione;

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Gtk+2.0 Usiamo le liste #2");

  vbox = gtk_vbox_new (FALSE, 12);
  gtk_container_add(GTK_CONTAINER(window), vbox);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
  gtk_widget_show (vbox);

  scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
  gtk_box_pack_start (GTK_BOX (vbox), 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);

  esci = gtk_button_new_with_label("Esci");
  gtk_box_pack_start (GTK_BOX (vbox), esci, FALSE, FALSE, 0);
  gtk_widget_show (esci);
  
  model = gtk_list_store_new(COLONNE, 
                             G_TYPE_INT,       
                             GDK_TYPE_PIXBUF,
                             G_TYPE_STRING,
                             G_TYPE_STRING);

  while (r->redattore)
    {
      image = create_new_pixbuf(r->pixmap_file);
      
      gtk_list_store_append(GTK_LIST_STORE(model), &iter);
      gtk_list_store_set(GTK_LIST_STORE(model), &iter, 
                         COLONNA_PIXMAP,    image,
                         COLONNA_BOOLEAN,   r->edit,
                         COLONNA_REDATTORE, r->redattore, 
                         COLONNA_RUBRICA,   r->rubrica,
                         -1);
      r++;
    }  
	
  view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(model));

  renderer = gtk_cell_renderer_toggle_new();
  g_signal_connect(G_OBJECT(renderer), "toggled", 
                   G_CALLBACK(cell_toggled_cb), model);
  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 
                                              -1,          // posiziona alla fine
                                              "Editabile", // titolo
                                              renderer,    // la cella        
                                              "active",    // attributo della cella
                                              COLONNA_BOOLEAN, // colonna   
                                              NULL);       // fine attributi/colonna
		       
  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 
                                             -1,     // posiziona alla fine             
                                              NULL,  // questa colonna non ha titolo
                                              renderer,  // la cella          
                                              "pixbuf",  // attributo della cella
                                              COLONNA_PIXMAP, // colonna   
                                              NULL);    // fine attributi/colonna
  
  renderer = gtk_cell_renderer_text_new();
  g_object_set_data(G_OBJECT(renderer), "column", (gint *) COLONNA_REDATTORE);
  g_signal_connect(G_OBJECT(renderer), "edited", 
                   G_CALLBACK(cell_edit_cb), model); 

  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, 
                                              "Redattore", 
                                              renderer, 
                                              "text", COLONNA_REDATTORE, 
                                              "editable", COLONNA_BOOLEAN,
                                              NULL);        
		   
  renderer = gtk_cell_renderer_text_new();
  g_object_set_data(G_OBJECT(renderer), "column", (gint *) COLONNA_RUBRICA);
  g_signal_connect(G_OBJECT(renderer), "edited", 
                   G_CALLBACK(cell_edit_cb), model);
  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 
                                              -1, 
                                              "Rubrica", 
                                              renderer, 
                                              "text", COLONNA_RUBRICA, 
                                              "editable", COLONNA_BOOLEAN,
                                              NULL);        
		   
  gtk_widget_show (view);
  g_object_unref(model);

  gtk_container_add (GTK_CONTAINER (scrolledwindow), view);
  gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);

  g_signal_connect(G_OBJECT (esci), "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;
}
		  		       	
		

Compiliamolo

Per compilare il programma, salvate l'esempio nel file lista.c e date il comando:


gcc -Wall lista.c -o lista `pkg-config --libs --cflags gtk+-2.0`
		

Mi raccomando, fate attenzione: gli apici inversi si ottengono dalla pressione del tasto ALT Gr e del tasto apice '. In figura 1 potete osservare il risultato finale.



figura 1 Le nuove liste di GTK+

Analizziamo il programma

In tabella sono elencate le funzioni che analizzeremo in questo articolo. Sono poche, ma molto potenti.


GtkCellRenderer* gtk_cell_renderer_pixbuf_new (void);		
GtkCellRenderer* gtk_cell_renderer_toggle_new (void);
GtkCellRenderer* gtk_cell_renderer_text_new   (void);

void user_function (GtkCellRendererToggle *cellrenderertoggle,
                    gchar *arg1,
                    gpointer user_data);
void user_function (GtkCellRendererText *cellrenderertext,
                    gchar *arg1,
                    gchar *arg2,
                    gpointer user_data);
		

Dal main richiamiamo la routine principale che si occuperà della creazione della nostra interfaccia grafica, che è formata, molto semplicemente, da una scrolledwindow all'interno della quale inseriremo la nostra lista, e da un bottone per chiudere l'applicazione.


typedef struct {
  gboolean edit;	
  gchar *pixmap_file;	
  gchar *redattore;
  gchar *rubrica;	
} RedazionePlutoJournal;

RedazionePlutoJournal redazione[] = {
  {TRUE, "star.png", "Giampaolo Podda",   "Coordinatore Pluto Journal"},	
  ...
  {FALSE, NULL, NULL, NULL}
};

enum {
  COLONNA_BOOLEAN,
  COLONNA_PIXMAP,
  COLONNA_REDATTORE,
  COLONNA_RUBRICA,
  COLONNE
};  	
		

La redazione del Journal è contenuta nel vettore redazione, ed ogni elemento del vettore ha 4 campi: un campo booleano, edit, che utilizzeremo per permettere od impedire che i campi contenenti il nome del responsabile di rubrica, redattore, ed il nome della rubrica, rubrica, possano essere modificati. L'ultimo campo pixmap_file conterrà il nome del file dell'immagine che inseriremo. Abbiamo quindi bisogno di quattro colonne, che enumereremo per facilitarci in seguito la vita (vedi l'articolo precedente http://www.pluto.linux.it/journal/pj0301/gtk+2-1.html).

Passiamo ora alla creazione del modello. Seguendo l'ordine stabilito nella struttura RedazionePlutoJournal, il modello definirà quattro tipi diversi di colonna: la prima conterrà valori di tipo intero G_TYPE_INT (il booleano), la seconda conterrà l'immagine GDK_TYPE_PIXBUF, la terza e la quarta conterranno delle stringhe G_TYPE_STRING. A questo punto non ci resta che popolare il modello con i dati contenuti nella struttura, utilizzando un ciclo while


  model = gtk_list_store_new(COLONNE, 
                             G_TYPE_INT,       
                             GDK_TYPE_PIXBUF,
                             G_TYPE_STRING,
                             G_TYPE_STRING);

  while (r->redattore)
    {
      image = create_new_pixbuf(r->pixmap_file);
      
      gtk_list_store_append(GTK_LIST_STORE(model), &iter);
      gtk_list_store_set(GTK_LIST_STORE(model), &iter, 
                         COLONNA_PIXMAP,    image,
                         COLONNA_BOOLEAN,   r->edit,
                         COLONNA_REDATTORE, r->redattore, 
                         COLONNA_RUBRICA,   r->rubrica,
                         -1);
      r++;
    }  
		

Ora che il modello è pronto, creiamo la vista.

Check button, scegliamo cosa fare


  renderer = gtk_cell_renderer_toggle_new();
  g_signal_connect(G_OBJECT(renderer), "toggled", 
                   G_CALLBACK(cell_toggled_cb), model);
  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 
                                        -1,          // posiziona alla fine
                                        "Editabile", // titolo
                                        renderer,    // la cella        
                                        "active",    // attributo della cella
                                        COLONNA_BOOLEAN, // colonna   
                                        NULL);       // fine attributi/colonna
			

La prima colonna deve contenere un check button, per permetterci di impostare un valore booleano (TRUE/FALSE). Richiameremo la funzione gtk_cell_renderer_toggle_new(), che ci ritornerà un puntatore al widget GtkCellRenderer. Quindi collegheremo l'oggetto rendered (il nostro check button) alla funzione di callback cell_toggled_cb, ed infine inseriremo la colonna nella vista.

Alla funzione gtk_tree_view_insert_column_with_attributes(), bisogna passare il puntatore alla vista, la posizione che la colonna occuperà nella vista (usiamo -1 per indicare di inserire la colonna in coda a tutte le altre), il titolo della colonna (o NULL se non vogliamo titoli) e, per finire, dobbiamo passare un elenco di coppie attributo/colonna. Tale elenco dev'essere terminato da NULL. Nel nostro caso, a noi interessa che gli elementi della colonna siano dei check button, degli elementi "attivi"; imposteremo quindi la stringa "active" come attributo e COLONNA_BOOLEAN come colonna.

Abbelliamo con le immagini


  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 
                                       -1,     // posiziona alla fine             
                                       NULL,   // questa colonna non ha titolo
                                       renderer,  // la cella          
                                       "pixbuf",  // attributo della cella
                                       COLONNA_PIXMAP, // colonna   
                                       NULL);    // fine attributi/colonna
		

È il momento della colonna delle immagini. Come sempre iniziamo creando il widget che sarà contenuto nella colonna, richiamando la funzione gtk_cell_renderer_pixbuf_new(), e, come nel caso precedente, creiamo la colonna. Le differenze rispetto all'esempio precedente sono relative alla coppia attributo/colonna. L'attributo della colonna, in questo caso è "pixbuf", mentre per colonna dobbiamo fornire quello che abbiamo stabilito essere il "numero" della colonna delle immagini, cioè COLONNA_PIXMAP.

L'immagine è creata dalla subroutine create_new_pixbuf(), alla quale forniamo il nome del file contenente l'immagine, la quale ci ritornerà un puntatore a GdkPixbuf se tutto è andato bene, NULL in caso contrario.

Editiamo il testo


  renderer = gtk_cell_renderer_text_new();
  g_object_set_data(G_OBJECT(renderer), "column", (gint *) COLONNA_REDATTORE);
  g_signal_connect(G_OBJECT(renderer), "edited", 
                   G_CALLBACK(cell_edit_cb), model); 

  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, 
                                        "Redattore", 
                                        renderer, 
                                        "text", COLONNA_REDATTORE, 
                                        "editable", COLONNA_BOOLEAN,
                                        NULL);        
		

Come nei casi precedenti, creiamo il widget che sarà contenuto nella colonna, (una casella di testo per le ultime due colonne), richiamando la funzione gtk_cell_renderer_text_new(), ed inseriamo le colonne in coda alle altre. In questo caso le coppie attributo/colonna del widget renderer sono due: con la prima coppia impostiamo l'attributo "text" per la colonna COLONNA_REDATTORE, con la seconda coppia impostiamo l'attributo "editable" e lo riferiamo alla colonna COLONNA_BOOLEAN.

Perché ho effettuato questa scelta? Il widget GtkCellRendererText ha molte proprietà; tra queste è presente "editable", che è di tipo booleano. In altre parole, la cella può essere o meno editabile a seconda del valore TRUE/FALSE assunto da questo attributo. Noi recupereremo questo valore dalla colonna dei check button. Se un check button è impostato, allora le caselle di testo presenti su quella riga saranno editabili.

Le funzioni di callback

La costruzione della lista è terminata, non ci resta che collegare i widget dei check button e delle caselle di testo alle opportune funzioni di callback. In tabella sono riportati i prototipi delle callback relative a questi widget.


void cell_toggled_cb(GtkCellRendererToggle *cell, 
                     const gchar *path_string,
                     gpointer data)
{	
...

void cell_edit_cb(GtkCellRendererText *cell, 
                  const gchar *path_string,
                  const gchar *new_text, 
                  gpointer data)
{
...
		

Il funzionamento di queste due fuzioni è veramente molto semplice. La prima, che è richiamata ogni qualvolta clicchiamo su di un check button, si occupa di recuperare lo stato del bottone, calcolare il suo nuovo valore e di salvarlo.

La seconda funzione di callback ha il compito di salvare il nuovo testo inserito nella casella. Preleva il vecchio testo, libera la memoria da esso occupata, ed inserisce quello da noi digitato. La sola particolarità è relativa al fatto che, avendo utilizzato una sola funzione di callback per due caselle di testo (la colonna dei redattori e la colonna delle rubriche), è necessario distinguere su quale renderer (quale delle due colonne) operare. Otteniamo questo risultato associando (g_object_set_data();) ad ogni renderer la propria colonna, che sarà poi recuperata (g_object_get_data();) nella callback.

Bene, anche questa volta siamo giunti alla fine, non mi resta che darvi appuntamento per la prossima puntata...

Webografia

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'autore

Nicola Fragale. È iscritto ad Ingegneria Informatica alla 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 - La stabilità dei personal computer - Indice Generale - Copertina - SL - Intro ->