Gli argomenti sono una della caratteristiche più interessanti di GtkObject. Gli argomenti sono un meccasismo per gestire quello che nella IDL (Interface Definition Language) di CORBA sono chiamati attributi: un valore con un "getter" e un "setter". Parlando concretamente, gli argomenti di un oggetti associano a una chiave (rappresentata da una stringa) un valore (rappresentato da un GtkArg). Ciascuna sottoclasse di GtkObject può registrare le chiavi consentite e il GtkType associato.
Utilizzando gli argomenti degli oggetti, è possibile scoprire a runtime quali attributi sono proprio dell'oggetto, e manipolare il loro valore. Questa caratteristisca si rivela molto utile per implementare applicativi per la creazione di interfacce grafiche, dato che alcune parti della configurazione dei widget sono praticamente automatizzate. Si rivela molto utile anche ai programmatori, dato che possono evitare di scrivere tutte le funzioni di lettura/scrittura. Ad esempio, il GnomeCanvas utilizza gli argomenti dell'oggetto in quasi tutta la sua API. Infine, questi argomenti devono essere configurabili tramite il file gtkrc nelle future versioni di GTK+, rendendo possibile per gli utenti la massima personalizzazione delle applicazioni GTK+.
Molto comunemente gli argomenti vengono utilizzati per impostare alcuni attributi dei widget. Tuttavia, non tutta la API di GTK+ è stata esportata attraverso gli argomenti, quindi non sempre è possibile eseguire questo tipo di operazione.
Per impostare gli attribuiti di un widget, l'interfaccia più conveniente risulta essere gtk_object_set(). Ecco un esempio:
gtk_object_set(GTK_OBJECT(vbox),
"GtkContainer::border_width", (gulong) 10,
NULL); |
Questo esempio ottiene lo stesso risultato di questo:
gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); |
Quale funzione utilizzare è una vostra scelta, che dipende dal contesto in cui si trova. Tipicamente, il meccanismo degli argomenti viene utilizzato se esistono buone ragioni per farlo, ad esempio se state utilizzando delle caratteristiche orientate al runtime, dinamiche. Tuttavia, se state impostando molteplici attributi, risulterà piu semplice digitare il codice e leggerlo.
gtk_object_set() prende un GtkObject come primo argomento, seguito da un numero qualsiasi di coppie chiave-valore. Se la coppia non è stata definita per l'oggetto a cui la passate, vi ritroverete a gestire un errore a runtime. La lista delle coppie chiave-valore deve essere terminata con NULL. Quando un GtkObject registra se stesso all'interno di GTK+, in quel momento definisce anche quali tipi di valore può accettare per ciascuna chiave. Per tipi fondamentali aggregati la funzione gtk_object_set() si aspetta più di un argomento di funzione C dopo la chiave. Ad esempio, prima una funzione segnale e poi un puntatore di dati utente saranno attesi dopo l'argomento GTK_TYPE_SIGNAL. (Tabella 1 nella la sezione GtkArg e il sistema di tipi fornisce maggiori informazioni).
È concesso omettere una porzione della classe dell'oggetto in un argomento: "GtkContainer::border_width" può semplicemente essere sostituito da "border_width":
gtk_object_set(GTK_OBJECT(vbox),
"border_width", (gulong) 10,
NULL); |
Se non specificate il nome della classe come parte dell'argomento, GTK+ inizierà con il tipo reale dell'oggetto e andrà a cercare fra gli argomenti tutte le classi superiori, alla ricerca di quella corretta (GtkContainer in questo caso). Se non specificate il nome della clsse, GTK+ cercherà unicamente per l'argomento nella tabella della classe specifica dell'argomento passato.
Dato che gtk_object_set() utilizza come argomenti una lista di variabili C, la sua sicurezza sui tipi è limitata. Questo può diventare un vero problema nel vostro codice. Potrete aver notato che il cast verso gulong nell'esempio della chiamata a gtk_object_set(). L'argomento GtkContainer::border_width è di tipo GTK_TYPE_ULONG. GTK+ estrarrà sizeof(gulong) bytes della lista degli argomenti quando si imbatte in questo. Se lasciate eseguire il cast al C, molto probabilmente passerete alla funzione sizeof(gint). Come potete immaginare, questo causa un errore di corruzione di memoria su molte piattaforme. Un problema molto simile compare quando utilizziamo un argomento di tipo GTK_TYPE_DOUBLE. Se digitate 5 invece che 5.0, il C passerà un intero a gtk_object_set(). Questi errori sono molto difficili da scovare, una volta commessi.
gtk_object_set() è unicamente un abbellimento sintattico per una funzione ancor più fondamentale, gtk_object_setv(). gtk_object_setv() prende un vettore di GtkArg (gtk_object_set() converte internamente ciascuna coppia chiave-valore presente nella lista dei suo argomenti in un GtkArg).
GtkArg args[1];
args[0].name = "GtkContainer::border_width";
args[0].type = GTK_TYPE_ULONG;
GTK_VALUE_ULONG(args[0]) = 10;
gtk_object_setv(GTK_OBJECT(button),
1,
args); |
Il secondo argomento per gtk_object_setv() è la lunghezza dell'array di GtkArg. gtk_object_set() è molto più semplice da utilizzare quando viene scritto manualmente il codice, ma a gtk_object_setv() può essere passata un array di argomento generato dinamicamente, il che risulta molto conveninente se state esportando le funzionalità di GTK+ in un ambiente interpretato.
È inoltre possibile impostare gli argomenti degli oggetti quando questi vengono creati. Creare nuovi oggetti con la funzione gtk_object_new(), e la maggior parte dei widget con gtk_widget_new() risulta ormai facile. Questo funzioni prendono un GtkType come primo argomento, e creano un oggetto o un widget di quel tipo. Quindi prendono la lista delle coppie chiave-valore dagli argomenti, proprio come gtk_object_set(). Esistono inoltre le varianti gtk_object_newv() e gtk_widget_newv().
Per ottenere il valore di uno o più argomenti, dovete semplicemente creare un array di GtkArg, e riempire il campo name per ciascun GtkArg. gtk_object_getv() rimpie il campo type e i valori degli argomenti. Se si verifica un errore, il campo type viene posto uguale a GTK_TYPE_INVALID. Se il tipo fondamentale del valore restituito è GTK_TYPE_STRING, GTK_TYPE_BOXED, oppure GTK_TYPE_ARGS, siete voi i responsabili delle liberazione della memoria occupata da questi.
Ecco un piccolo esempio:
GtkArg args[2];
args[0].name = "GtkContainer::border_width";
args[1].name = "GtkContainer::resize_mode";
gtk_object_getv(GTK_OBJECT(button),
2,
args);
g_assert(args[0].type == GTK_TYPE_ULONG);
g_assert(args[1].type == GTK_TYPE_RESIZE_MODE);
g_assert(GTK_FUNDAMENTAL_TYPE(args[1].type) == GTK_TYPE_ENUM);
printf("Spessore bordo: %lu Modo Resize: %d\n",
GTK_VALUE_ULONG(args[0]), GTK_VALUE_ENUM(args[1])); |
Se state scrivendo un GtkObject personalizzato oppure una nuova sottoclasse di un oggetto già esistente, è possibile registrare gli argomenti del vostro nuovo oggetto nella funzione di inizializzazione della classe, nello stesso momento in cui registrati i segnali. Per eseguire questo, chiamate la funzione gtk_object_add_arg_type(). Ecco un esempio tratto da GtkContainer:
gtk_object_add_arg_type("GtkContainer::border_width",
GTK_TYPE_ULONG,
GTK_ARG_READWRITE,
ARG_BORDER_WIDTH);
|
Il primo argomento deve essere una stringa costante static (static const), poiché GTK+ non la copierà. Deve inoltre iniziare con il nome della vostra nuova classe, separata dal nome dell'argomento da due due-punti (::). Il secondo argomento dovrebbe essere il tipo dell'argomento. Questo può essere un qualunque GtkType di cui GTK+ sia a conoscenza.
Il terzo argomento contiene uno o più flag, definiti in gtk/gtkobject.h. I flag disponibili sono:
GTK_ARG_READABLE significa che il valore dell'argomento può essere letto, utilizzando gtk_object_getv().
GTK_ARG_WRITABLE significa che il valore dell'argomento può essere impstato, utilizzando gtk_object_set() oppure gtk_object_setv().
GTK_ARG_CONSTRUCT indica che l'argomento deve essere inizializzato con un valore di default. Questo si applica per i tipi numerici oppure puntatori. Vengono impostati uguali a 0 oppure a NULL, rispettivamente. (Questo accade durante la chiamata a gtk_object_new() oppure gtk_widget_new(), le quali chiamano internamente gtk_object_default_construct().).
GTK_ARG_CONSTRUCT_ONLY significa che l'argomento viene utilizzato unicamente per la costruzione dell'oggetto. Non può essere letto o scritto successivamente, quindi non è possibile utilizzare questo tipi di argomento con gtk_object_set().
GTK_ARG_CHILD_ARG viene utilizzato dalle sottoclassi di GtkContainer. GtkContainer implementa una particolare variazione nel sistema degli argomenti per permettere le impostazioni sul contenimento dei widget. Utilizzerete questi flag unicamente se state implementando un nuovo tipo di contenitore, o un oggetto con semantica similae.
GTK_ARG_READWRITE è una abbreviazione per (GTK_ARG_READABLE | GTK_ARG_WRITABLE).
Esistono tuttavia delle limitazioni al loro utilizzo.
Tutti gli argomenti devono essere almeno leggibili o scrivibili.
Gli argomenti di tipo GTK_ARG_CONSTRUCT devono essere sia leggibili che scrivibili.
Gli argomenti GTK_ARG_CONSTRUCT_ONLY devono essere scrivibili.
GTK_ARG_CHILD_ARG non deve essere utilizzato al di fuori di una implementazione di un oggetto contenitore. Viene utilizzato internamente dalle funzioni di GtkContainer.
Il quarto e ultimo argomento per gtk_object_add_arg_type() consiste in un identificatore, utilizzato dalle sottoclassi dell'oggetto per identificare proprio questo argomento. Può essere un qualsiasi numero intero tranne che 0, ma è anche possibile utilizzare una enumerazione private nel file .c dell'implementazione dell'oggetto. GtkObject fornisce due funzioni di classe che tutte le sottoclassi con argomenti devono implementare: una ottiene gli argomenti specifici della sottoclasse, mentra la secondo li scrive. Queste funzioni vengono passate all'argomento identificatore, in modo da sapere quali argomenti leggere e quali scrivere.
Ad esempio, GtkContainer defisce queste funzioni:
static void gtk_container_get_arg(GtkObject* object,
GtkArg* arg,
guint arg_id);
static void gtk_container_set_arg(GtkObject* object,
GtkArg* arg,
guint arg_id); |
Utilizza questa enumerazione per creare gli identificatori dei suoi argomenti.
enum {
ARG_0, /* Skip 0, an invalid argument ID */
ARG_BORDER_WIDTH,
ARG_RESIZE_MODE,
ARG_CHILD
}; |
Inoltre, registra i suoi argomenti in gtk_container_class_init() in questo modo:
gtk_object_add_arg_type("GtkContainer::border_width",
GTK_TYPE_ULONG,
GTK_ARG_READWRITE,
ARG_BORDER_WIDTH);
gtk_object_add_arg_type("GtkContainer::resize_mode",
GTK_TYPE_RESIZE_MODE,
GTK_ARG_READWRITE,
ARG_RESIZE_MODE);
gtk_object_add_arg_type("GtkContainer::child",
GTK_TYPE_WIDGET,
GTK_ARG_WRITABLE,
ARG_CHILD); |
gtk_container_set_arg() e gtk_container_get_arg() vengono chiamate nella struttura della classe:
object_class->get_arg = gtk_container_get_arg; object_class->set_arg = gtk_container_set_arg; |
gtk_container_set_arg() e gtk_container_get_arg() sono implementate come segue:
static void
gtk_container_set_arg (GtkObject *object,
GtkArg *arg,
guint arg_id)
{
GtkContainer *container;
container = GTK_CONTAINER (object);
switch (arg_id)
{
case ARG_BORDER_WIDTH:
gtk_container_set_border_width (container, GTK_VALUE_ULONG (*arg));
break;
case ARG_RESIZE_MODE:
gtk_container_set_resize_mode (container, GTK_VALUE_ENUM (*arg));
break;
case ARG_CHILD:
gtk_container_add (container, GTK_WIDGET (GTK_VALUE_OBJECT (*arg)));
break;
default:
break;
}
}
static void
gtk_container_get_arg (GtkObject *object,
GtkArg *arg,
guint arg_id)
{
GtkContainer *container;
container = GTK_CONTAINER (object);
switch (arg_id)
{
case ARG_BORDER_WIDTH:
GTK_VALUE_ULONG (*arg) = container->border_width;
break;
case ARG_RESIZE_MODE:
GTK_VALUE_ENUM (*arg) = container->resize_mode;
break;
default:
arg->type = GTK_TYPE_INVALID;
break;
}
} |
Notare che il tipo deve essere impostato uguale a GTK_TYPE_INVALID se la vostra sottoclasse non supporta gli identificatori degli argomenti. Questo viene utilizzato come un indicatore di errore. Gli utenti che chiamano gtk_object_getv() potranno valutare questa situazione.
Se tornate un attimo indietro nel libro e guardate nuovamente la funzione per l'inizializzazione della classe GtkButton, dovreste ora comprendere cosa accade riguardo gli argomenti per gli oggetti.
È molto semplice scoprire a runtime quali siano gli argomenti disponibili di un dato GtkObject, utilizzando la funzione gtk_object_query_args(). Ecco un interessante esempio di codice che stampa tutti gli argomenti disponili nella classe di un dato GtkObject:
void
print_arguments(GtkObject* object)
{
GtkType type;
type = GTK_OBJECT_TYPE(object);
do {
GtkArg* args;
guint32* flags;
guint n_args;
guint i;
args = gtk_object_query_args(type,
&flags,
&n_args);
printf("Displaying arguments for object type `%s'\n",
gtk_type_name(type));
i = 0;
while (i < n_args)
{
printf(" - Argument %u is called `%s' and has type `%s'\n",
i,
args[i].name,
gtk_type_name(args[i].type));
++i;
}
g_free(args);
g_free(flags);
type = gtk_type_parent(type);
}
while (type != GTK_TYPE_INVALID);
} |
Notare che il tipo padre di un tipo può essere ottenuto utilizzando gtk_type_parent(), e che è possibile estrarre il tag GtkType da un GtkObject utilizzando la macro GTK_OBJECT_TYPE(). GTK_OBJECT_TYPE() viene così definita:
#define GTK_OBJECT_TYPE(obj) (GTK_OBJECT (obj)->klass->type) |
Il tipo di un oggetto viene registrato all'interno della struttura della propria classe, e in ciascuna istanza dell'oggetto viene mantenuto un puntatore alla struttura della classe. (Il puntatore alla struttura della classe viene chiamato klass anziché class per non creare problemi ai compilatori C++).
Figura 3 elenca le funzioni per leggere, scrivere e ottenere gli argomenti di un oggetto.
#include <gtk/gtkobject.h> |
void gtk_object_getv(GtkObject* object, guint n_args, GtkArg* args);
void gtk_object_set(GtkObject* object, const gchar* first_arg_name, ...);
void gtk_object_setv(GtkObjec* object, guint n_args, GtkArg* args);
void gtk_object_add_arg_type(const gchar* arg_name, GtkType arg_type, guint arg_flags, guint arg_id);
GtkArg* gtk_object_query_args(GtkType class_type, guint32** arg_flags, guint* n_args);
Figura 3. Manipolare gli argomenti degli oggetti