2.4 Properties


2.4 Properties

You should set and retrieve attribute data on your GObject instances using the property system so that other programmers can get at data through a uniform interface, rather than going through a series of custom access functions.

Properties have names and descriptions, so they are self-documenting to a certain extent. In addition, exposing a GObject's data with properties allows you to employ an object design tool (such as Glade, see Chapter 5).

You'll encounter properties ad infinitum in GTK+ when you read Chapter 3, primarily when manipulating widget settings. [2]

2.4.1 Declaring Parameters

You must define each property in a class as a GObject parameter . To get started, you should obtain a GParamSpec structure in your class initialization function (described in Section 2.2.3).

You'll need the following information to create a GParamSpec structure:

  • An identifier . A short string will do, such as inventory-id .

  • A nickname . The full name of the parameter, such as inventory number .

  • A description . A concise explanation, such as number on the inventory label .

  • Options , such as read-write access.

  • Type-specific information, such as

    • Minimum value

    • Maximum value

    • Default value

    • A secondary type if you're encapsulating parameters (for example, G_TYPE_BOXED , G_TYPE_ENUM , G_TYPE_FLAGS , G_TYPE_OBJECT , G_TYPE_PARAM ).

    • Sizes of arrays.

Because it is somewhat complicated, you don't create a GParamSpec structure by hand. Instead, use one of the g_param_spec_ functions in the following table to allocate the structure and set its fields.

Function

Type

g_param_spec_boolean()

gboolean

g_param_spec_boxed()

GBoxed

g_param_spec_char()

gchar

g_param_spec_double()

gdouble

g_param_spec_enum()

GEnumClass, GEnumValue

g_param_spec_flags()

GFlagsClass

g_param_spec_float()

gfloat

g_param_spec_int()

gint

g_param_spec_int64()

gint64

g_param_spec_long()

glong

g_param_spec_object()

GObject

g_param_spec_param()

GParamSpec

g_param_spec_pointer()

gpointer

g_param_spec_string()

gchar *

g_param_spec_uchar()

guchar

g_param_spec_uint()

guint

g_param_spec_uint64()

guint64

g_param_spec_unichar()

gunichar

g_param_spec_value_array()

Array of some other type

Typically, you place the call to a g_param_spec_ function in your class initializer function. You may recall that the class initializer for the Media example is media_class_init() . Therefore, using parameters for inventory-id and orig-package , media_class_init() would look something like this:

 static void media_class_init(MediaClass *class) {   GParamSpec *inv_nr_param;   GParamSpec *orig_package_param;   << ... >>   /* create GParamSpec descriptions for properties */   inv_nr_param = g_param_spec_uint("inventory-id",     /* identifier */                                    "inventory number", /* nickname */                                    "number on inventory label",                                                        /* description */                                    0,                  /* minimum */                                    UINT_MAX,           /* maximum */                                    0,                  /* default */                                    G_PARAM_READWRITE); /* flags */   orig_package_param = g_param_spec_boolean("orig-package",                                             "original package?",                                             "is item in its original package?",                                             FALSE,                                             G_PARAM_READWRITE);   << ... >> } 

Although the actual inv_nr and orig_package fields from the Media instance structure aren't in this function, you will need to come back to them when you actually install the property in the class. The GParamSpec structure serves to describe the property: its purpose, type, and permissible values.

The last parameter to a g_param_spec_ function is a bit mask that you can specify with a bitwise OR of any of the following:

  • G_PARAM_CONSTRUCT indicates that GObject will assign a value to the property upon object instantiation. At the moment, this works only in conjunction with G_PARAM_CONSTRUCT .

  • G_PARAM_CONSTRUCT_ONLY indicates that the property may take on a value only when an object is instantiated .

  • G_PARAM_LAX_VALIDATION disables type checks when you write to this property. Set this only if you know exactly what you're doing.

  • G_PARAM_READABLE allows read access to the property.

  • G_PARAM_WRITABLE allows write access to the property.

  • G_PARAM_READWRITE is shorthand for G_PARAM_READABLEG_PARAM_WRITABLE .

Note  

As this chapter progresses, the media_class_init() function code in this section will grow.

2.4.2 Tangent: Generic Containers for Values

Before you get to Section 2.4.3, where you see how to activate a property in the class initializer, you need to familiarize yourself with how GObject moves the property values from place to place.

Untyped gpointer -style pointers normally take on the task of setting and retrieving function parameters of an arbitrary type. However, you need to store and check the type information at run time so that you don't try to do something disastrous, like attempting to copy a string into a memory location that corresponds to a random integer.

GObject has a mechanism for holding a value along with its type into a single "container," so that you can pass the container along as a parameter and pull its value and type out when necessary. This system is called GValue, with function names that start with g_value_ .

The actual container is the GValue data structure. If you need to create one, you can do it with GLib's elementary memory management utilities; there aren't any special functions just for GValues .

Warning  

That said, you must use g_malloc0() , g_new0() , or some other similar allocation function that sets all of the new memory bytes to zero. You'll get an error when you try to initialize a GValue structure with random bytes.

After creating a GValue structure gvalue , initialize it with

 g_value_init(  gvalue, type  ) 

where type is an identifier such as G_TYPE_INT (see page 78 for a full list). For each type identifier, the following are available:

  • A verification macro, G_VALUE_HOLDS_ TYPE ( gvalue ) , that returns TRUE when gvalue contains the type.

  • A writer function, g_value_set_ type ( gvalue , value ) , to place value into the gvalue container.

  • A reader function, g_value_get_ type ( gvalue ) , to fetch the value from gvalue .

Note  

If you can't decide on how often to verify the type inside a container, err on doing it too often instead of one time too few when you go to read or write a value.

The standard types are listed below. Here is a small GValue demonstration:

 /* gvaluedemo.c -- demonstrate GValue */ #include <glib-object.h> #include <stdio.h> int main(int argc, char **argv) {   GValue *value;   g_type_init(); /* initialize type system */   /* allocate new GValue, zero out contents */   value = g_new0(GValue, 1);   /* initialize GValue type to gint */   g_value_init(value, G_TYPE_INT);   /* set the value to 42 and read it out again */   g_value_set_int(value, 42);   g_print("Value: %d\n", g_value_get_int(value));   /* is the type of the GValue a GObject? */   if (G_VALUE_HOLDS_OBJECT(value))   {      g_print("Container holds a GObject\n");   } else {      g_print("Container does not hold a GObject\n");   }   /* expect "Container does not hold a GObject" */   g_free(value);   return 0; } 

There are two special access functions for GValue structures of G_TYPE_STRING : g_value_set_static_string( gvalue , str ) transfers a string pointer str into a gvalue , and g_value_dup_string( gvalue ) returns a copy of a string in gvalue (you will need to deallocate that copy when you're done with it, though).

Otherwise, the names are uniform. For each of the types in the following list, there is a verification macro and a reader and writer access function, as described earlier. For example, G_TYPE_CHAR comes with G_VALUE_HOLDS_CHAR() , g_value_get_char() , and g_value_set_char() .

G_TYPE_BOOLEAN

G_TYPE_FLOAT

G_TYPE_POINTER

G_TYPE_BOXED

G_TYPE_INT

G_TYPE_STRING

G_TYPE_CHAR

G_TYPE_INT64

G_TYPE_UCHAR

G_TYPE_DOUBLE

G_TYPE_LONG

G_TYPE_UINT

G_TYPE_ENUM

G_TYPE_OBJECT

G_TYPE_UINT64

G_TYPE_FLAGS

G_TYPE_PARAM

G_TYPE_ULONG

It might be prudent to note that GObject also defines a gchararray type that stands for gchar * . The advantage of using gchararray over a simple gchar pointer is in name only; when you have a gchararray array, you can be certain that it's a string and not some other type that you've cast to gchar * .

To reset a GValue to its original state ” that is, the zeroed memory that you had just before you ran g_value_init() ” use g_value_unset(gvalue) . This frees up any extra memory that gvalue is currently using and sets its bytes in memory to zero. At that point, it's ready to be used again.

2.4.3 Installing Properties

Now you're ready to install some properties. Recall that in Section 2.4.1 you came up with the GParamSpec structures for the Media class properties in the media_class_init() function. The property installation continues in that function, with a call to

 g_object_class_install_property(  class  ,  id  ,  param  ) 

where class is the class structure, param is the property's GParamSpec structure, and id is a unique identifier obtained with an enumeration type. This identifier should begin with PROP_ .

Warning  

The property identifier must be greater than zero, so you will need to place a dummy value like PROP_MEDIA_0 at the head of your enumeration type.

The code for media_class_init() should make things clear:

 enum {   PROP_MEDIA_0,   PROP_INV_NR,   PROP_ORIG_PACKAGE }; static void media_class_init(MediaClass *class) {   GParamSpec *inv_nr_param;   GParamSpec *orig_package_param;   GObjectClass *g_object_class;   /* get handle to base object */   g_object_class = G_OBJECT_CLASS(class);   << param structure setup from Section 2.4.1 >>   /* override base object methods */   g_object_class->set_property = media_set_property;   g_object_class->get_property = media_get_property; 
Warning  

Notice that you must set the set_property and get_property methods before installing the class properties.

 << ... >>   /* install properties */   g_object_class_install_property(g_object_class,                                   PROP_INV_NR,                                   inv_nr_param);   g_object_class_install_property(g_object_class,                                   PROP_ORIG_PACKAGE,                                   orig_package_param);   << ... >> } 
Note  

The code in Section 2.4.1 and here accesses GParamSpec structures with the temporary variables inv_nr_param and orig_package_param before installation with g_object_class_install_property() . Programmers normally omit these temporary variables, using the entire call to g_param_spec_boolean() as a parameter when they install the property.

To use the property system in the new class, media_class_init() must cast the new Media class structure ( class ) pointer to a GObject class structure named g_object_class . When you get a handle from a cast like this, you can override the set_property and get_property methods in the GObject base class. This is exactly what you must do to install the new properties such as inventory-id and orig-package . Remember that the base object knows nothing about the properties and instance structures of its subclasses.

The example overrides the base class methods with media_set_property() and media_get_property() , and therefore, you must supply prototypes before media_class_init() . These are straightforward, and because they are replacements for methods in the base class structure, they must conform to the prototypes in the base class structure:

 static void media_set_property(GObject *object,                                guint prop_id,                                const GValue *value,                                GParamSpec *pspec); static void media_get_property(GObject *object,                                guint prop_id,                                GValue *value,                                GParamSpec *pspec); 
Note  

You can avoid prototypes for static functions by placing the actual functions (described below) before media_class_init() .

Now that you've set up all of this infrastructure to handle the properties, you can write the functions that actually deal with the inv_nr and orig_package fields in the instance structure. The implementations consist of some busywork; to set a field, media_set_property() does the following:

  1. Determines the property to set.

  2. Removes the new property value from the GValue container from its parameter list.

  3. Sets the field in the instance structure ( finally! ).

Here is the actual code:

 static void media_set_property(GObject *object,                                guint prop_id,                                const GValue *value,                                GParamSpec *pspec) {   Media *media;   guint new_inv_nr;   gboolean new_orig_package;   media = MEDIA(object);   switch(prop_id)   {      case PROP_INV_NR:         new_inv_nr = g_value_get_uint(value);         if (media->inv_nr != new_inv_nr)         {            media->inv_nr = new_inv_nr;         }         break;      case PROP_ORIG_PACKAGE:         new_orig_package = g_value_get_boolean(value);         if (media->orig_package != new_orig_package)         {            media->orig_package = new_orig_package;         }         break;      default:         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);         break;   } } 

The media_get_property() code is similar, except that it needs to put one of the instance structure fields into the container rather than the other way around. No notification is necessary. (It would be very nosy of your program to tell the object every time someone looked at a property, but you can do it if you really want to.)

 static void media_get_property(GObject *object,                                guint prop_id,                                GValue *value,                                GParamSpec *pspec)   {    Media *media;    media = MEDIA(object);    switch(prop_id)    {       case PROP_INV_NR:          g_value_set_uint(value, media->inv_nr);          break;       case PROP_ORIG_PACKAGE:          g_value_set_boolean(value, media->orig_package);          break;       default:          G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);       break;   } } 

Take a close look at the default case in each of these functions. The default comes into play only when the function encounters an invalid property, and it runs G_OBJECT_WARN_INVALID_PROPERTY_ID() on the object, invalid property, and parameter structure. The resulting warning message will hopefully be strong enough to get you to check your property access function calls.

Why Properties?

You may be wondering why you need such a complicated system just to set a bunch of fields in your instance structure. After all, you could write access methods to do this, or you could even just tell the programmer to set the fields. However, you want a uniform system such as properties for the following reasons:

  • You need a dynamic system. Subclasses can add their own properties with little effort, as you will see in Section 2.7.

  • You want to define behavior for when properties change. This capability is extremely important in GUI programming, where you want the program to react to changes in buttons , check boxes, and other elements. If you haphazardly set instance structure fields instead of using properties, you would need to define a "reaction" function and make sure that any code that sets a field also calls that function. Even with access methods, this can get out of hand very quickly, especially if your "reaction" function needs to set other fields.

  • You want a system that's easy to document. It's easy to list property names with their possible values and descriptions. Access method APIs are considerably harder to describe, especially if the methods aren't uniform.

But What About Those Access Methods That I Keep Seeing?

In practice, you might see access methods for a class that correspond to properties in the class, especially with older code. Functionally, there is no difference; it's just one more layer of indirection.

Let's say that you have this to set the Media orig-package property:

 void media_set_orig_package(Media *object, gboolean new_value) {   Media *media;   g_return_if_fail(IS_MEDIA(object));   media = MEDIA(object);   if (media->orig_package != new_value)   {      media->orig_package = new_value;      g_object_notify(G_OBJECT(media), "orig-package");   } } 

This does all of the work that you see under case PROP_ORIG_PACKAGE: in media_set_property() from page 81. Therefore, you can rewrite that part:

 case PROP_ORIG_PACKAGE:        new_orig_package = g_value_set_boolean(value);        media_set_orig_package(media, new_orig_package);        break; 

This is primarily a matter of convention and a function of the age of the code. When reading API documentation, you may see an object with more properties than access function pairs, indicating that someone may have added more properties to some older code without bothering to use access functions (developers don't usually remove access functions for fear of breaking third-party applications). In that case, class _set_ property () contains a mix of access function calls and field assignments/notifications.

Direct access function calls are slightly faster because they do not have to look up the property identifier and encapsulate any values. However, this speedup usually doesn't matter.

[2] Unfortunately, some GTK+ classes do not have property interfaces; hopefully, that's not a permanent situation.




The Official GNOME 2 Developers Guide
The Official GNOME 2 Developers Guide
ISBN: 1593270305
EAN: 2147483647
Year: 2004
Pages: 108

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net