Object Arguments

Arguments are one of the most interesting features of GtkObject. Arguments are a mechanism for handling what CORBA's Interface Definition Language (IDL) calls an attribute: a value with a "getter" and a "setter." In concrete terms, object arguments pair a key (which is a string) with a value (represented as a GtkArg). Each GtkObject subclass can register permissible keys and the GtkTypes of their associated values.

Using object arguments, one can discover at runtime what attributes an object has, and then get or set their values. This is very useful for people implementing GUI builders, since some of the widget configuration dialogs can be automated. Similarly, it makes it much easier to write GTK+ bindings for scripting languages. It can also be convenient for programmers, since they can avoid writing all the get/set functions---the GnomeCanvas, for example, uses object arguments for almost all of its API. Finally, object arguments may be configurable via the gtkrc configuration mechanism in a future version of GTK+, making it possible for users to extensively customize GTK+ software.

Setting Object Arguments

Most commonly, arguments are used as an API to set attributes of widgets. However, not all of the GTK+ API has been exported via arguments, so it is not always possible.

To set widget attributes, the most convenient interface is gtk_object_set(). Here's an example:


   gtk_object_set(GTK_OBJECT(vbox), 
                  "GtkContainer::border_width", (gulong) 10,
                  NULL);

The above code is identical in effect to:


   gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);

It's up to you which to use; it depends on the context. Typically, you would use the argument mechanism if you have a reason to, i.e. if you are using its dynamic, runtime-oriented features. However, if you are setting several attributes, it may be easier to type and read.

gtk_object_set() takes a GtkObject as the first argument, followed by any number of key-value pairs. If a key is not defined for the object you pass in, a runtime error will be triggered. The list of key-value pairs must be terminated with a NULL key. When a GtkObject registers itself with GTK+, it tells GTK+ what type of value to expect after each key. For the aggregate fundamental types gtk_object_set() will expect more than one C function argument after the key. For example, first a signal function and then a user data pointer will be expected after GTK_TYPE_SIGNAL arguments. (Table 1 in the section called GtkArg and the Type System gives the types of the expected arguments.)

It is permissible to leave off the object class portion of an argument name---"GtkContainer::border_width" can be simply "border_width":


   gtk_object_set(GTK_OBJECT(vbox), 
                  "border_width", (gulong) 10,
                  NULL);

If you do not specify the class name as part of the argument name, GTK+ will start with the real type of the object and look up the argument name in the argument table for each superclass until it finds the right one (GtkContainer in this case). If you do specify the class name, GTK+ will only look for the argument in the specified class's argument table.

Since gtk_object_set() uses C variable argument lists, it has limited type safety. This can be a real problem in your code. You may have noticed the cast to gulong in the sample call to gtk_object_set(). The argument GtkContainer::border_width has type GTK_TYPE_ULONG. GTK+ will extract sizeof(gulong) bytes from the argument list when it encounters this argument. If you leave out the cast, C will probably pass only sizeof(gint) bytes to the function. As you might imagine, this causes memory corruption on many platforms. A similar problem arises with arguments of type GTK_TYPE_DOUBLE; if you type 5 instead of 5.0, C will pass an integer to gtk_object_set(). These bugs are very hard to find, once you introduce them.

gtk_object_set() is syntactic sugar for a more fundamental function call, gtk_object_setv(). gtk_object_setv() takes a vector of GtkArg (gtk_object_set() converts each key-value pair in its argument list to GtkArg internally).


   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);

The second argument to gtk_object_setv() is the length of the array of GtkArg. gtk_object_set() is plainly easier to use when you are typing the code in manually, but gtk_object_setv() can be passed a dynamically-constructed argument array---which is convenient if you're exporting GTK+ functionality to an interpreted environment.

It is also possible to set object arguments when objects are created. You can create most objects using the gtk_object_new() function, and most widgets with the gtk_widget_new() function. The routines take a GtkType as their first argument, and create an object or widget of that type. They then take a list of argument-value pairs, just as gtk_object_set() does. There are also gtk_object_newv() and gtk_widget_newv() variants.

Reading Object Arguments

To get the value of one or more arguments, you simply create an array of GtkArg, filling in the name field of each GtkArg. gtk_object_getv() fills in the type fields and the argument values. If an error occurs, the type field is set to GTK_TYPE_INVALID. If the fundamental type of the returned value is GTK_TYPE_STRING, GTK_TYPE_BOXED, or GTK_TYPE_ARGS, you are responsible for freeing it.

Here's a simple example:


  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("Border width: %lu Resize mode: %d\n", 
         GTK_VALUE_ULONG(args[0]), GTK_VALUE_ENUM(args[1]));

Using Object Arguments in Your Own GtkObject Subclass

If you're writing a custom GtkObject or a custom subclass of some existing object, you can register your own object arguments in the class initialization function, at the same time you register your object's signals. To do this, call gtk_object_add_arg_type()---here's an example from GtkContainer:


    gtk_object_add_arg_type("GtkContainer::border_width", 
                            GTK_TYPE_ULONG, 
                            GTK_ARG_READWRITE, 
                            ARG_BORDER_WIDTH);
  

The first argument must be a static string constant, because GTK+ does not copy it. It must also begin with the name of your new class, separated from the name of the argument by two colons (reminiscent of the C++ scope operator). The second argument should be the type of the argument; this can be any GtkType GTK+ knows about.

The third argument contains one or more flags, defined in gtk/gtkobject.h. The available flags are:

There are some limitations on which flags can be used.

The fourth and final argument to gtk_object_add_arg_type() is an argument ID to be used by the object subclass to identify this argument. This can be any integer except 0, but it is customary to use a private enumeration in the object implementation's .c file. GtkObject has two class functions any subclass with arguments must implement: one to get arguments specific to the subclass, and one to set them. These functions are passed the argument ID, so they know which argument to get or set. Argument IDs reduce the need for string comparisons, increasing the efficiency of argument manipulation.

For example, GtkContainer defines these functions:

 
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);

It uses this enumeration to create its argument IDs:


enum {
  ARG_0,              /* Skip 0, an invalid argument ID */
  ARG_BORDER_WIDTH,
  ARG_RESIZE_MODE,
  ARG_CHILD
};

It registers its arguments in gtk_container_class_init() as follows:


  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() and gtk_container_get_arg() are installed in the class struct:


  object_class->get_arg = gtk_container_get_arg;
  object_class->set_arg = gtk_container_set_arg;

gtk_container_set_arg() and gtk_container_get_arg() are then implemented like this:


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;
    }
}    

Notice that the type must be set to GTK_TYPE_INVALID if your subclass doesn't understand the argument ID. This is used as an error indicator; users who call gtk_object_getv() will check for it.

If you flip back to page XXXX and have another look at the GtkButton class initialization function, you should now understand what is going on with respect to object arguments.

Discovering the Available Object Arguments

You can easily find out at runtime what arguments a given GtkObject understands, using the gtk_object_query_args(). Here is a nifty piece of code which prints out the arguments for the entire class hierarchy of a given 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);
}

Notice that a type's parent type can be obtained using the gtk_type_parent() function, and that you can extract the GtkType tag from a GtkObject using the GTK_OBJECT_TYPE() macro. GTK_OBJECT_TYPE() is defined as follows:


#define GTK_OBJECT_TYPE(obj) (GTK_OBJECT (obj)->klass->type)

An object's type is stored in its class structure, and a pointer to an object's class structure is stored in each instance of the object. (The class structure pointer is called klass rather than class to avoid confusing C++ compilers.)

Figure 3 summarizes the functions for reading, writing, and querying object arguments.

#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);

Figure 3. Manipulating Object Arguments