<- PW: Intro - Copertina - PW: MultiGnomeTerminal ->

PlutoWare


GTK+: Alle radici dello GNOME

Terza puntata: Bottoni, decorazioni e...

di Nicola Fragale


L'articolo

Siamo arrivati alla terza puntata del nostro corso sulla programmazione sotto GTK+, le librerie su cui si basa GNOME. In questa puntata termineremo la descrizione dei widget contenitori, trattando in modo più approfondito le tabelle; introdurremo alcuni bottoni molto utili, e presenteremo qualche widget decorativo, in modo da rendere le nostre applicazioni più belle da vedere ( anche l'occhio vuole la sua parte! :-) ). Faremo questo introducendo e analizzando un programma di prova; come sempre, questo semplificherà molto la comprensione dei meccanismi, fornendo un esempio pratico e semplice che potrete poi adattare alle vostre applicazioni. I widget trattati in questa puntata sono molto eterogenei, ma osserveremo come con pochi elementi sia possibile costruire qualcosa che somigli, vagamente ;-), ad un pannello di controllo per una applicazione, il tutto senza disdegnare qualche "abbellimento".


Indice


Primo esempio

Abbiamo visto nelle puntate precedenti come sia semplice costruire una applicazione grafica utilizzando le librerie di GTK+. Prima di continuare, se ancora non lo avete fatto, vi invito a scaricare le API Reference di 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 <gtk/gtk.h>

/* la variabile globale cambia viene modificata 
   ad ogni pressione del toggle button */
gboolean cambia = TRUE;


/* *** le funzioni di callback *** */

/* on_checkbutton1_clicked viene richiamata 
   quando si clicca sul primo check button 
*/
  
void
on_checkbutton1_clicked (GtkButton *button, gpointer user_data)
{
  gboolean bool;

  bool = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
  
  if (bool)
    {
      gtk_tooltips_disable(GTK_TOOLTIPS(user_data));
      g_print("\nHai scelto di disabilitare i suggerimenti");
    }
  else
    {
      gtk_tooltips_enable(GTK_TOOLTIPS(user_data)); 
      g_print("\nOk, ripristiniamo i suggerimenti");
    }
}


void
on_checkbutton2_clicked(GtkButton *button, gpointer user_data)
{
  gboolean bool;
  
  bool = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
  
  g_print("\nl'opzione n° 2 %s settata", bool ? "è" : "non è");
}


/* le funzioni di callback associate ai radio button:
*/ 

void
on_radiobutton1_clicked(GtkButton *button, gpointer user_data)
{
  GtkWidget *frame = GTK_WIDGET(user_data);

  if (cambia)
    {
      gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
      gtk_frame_set_label(GTK_FRAME(frame), " Ombra attiva: GTK_SHADOW_NONE ");
    }
}


void
on_radiobutton2_clicked(GtkButton *button, gpointer user_data)
{
  GtkFrame *frame = GTK_FRAME(user_data);
  
  if (cambia)
    {
      gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN);
      gtk_frame_set_label(frame, " Ombra attiva: GTK_SHADOW_IN ");
    }     
}


void
on_radiobutton3_clicked(GtkButton *button, gpointer user_data)
{
  if (cambia)
    {
      gtk_frame_set_shadow_type(GTK_FRAME(user_data), GTK_SHADOW_OUT);
      gtk_frame_set_label(GTK_FRAME(user_data), " Ombra attiva: GTK_SHADOW_OUT ");
    }
}


void
on_radiobutton4_clicked (GtkButton *button, gpointer user_data)
{
  GtkFrame *frame = GTK_FRAME(user_data);

  if (cambia)
    {
      gtk_frame_set_shadow_type(frame, GTK_SHADOW_ETCHED_IN);
      gtk_frame_set_label(frame, " Ombra attiva: GTK_SHADOW_ETCHED_IN "); 
    }
}


void 
on_radiobutton5_clicked (GtkButton *button, gpointer user_data)
{
  GtkFrame *frame = GTK_FRAME(user_data);

  if (cambia)
    {
      gtk_frame_set_shadow_type(frame, GTK_SHADOW_ETCHED_OUT);
      gtk_frame_set_label(frame, " Ombra attiva: GTK_SHADOW_ETCHED_OUT "); 
    }
}


/* la funzione di callback per il toggle button:
   verifica lo stato del bottone e setta la variabile
   globale "cambia" di conseguenza
*/    
void
on_togglebutton_clicked (GtkButton *button, gpointer user_data)
{
  gboolean bool;

  bool = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));

  if (bool)
    {
      g_print("\nCon il togglebutton settato non è");
      g_print("\npermesso cambiare l'ombreggiatura al frame");
      cambia = FALSE;
    }
  else
    {
      g_print("\nIl togglebutton non è selezionato. Puoi fare ciò che vuoi");
      cambia = TRUE;
    }
}


int
main (int argc, char *argv[])
{
                                  /* prendete: */
  GtkWidget *window;              /* la finestra principale dell'applicazione */

  GtkWidget *frame1;              /* dei bordi decorativi */
  GtkWidget *frame2;

  GtkWidget *vbox1;               /* qualche scatola */
  GtkWidget *vbox2;
  GtkWidget *hbox1;
  GtkWidget *hbox2;

  GtkWidget *tabella;             /* una tabella */

  GtkWidget *etichetta;           /* una etichetta, per comunicare con l'utente */

  GtkWidget *togglebutton;        /* un toggle button */

  GtkWidget *checkbutton1;        /* qualche bottone check */
  GtkWidget *checkbutton2;

  GSList *gruppo_radio = NULL;    /* bottoni radio quanto bastano,
                                     e il loro gruppo */
  GtkWidget *radiobutton1;
  GtkWidget *radiobutton2;
  GtkWidget *radiobutton3;
  GtkWidget *radiobutton4;
  GtkWidget *radiobutton5;

  GtkWidget *vseparator;          /* altri widget decorativi */
  GtkWidget *hseparator;

  GtkWidget *esci;                /* un bottone di uscita */

  GtkTooltips *tooltip;           /* e se dessimo qualche suggerimeto? */
  
                                  /* mescolate con cura ed otterrete... */
   

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Bottoni e decorazioni!!");


  /* creiamo una cornice con l'etichetta "Bottoni e decorazioni" 
  */ 
  frame1 = gtk_frame_new (" Bottoni e decorazioni ");
  gtk_container_set_border_width (GTK_CONTAINER (frame1), 5);
  
  /* impacchettiamo la cornice direttamente nella finestra principale 
  */
  gtk_container_add (GTK_CONTAINER (window), frame1);
 
  /* mostriamola 
  */
  gtk_widget_show (frame1);

   /* creiamo un tooltips 
   */  
  tooltip = gtk_tooltips_new(); 
  
  /* creiamo alcune scatole, che ci permetteranno
     di disporre i widget della nostra applicazione
  */
  vbox1 = gtk_vbox_new (FALSE, 5);
  gtk_container_add (GTK_CONTAINER (frame1), vbox1);
  gtk_container_set_border_width (GTK_CONTAINER (vbox1), 5);
  gtk_widget_show (vbox1);

  vbox2 = gtk_vbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (vbox2), 5);
  gtk_widget_show (vbox2);

  /* hbox1 conterrà i check button 
  */
  hbox1 = gtk_hbox_new (TRUE, 5);
  gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox1), 5);
  gtk_widget_show (hbox1);

  /* creiamo un check button, 
     lo impacchettiamo in hbox1 
     e lo mostriamo  
   */
  checkbutton1 = gtk_check_button_new_with_label("Nascondi i suggerimenti");
  gtk_box_pack_start (GTK_BOX (hbox1), checkbutton1, FALSE, FALSE, 0);
  gtk_widget_show (checkbutton1);

  checkbutton2 = gtk_check_button_new_with_label("Opzione n. 2");
  gtk_box_pack_start (GTK_BOX (hbox1), checkbutton2, FALSE, FALSE, 0);
  gtk_widget_show (checkbutton2);

  /* un altro frame 
  */
  frame2 = gtk_frame_new (" Ombra attiva: ");
  gtk_widget_show (frame2);
  gtk_box_pack_start (GTK_BOX (vbox2), frame2, TRUE, TRUE, 0);

  /* una tabella di 2 righe x 3 colonne, ci consentirà di disporre
     una serie di widget in un modo gradevole da vedere:
     
     come esercizio provate a sostituire la tabella con 
     delle scatole (hbox e vbox) e osservate le differenze 
     estetiche. Usate una scatola verticale nella quale
     impacchetterere tre scatole orizzontali, nelle scatole
     orizzontali inserite rispettivamente 2, 2 e 1 radio button 
   */
  tabella = gtk_table_new (2, 3, FALSE);
  gtk_widget_show (tabella);
  gtk_container_add (GTK_CONTAINER (frame2), tabella);

  /* i radio button 
  */
  /* creiamo un radio button con etichetta "nessuna"
     e lo associamo al gruppo "gruppo_radio"
  */
  radiobutton1 = gtk_radio_button_new_with_label (gruppo_radio, "nessuna");
  gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton1));
  
  /* il radio button creato lo inseriamo nella prima
     cella in alto a sinistra della tabella
  */ 
  gtk_table_attach (GTK_TABLE (tabella), radiobutton1, 0, 1, 0, 1,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
        
   /* lo mostriamo e 
   */    
  gtk_widget_show (radiobutton1);

  /* gli associamo un tooltip, che verrà visualizzato
     ogni volta che il puntatore del mouse
     gli si fermerà su
   */
  gtk_tooltips_set_tip(tooltip, radiobutton1, "Non voglio ombre!!", NULL);

  radiobutton2 = gtk_radio_button_new_with_label (gruppo_radio, "interna");
  gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton2));
  gtk_table_attach (GTK_TABLE (tabella), radiobutton2, 1, 2, 0, 1,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
  gtk_widget_show (radiobutton2);
  gtk_tooltips_set_tip(tooltip, radiobutton2, "Dammi un'ombra interna", NULL);

  radiobutton3 = gtk_radio_button_new_with_label (gruppo_radio, "esterna");
  gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton3));
  gtk_table_attach (GTK_TABLE (tabella), radiobutton3, 2, 3, 0, 1,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
  gtk_widget_show (radiobutton3);
  gtk_tooltips_set_tip(tooltip, radiobutton3, 
                             "Applica un'ombreggiatura esterna", 
                             NULL);

  radiobutton4 = gtk_radio_button_new_with_label (gruppo_radio, "scolpita interna");
  gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton4));
  gtk_table_attach (GTK_TABLE (tabella), radiobutton4, 0, 1, 1, 2,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
  gtk_widget_show (radiobutton4);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radiobutton4), TRUE);
  gtk_tooltips_set_tip(tooltip, radiobutton4, 
		 "L'ombreggiatura scolpita interna, è il default", NULL);
  
  radiobutton5 = gtk_radio_button_new_with_label (gruppo_radio, "scolpita esterna");
  gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton5));
  gtk_table_attach (GTK_TABLE (tabella), radiobutton5, 1, 2, 1, 2,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
  gtk_widget_show (radiobutton5);
  gtk_tooltips_set_tip(tooltip, radiobutton5, 
		 "Proviamo anche l'ombreggiatura scolpita esterna", NULL);

  hbox2 = gtk_hbox_new (TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0);
  gtk_widget_show (hbox2);

  etichetta = gtk_label_new ("Un togglebutton");
  gtk_box_pack_start (GTK_BOX (hbox2), etichetta, TRUE, TRUE, 0);
  gtk_widget_show (etichetta);

  vseparator = gtk_vseparator_new ();
  gtk_box_pack_start (GTK_BOX (hbox2), vseparator, FALSE, FALSE, 0);
  gtk_widget_show (vseparator);

  togglebutton = gtk_toggle_button_new_with_label ("Cliccami");
  gtk_box_pack_start (GTK_BOX (hbox2), togglebutton, TRUE, TRUE, 0);
  gtk_widget_show (togglebutton);

  gtk_tooltips_set_tip(tooltip, togglebutton, 
		 "Se attivo, il togglebutton impedisce il cambiamento delle ombre", 
		 NULL);

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

  esci = gtk_button_new_with_label ("Chiudi");
  gtk_box_pack_start (GTK_BOX (vbox1), esci, FALSE, FALSE, 0);
  gtk_widget_show (esci);
  gtk_tooltips_set_tip(tooltip, esci, "Fammi uscire da questo programma.", NULL);


  /*    Le funzioni callback
  */
  gtk_signal_connect (GTK_OBJECT (window), "delete_event",
    GTK_SIGNAL_FUNC (gtk_main_quit),
    NULL);

  gtk_signal_connect (GTK_OBJECT (esci), "clicked",
    GTK_SIGNAL_FUNC (gtk_main_quit),
    NULL);

  gtk_signal_connect (GTK_OBJECT (checkbutton1), "clicked",
    GTK_SIGNAL_FUNC (on_checkbutton1_clicked),
    tooltip);
  gtk_signal_connect (GTK_OBJECT (checkbutton2), "clicked",
    GTK_SIGNAL_FUNC (on_checkbutton2_clicked),
    NULL);

  gtk_signal_connect (GTK_OBJECT (radiobutton1), "clicked",
    GTK_SIGNAL_FUNC (on_radiobutton1_clicked),
    frame2);
  gtk_signal_connect (GTK_OBJECT (radiobutton2), "clicked",
    GTK_SIGNAL_FUNC (on_radiobutton2_clicked),
    frame2);
  gtk_signal_connect (GTK_OBJECT (radiobutton3), "clicked",
    GTK_SIGNAL_FUNC (on_radiobutton3_clicked),
    frame2);
  gtk_signal_connect (GTK_OBJECT (radiobutton4), "clicked",
    GTK_SIGNAL_FUNC (on_radiobutton4_clicked),
    frame2);
  gtk_signal_connect (GTK_OBJECT (radiobutton5), "clicked",
    GTK_SIGNAL_FUNC (on_radiobutton5_clicked),
    frame2);

  gtk_signal_connect (GTK_OBJECT (togglebutton), "clicked",
    GTK_SIGNAL_FUNC (on_togglebutton_clicked),
    NULL);


  gtk_widget_show (window);

  gtk_main ();

  g_print("\nContinua...\n\n");
  return 0;
}

Per compilare il programma utilizzate il comando:

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

Ricordiamo che lo script gtk-config si occupa di fornire al preprocessore C e al linker gli opportuni percorsi per gli header da includere e per le librerie da linkare.

Lanciate l'eseguibile da una finestra di terminale; il programma stamperà a video alcuni messaggi, che aiuteranno a comprenderne l'esecuzione.

Il frame

Iniziamo ad esaminare il codice, partendo sempre dalla funzione main():

   frame1 = gtk_frame_new (" Bottoni e decorazioni "); 
   gtk_container_set_border_width (GTK_CONTAINER (frame1), 5); 
   gtk_container_add (GTK_CONTAINER (window), frame1); 
   gtk_widget_show (frame1);

il primo nuovo statement GTK+ che si incontra è il gtk_frame_new(), che istanzia un widget che non avevamo mai incontrato: il Frame. Un Frame è fondamentalmente una cornice, utilizzata per racchiudere uno o più widget; su di essa può inoltre essere applicata un'etichetta (figura 1). Il codice qui sopra creerà una cornice contenente l'etichetta "Bottoni e decorazioni"; se si desiderasse di non avere etichette nel frame sarebbe sufficiente passare NULL come parametro alla funzione, nel qual caso verrebbe creato un semplice rettangolo (figura 2).


Figura 1: un frame con etichette

Figura 2: un frame senza etichette

La chiamata a funzione è la seguente:

   GtkWidget *gtk_frame_new(const gchar *label);

Durante l'elaborazione, potrebbe presentarsi la necessità di dover cambiare l'etichetta contenuta nel frame; in questo caso si ricorre a:

   void gtk_frame_set_label(GtkFrame *frame, 
                              const gchar *label);

Il frame può essere disegnato in 5 modi diversi, a seconda del tipo di ombreggiatura che si vuole impostare; la funzione preposta alla variazione delle ombre è:

   void gtk_frame_set_shadow_type(GtkFrame *frame, 
                                    GtkShadowType type);

Pensate il frame come una linea che può essere sottoposta ad una sorgente di luce: cambiando la posizione della sorgente, si otterranno diversi tipi di ombra. Ad esempio illuminando la retta da sinistra, la sua ombra sarà sulla sua destra. I tipi di ombra disponibili sono 5 e sono definiti nel modo seguente:

   GTK_SHADOW_NONE  
   GTK_SHADOW_IN 	 
   GTK_SHADOW_OUT  	 
   GTK_SHADOW_ETCHED_IN  
   GTK_SHADOW_ETCHED_OUT

Vediamo in dettaglio le loro caratteristiche:

I separatori

Altri elementi decorativi presenti in questo programma sono i separatori, che sostanzialmente si limitano a disegnare una linea. Sono dei widget veramente semplici da usare, basta crearli, impacchettarli e mostrarli! Possono essere di due tipi, orizzontali e verticali. Le funzioni da richiamare per creare un separatore sono le seguenti:

   GtkWidget *gtk_vseparator_new (); 
   GtkWidget *gtk_hseparator_new ();  

La prima crea un separatore verticale, la seconda, com'è facile intuire, uno orizzontale (figura 3).


Figura 3: i separatori


I tooltip

Chi non ha mai visto un tooltip? Si tratta di frasi stampate su uno sfondo generalmente giallo, che vengono mostrate quando il puntatore del mouse staziona per un certo periodo di tempo su di un widget. Anche i tooltip sono molto semplici da utilizzare: una volta creati, è sufficiente collegarli al widget che ci interessa, e in seguito ogni volta che il puntatore si fermerà sul widget, farà apparire il tooltip a lui associato. (figura 4)

Ecco alcune delle funzioni necessarie alla creazione e alla gestione dei tooltip:

   GtkTooltips *gtk_tooltips_new();          
   void gtk_tooltips_set_tip(GtkTooltips *tooltips,   
                               GtkWidget *widget,    
                               const gchar *tip_text,      
                               const gchar *tip_private);   
   void gtk_tooltips_enable(GtkTooltips *tooltips);     
   void gtk_tooltips_disable(GtkTooltips *tooltips);


Figura 4 Un tooltip

Un tooltip si crea richiamando gtk_tooltips_new(), che ci ritornerà un puntatore all'oggetto tooltip. Per collegare il tooltip al widget si utilizza la funzione gtk_tooltips_set_tip(). Questa funzione accetta come primo argomento il puntatore al tooltip, come secondo il puntatore al widget sul quale vogliamo che il suggerimento appaia, come terzo la stringa che vogliamo mostrare e infine, come quarto, una stringa che per il momento può essere tranquillamente impostata a NULL.

Ecco un esempio estratto dal programma allegato:

   /* creiamo il tooltip */	
   tooltip = gtk_tooltips_new(); 
	   ...	 
   esci = gtk_button_new_with_label ("Chiudi"); 
   gtk_box_pack_start (GTK_BOX (vbox1), esci, FALSE, FALSE, 0); 
   gtk_widget_show (esci); 
   
   /* associamo al bottone il tooltip con la stringa "Fammi uscire ..." */
   gtk_tooltips_set_tip(tooltip, esci, "Fammi uscire da questo programma.", NULL); 
   	   ... 

Attenzione: diversamente dagli altri widget, il widget tooltips non ha bisogno di essere mostrato, quindi non richiamate gtk_widget_show(tooltip); inoltre, è sufficiente creare un solo tooltip, e di volta in volta utilizzando la funzione gtk_tooltips_set_tip(), si assocerà una diversa stringa a widget diversi.

E se volessimo scrivere una qualche routine di configurazione per dare all'utente l'opportunità di abilitare o disabilitare l'uso dei tooltip? Allora utilizzeremo le chiamate gtk_tooltips_enable() e gtk_tooltips_disable(); la prima permette la visualizzazione del tooltip, la seconda la impedisce.

Le etichette

	... 
   etichetta = gtk_label_new ("Un togglebutton"); 
   gtk_box_pack_start (GTK_BOX (hbox2), etichetta, TRUE, TRUE, 0); 
   gtk_widget_show (etichetta); 
  	... 

Altro widget che si usa molto ed è molto facile da utilizzare: l'etichetta. Ecco qualche funzione:

   GtkWidget *gtk_label_new(gchar *str); 
   void gtk_label_set_text(GtkLabel *label,  
                             gchar *str); 
   void gtk_label_get_text(GtkLabel *label,  
                             gchar **str); 
   void gtk_label_set_justify(GtkLabel *label,  
                                GtkJustification jtype);

come sempre, è necessario creare il nostro widget chiamando la funzione gtk_label_new(); ottenuto il puntatore all'oggetto possiamo lavorarci su. La funzione gtk_label_new() accetta come parametro la stringa che dovrà mostrare; se si dovesse modificare il valore della stringa durante l'elaborazione, si ricorra alla chiamata gtk_label_set_text(). Questa funzione accetta il puntatore al widget etichetta e la nuova stringa da mostrare. Se invece volessimo recuperare la stringa contenuta nella label, dovremmo usare la funzione gtk_label_get_text(), che ritornerà nella variabile str la stringa attualmente visualizzata. Infine vediamo l'ultima di queste funzioni, gtk_label_set_justify(). Trattandosi di etichette, è naturale cercare di allinearle. I valori che è possibile passare come jtype sono i seguenti, e il significato dovrebbe essere intuitivo a chiunque abbia mai usato un word processor:

   GTK_JUSTIFY_LEFT 
   GTK_JUSTIFY_RIGHT 
   GTK_JUSTIFY_CENTER 
   GTK_JUSTIFY_FILL 

Le tabelle

Come accennato la volta scorsa, GTK+ oltre alla scatola ci mette a disposizione un altro contenitore, la tabella. Le tabelle sono formate da un'insieme di celle (Figura 5), all'interno delle quali è possibile inserire un widget (o un insieme di widget composti fra loro).

	... 
   tabella = gtk_table_new (2, 3, FALSE); 
   gtk_widget_show (tabella); 
   gtk_container_add (GTK_CONTAINER (frame2), tabella);	 
  	... 

due funzioni:

   GtkWidget *gtk_table_new(gint rows, 
                              gint columns, 
                              gboolean homogeneous); 
   void gtk_table_attach(GtkTable *table, 
                           GtkWidget *child, 
                           gint left_attach, 
                           gint right_attach, 
                           gint top_attach, 
                           gint bottom_attach, 
                           GtkAttachOptions xoptions, 
                           GtkAttachOptions yoptions, 
                           gint xpadding, 
                           gint ypadding);

La prima funzione crea una nuova tabella, di dimensione rows x columns; homogeneous è un valore booleano, se impostato a TRUE renderà la grandezza delle celle omogenea, farà in modo, cioè, che tutte le celle abbiano dimensione uguale a quella della cella di dimensione massima; se homogeneous fosse settato a FALSE, allora ogni cella avrebbe avuto la dimensione minima necessaria per lei sola. Quindi se volessimo creare una tabella 2x3 (di due righe e tre colonne) omogenea, bisognerebbe richiamare gtk_table_new(2, 3, TRUE); L'angolo in alto a sinistra della tabella avrà coordinate (0, 0), la cella contenuta nella prima riga/prima colonna avrà quindi coordinate (0, 0). (Figura 5)

Questo sistema di coordinate è utilizzato nella seconda funzione, che si occupa di inserire il widget nella cella scelta. Vediamola in dettaglio: table è naturalmente la tabella, child è il widget che vogliamo inserire in essa (un bottone, una etichetta, una pixmap, ...), left_attach, right_attach, top_attach e bottom_attach definiscono le coordinate del punto in cui attaccare il widget. left e right definiscono anche quante celle il widget deve occupare: se infatti volessimo che il nostro widget occupi tutta la prima riga della tabella 2x3 precedentemente creata, allora left_attach avrebbe valore 0, mentre right_attach avrebbe valore 3; se avessimo voluto occupare solo la prima cella, allora right_attach avrebbe avuto valore 1. Trattandosi della prima riga, i valori di top e bottom attach sarebbero stati in ogni caso 0 e 1, rispettivamente. xoptions e yoptions specificano come attaccare il widget. I possibili valori che possono assumere (e che è possibile combinare con un or, mediante l'operatore "|" in modo da avere più proprietà contemporaneamente) sono i seguenti:

   GTK_FILL 
   GTK_SHRINK 
   GTK_EXPAND
con GTK_FILL si permetterà al widget di espandersi e di utilizzare tutto lo spazio disponibile, nel caso in cui la cella fosse più grande del widget stesso. GTK_SHRINK farà restringere il widget nel caso in cui lo spazio a disposizione della tabella non fosse sufficente. GTK_EXPAND, infine, permetterà alla tabella di espandersi e di occupare tutto lo spazio in eccesso nella finestra.

xpadding e ypadding indicano quanti pixel lasciare attorno al widget come spazio vuoto.


Figura 5: una tabella

I toggle Button

I toggle button sono derivati dai normali bottoni ed hanno un aspetto simile al loro, ma ciò che li differenzia dai bottoni è il fatto che possono assumere due stati (premuto o rilasciato); mantengono il loro stato finchè esso non viene esplicitamente modificato. Le funzioni che gestiscono i toggle button sono le seguenti:

    GtkWidget *gtk_toggle_button_new( void );
    GtkWidget *gtk_toggle_button_new_with_label(gchar *label );
    gboolean gtk_toggle_button_get_active(GtkToggleButton *toggle_button);
    void gtk_toggle_button_set_active(GtkToggleButton *toggle_button,
                                        gint state);

gtk_toggle_button_new() e gtk_toggle_button_new_with_label() creano il nuovo oggetto; si differenziano nel fatto che la prima crea un toggle button vuoto, e toccherà a noi inserirvi un'etichetta o un'immagine; la seconda invece crea un bottone con l'etichetta passatagli. Il toggle button è utile soprattutto per la capacità di ricordare e mantenere il proprio stato. Esteticamente (ma questo è un giudizio del tutto personale) sono da preferire i check button ai toggle button. Le proprietà del toggle button (memoria dello stato) sono ereditate dai check button e dai radio button.

Per verificare se un toggle button è premuto o meno si utilizza la funzione gtk_toggle_button_get_active(), la quale ritornerà un valore booleano, TRUE se il bottone che è passato alla funzione è premuto, FALSE altrimenti. Naturalmente è anche possibile modificare lo stato del bottone senza cliccarlo. Ad esempio, immaginiamo di dover scrivere una routine di configurazione, e di voler settare il toggle button a TRUE per default. Per far ciò si ricorre alla funzione gtk_toggle_button_set_active(), questa funzione accetta come primo argomento il puntatore al bottone da modificare, e come secondo un valore booleano, TRUE se vogliamo il bottone premuto, FALSE se lo vogliamo rilasciato.

I check Button

I check button, come accennato, ereditano dai toggle button la proprietà di ricordare il proprio stato, ma esteticamente sono molto diversi. Si presentano come un quadratino con a fianco, a scelta, una stringa; il bottone verrà selezionato cliccando nel quadratino. (Figura 6). Esistono solo due funzioni per i check button, le seguenti:

    GtkWidget *gtk_check_button_new( void );
    GtkWidget *gtk_check_button_new_with_label ( gchar *label );

la prima, gtk_check_button_new() quando richiamata crea un check button senza etichetta e ne ritorna il puntatore; la seconda gtk_check_button_new_with_label() crea il check button con etichetta allegata.


Figura 6 I check button

Ma come possiamo usare i check button, se le uniche funzioni a disposizione sono queste? La risposta è nella struttura ad oggetti di GTK+: osserviamo la gerarchia dell'oggetto check button

   GtkObject
    +----GtkWidget
          +----GtkContainer
                +----GtkBin
                      +----GtkButton
                            +----GtkToggleButton
                                  +----GtkCheckButton	

GtkCheckButton è derivato da GtkToggleButton, quindi eredita da quest'ultimo tutte le proprietà; dunque, per verificare lo stato di un check button utilizzeremo la funzione che verifica lo stato di un toggle button. Osserviamo le funzioni di callback associate ai due check button (ricordiamo che il primo argomento della funzione di callback è un puntatore all'oggetto che ha emesso il segnale che ha fatto richiamare la funzione, mentre il secondo è un puntatore agli eventuali dati da passare alla funzione):

    void on_checkbutton1_clicked (GtkButton *button, gpointer user_data)
    {
    gboolean bool;

    bool = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
    ...

button è il nostro check button, derivato dai toggle button, quindi per conoscere il suo stato applichiamo la macro GTK_TOGGLE_BUTTON, usate per trasformarlo in toggle button, e utilizziamo l'opportuna funzione per recuperarne lo stato. Naturalmente possiamo utilizzare lo stesso procedimento per settare lo stato del check button a TRUE o FALSE, ad esempio con gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE) si setterà il check button.

I Radio Button

I radio button si differenziano dai check button, oltre che per la forma (figura 7), anche per il fatto di essere utilizzati in gruppi; solo uno dei radio button appartenenti ad un determinato gruppo può essere attivo in un determinato istante. Utilizzando i radio button si può far effettuare all'utente una scelta fra più opzioni. Sono solo un po' più difficili da utilizzare dei check button, proprio per il fatto di dover considerare il gruppo di appartenenza dei bottoni. Ecco alcune funzioni che creano i radio button e il gruppo di appartenenza:

    GtkWidget *gtk_radio_button_new(GSList *group);
    GtkWidget *gtk_radio_button_new_with_label(GSList *group,
                                                 gchar  *label);
    GSList *gtk_radio_button_group(GtkRadioButton *radio_button);


Figura 7 I radio button

Per la creazione di un radio button si può utilizzare gtk_radio_button_new() o gtk_radio_button_new_with_label(), la prima creerà un radio button, la seconda affiancherà al radio button un'etichetta. Entrambe le funzioni richiedono un puntatore a GSList. Ma cos'è una GSList? Come accennato nel primo articolo, una delle librerie su cui si appoggia GTK+, Glib, mette a disposizione funzioni e strutture dati per trattare liste concatenate, alberi, tabelle hash. Sono disponibili due tipi di liste concatenate (linked list): le liste semplici, appunto le GSList (in cui un elemento punta al successivo) e liste doppie, le GList (nelle quali un elemento punta sia all'elemento successivo, sia a quello precedente).

Ogni volta che si crea un nuovo radio button è necessario chiamare la funzione gtk_radio_button_group(), in modo da associare il bottone appena creato al relativo gruppo di appartenenza. Inoltre, è opportuno che il puntatore alla lista del gruppo sia inizializzato a NULL, la prima volta che si creano gruppo e primo radio button. Osserviamo in basso alcune righe di codice del programma di prova: la variabile del gruppo viene inizializzata nel momento della sua dichiarazione, poi si crea il primo dei radio button che andranno a far parte del gruppo "gruppo_radio", e si inserisce il puntatore al bottone nella lista gruppo_radio. Questo procedimento si ripeterà per ogni bottone che appartiene al gruppo "gruppo_radio". Se la nostra applicazione prevede più gruppi di radio button, tutte queste operazioni vanno replicate per tutti i gruppi e per tutti i bottoni di ogni gruppo.

    ...  
    GSList *gruppo_radio = NULL; 	 
    ...
    	 
    radiobutton1 = gtk_radio_button_new_with_label (gruppo_radio, "nessuna");
    gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton1)); 

    ...
    
    radiobutton2 = gtk_radio_button_new_with_label (gruppo_radio, "interna");
    gruppo_radio = gtk_radio_button_group (GTK_RADIO_BUTTON (radiobutton2)); 
    ...

E' inoltre opportuno settare un bottone di ogni gruppo come bottone di default. Analogamente ai check button, per poter accedere al valore del bottone ed eventualmente modificarlo si utilizzano le funzioni definite per i toggle button, applicando al puntatore al radio button l'opportuno cast. Quindi se vogliamo, ad esempio, che radiobutton1 sia il bottone che appare selezionato all'avvio dell'applicazione, utilizzeremo una chiamata come questa:
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radiobutton1), TRUE);. Se vogliamo sapere se radiobutton2 è attivo utilizzeremo la chiamata
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radiobutton2));, che ci ritornerà un valore booleano (TRUE se il bottone è selezionato, FALSE se non lo è).

Siamo così giunti alla conclusione anche di questo articolo. In attesa del prossimo, vi invito ad esercitarvi, modificando il programma allegato. Provate ad esempio a far sì che uno qualunque dei widget della finestra "si nasconda" alla pressione di un bottone. Non dovrebbe essere difficile (leggete anche il primo articolo, se non ricordate qualcosa)! Ancora, provate a cambiare l'etichetta di un bottone, sempre alla pressione di un altro bottone. Suggerimento: intercettate il segnale "clicked" di un bottone e passate alla funzione di callback il puntatore al widget da nascondere o l'etichetta da modificare, e nella funzione callback mostrate/nascondete il widget o modificate l'etichetta. Alla prossima!




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: MultiGnomeTerminal ->