GTK+

The GIMP Tookit (GTK+) contains the basic visual building blocks in a GNOME application. These building blocks make use of the resources provided by GDK, like fonts, windows, colors, and events, to create an interface that looks and behaves like a modern GUI application.

Widgets

In GTK+, anything that can respond to your clicks, drags, and key presses is a widget of some sort. Windows, menus, push-buttons, and edit boxes are all widgets. Widgets can be grouped into larger composite widgets, which then act as a single widget. Menus work like this. The menu bar is a widget that contains smaller individual label widgets, each of which pops up another menu widget when you click on it. Another example of a composite widget is the dialog box, a pop-up window that contains an assortment of widgets and one or more push-buttons to accept or dismiss the changes or information contained within the dialog box.

All widgets have a set of basic properties that tell GTK+ how to interact with them. The most obvious state of a widget is visibility. You can show a widget to make it visible, or you can hide it to make it invisible. Users can interact only with visible widgets. Nested widgets always hide when their parents hide; if you hide a dialog box widget, GTK+ will hide all of its contents as well.

If you want to turn off a widget without hiding it, you can set its sensitivity to FALSE. An insensitive widget takes on a different appearance, usually grayed out or dulled, and ignores all input. An insensitive push-button will not depress when you click on it. By default, all widgets are created with a sensitivity of TRUE.

When you interact with widgets using the mouse, it's obvious which widget you are referring to: the one beneath the mouse cursor. Keyboard interaction is a little different. You wouldn't want the current position of the cursor to indicate which widget should receive the key presses. If you hap- pened to bump the mouse while you were typing, the cursor might move off of that widget and onto another one. Half of your text would go into the text widget, and the rest would disappear forever into that nearby button widget. Fur- thermore, if you were typing into a particularly small text widget, the cursor might end up blocking your view of what you've typed.

By established GUI convention, the latest mouse click determines the focus. You can click on the text widget (or another focus-bearing widget), and the widget will receive all key presses until you explicitly change the focus by clicking on something different, or by hitting the Tab key. Each window in an application will have a single widget with the default focus. If you start typing without first clicking on a widget, the default widget will receive all the key presses.

Figure 2.6 shows some common widgets as used in the GNOME menu editor application, including the menu bar and toolbar, a tree control on the left, and a notebook widget on the right, with two tabs, Basic and Advanced. Within the Advanced notebook tab, we see a couple grayed-out (insensitive) text widgets at the top, a GtkCList column list widget for the translations, three active (sensitive) text widgets, and four push-buttons at the bottom. The Save and Revert buttons have icons in them and are insensitive.

Figure 2-6. Widget Appearances

The GTK+ Type System

One of the more impressive features of GTK+ is its dynamic, hierarchical data-typing system. GTK+ uses dynamic typing for various things, including object comparisons, type checking in variable parameter lists, and signal mar- shaling. The dynamic nature of the type system allows GTK+ to initialize types only as it needs them, rather than loading a huge master list of types that it may never use. Also, bindings to interactive programming languages (like Python or Perl) can make better use of the type system during runtime than they could during compile time if the type system were static. Dynamic typing also makes it possible to load in new types without recompiling GTK+ -for example, if your application creates its own set of private widgets.

GTK+ registers each type the first time it is accessed,2 referring to each type by a unique integer value. You can access this type value through a function that, by convention, has the form nnn_www_get_type( ), where the nnn is the namespace, and www is the name of the object or widget in question. Thus you can get GtkWidget's type with gtk_widget_get_type( ), and GnomeApp's type with gnome_app_get_type( ). The get_type( ) functions are global, so all instances of GtkWidget will have the same type, and so on. Often a widget defines a macro for its type function. For example, GTK_TYPE_WIDGET is equivalent to gtk_widget_get_type( ).

A more useful macro is the nnn_IS_www(obj) Boolean query-for example, GTK_IS_WINDOW(obj) and GNOME_IS_APP(obj). This query macro returns TRUE if the type of the supplied obj instance is a derivative of the www type, and FALSE if obj is not derived from www. This brings us to the hierarchical nature of the GTK+ type system. Any new widget must supply a base type that it wraps and extends. To illustrate this concept, consider the derivation path of the GnomeApp widget:

GtkObject  GtkWidget  GtkContainer  GtkBin  GtkWindow  GnomeApp
        

The base type of GtkContainer is GtkWidget, and the base type of GnomeApp is GtkWindow, and so on. More than one object can derive from the same base: For example, GtkButton and GtkWindow are both derived from GtkBin; thus GtkButton is outside the inheritance path of GnomeApp, even though the two share a common ancestor in the GtkBin widget. To make sense of this many-forked tree of types, each widget declares the nnn_IS_www(obj) macro. The following code sample illustrates the use of this macro.

GtkWindow *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
GtkButton *button = gtk_button_new(  );
GnomeApp *app = gnome_app_new("myapp", "App Title");

if(GTK_IS_WINDOW(window))
  g_message("TRUE: window is a window");
if(GTK_IS_WINDOW(app))
  g_message("TRUE: app is a window");

if(GTK_IS_CONTAINER(window))
  g_message("TRUE: window is a container");
if(GTK_IS_CONTAINER(button))
  g_message("TRUE: button is a container");

if(!GTK_IS_WINDOW(button))
  g_warning("FALSE: button is not derived from window");
if(!GNOME_IS_APP(window)) 
  g_warning("FALSE: doesn't work in both directions...");
        

You may have noticed that we did not correctly cast the initial GtkWindow pointer in this example. The gtk_window_new( ) function actually returns a GtkWidget, as do most of the other _new( ) functions in GTK+ and GNOME. The correct way to handle this is with another casting macro:

GtkWindow *window =
  GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
GtkButton *button = GTK_BUTTON(gtk_button_new(  ));
GnomeApp *app = GNOME_APP(gnome_app_new("myapp", "App Title"));
        

If GTK+ and GNOME are compiled with widget debugging enabled, an improper cast will result in a warning message. This makes it easy to track casting errors. If GTK+ and GNOME are compiled with widget debugging disabled, as with a distribution release, the casting macros do only a straight C-style cast, without the overhead of a GTK+ type check and logging message. Note that the behavior of the casting macros is determined by the --enable-debug option to the configure script for GTK+ and GNOME, and not by the -g compiler option.

Object Classes

Even though GTK+ is written in C, a non-object-oriented language, it is nevertheless implemented with an object-oriented structure. It makes use of some clever techniques to provide all of the important features of object-oriented programming, including private data, encapsulation, and even virtual function calls. Although GTK+ uses primarily its object system to express widgets and its graphical subsystem, GTK+'s object system is generic enough to use outside of graphical toolkits. This will become even more pertinent in GTK+ 2.0, when the foundation of the object system will be distilled from GTK+ and moved into GLib for non-GTK+ applications to use.

As we saw in the previous section, the GTK+ type system is already geared to handle hierarchical derivation of types. This is exactly what we need for an object-oriented infrastructure, to work as the backbone of the object system. For an object to inherit properties and behaviors from parent objects, it must be able to trace a path to those parents. GTK+ uses inheritance in all of its GUI objects.

GTK+ makes use of virtual functions to handle event-related callbacks. A virtual function is a reroutable function inherited from its parent by a derived object, which the derived object can intercept if it desires. GTK+ fre- quently uses virtual functions to set up custom callback functions to handle the same event differently in derived widgets. For example, GtkWidget declares a draw virtual function that points to the gtk_widget_draw( ) function. GtkButton inherits the same draw virtual function from GtkWidget but overrides it to point to its own gtk_button_draw( ) function instead. A derived object does not have to override a virtual function if it doesn't want to. In fact, GtkWidget implements many virtual functions, most of which are kept and used by the objects derived from it.

The outcome of all this is that each new type of object carries a table of its own virtual functions, plus all the virtual functions of its parent types. In our inheritance example in the previous section, the GnomeApp widget contains all the virtual functions of GtkWindow, GtkBin, GtkContainer, GtkWidget, and GtkObject, any of which it can override as it sees fit. Each object type must have its own private copy of the function tables so that overriding a virtual function in a child type doesn't affect the virtual function set up in the parent type.

To keep things organized, GTK+ defines a class structure to contain each object type's virtual function table. The class is more or less a concrete declaration of the object type's behavior, or more precisely a template for defining it. The class structure is usually pretty simple. The first field of the structure is always the parent class structure, which is what causes the derived class to in- herit its parent's virtual functions. If the object type introduces any new virtual functions, they follow the parent class as prototyped function pointers. Let's take a look at a pair of parent and child class structures, GtkButton and GtkToggleButton:

typedef struct _GtkButtonClass GtkButtonClass;
struct _GtkButtonClass
{
  GtkBinClass        parent_class;

  void (* pressed)  (GtkButton *button);
  void (* released) (GtkButton *button);
  void (* clicked)  (GtkButton *button);
  void (* enter)    (GtkButton *button);
  void (* leave)    (GtkButton *button);
};

typedef struct _GtkToggleButtonClass GtkToggleButtonClass;
struct _GtkToggleButtonClass
{
  GtkButtonClass parent_class;

  void (* toggled) (GtkToggleButton *toggle_button);
};
        

The GtkToggleButton class contains-or technically aggregates-the entire GtkButtonClass as the parent_class field, which in turn contains GtkBinClass, and so on, all the way back to GtkObjectClass. When the GtkToggleButton type is initialized, GTK+ copies the previously initialized contents of GtkButtonClass into GtkToggleButton's parent_class, and then customizes that copy to its own needs.

When the toggle button needs to invoke any of its inherited virtual functions, it can access them through the parent_class field:

toggle_button_class.parent_class.pressed(button);
        

More commonly, though, the object casts its own class to the parent class; for this reason, the parent class must always be the first field in the structure. Otherwise, the class casting won't work.

GTK_BUTTON_CLASS(toggle_button_class)->pressed(button);
        

The GTK_BUTTON_CLASS(klass) macro is very similar to the GTK_BUTTON(obj) macro, except that it casts an object's class, rather than the instance of that object.

Object Properties

An object's class is global. All objects of that type will use the same class to invoke their virtual functions. If you have three GtkButton widgets in your application, they will all call into the same virtual function to perform their duties, such as reacting to clicks and redrawing themselves. One class structure services all three buttons. This model works great for function tables, but it doesn't really help keep the instance data separate. At the very least, each button widget needs to keep track of its own position and label text. If you move one button, you don't want all of them to move, nor do you want all of them to display the same text.

So just as each object type has its own class structure, each instance of each object has its own personal data structure. This data structure looks similar to the class structure because it, too, must contain an instance of its parent's data structure. This convention is critical for the GTK+ object system because it allows a derived object to contain the same properties as its parent objects. For this reason you can cast an object down to any of its parent types. GTK+ uses this casting to make function calls on an object from various parts of the hierarchy. Given a GtkWindow widget, which is derived from a GtkWidget, the following is possible:

GtkWindow *window;
window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gtk_window_set_title(window, "New Window Title");
gtk_widget_show(GTK_WIDGET(window));
        

The casting macros we introduced in Section 2.3.2 safely convert the GtkWindow pointer into a GtkWidget pointer. The GtkWindow pointer actually refers to an instance of object data. The _new( ) functions dynamically allocate a new structure of object data each time they are called. It is your job to free up that data when you are done with the object, by calling gtk_object_destroy( ) on it (or by unreferencing it, as the case may be). GTK+ does a good job of cleaning up embedded objects, so if you have a window object filled to the brim with other objects, you have to destroy only the outermost:

gtk_object_destroy(GTK_OBJECT(window));
        

To return to our toggle button example in the previous section, the data structures look something like the following. Compare these to the class structures.

typedef struct _GtkButton GtkButton;
struct _GtkButton
{
  GtkBin bin;

  GtkWidget *child;
  guint in_button : 1;
  guint button_down : 1;
  guint relief : 2;
};

typedef struct _GtkToggleButton GtkToggleButton;
struct _GtkToggleButton
{
  GtkButton button;

  guint active : 1;
  guint draw_indicator : 1;
  GdkWindow *event_window;
};
        

Normally you wouldn't access these data fields directly. Rather than referring to toggle_button->active, you would use the accessor function gtk_toggle_button_set_active( ). Most objects supply accessor functions for their important data fields, so you should use them whenever possible. If an object doesn't have an accessor function for a field in its data structure, you should think twice before accessing it directly.

Signals

Any GUI toolkit needs some sort of communication system to propagate events and data from widget to widget. Even though GTK+ already contains an event loop (from GLib) and a basic event system (from GDK), it needs more to carry out its duties. GDK doesn't provide enough flexibility for a full-scale message-passing system. GdkEvent is too narrowly focused on propagating events from the X server to the application. It can't handle the more generic requirements that GTK+ needs, like state change notifications, callback chains, and a fine-toothed control over the propagation of the events from widget to widget. To get all this, the GTK+ developers had to overlay a more powerful event system on top of GDK's, called the GTK+ signal system (not related to UNIX signals such as SIGINT or SIGTERM).

GTK+ signals are very general and extremely customizable, and they are rooted in the GTK+ widget system. Signals are typically triggered by a GdkEvent, then passed on by the main loop; GTK+ decides which widget the event was intended for, converts it into a signal, and sends the signal to that widget. The target widget handles the signal as it sees fit, then perhaps passes the signal on to other widgets. Depending on the outcome, the widget might also trigger new signals of its own, thus causing a potential cascade of various signals, all a result of the single original GdkEvent. This further demonstrates the need for a finer-grained communication system than the one GDK provides.

Each GTK+ object can define its own unique set of signals as part of its GTK+ class initialization; a common approach is to wire signals into the class's virtual function table so that when the signal is received, the object runs the corresponding function. This approach is used by derived object classes to take over handling of signals from their parent class. The virtual function is often set up as the default handler for the signal. The default handler is visible only to the object's internals, not to the outside world.

One of the most important uses of GTK+ signals is notification. Anyone can connect a callback function to an object's signal. Whenever the signal reaches that object, GTK+ invokes each callback function connected to that ob- ject. A single object might have many callback functions connected to each one of its signals. Signal connections are not, however, global. Connecting a signal to one object does not affect the same signal on a different object. This makes good sense: When you click on a push-button widget, you want only that one to respond, not the other three nearby buttons.

All you need to connect to a signal is a handler function and a call to gtk_signal_connect( ). Each signal might expect a different function prototype for its callback, so you should pay attention when you're writing your handlers. For example, GtkButton's pressed signal has two parameters, while GtkWidget's event signal has three:

void (* pressed)  (GtkButton *button, gpointer user_data);
gint (* event) (GtkWidget* widget, GdkEvent* event,
    gpointer user_data);
        

When in doubt, look inside the object's header file, in the class structure. The prototype for the pressed signal looks almost exactly like the pressed virtual function in GtkButtonClass. The difference is the extra user_data parameter tacked onto the end of each handler prototype. This extra parameter lets you assign additional data to specific handler invocations; each time you call gtk_signal_connect( ) to add another handler, you can pass this extra data as its user_data parameter. For example, you may want a single button press to trigger changes in two different data structures. You might connect twice to the same signal, passing the first data structure as the user_data on the first connection to the pressed signal, and the second data structure to the second connection. Each time the user clicks the button, GTK+ will call both handlers with the appropriate extra data.

The gtk_signal_connect( ) function looks like this:

guint gtk_signal_connect(GtkObject *object, const gchar *name,
    GtkSignalFunc func, gpointer user_data);
        

The object parameter is the object or widget to which you want to connect the signal handler; name holds the text name for the signal, which usually has the same name as the corresponding virtual function in the object's class. The func parameter is a function pointer to your handler, and user_data is the user data we mentioned earlier. Listing 2.2 shows how our double-connection example might look with two separate handlers. If your two chunks of user data have a similar structure-for example, if you're passing string values in the user_data parameter-you might even be able to get by with only a single handler function and connect twice to it, using the user_data parameter to distinguish between the two. This technique is commonly used with event handlers. Listing 2.3 explores this possibility.

Listing 2.2 Signal Connection Example with Two Handlers

void first_pressed_cb(GtkButton *button, gpointer data)
{
  MyFirstStruct *first = (MyFirstStruct*)data;
  update_first(first);
}

void second_pressed_cb(GtkButton *button, gpointer data)
{
  MySecondStruct *second = (MySecondStruct*)data;
  update_second(second);
}

int main(int argc, char *argv[])
{
  MyFirstStruct data1;
  MySecondStruc data2;
  GtkButton *my_button = gtk_button_new_with_label("Hit me");

  /* Connect to different handlers */
  gtk_signal_connect(GTK_OBJECT(my_button), "pressed",
    GTK_SIGNAL_FUNC(first_pressed_cb), (gpointer)&data1);
  gtk_signal_connect(GTK_OBJECT(my_button), "pressed",
    GTK_SIGNAL_FUNC(second_pressed_cb), (gpointer)&data2);
}
        
Listing 2.3 Signal Connection Example with Only One Handler
 
/*Globally declared this time */
MyFirstStruct data1;
MySecondStruc data2;

void pressed_cb(GtkButton *button, gpointer data)
{
  char *action = (char*)data;

  if(strcmp(action, "first") == 0)
    update_first(&data1);
  else
    update_second(&data2);
}

int main(int argc, char *argv[])
{
  GtkButton *my_button = gtk_button_new_with_label("Hit me");

  /* Connect twice to the same handler */
  gtk_signal_connect(GTK_OBJECT(my_button), "pressed",
    GTK_SIGNAL_FUNC(pressed_cb), (gpointer)"first");
  gtk_signal_connect(GTK_OBJECT(my_button), "pressed",
    GTK_SIGNAL_FUNC(pressed_cb), (gpointer)"second");
}
        

The entire life cycle of a signal is called an emission. A signal emission lasts until it runs its full natural course, or until you deliberately tell it to stop. An emission consists of one or more handler functions invoked on a single object.

When a signal emission hits a particular object, the object follows a carefully choreographed sequence of events to handle that signal. As we mentioned earlier, when an object class first registers a signal, it can declare a default handler. The purpose of this default handler is to allow special processing of the signal outside of normal application-level usage. Depending on how the signal is initialized, GTK+ will call the default handler before, after, or both before and after all the handlers you've connected to that object with gtk_signal_connect( ). The calling order is object dependent but doesn't change for a given object. For example, the GtkButton widget always calls its default pressed handler before connected signals, but never after.

If you have the source code for the widget, you can see for yourself what default handler-if any-the widget installs, and when it runs the handler. The function to look for is traditionally called gtk_xxx_class_init( ) or gnome_xxx_class_init( ). This is the function that initializes the object's class, including setting up the virtual function table and registering the object's signals. The object registers its signals with the gtk_signal_new( ) function, most commonly using a signal run value of GTK_RUN_FIRST, GTK_RUN_LAST, or GTK_RUN_BOTH.

Each signal emission goes through potentially four stages of handlers, depending on where the object's default handler is invoked and how you've connected your signal handlers to it:

  1. Default handler (GTK_RUN_FIRST)

  2. gtk_signal_connect( ) signals

  3. Default handler (GTK_RUN_LAST)

  4. gtk_signal_connect_after( ) signals

According to the GtkSignalFunc prototype, all signal handlers must have a return value of gint. Each handler can return the value TRUE to try to stop the emission or FALSE to continue to the next handler. One way to look at it is that each handler asks the question, "Has the signal now been completely handled?"

Of course, the fact that a handler returns TRUE does not always mean that the emission will stop. This strange behavior is explained by the fact that only a handler in the final stage executed in a signal emission can stop that emission with its return value. If a signal has a GTK_RUN_FIRST default handler and no other connections, the return value of the default handler will determine the return value of the emission as a whole. If you connect any handlers to that signal, your handlers will determine the fate of the emission. On the other hand, if the signal has a GTK_RUN_LAST default handler, it won't matter what your application's handlers return; the default handler will override your return values. This means that normally you can't force an early return from a GTK_RUN_LAST signal with a gtk_signal_connect( ) handler. If you really must determine the emission return value of a GTK_RUN_LAST signal, you can use gtk_signal_connect_after( ) instead.

To summarize, a single GdkEvent can cause several signal emissions; each of those signal emissions might in turn hit several objects. Each object can have several signal connections, each of which will invoke a separate handler function. The potential for mischief is endless.