<- PW: Alle radici dello GNOME - Prima Puntata - Copertina - PW: Come ti metto in moto il telescopio ->

PlutoWare


GTK+: Alle radici dello GNOME

Seconda puntata: come creare un oggetto

L'elemento grafico fondamentale in GTK+ è, come già detto, il widget. Esistono diversi tipi di widget; si va dal widget che permette di rappresentare un bottone al widget che implementa un tooltip, al widget per le barre di scorrimento, e cosi via. Ogni widget deriva dalla classe base GtkWidget, e da questa eredita una serie di proprietà.

Ma come sono realmente realizzati gli oggetti in GTK+? Ogni GtkObject è formato essenzialmente da due strutture: una rappresenta la classe dell'oggetto, l'altra una istanza di quell'oggetto.
Osservate questo codice, estratto da un programma che sto scrivendo, una rubrica:

typedef struct _ModuloRubrica         ModuloRubrica;        
typedef struct _ModuloRubricaClass    ModuloRubricaClass;   

struct _ModuloRubrica { 
  GnomeDialog dialog;   

  /* il notebook */     
  GtkWidget *notebook;  

  /* lista degli oggetti contenuti nelle pagine del notebook */
  GList *childs;     

  ...
  
  MODO_MODULO modo;                            
  }                            

struct _ModuloRubricaClass {   
  GnomeDialogClass parent_class;    

  void (* modulo_rubrica_aiuto)   (ModuloRubrica *modulo, gint pagina);     
};                                                                          
 
guint modulo_rubrica_get_type (void);                         
GtkWidget *modulo_rubrica_new (MODO_MODULO modo);             

Per il mio programma avevo bisogno di una finestra di dialogo che presentasse all'utente i vari campi da riempire con i dati dell'indirizzo. Ho allora derivato dalla classe genitore GnomeDialog la mia classe ModuloRubrica; così facendo, la mia classe ha ereditato tutti metodi di GnomeDialog (che a sua volta è derivato da GtkWindow, derivata da un derivato di ... GtkWidget, ed ha ereditato i metodi di GtkWindow che ha ereditato ...). Come potete osservate il primo membro della struttura ModuloRubrica è GnomeDialog; in questo modo ModuloRubrica erediterà tutti i metodi e i campi di quest'ultimo, di fatto estendendolo, con un concetto di ereditarietà simile a quello di altri linguaggi; sarà poi possibile utilizzare i metodi della classe genitore effettuando un cast da ModuloRubrica a GnomeDialog. Lo stesso principio si applica per ModuloRubricaClass; in questo è inoltre presente un metodo che opererà sull'oggetto. Una piccola premessa: modulo_rubrica_aiuto() è in pratica una funzione di callback, una funzione cioè che verrà richiamata ogni volta che il widget ModuloRubrica emette un particolare segnale.

Per la realizzazione di un widget, è necessario scrivere alcune funzioni.

In GTK+, ogni oggetto ha un tipo, e ogni tipo è associato ad un intero, che deve essere unico. Quando si scrive un nuovo widget è necessario scrivere una funzione che restituisca il tipo del nuovo oggetto. La funzione Nome_del_Widget_get_type(), quando è chiamata per la prima volta, farà registrare il nuovo oggetto Nome_del_Widget e ritornerà l'identificatore assegnato all'oggetto. Questa funzione sostanzialmente riempie una struct con il nome del widget, la dimensione delle strutture create per il widget, i puntatori a due funzioni di inizializzazione (costruttori), una per la classe e l'altra per l'istanza dell'oggetto; due puntatori, mantenuti per compatibilità all'indietro e settati a NULL, e infine un puntatore ad una funzione di inizializzazione della classe di base. Una volta registrata la struttura, viene restituito un unsigned int che identifica in modo univoco il nostro widget.
Dopo la registrazione, ogni chiamata alla funzione Nome_del_Widget_new(), ritorna un oggetto opportunamente inizializzato.

Vediamo ora come avvengono l'inizializzazione della classe e dell'oggetto. Per inizializzare la classe, GTK+ verifica che tutte le classi superiori siano state inizializzate, e in caso negativo le inizializza in modo opportuno; quindi effettua una copia byte per byte della classe genitore nella classe derivata. In questo modo, la classe derivata eredita i puntatori a funzione della classe superiore (utilizzabili, come accennato precedentemente, tramite cast). Infine viene inizializzata la classe derivata. Se volessimo sostituire una funzione ereditata con una nostra funzione, basterebbe sostituire i puntatori a funzione nella nostra classe in modo appropriato.
Ad esempio, la classe dalla quale vengono derivati tutti i widget è GtkWidgetClass. In questa struttura sono definite le funzioni che operano sulla classe e, praticamente, su tutti i widget. La funzione che si occupa di visualizzare un widget è definita nel modo seguente:

void (* show) (GtkWidget *widget);

Se volessimo sostituire la funzione che ci mostra il widget, con la nostra funzione mostra_il_mio_widget(), basterebbe sostituire il puntatore a funzione durante l'inizializzazione nel modo seguente:

widget_class->show = mostra_il_mio_widget();

Oltre al costruttore, ogni widget deve avere un distruttore. La funzione distruttore è definita così in GtkObject:

void (*destroy)  (GtkObject *object);

Come potete osservare nella funzione che inizializza la mia classe, la funzione ereditata da GtkObject è stata sostituita con una funzione appropriata, che sa come distruggere il widget (ancora da terminare :).

object_class->destroy = modulo_rubrica_destroy;

In questo modo ogni volta che si distrugge il modulo_rubrica, viene richiamata la funzione puntata da object_class->destroy, cioè modulo_rubrica_destroy(). Durante l'inizializzazione della classe si definiscono, inoltre, gli eventuali segnali che il widget deve riconoscere, intercettare e gestire.

Dopo la classe viene inizializzato l'oggetto. Questa funzione è eseguita ogni volta che un nuovo oggetto viene istanziato.

Per finire un accenno alle macro che effettuano il cast.

#define MODULO_RUBRICA_TYPE                (modulo_rubrica_get_type())
#define MODULO_RUBRICA(obj)                    (GTK_CHECK_CAST((obj), MODULO_RUBRICA_TYPE, ModuloRubrica))
#define MODULO_RUBRICA_CLASS(klass)     (GTK_CHECK_CLASS_CAST((klass), MODULO_RUBRICA_TYPE, ModuloRubricaClass))
#define IS_MODULO_RUBRICA(obj)                (GTK_CHECK_TYPE((obj), MODULO_RUBRICA_TYPE))
#define IS_MODULO_RUBRICA_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), MODULO_RUBRICA_TYPE))

Queste macro permettono, ad esempio, di verificare se il widget passato ad una funzione è del tipo corretto; ad es:
void foo (ModuloRubrica *modulo)
{
	g_return_if_fail(IS_MODULO_RUBRICA(modulo));
	
	...
}
foo si aspetta come parametro un puntatore a ModuloRubrica, quindi a run time le librerie di GTK+ effettuano una verifica sul tipo del puntatore passato, utilizzando la macro IS_MODULO_RUBRICA(). Come detto nel precedente articolo, il nostro programma sta per la maggior parte del tempo a non fare nulla, o meglio esegue un loop infinito, in attesa che avvenga un qualche evento e di passare il controllo dell'applicazione ad una qualche funzione di callback, per ritornare poi nel ciclo principale dell'applicazione (gtk_main). Ora se nella funzione foo il test sul tipo è positivo l'elaborazione continua normalmente, altrimenti ritorna nel loop gtk_main(). Tramite queste macro è possibile accedere alle classi genitrici. Come già detto, ModuloRubrica è derivato da GnomeDialog, che è una finestra di dialogo con tre bottoni alla base. Per poter accedere ai bottoni, è stato necessario ricorrere ad una macro per convertire ModuloRubrica in uno GnomeDialog, e accedere così agli elementi della classe genitore.

In conclusione, se volete scrivere un widget per GTK+ (o gnome) avete bisogno almeno di:

  • una funzione che registri il vostro widget e vi ritorni il tipo appropriato
    GtkType nome_del_widget_get_type();
  • una funzione di inizializzazione per la classe e una per l'inizializzazione dell'oggetto
    static void nome_del_widget_class_init();
    static void nome_del_widget_init();
  • una funzione che distrugga il widget
    static void nome_del_widget_destroy(GtkObject *object)
  • una funzione che ritorni un puntatore ad una nuova istanza dell'oggetto
    nome_del_widget_new();


Ed ecco come appare il ModuloRubrica.

 
GtkType 
modulo_rubrica_get_type(void)
{
  static guint modulo_rubrica_type = 0;
  
  if (!modulo_rubrica_type)
    {
      static const GtkTypeInfo modulo_rubrica_info =
      {
		"ModuloRubrica",
		sizeof(ModuloRubrica),
		sizeof(ModuloRubricaClass),
		(GtkClassInitFunc)  modulo_rubrica_class_init,
		(GtkObjectInitFunc) modulo_rubrica_init,
		NULL,                   /* riservato per compatibilità */
		NULL,                   /* riservato per compatibilità */
      (GtkClassInitFunc) NULL
      };

      modulo_rubrica_type = gtk_type_unique(gnome_dialog_get_type(), 
                                            &modulo_rubrica_info);
    }

  return modulo_rubrica_type;
};

torna su

 
/*  inizializzazione della classe ModuloRubrica
*/
static void
modulo_rubrica_class_init(ModuloRubricaClass *klass)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkWindowClass *window_class;

  object_class = (GtkObjectClass*) klass;
  widget_class = (GtkWidgetClass*) klass;
  window_class = (GtkWindowClass*) klass;

  object_class->destroy = modulo_rubrica_destroy;
 
  parent_class = gtk_type_class(gnome_dialog_get_type());

/*  segnali gestiti dalla classe
*/
 modulo_rubrica_signals[HELP] =
    gtk_signal_new ("help", 
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET(ModuloRubricaClass, modulo_rubrica_aiuto),
		    modulo_rubrica_marshal_signal,
		    GTK_TYPE_NONE, 1, GTK_TYPE_INT);

 gtk_object_class_add_signals (object_class, modulo_rubrica_signals, LAST_SIGNAL);

 klass->modulo_rubrica_aiuto = NULL;
}


static void
modulo_rubrica_marshal_signal (GtkObject *object, GtkSignalFunc func,
			       gpointer func_data, GtkArg *args)
{
  ModuloRubricaSignal rfunc;

  rfunc = (ModuloRubricaSignal) func;
  (*rfunc) (object, GTK_VALUE_INT(args[0]), func_data);
}

torna su

 
/*  inizializzazione della struttura dati ModuloRubrica
*/
static void
modulo_rubrica_init (ModuloRubrica *modulo)
{
  /* bottoni globali al widget */ 
  GList *button_list;

  /* frame */
  GtkWidget *frame_dati;
  GtkWidget *frame_rete;
  GtkWidget *frame_telefono;
  GtkWidget *frame_note;

  /* etichette notebook */
  GtkWidget *label_dati;
  GtkWidget *label_rete;
  GtkWidget *label_telefono;
  GtkWidget *label_note;

  modulo->notebook = gtk_notebook_new();

  
  /* bottoni globali alla finestra di dialogo modulo */
  gnome_dialog_append_buttons(GNOME_DIALOG(modulo),
			      GNOME_STOCK_BUTTON_OK,				
			      GNOME_STOCK_BUTTON_HELP,
  			      GNOME_STOCK_BUTTON_CLOSE,
			      NULL);

su			      

  /*     bottone di default: -- ok
  */
  gnome_dialog_set_default (GNOME_DIALOG(modulo), 0);

  ...
  
  gtk_widget_show (modulo->notebook);
}

torna su

 
static void
modulo_rubrica_destroy(GtkObject *object)
{
  ModuloRubrica *modulo;
  
  g_return_if_fail (object != NULL);
  g_return_if_fail (IS_MODULO_RUBRICA(object));

  modulo = MODULO_RUBRICA(object);

  GTK_OBJECT_CLASS(parent_class)->destroy (object);
}

torna su

 
GtkWidget *
modulo_rubrica_new(MODO_MODULO modo)
{
  GtkWidget *modulo;
  
  if (count == 0)
    {
    modulo = gtk_type_new(modulo_rubrica_get_type());

    modulo_rubrica_set_modulo(modulo);
    }
  else 
    {
    modulo = modulo_rubrica_get_modulo();

    modulo_rubrica_clean_modulo(MODULO_RUBRICA(modulo));
    }

  MODULO_RUBRICA(modulo)->modo = modo;    

  ...

  return GTK_WIDGET(modulo);

}

torna su


<- PW: Alle radici dello GNOME - Prima Puntata - Copertina - PW: Come ti metto in moto il telescopio ->