<- PW: Intro - Copertina - PW: Introduzione al Pascal ->

PlutoWare


GTK+: Alle radici dello GNOME

Quarta puntata: "Fiat GUI", et GUI fuit

di Nicola Fragale


L'articolo

Siamo giunti alla fine di questa serie di articoli che ci ha introdotti nel magico mondo di GTK+. Questo mese vedremo come si utilizzano i notebook, i widget entry, gli spin button e le progress bar. Saranno accennati, molto velocemente, alcuni widget come le clist (columned list) e text, che dalla prossima versine delle librerie GTK+ subiranno profonde modifiche e migliorie. In attesa delle nuove fiammanti GTK+2.0, nei prossimi mesi, faremo conoscenza con il modo di Gnome. Intanto anche questo mese, come consuetudine, esamineremo un programma di prova che ci faciliterà la comprensione delle funzioni di libreria utilizzate.


Indice


Primo esempio

Terminiamo, con questa puntata, la presentazione dei principali widget messici a disposizione da GTK+. Con gli elementi che conosciamo dovremmo essere in grado di realizzare piccole applicazioni grafiche. Si, lo so, ;-) non ho dimenticato menu e toolbar, ma li affronteremo con Gnome, che permette di trattarli in modo più semplice e uniforme rispetto a GTK+.

Ricordo i link alle API Reference di GTK+ 1.2:
GTK+ (http://developer.gnome.org/doc/API/gtk-docs.tar.gz) , GLib (http://developer.gnome.org/doc/API/glib-docs.tar.gz) e GDK (http://developer.gnome.org/doc/API/gdk-docs.tar.gz)

#include <stdio.h>
#include <gtk/gtk.h>

GtkWidget *text;

/*	funzioni ausiliarie richiamate
	da on_load_clicked e on_save_clicked
*/
void carica_file (GtkFileSelection *fs, gpointer user_data)
{
  gchar *filename;
  FILE *fp;

  /*    recuperiamo il nome del file dal widget
        file selection
  */
  filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION(user_data));

  fp = fopen(filename, "r");

  while (!feof(fp))
    {
      gchar buffer[1024];

      fgets(buffer, 1024, fp);

      /*    visualizziamo il contenuto del buffer
      */
      gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, buffer, -1);
    }

  fclose(fp);
}

void salva_file (GtkFileSelection *fs, gpointer user_data)
{
  gchar *filename;
  gchar *buffer;
  FILE *fp;

  filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION(user_data));

  fp = fopen(filename, "w");

  /*   recuperiamo tutto (0, -1) il testo
       contenuto del widget text
  */
  buffer = gtk_editable_get_chars(GTK_EDITABLE(text), 0, -1);

  fprintf(fp, "%s", buffer);
  fflush(fp);
  fclose(fp);
}


/*	callback richiamata alla pressione del tasto
	invio nel widget entry
*/
void
on_entry_activate (GtkEditable *editable, gpointer user_data)
{
  gchar *tmp;
  gchar *buffer[3];

  tmp = gtk_entry_get_text(GTK_ENTRY(editable));

  buffer[0] = g_strdup(tmp);

  /*  g_strreverse è una funzione contenuta nella
      libreria glib, il suo scopo è quello di
      capovolgere la stringa passatagli.
      es.
      char *buffer="pippo";
      g_strreverse(buffer);

      ora buffer conterrà la stringa oppip
  */
  g_strreverse(tmp);

  buffer[1] = g_strdup(tmp);
  buffer[2] = NULL;

  /*  aggiungiamo la nuova riga alla lista
  */
  gtk_clist_append(GTK_CLIST(user_data), buffer);

  /*  puliamo il widget entry
  */
  gtk_entry_set_text(GTK_ENTRY(editable), "");
}


/*	callback richiamata quando si seleziona un
	elemento contenuto nella clist
*/
void
on_clist_select_row (GtkCList *clist,
		     gint row,
		     gint column,
		     GdkEvent *event,
		     gpointer user_data)
{
  gchar *buffer;

  /*   recuperiamo la stringa contenuta
       alla riga "row" e colonna "column"
       il risultato è in buffer
  */
  gtk_clist_get_text(GTK_CLIST(clist), row, column, &buffer);

  gtk_label_set_text(GTK_LABEL(user_data), buffer);
}


/*	callback richiamata quando viene modificato
	lo stato dello spin button
*/
void
on_spinbutton_changed (GtkWidget *spin, gpointer user_data)
{
  gint val;

  /*  recuperiamo il valore dello spin button
  */
  val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));

  /*  e modifichiamo la progress bar di conseguenza
  */
  gtk_progress_set_value(GTK_PROGRESS(user_data), (gfloat) val);
}


/*  le callback relative al notebook
 */
void
on_next_page_clicked (GtkButton  *button, gpointer user_data)
{
  /*  possiamo spostarci sulla prossima
      pagina del notebook
  */
  gtk_notebook_next_page(GTK_NOTEBOOK(user_data));
}


void
on_prev_page_clicked (GtkButton  *button, gpointer user_data)
{
  /*  oppure sulla pagina precedente
  */
  gtk_notebook_prev_page(GTK_NOTEBOOK(user_data));
}


void
on_del_page_clicked (GtkButton *button, gpointer user_data)
{
  gint page;

  /*  prendiamo il numero della pagina corrente
  */
  page = gtk_notebook_get_current_page(GTK_NOTEBOOK(user_data));

  /* ed eliminiamola
  */
  gtk_notebook_remove_page(GTK_NOTEBOOK(user_data), page);
}


void
on_add_page_clicked (GtkButton *button, gpointer user_data)
{
  GtkWidget *label;
  GtkWidget *tab;

  /*  creiamo un figlio al notebook
  */
  label = gtk_label_new("Pagina aggiunta dinamicamente");
  gtk_widget_show(label);
  tab = gtk_label_new("tab");

  /*  inseriamolo nella nuova pagina
  */
  gtk_notebook_append_page(GTK_NOTEBOOK(user_data), label, tab);
}


/*  le callback richiamate alla pressione
    dei bottoni load e save
*/
void
on_load_clicked (GtkButton  *button, gpointer user_data)
{
  GtkWidget *fs;

  /*  creiamo un file selection
  */
  fs = gtk_file_selection_new("Gtk+ -- Apri un file --");

  gtk_widget_show(fs);

  /*  colleghiamo i bottoni ok e cancel alle relative
      funzioni di callback
  */
  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                              "clicked", GTK_SIGNAL_FUNC(carica_file),
                              (gpointer) fs);
  gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                                        "clicked",
                                        GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                        (gpointer) fs);

  gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->cancel_button),
                                        "clicked",
                                        GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                        (gpointer) fs);
}


void
on_save_clicked (GtkButton  *button, gpointer user_data)
{
  GtkWidget *fs;

  fs = gtk_file_selection_new("Gtk+ -- Salva un file --");

  gtk_widget_show(fs);

  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                              "clicked", GTK_SIGNAL_FUNC(salva_file),
                              (gpointer) fs);
  gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                                        "clicked",
                                        GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                        (gpointer) fs);

  gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->cancel_button),
                                        "clicked",
                                        GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                        (gpointer) fs);
}


/* le callback relative ai radio button
*/
void
on_radio_sd_toggled (GtkToggleButton *togglebutton, gpointer user_data)
{
 gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR(user_data),
				   GTK_PROGRESS_LEFT_TO_RIGHT);
}


void
on_radio_ds_toggled (GtkToggleButton *togglebutton, gpointer user_data)
{
 gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR(user_data),
				   GTK_PROGRESS_RIGHT_TO_LEFT);
}


void
on_radio_cont_toggled (GtkToggleButton *togglebutton, gpointer user_data)
{
 gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(user_data),
 	GTK_PROGRESS_CONTINUOUS);
}


void
on_radio_disc_toggled (GtkToggleButton *togglebutton, gpointer user_data)
{
  gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(user_data),
  	GTK_PROGRESS_DISCRETE);
}


/*	creazione dell'interfaccia grafica
*/
GtkWidget*
create_window (void)
{
  GtkWidget *window;

  GtkWidget *notebook;

  GtkWidget *clist;
  GtkWidget *entry;

  GtkWidget *scrolledwindow1;
  GtkWidget *scrolledwindow2;

  GtkObject *spinbutton_adj;
  GtkWidget *spinbutton;

  GtkWidget *progressbar;

  GSList *orientamento_group = NULL;
  GSList *stile_group = NULL;
  GtkWidget *radio_sd;
  GtkWidget *radio_ds;
  GtkWidget *radio_cont;
  GtkWidget *radio_disc;

  GtkWidget *clist_string;

  GtkWidget *vbox1;
  GtkWidget *vbox2;
  GtkWidget *vbox3;
  GtkWidget *vbox4;

  GtkWidget *hbox;
  GtkWidget *button_box;
  GtkWidget *hbox1;
  GtkWidget *hbox2;
  GtkWidget *hbox3;
  GtkWidget *hbox4;

  GtkWidget *table;

  GtkWidget *label1;
  GtkWidget *label2;
  GtkWidget *label3;
  GtkWidget *label4;
  GtkWidget *label5;
  GtkWidget *label6;
  GtkWidget *label7;
  GtkWidget *label8;

  GtkWidget *tab1;
  GtkWidget *tab2;
  GtkWidget *tab3;

  /* widget bottoni */
  GtkWidget *next_page;
  GtkWidget *prev_page;
  GtkWidget *del_page;
  GtkWidget *add_page;
  GtkWidget *load;
  GtkWidget *save;
  GtkWidget *esci;

  GtkWidget *hseparator;
  GtkWidget *hbuttonbox1;


  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_object_set_data (GTK_OBJECT (window), "window", window);
  gtk_window_set_title (GTK_WINDOW (window), "GTK+ 4°");

  hbox = gtk_hbox_new (FALSE, 5);
  gtk_widget_show (hbox);
  gtk_container_add (GTK_CONTAINER (window), hbox);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 4);

  button_box = gtk_vbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX (hbox), button_box, FALSE, FALSE, 0);
  gtk_widget_show (button_box);

  prev_page = gtk_button_new_with_label ("Pagina precedente");
  gtk_box_pack_start (GTK_BOX (button_box), prev_page, FALSE, FALSE, 0);
  gtk_widget_show (prev_page);

  next_page = gtk_button_new_with_label ("Pagina successiva");
  gtk_box_pack_start (GTK_BOX (button_box), next_page, FALSE, FALSE, 0);
  gtk_widget_show (next_page);

  del_page = gtk_button_new_with_label ("Elimina una pagina");
  gtk_box_pack_start (GTK_BOX (button_box), del_page, FALSE, FALSE, 0);
  gtk_widget_show (del_page);

  add_page = gtk_button_new_with_label ("Aggiungi una pagina");
  gtk_box_pack_start (GTK_BOX (button_box), add_page, FALSE, FALSE, 0);
  gtk_widget_show (add_page);

  vbox1 = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), vbox1, FALSE, FALSE, 0);
  gtk_widget_show (vbox1);

  /*    costruiamo il notebook
   */
  notebook = gtk_notebook_new ();
  gtk_widget_show (notebook);
  gtk_box_pack_start (GTK_BOX (vbox1), notebook, TRUE, TRUE, 0);

  /*    realiziamo uno dei "figli" del notebook. Questo figlio conterrà:
  	- una columned list (inserita in una scrolled window)
  	- un widget entry
  	- alcune etichette
  */
  vbox2 = gtk_vbox_new (FALSE, 0);
  gtk_widget_show (vbox2);

  scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (scrolledwindow1);
  gtk_box_pack_start (GTK_BOX (vbox2), scrolledwindow1, TRUE, TRUE, 0);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  clist = gtk_clist_new (2);
  gtk_widget_show (clist);
  gtk_container_add (GTK_CONTAINER (scrolledwindow1), clist);
  gtk_clist_set_column_width (GTK_CLIST (clist), 0, 189);
  gtk_clist_set_column_width (GTK_CLIST (clist), 1, 80);
  gtk_clist_column_titles_show (GTK_CLIST (clist));

  label1 = gtk_label_new ("stringa1");
  gtk_widget_show (label1);
  gtk_clist_set_column_widget (GTK_CLIST (clist), 0, label1);

  label2 = gtk_label_new ("stringa2");
  gtk_widget_show (label2);
  gtk_clist_set_column_widget (GTK_CLIST (clist), 1, label2);

  hbox1 = gtk_hbox_new (FALSE, 10);
  gtk_widget_show (hbox1);
  gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox1), 5);

  label3 = gtk_label_new ("Inserisci una stringa");
  gtk_widget_show (label3);
  gtk_box_pack_start (GTK_BOX (hbox1), label3, FALSE, FALSE, 0);

  entry = gtk_entry_new ();
  gtk_widget_show (entry);
  gtk_box_pack_start (GTK_BOX (hbox1), entry, TRUE, TRUE, 0);

  hbox2 = gtk_hbox_new (FALSE, 10);
  gtk_widget_show (hbox2);
  gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox2), 5);

  label4 = gtk_label_new ("Stringa selezionata:");
  gtk_widget_show (label4);
  gtk_box_pack_start (GTK_BOX (hbox2), label4, FALSE, FALSE, 0);

  clist_string = gtk_label_new ("");
  gtk_widget_show (clist_string);
  gtk_box_pack_start (GTK_BOX (hbox2), clist_string, TRUE, TRUE, 0);

  /*    terminata la costruzione del figlio del
	notebook, inseriamolo nella pagina
  */
  tab1 = gtk_label_new ("Pagina 1");
  gtk_widget_show (tab1);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox2, tab1);

  /*     altro figlio
   */
  vbox3 = gtk_vbox_new (FALSE, 0);
  gtk_widget_show (vbox3);
  gtk_container_add (GTK_CONTAINER (notebook), vbox3);
  gtk_container_set_border_width (GTK_CONTAINER (vbox3), 5);

  hbox3 = gtk_hbox_new (FALSE, 5);
  gtk_widget_show (hbox3);
  gtk_box_pack_start (GTK_BOX (vbox3), hbox3, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox3), 5);

  label5 = gtk_label_new ("Seleziona un numero da 1 a 100");
  gtk_widget_show (label5);
  gtk_box_pack_start (GTK_BOX (hbox3), label5, FALSE, FALSE, 0);

  spinbutton_adj = gtk_adjustment_new (0, 0, 100, 1, 10, 10);
  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton_adj), 1, 0);
  gtk_widget_show (spinbutton);
  gtk_box_pack_start (GTK_BOX (hbox3), spinbutton, FALSE, FALSE, 0);

  hbox4 = gtk_hbox_new (FALSE, 5);
  gtk_widget_show (hbox4);
  gtk_box_pack_start (GTK_BOX (vbox3), hbox4, TRUE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox4), 5);

  label6 = gtk_label_new ("Il numero da te inserito");
  gtk_widget_show (label6);
  gtk_box_pack_start (GTK_BOX (hbox4), label6, FALSE, FALSE, 0);

  progressbar = gtk_progress_bar_new ();
  gtk_widget_show (progressbar);
  gtk_box_pack_start (GTK_BOX (hbox4), progressbar, TRUE, TRUE, 0);
  gtk_progress_set_show_text (GTK_PROGRESS (progressbar), TRUE);

  table = gtk_table_new (2, 3, TRUE);
  gtk_widget_show (table);
  gtk_box_pack_start (GTK_BOX (vbox3), table, TRUE, FALSE, 0);

  radio_sd = gtk_radio_button_new_with_label (orientamento_group,
					      "da sinistra a destra");
  orientamento_group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio_sd));
  gtk_widget_show (radio_sd);
  gtk_table_attach (GTK_TABLE (table), radio_sd, 1, 2, 0, 1,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);

  radio_ds = gtk_radio_button_new_with_label (orientamento_group,
					      "da destra a sinistra");
  orientamento_group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio_ds));
  gtk_widget_show (radio_ds);
  gtk_table_attach (GTK_TABLE (table), radio_ds, 2, 3, 0, 1,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);

  radio_cont = gtk_radio_button_new_with_label (stile_group, "continuo");
  stile_group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio_cont));
  gtk_widget_show (radio_cont);
  gtk_table_attach (GTK_TABLE (table), radio_cont, 1, 2, 1, 2,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);

  radio_disc = gtk_radio_button_new_with_label (stile_group, "discreto");
  stile_group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio_disc));
  gtk_widget_show (radio_disc);
  gtk_table_attach (GTK_TABLE (table), radio_disc, 2, 3, 1, 2,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);

  label7 = gtk_label_new ("Orientamento: ");
  gtk_widget_show (label7);
  gtk_table_attach (GTK_TABLE (table), label7, 0, 1, 0, 1,
                    (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);
  gtk_misc_set_alignment (GTK_MISC (label7), 1, 0.5);

  label8 = gtk_label_new ("Stile:");
  gtk_widget_show (label8);
  gtk_table_attach (GTK_TABLE (table), label8, 0, 1, 1, 2,
                    (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);
  gtk_misc_set_alignment (GTK_MISC (label8), 1, 0.5);


  tab2 = gtk_label_new ("Pagina 2");
  gtk_widget_show (tab2);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox3, tab2);

  /*    l'ultimo figlio
  */
  vbox4 = gtk_vbox_new (FALSE, 5);
  gtk_widget_show (vbox4);
  gtk_container_add (GTK_CONTAINER (notebook), vbox4);
  gtk_container_set_border_width (GTK_CONTAINER (vbox4), 5);

  scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (scrolledwindow2);
  gtk_box_pack_start (GTK_BOX (vbox4), scrolledwindow2, TRUE, TRUE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow2), 3);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  text = gtk_text_new (NULL, NULL);
  gtk_text_set_editable(GTK_TEXT(text), TRUE);
  gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
  	"Trivial editor ;-)...\nCopyLeft 2002 Nicola Fragale\n", -1);
  gtk_widget_show (text);
  gtk_container_add (GTK_CONTAINER (scrolledwindow2), text);

  hseparator = gtk_hseparator_new ();
  gtk_widget_show (hseparator);
  gtk_box_pack_start (GTK_BOX (vbox4), hseparator, FALSE, TRUE, 0);

  hbuttonbox1 = gtk_hbutton_box_new ();
  gtk_widget_show (hbuttonbox1);
  gtk_box_pack_start (GTK_BOX (vbox4), hbuttonbox1, FALSE, TRUE, 0);
  gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox1),
  	GTK_BUTTONBOX_SPREAD);

  load = gtk_button_new_with_label ("Carica");
  gtk_widget_show (load);
  gtk_container_add (GTK_CONTAINER (hbuttonbox1), load);
  GTK_WIDGET_SET_FLAGS (load, GTK_CAN_DEFAULT);

  save = gtk_button_new_with_label ("Salva");
  gtk_widget_show (save);
  gtk_container_add (GTK_CONTAINER (hbuttonbox1), save);
  GTK_WIDGET_SET_FLAGS (save, GTK_CAN_DEFAULT);

  tab3 = gtk_label_new ("Pagina 3");
  gtk_widget_show (tab3);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox4, tab3);


  esci = gtk_button_new_with_label ("Esci");
  gtk_widget_show (esci);
  gtk_box_pack_start (GTK_BOX (vbox1), esci, FALSE, FALSE, 0);


  /* quando si seleziona un elemento della lista, viene
     emesso il segnale select_row, che è gestito dalla
     nostra funzione di callback on_clist_select_row
  */
  gtk_signal_connect (GTK_OBJECT (clist), "select_row",
		      GTK_SIGNAL_FUNC (on_clist_select_row),
		      clist_string);

  /* collegamento alla callback richiamata alla
     pressione del tasto invio nel widget entry
  */
  gtk_signal_connect (GTK_OBJECT (entry), "activate",
                      GTK_SIGNAL_FUNC (on_entry_activate),
                      clist);

  /* collegamento alla callback richiamata alla
     pressione del bottone esci
  */
  gtk_signal_connect (GTK_OBJECT (esci), "clicked",
                      GTK_SIGNAL_FUNC (gtk_main_quit),
                      NULL);

  /* Le callback relative al notebook
   */
  gtk_signal_connect (GTK_OBJECT (next_page), "clicked",
                      GTK_SIGNAL_FUNC (on_next_page_clicked),
                      notebook);
  gtk_signal_connect (GTK_OBJECT (prev_page), "clicked",
                      GTK_SIGNAL_FUNC (on_prev_page_clicked),
                      notebook);
  gtk_signal_connect (GTK_OBJECT (del_page), "clicked",
                      GTK_SIGNAL_FUNC (on_del_page_clicked),
                      notebook);
  gtk_signal_connect (GTK_OBJECT (add_page), "clicked",
                      GTK_SIGNAL_FUNC (on_add_page_clicked),
                      notebook);

  /* le callback relative ai bottoni
     load e save
  */
  gtk_signal_connect (GTK_OBJECT (load), "clicked",
                      GTK_SIGNAL_FUNC (on_load_clicked),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (save), "clicked",
                      GTK_SIGNAL_FUNC (on_save_clicked),
                      NULL);

  /* le callback per gli spin button
   */
  gtk_signal_connect (GTK_OBJECT (spinbutton), "activate",
                      GTK_SIGNAL_FUNC (on_spinbutton_changed),
                      progressbar);
  gtk_signal_connect (GTK_OBJECT (spinbutton), "changed",
                      GTK_SIGNAL_FUNC (on_spinbutton_changed),
                      progressbar);

  /* e quelle per i radio button
   */
  gtk_signal_connect (GTK_OBJECT (radio_sd), "toggled",
                      GTK_SIGNAL_FUNC (on_radio_sd_toggled),
		      progressbar);
  gtk_signal_connect (GTK_OBJECT (radio_ds), "toggled",
                      GTK_SIGNAL_FUNC (on_radio_ds_toggled),
		      progressbar);
  gtk_signal_connect (GTK_OBJECT (radio_cont), "toggled",
                      GTK_SIGNAL_FUNC (on_radio_cont_toggled),
		      progressbar);
  gtk_signal_connect (GTK_OBJECT (radio_disc), "toggled",
                      GTK_SIGNAL_FUNC (on_radio_disc_toggled),
		      progressbar);

  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 nel file gui.c e compilate utilizzando il comando:

   gcc -Wall -g gui.c -o gui `gtk-config --libs --cflags`

Il widget Notebook

Il notebook appartiene alla famiglia dei widget contenitori; il suo scopo principale è, infatti, quello di contenere altri widget, tuttavia si differenzia dagli altri per il modo in cui assolve al suo compito. Il notebook si presenta come un insieme di pagine sovrapposte, ognuna delle quali è dotata di una linguetta, generalmente posta nella parte superiore della pagina (figura 1). La navigazione tra le pagine avviene cliccando sulle opportune linguette.


figura 1 Notebook e widget contenuti

Per creare un nuovo notebook, è sufficiente chiamare la funzione gtk_notebook_new();, che ritornerà il puntatore al nuovo oggetto.


    notebook = gtk_notebook_new ();

Come detto il notebook appartiene alla famiglia dei widget contenitori. Osserviamo i prototipi delle seguenti funzioni:


    void gtk_notebook_append_page (GtkNotebook *notebook,
                                     GtkWidget *child,
                                     GtkWidget *tab_label);

    void gtk_notebook_prepend_page (GtkNotebook *notebook,
                                      GtkWidget *child,
                                      GtkWidget *tab_label);

    void gtk_notebook_insert_page (GtkNotebook *notebook,
                                     GtkWidget *child,
                                     GtkWidget *tab_label,
                                     gint position);

Tutte richiedono come primo argomento il puntatore al notebook, e come secondo un puntatore ad un widget child (figlio). Il figlio in questione è il widget o l'insieme di widget che vogliamo inserire nella pagina del notebook, pagina che avrà come etichetta quella definita con il widget tab_label. La prima di queste funzioni "appende" la pagina creata al notebook; la inserisce, cioè, alla fine, dopo tutte quelle già presenti. La seconda funzione, al contrario, inserisce la pagina all'inizio del notebook, prima di tutte; la terza infine inserisce la pagina di notebook in una generica posizione identificata dall'intero position.

Osserviamo il codice del programma:

	vbox2 = gtk_vbox_new (FALSE, 0);
	gtk_widget_show (vbox2);

	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
	gtk_widget_show (scrolledwindow1);
	gtk_box_pack_start (GTK_BOX (vbox2), scrolledwindow1, TRUE, TRUE, 0);

	....

	tab1 = gtk_label_new ("Pagina 1");
	gtk_widget_show (tab1);
	gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox2, tab1);

vbox2 è una scatola verticale che conterrà i widget che vogliamo mostrare, e che interpreterà, contemporaneamente, la parte di figlio per il notebook. La pagina avrà come etichetta la stringa "Pagina 1".

Vediamo ora alcune delle funzioni disponibili per il notebook:


    void gtk_notebook_remove_page (GtkNotebook *notebook,
                                     gint page_num);

gtk_notebook_remove_page(); è utilizzata, come il nome stesso suggerisce, per rimuovere la pagina posta in posizione page_num dal notebook


    gint gtk_notebook_page_num (GtkNotebook *notebook,
                                  GtkWidget *child);

gtk_notebook_page_num(); restituisce il numero della pagina che contiene il "figlio" child che è stato passato come secondo argomento nella funzione.


    void gtk_notebook_set_page (GtkNotebook *notebook,
                                  gint page_num);

gtk_notebook_set_page(); questa funzione porta in primo piano la pagina numero page_num. Se si inserisce un valore negativo, la pagina che verrà mostrata sarà l'ultima.


    void gtk_notebook_prev_page (GtkNotebook *notebook);
    void gtk_notebook_next_page (GtkNotebook *notebook);

gtk_notebook_prev_page(); e gtk_notebook_next_page(); si occupano di mostrare rispettivamente la pagina precedente e quella successiva rispetto alla pagina corrente.


    void gtk_notebook_set_tab_pos (GtkNotebook *notebook,
                                     GtkPositionType pos);

Con gtk_notebook_set_tab_pos(); si può decidere dove posizionare le linguette del notebook. I possibili valori sono:


    void gtk_notebook_set_show_tabs (GtkNotebook *notebook,
                                       gboolean show_tabs);

...e se non volessimo mostrare nessuna linguetta? Allora ricorreremo alla funzione gtk_notebook_set_show_tabs();, passandole come valore booleano FALSE.

Dovremmo però poi trovare un metodo alternativo per spostarci tra le pagine. Una alternativa potrebbe essere quella di affiancare al notebook un widget list (o tree), e in funzione della posizione dell'elemento selezionato nella lista richiamare la funzione gtk_notebook_set_page(); che ci mostrerà la pagina richiesta.


    void gtk_notebook_set_scrollable (GtkNotebook *notebook,
                                        gboolean scrollable);

Cosa accade se il nostro notebook contiene molte pagine e la nostra finestra è troppo piccola per poter visualizzare tutte le linguette? Possiamo rassegnarci all'idea di non poter selezionare alcune pagine? Certo che no! ;-). Ci verrà in aiuto la funzione gtk_notebook_set_scrollable();, la quale accetta come secondo argomento un valore booleano, che se posto a TRUE e nel caso in cui ci siano pagine per le quali non si possa mostrare la linguatta, porrà a fianco del notebook due freccette, che se cliccate mostreranno le pagine nascoste.


    gint gtk_notebook_get_current_page (GtkNotebook *notebook);

gtk_notebook_get_current_page(); Ritornerà il numero della pagina attualmente selezionata


    void gtk_notebook_set_tab_label (GtkNotebook *notebook,
                                       GtkWidget *child,
                                       GtkWidget *tab_label);

Con gtk_notebook_set_tab_label(); è possibile modificare l'etichetta posta su una linguetta. Per identificare l'etichetta che vogliamo modificare occorrerà passare come secondo argomento il puntatore al figlio contenuto nel notebook.


    GtkWidget*  gtk_notebook_get_tab_label (GtkNotebook *notebook,
                                              GkWidget *child);

Se invece abbiamo la necessità di conoscere l'etichetta (o il widget) contenuto nella linguetta, ricorreremo alla funzione gtk_notebook_get_tab_label.


    GtkWidget*  gtk_notebook_get_nth_page (GtkNotebook *notebook,
                                             gint page_num);

Infine, potrebbe verificarsi il caso in cui conosciamo il numero di una pagina, ma non il widget figlio che è contenuto nella pagina stessa. In questo caso si ricorre alla funzione gtk_notebook_get_nth_page();

Nel programma proposto sono definite quattro funzioni di callback che modificheranno lo stato del notebook. Le prime due sono on_next_page_clicked (GtkButton *button, gpointer user_data); e
on_prev_page_clicked (GtkButton *button, gpointer user_data);
che sono richiamate alla pressione di due diversi bottoni.

A queste funzioni viene passato, come user_data, il puntatore al widget notebook; avranno il compito di mostrare la pagina precedente e quella successiva all'attuale. La callback on_del_page_clicked (); ricavato il numero di pagina corrente, la eliminerà dal notebook, infine on_add_page_clicked (); crea un semplice widget figlio (un'etichetta) e inserisce una nuova pagina nel notebook.

Scrolled Windows

Le scrolled windows sono utilizzate per creare aree scorrevoli, all'interno delle quali è possibile inserire qualsiasi widget o composizione di widget. In pratica, utilizzando le scrolled windows vedremo solo parte del widget che vi è contenuto, e per poter vedere le parti nascoste sposteremo la visuale utilizzando le barre di scorrimento che il widget ci mette a disposizione (figura 2).


figura 2 Un GtkText in una scrolled window

    void gtk_scrolled_window_new(GtkAdjustment *hadjustment,
                                   GtkAdjustment *vadjustment);

E' la funzione da richiamare per creare una scrolled window. Accetta come argomenti due puntatori all'oggetto GtkAdjustment, che possiamo tranquillamente impostare a NULL.

Come detto le scrolled windows sono dotate di barre di scorrimento, una per gli spostamenti orizzontali, l'altra per quelli verticali. Possiamo influenzare il comportamento delle barre di scorrimento ricorrendo alla seguente funzione:


    void  gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window,
                                            GtkPolicyType hscrollbar_policy,
                                            GtkPolicyType vscrollbar_policy);

Il primo argomento è il puntatore restituitoci dalla gtk_scrolled_window_new();, mentre con il secondo e il terzo argomento indichiamo alla funzione il tipo di barra desiderata. hscrollbar_policy e vscrollbar_policy possono assumere uno tra i seguenti valori:

Il widget GtkAdjustment

Le scrolled windows, così come altri widget, hanno bisogno di un GtkAdjustment. Ma cos'è un GtkAdjustment? Molti widget possono essere adattati o regolati utilizzando il mouse o la tastiera (ad esempio le scrolled windows, i widget text, gli spin button). Pensiamo ad un editor di testo, possiamo scorrere il testo sullo schermo utilizzando i tasti "pag" o ancora cliccando sulle barre di scorrimento; o possiamo inserire un valore in uno spin button, cliccando sulle freccette per incrementare o decrementare il valore che contiene.

Ogni widget di questo tipo dovrebbe avere i propri segnali per notificare un cambiamento e le proprie funzioni per leggere e scrivere i valori aggiornati. Dovrebbe essere compito del programmatore occuparsi della gestione di tali cambiamenti, aggiornando continuamente lo stato del widget (spostare il testo visualizzato nell'editor, incrementare o decrementare il numero contenuto nello spin button). Per evitare tutto ciò, GTK+ mette a disposizione un oggetto che può essere utilizzato da tutti quei widget che hanno bisogno di essere regolati e adattati, il GtkAdjustment. Ecco la funzione da richiamare per creare un GtkAdjustment.


	GtkObject *gtk_adjustment_new(gfloat value,
                                        gfloat lower,
                                        gfloat upper,
                                        gfloat step_increment,
                                        gfloat page_increment,
                                        gfloat page_size );

osserviamo come appare uno spin button (figura 1); di seguito il pezzo di codice, estratto dal programma proposto, utitizzato per crearlo.

    spinbutton_adj = gtk_adjustment_new (0, 0, 100, 1, 10, 10);
    spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton_adj), 1, 0);
    gtk_widget_show (spinbutton);

Per poter impostare esplicitamente il valore di un'adjustment si ricorre alla seguente funzione:


    void gtk_adjustment_set_value(GtkAdjustment *adjustment
                                    gfloat value);

che accetta come primo argomento il puntatore all'adjustment e come secondo il valore da settare.

Entry

Continuiamo l'analisi dei widget di input esaminando il widget gtkentry (figura 3). GtkEntry ci mette a disposizione una casella in cui inserire caratteri alfanumerici, ed è generalmente utilizzato per permettere all'utente l'immissione di brevi stringhe di testo.


figura 3 Il widget Entry.

La funzione che ci restituisce un puntatore a GtkEntry è naturalmente:


    GtkWidget*  gtk_entry_new (void);

Disponiamo di tre funzioni per l'inserimento del testo in un entry, che sono:


    void gtk_entry_set_text (GtkEntry *entry,
                               const gchar *text);
    void gtk_entry_append_text (GtkEntry *entry,
                                  const gchar *text);
    void gtk_entry_prepend_text (GtkEntry *entry,
                                   const gchar *text);

Tutte e tre le funzioni accettano due argomenti, il puntatore al widget e il testo da inserire nel widget stesso, ciò che le differenzia è il modo in cui il testo viene inserito. La prima funzione inserisce il testo nel widget sostituendo ogni precedente stringa, la seconda inserisce il testo alla fine della stringa già presente, mentre l'ultima inserisce il testo prima dell'eventuale stringa presente.

E se volessimo inserire del testo in una posizione qualsiasi? Se esaminiamo le funzioni messe a disposizione da GtkEntry, ci accorgeremo che non c'è una routine che ci permette di fare ciò. Anche in questo caso, la risposta al nostro problema viene dalla gerarchia ad oggetti di Gtk+, esaminiamo quella di GtkEntry:

  GtkObject
    +----GtkWidget
          +----GtkEditable
                +----GtkEntry

Come possiamo osservare, GtkEntry è derivato da GtkEditable; spulciando tra le funzioni disponibili per GtkEditable troveremo:


    void gtk_editable_insert_text (GtkEditable *editable,
                                     const gchar *new_text,
                                     gint new_text_length,
                                     gint *position);

il primo argomento (editable) da passare è il puntatore all'entry, new_text è il puntatore al testo da inserire, il terzo argomento (new_text_length) indica la lunghezza del testo da inserire. L'ultimo (position) è un parametro di input/output, che va inizializzato con la posizione in cui inserire il testo, e che al ritorno dalla funzione position punterà alla posizione dopo il nuovo testo inserito.

La prima volta che si usa questa funzione potremmo però non conoscere la posizione occupata dal cursore in quel momento. Per ovviare si utilizza la seguente chiamata:


    gint gtk_editable_get_position (GtkEditable *editable);

che ci ritornerà la posizione del cursore.

Esaminiamo ora le seguenti funzioni:


    gchar* gtk_entry_get_text (GtkEntry *entry);
    void gtk_entry_set_visibility (GtkEntry *entry,
                                     gboolean visible);

La prima è utilizzata per recuperare il testo contenuto nell'entry, mentre la seconda permette di stabilire se visualizzare o meno il contenuto dell'entry. Se visible è impostato a TRUE, il testo contenuto nell'entry è mostrato normalmente; se invece è impostato a FALSE, i caratteri immessi saranno mascherati da altrettanti asterischi. Un'utilissima funzione per tutti coloro che devono implementare una routine per l'acquisizione di una password.

Spin Button

Come abbiamo visto (figura 1), lo spin button è utilizzato per selezionare un numero da un determinato insieme. E' formato da un entry e da due frecce sovrapposte. Il valore contenuto nello spin button può essere modificato sia cliccando sulle frecce, sia editando il valore direttamente nell'entry.

Osserviamo ancora il codice utilizzato per la creazione dello spin button:

    spinbutton_adj = gtk_adjustment_new (0, 0, 100, 1, 10, 10);
    spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton_adj), 1, 0);
    gtk_widget_show (spinbutton);
         ...

e il prototipo della funzione:


    GtkWidget* gtk_spin_button_new (GtkAdjustment *adjustment,
                                      gfloat climb_rate,
                                      guint digits);

Per creare lo spin button, occorre richiamare la funzione gtk_spin_button_new e fornire come primo argomento un puntatore ad un GtkAdjustment. Il secondo argomento climb_rate, indica di quanto lo spin button deve cambiare quando si clicca su una delle frecce; l'ultimo digits indica, se il numero da visualizzare ha una parte decimale, quante cifre mostrare dopo la virgola.

Esaminiamo alcune delle funzioni disponibili per manipolare uno spin button:


    void gtk_spin_button_set_digits (GtkSpinButton *spin_button,
                                       guint digits);
    gfloat gtk_spin_button_get_value_as_float (GtkSpinButton *spin_button);
    gint gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button);
    void gtk_spin_button_set_value (GtkSpinButton *spin_button,
                                      gfloat value);
    void gtk_spin_button_set_numeric (GtkSpinButton *spin_button,
                                        gboolean numeric);

Progress Bar

Chi di noi non ha mai visto una frase del genere: "Attendere, prego! Il 37% del processo è completato!!", o un'altra come questa: "Questo computer si autodistruggerà (con conseguente crollo del palazzo che lo contiene) esattamente fra 10 secondi. Avete tempo per: ripensare alla vostra infanzia, uccidere il cattivo e salvare il mondo, portare la nonna a distanza di sicurezza e dar da mangiare al gatto. La Acme Spa vi augura una buona giornata!!" ;-)

Il trascorrere del tempo, l'indicazione dello spazio disponibile su di un disco, la percentuale di processo concluso, tutte queste e molte altre operazioni possono essere implementate utilizzando le progress bar (figura 1) e (figura 4).

Questa è la gerarchia dell'oggetto GtkProgressBar, come vedremo molte delle funzioni che ci permettono di operare sulle progress bar, in realtà sono definite sull'oggetto GtkProgress, genitore di GtkProgressBar.

   GtkObject
    +----GtkWidget
          +----GtkProgress
                +----GtkProgressBar

GTK+ ci mette a disposizione molte funzioni per gestire le progress bar, esaminiamone alcune partendo da quelle che ci restituiscono il puntatore all'oggetto.


    GtkWidget* gtk_progress_bar_new (void);
    GtkWidget* gtk_progress_bar_new_with_adjustment (GtkAdjustment *adjustment);

è sufficiente richiamare la prima funzione ed avremo un puntatore all'oggetto. Se abbiamo delle esigenze particolari, e vogliamo che la nostra barra vari in un determinato range di valori, ricorreremo alla seconda funzione, alla quale dovremo fornire un puntatore ad un GtkAdjustment

Osserviamo alcune funzioni di callback, ed il codice utilizzato per l'inizializzazione della progressbar, estratte dal programma allegato.

    void
    on_spinbutton_changed (GtkWidget *spin, gpointer user_data)
    {
      gint val;

      val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));

      gtk_progress_set_value(GTK_PROGRESS(user_data), (gfloat) val);
    }

	...

    void
    on_radio_ds_toggled (GtkToggleButton *togglebutton, gpointer user_data)
    {
     gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR(user_data),
				       GTK_PROGRESS_RIGHT_TO_LEFT);
    }


    void
    on_radio_cont_toggled (GtkToggleButton *togglebutton, gpointer user_data)
    {
     gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(user_data),
                                    GTK_PROGRESS_CONTINUOUS);
    }

    ...

    progressbar = gtk_progress_bar_new ();
    gtk_widget_show (progressbar);
    gtk_box_pack_start (GTK_BOX (hbox4), progressbar, TRUE, TRUE, 0);
    gtk_progress_set_show_text (GTK_PROGRESS (progressbar), TRUE);

    ...

Si inizializza il puntatore richiamando gtk_progress_bar_new ();, si impacchetta in una scatola e si richiama la funzione gtk_progress_set_show_text();,


    void gtk_progress_set_show_text (GtkProgress *progress,
                                       gint show_text);

che accetta come primo parametro un puntatore a GtkProgress, e come secondo un booleano, che se settato a TRUE permette la visualizzazione di un testo all'interno della barra.

Per impostare la barra ad un determinato valore si ricorre alla funzione gtk_progress_set_value();, che accetta anch'essa un puntatore a GtkProgress come primo argomento e il valore (un numero reale) da impostare. Con la funzione gtk_progress_get_value(); potremo recuperare il valore attuale della progress bar.


    void gtk_progress_set_value (GtkProgress *progress,
                                   gfloat value);
    gfloat gtk_progress_get_value (GtkProgress *progress);

Abbiamo visto come impostare e recuperare un valore dalla progress bar; occupiamoci ora della parte estetica. Possiamo fissare l'orientamento della barra, scegliendo tra orizzontale e verticale, possiamo impostarla in modo che cresca da sinistra a destra o da destra a sinistra (per la barra orizzontale), oppure dal basso verso l'alto o dall'alto verso il basso (barra verticale). Inoltre possiamo scegliere come disegnarla, se discreta o continua (figura 4). Tutto questo ricorrendo a due sole funzioni


    void gtk_progress_bar_set_bar_style (GtkProgressBar *pbar,
                                           GtkProgressBarStyle style);
    void gtk_progress_bar_set_orientation (GtkProgressBar *pbar,
                                             GtkProgressBarOrientation orientation);


figura 4 Le possibili progress bar

entrambe le funzioni vogliono come primo argomento un puntatore al GtkProgressBar e come secondo un valore indicante lo stile della barra o l'orientazione della stessa.

I possibili valori per GtkProgressBarStyle sono i seguenti:

GtkProgressBarOrientation potrà assumere uno fra i seguenti valori:

File Selection

Dopo tante ore passate davanti allo schermo a debuggare, finalmente abbiamo una bellissima routine che stima la popolazione di pinguini, gnomi, draghi e diavoletti in giro per l'Italia, passiamo ore a rimirare il grafico a colori, ad inorgoglirci per le nostre conoscenze informatiche e poi ci vien voglia di far partecipe il mondo dei nostri successi, ma acc@#! abbiamo dimenticato la routine di salvataggio!

Esportare dati verso il mondo esterno e caricare dati dal mondo esteno è una delle principali operazioni compiute giornalmente da ogni computer. Se la nostra applicazione grafica richiede di salvare e caricare file, dovremo prevedere una finestra tramite la quale l'utente possa compiere le normali operazioni di input/output. Anche in questo caso GTK+ ci viene in aiuto, fornendoci una comoda finestra di dialogo dotata di ogni comfort per permetterci di salvare e caricare i nostri preziosi dati. In figura 5 possiamo osservare come appare questa finestra.


figura 5 File Selection

Vediamo come utilizzare questo widget:

    void carica_file (GtkFileSelection *fs, gpointer user_data)
    {
      gchar *filename;
      FILE *fp;

      filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION(user_data));
      ...
     }


    void on_load_clicked (GtkButton  *button, gpointer user_data)
    {
      GtkWidget *fs;

      fs = gtk_file_selection_new("Gtk+ -- Apri un file --");

      gtk_widget_show(fs);

      gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                                    "clicked", GTK_SIGNAL_FUNC(carica_file),
                                    (gpointer) fs);
      gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                                           "clicked",
                                           GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                           (gpointer) fs);

      gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->cancel_button),
                                           "clicked",
                                           GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                           (gpointer) fs);
    }

    ....

    gtk_signal_connect (GTK_OBJECT (load), "clicked",
                        GTK_SIGNAL_FUNC (on_load_clicked),
                        NULL);
     ....

Nel nostro programma sono presenti due bottoni, "load" e "save", ognuno collegato alla propria funzione di callback, che sarà richiamata quando cliccheremo su uno di essi. Esaminiamo il comportamento del bottone load (per il save è tutto identico). Nella funzione di callback, creeremo il widget file selection, che come possiamo vedere in figura 5 presenta nella parte bassa altri due bottoni, uno con etichetta "OK", l'altro con etichetta "Annulla". Sarà compito nostro collegare questi due bottoni alle funzioni di callback che svolgeranno le vere operazioni di input/output.

Ecco alcune delle funzioni atte a trattare il widget File Selection


    GtkWidget* gtk_file_selection_new (const gchar *title);
    void gtk_file_selection_set_filename (GtkFileSelection *filesel,
                                            const gchar *filename);
    gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);
    void gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel);
    void gtk_file_selection_hide_fileop_buttons  (GtkFileSelection *filesel);

Mentre questo è l'elenco dei widget contenuti nella struttura FileSelection (e sì, il widget GtkFileSelection è un esempio di widget composito):

    *fileop_dialog;
    *dir_list, *file_list;
    *ok_button;
    *cancel_button;
    *history_pulldown;

    *fileop_c_dir,
    *fileop_del_file,
    *fileop_ren_file

gtk_file_selection_new(); crea una finestra di dialogo file selection e ci ritorna un puntatore al nuovo oggetto. Possiamo passare alla funzione una stringa, che verrà visualizzata come titolo sulla cornice della finestra.

Nelle due liste (dir_list e file_list), come è facile intuire, verrano mostrate la directory corrente (lista di sinistra) e i file contenuti nella directory (lista di destra). In alto saranno presenti tre bottoni (fileop_c_dir, fileop_del_dir, fileop_ren_dir), che ci permetteranno di creare una nuova directory, cancellare e rinominare un file. Utilizzando il menu pull down (history_pulldown) subito sottostante possiamo spostarci in un'altra directory appartenente al ramo corrente. Sotto alle liste troviamo un widget entry, nel quale possiamo inserire il nome del file che desideriamo aprire o il nome con cui vogliamo salvare un file.

Con le funzioni gtk_file_selection_show_fileop_buttons(); e gtk_file_selection_hide_fileop_buttons(); possiamo mostrare o nascondere i bottoni della parte alta (crea directory, ...); entrambe vogliono come parametro il puntatore al widget GtkFileSelection

Per recuperare il nome del file selezionato cliccando su di un elemento della lista a destra o scritto nell'entry si ricorre alla funzione gtk_file_selection_get_filename();, anche questa funzione vuole il puntatore al widget GtkFileSelection e ritorna il nome del file. Attenzione però: per utilizzarla il widget non deve ancora essere stato distrutto... sembra una banalità, ma spesso succede di pretendere di utilizzarla dopo che il widget ha concluso il suo ciclo vitale!!

Per inserire nell'entry il nome del file si ricorre invece alla funzione gtk_file_selection_set_filename();, che come primo argomento necessita del solito puntatore a GtkFileSelection, e come secondo la stringa contenente il nome del file.

Queste sono le funzioni di cui abbiamo bisogno, ma come realizzeremo effettivamente le operazioni di salvataggio e caricamento? Come accennato precedentemente, dovremo collegare i bottoni "OK" e "Annulla" ad opportune funzioni di callback; il bottone "Annulla" dovrà solo chiudere la finestra di dialogo, mentre cliccando sul bottone "OK" dovremo effettuare alcune operazioni (salvare o caricare) e poi chiudere la finestra.

    void on_load_clicked (GtkButton  *button, gpointer user_data)
    {
      GtkWidget *fs;

      fs = gtk_file_selection_new("Gtk+ -- Apri un file --");

      gtk_widget_show(fs);

      gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                                    "clicked", GTK_SIGNAL_FUNC(carica_file),
                                    (gpointer) fs);
      gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button),
                                           "clicked",
                                           GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                           (gpointer) fs);

      gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->cancel_button),
                                           "clicked",
                                           GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                           (gpointer) fs);
    }

Nella funzione di callback richiamata alla pressione del bottone "load", creeremo il widget FileSelection e collegheremo i bottoni ok_button e cancel_button. Alla pressione di ok_button sarà prima richiamata la funzione carica_file, alla quale inoltre passeremo come dati il puntatore allo stesso FileSelection, quindi al ritorno da carica_file sarà chiamata la funzione di libreria gtk_widget_destroy, che distruggerà il widget fs. Il bottone "Annulla" sarà collegato semplicemente a gtk_widget_destroy

    void carica_file (GtkFileSelection *fs, gpointer user_data)
    {
      gchar *filename;
      FILE *fp;

      filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION(user_data));
      ...
     }

Nella funzione carica_file, recupereremo dal widget GtkFileSelection con la funzione gtk_file_selection_get_filename(); il nome del file, ed effettueremo le operazioni necessarie.

I widget deprecati: GtkCList (GtkCTree)

Molto brevemente, introdurremo ora i widget clist e text, che come anticipato saranno profondamente modificati nelle nuove GTK+2.0. Oltre alle columed list (clist) e al widget text saranno modificati anche il widget Ctree (columed tree) e i widget tree e list.

Nella figura 6 possiamo vedere la clist, mentre in figura 7 è mostrato il widget text (nel quale è stato caricato il programma di prova che accompagna questo articolo)


figura 6 Una clist di due colonne

Il widget GtkClist mette a disposizione una lista su più colonne, ognuna delle quali può avere un titolo.

    void
    on_entry_activate (GtkEditable *editable, gpointer user_data)
    {
      gchar *tmp;
      gchar *buffer[3];

      tmp = gtk_entry_get_text(GTK_ENTRY(editable));

      buffer[0] = g_strdup(tmp);
      g_strreverse(tmp);

      buffer[1] = g_strdup(tmp);
      buffer[2] = NULL;

      gtk_clist_append(GTK_CLIST(user_data), buffer);

      gtk_entry_set_text(GTK_ENTRY(editable), "");
    }


    void on_clist_select_row (GtkCList *clist, gint row, gint column,
                                GdkEvent *event, gpointer user_data)
    {
      gchar *buffer;

      gtk_clist_get_text(GTK_CLIST(clist), row, column, &buffer);

      gtk_label_set_text(GTK_LABEL(user_data), buffer);
    }


    ...

    clist = gtk_clist_new (2);

    ...

    label1 = gtk_label_new ("stringa1");
    gtk_widget_show (label1);
    gtk_clist_set_column_widget (GTK_CLIST (clist), 0, label1);

    ...


    GtkWidget* gtk_clist_new (gint columns);
    GtkWidget* gtk_clist_new_with_titles (gint columns,
                                            gchar *titles[]);

    void gtk_clist_set_column_title (GtkCList *clist,
                                       gint column,
                                       const gchar *title);
    void gtk_clist_set_column_widget (GtkCList *clist,
                                        gint column,
                                        GtkWidget *widget);
    void gtk_clist_column_title_active (GtkCList *clist,
                                          gint column);

    void gtk_clist_set_text (GtkCList *clist,
                               gint row,
                               gint column,
                               const gchar *text);
    gint gtk_clist_get_text (GtkCList *clist,
                               gint row,
                               gint column,
                               gchar **text);

    gint gtk_clist_append (GtkCList *clist,
                             gchar *text[]);
    gint gtk_clist_prepend (GtkCList *clist,
                              gchar *text[]);
    gint gtk_clist_insert (GtkCList *clist,
                             gint row,
                             gchar *text[]);

    void gtk_clist_remove (GtkCList *clist,
                             gint row);
    void gtk_clist_clear (GtkCList *clist);

    void user_function (GtkCList *clist,
                          gint row,
                          gint column,
                          GdkEventButton *event,
                          gpointer user_data);

La clist si crea richiamando o la funzione gtk_clist_new(); o gtk_clist_new_with_titles(); la prima ha un solo parametro in input, il numero di colonne che la clist deve avere, mentre alla seconda oltre al numero di colonne bisogna fornire anche i rispettivi titoli. Ad esempio, gtk_clist_new(3); crea una clist con tre colonne, mentre gtk_clist_new(3, titolo); (dove titolo è definito come char *titolo[3] = {"prima", "seconda", "terza"};) crea sempre una clist con tre colonne, ma alla prima colonna assegna il titolo "prima", alla seconda il titolo "seconda" e cosi via.

Se per la creazione della clist si è utilizzata la prima funzione, è sempre possibile assegnare (o modificare) in un successivo momento il titolo di una o più colonne. Per far ciò si ricorre a gtk_clist_set_column_title();. Il primo argomento da passare è naturalmente il puntatore alla clist, il secondo (column) è il numero della colonna che ci interessa, ed il terzo è il titolo. Attenzione, le colonne sono numerate partendo da zero.

In molti programmi, quando clicchiamo sul titolo di una clist vediamo comparire sul titolo stesso una freccetta puntata verso l'alto o verso il basso, mentre gli elementi contenuti nella clist si riorganizzano (magari in ordine alfabetico). Bene; anche i nostri programmi possono offrire un feature di questo tipo.

Per prima cosa occore un titolo "speciale", invece di una semplice stringa creeremo una scatola contenente altre due scatole, nella prima delle quali inseriremo il titolo vero e proprio e nella seconda una pixmap. Quindi ricorreremo alla funzione gtk_clist_set_column_widget();, che accetta tre parametri, il puntatore alla clist, il numero della colonna in cui inserire il widget e il puntatore al widget stesso (la nostra scatola), per inserire il widget nel titolo della colonna. Useremo, poi, la funzione gtk_clist_column_title_active();, per attivare la colonna (le facciamo emettere il segnale "clicked" quando clicchiamo sul titolo) alla quale passeremo il puntatore alla clist e il numero della colonna che stiamo trattando. Dovremo intercettare il segnale "clicked", e nella nostra funzione di callback creeremo una nuova scatola contenente la stringa da mostrare e la pixmap di una freccia rivolta nel verso opportuno (l'opposto di quello dell'attuale). Richiamando gtk_clist_set_column_widget(); modificheremo il titolo della colonna.

I dati possono essere inseriti o nelle singole celle o su intere righe; vediamo come inserire le righe. Se la nostra lista ha 2 colonne, dovremo creare un vettore di tre elementi (una stringa a colonna, più NULL come terminatore) da passare ad apposite funzioni. Per inserire dati alla fine della lista si ricorre alla funzione gtk_clist_append(); (o alle analoghe gtk_clist_prepend(); o gtk_clist_insert(); per inserire il vettore rispettivamente all'inizio della lista o in una posizione generica). Tutte necessitano come primo parametro del puntatore alla clist, e del vettore di stringhe da inserire; gtk_clist_insert(); necessita inoltre del numero di riga in cui inserire le stringhe.

Per modificare la cella contenuta alla riga x, colonna y, si ricorre alla funzione gtk_clist_set_text();, alla quale passeremo il puntatore alla clist, il numero di riga, il numero di colonna e la stringa da inserire. Invece per recuperare la stringa contenuta nella cella alla riga x, colonna y si ricorre alla funzione gtk_clist_get_text();, la stringa ci verrà restituita nella variabile text.

Per pulire l'intera lista si usa la funzione gtk_clist_clear();, mentre per eliminare una riga si richiama la funzione gtk_clist_remove();.

Cosa accade quando clicchiamo con il mouse su di una cella della lista? Verrà emesso il segnale "select-row", il quale, se vogliamo compiere operazioni sugli elementi contenuti nella lista, deve essere intercettato e gestito tramite una funzione di callback, che deve avere un prototipo simile a quello della funzione user_function(); riportato in alto.

I widget deprecati: GtkText


figura 7 Il programma di prova caricato in un widget Text



    GtkWidget* gtk_text_new (GtkAdjustment *hadj,
                               GtkAdjustment *vadj);

    guint gtk_text_get_point (GtkText *text);
    void gtk_text_set_point (GtkText *text,
                               guint index);

    guint gtk_text_get_length (GtkText *text);

    void gtk_text_insert (GtkText *text,
                            GdkFont *font,
                            GdkColor *fore,
                            GdkColor *back,
                            const char *chars,
                            gint length);
  GtkObject
    +----GtkWidget
          +----GtkEditable
                +----GtkEntry

Anche il widget GtkText è derivato da GtkEditable, e ci permette di gestire (visualizzare, editare, ...) un testo disposto su più linee. Questo widget ha una serie di "shortcuts" per le più comuni operazioni effettuabili da tastiera, definite di default. Ad esempio:

    Ctrl-X Taglia il testo selezionato e lo salva nel clipboard
    Ctrl-C Copia il testo selezionato nel clipboard
    Ctrl-V Incolla il testo dal clipboard

    Ctrl-A Porta il cursore all'inizio della linea
    Ctrl-E Porta il cursore alla fine della linea
    ...

Il puntatore all'oggetto GtkText si ottiene dalla chiamata a gtk_text_new(); hadj e vadj possono essere impostati a NULL, in questo caso sarà GTK+ ad occuparsi di tutto.

gtk_text_get_point(); e gtk_text_set_point(); sono usate, rispettivamente, per recuperare la posizione corrente del cusore, intesa come numero di caratteri dall'angolo in alto a sinistra del widget, e per posizionare il cursore in un determinato punto; anche in questo caso la posizione va intesa come numero di caratteri a partire dall'angolo in alto a sinistra.

gtk_text_get_length(); restituisce la lunghezza del testo, cioè il numero totale di caratteri immessi.

Per inserire una serie di caratteri nel widget si ricorre alla funzione gtk_text_insert(); il primo argomento è il puntatore al widget GtkText, il secondo è un puntatore al font che si vuole utilizzare, il terzo e il quarto indicano rispettivamente il colore del testo e di sfondo, il quinto è il buffer di caratteri da inserire, l'ultimo è il numero di caratteri da inserire dal buffer, se si passa -1 tutto il buffer sarà inserito nel widget. Se a font, fore, back si passa il valore NULL, allora saranno utilizzati il font e i colori di default.



L'autore

Nicola Fragale. E' iscritto a 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. Nel tempo libero gli piace programmare, leggere (Stephen King l'autore preferito), stare con la sua ragazza e sentire i suoi amici.


<- PW: Intro - Copertina - PW: Introduzione al Pascal ->