A detailed How-To for using and extending GNOMEs developer toolkit.
Gtk+ is one of several GUI (graphical user interface) toolkits currently in use by Linux developers. Figure 1 illustrates some of the choices currently available. Of these, Gtk+ is of special interest in the Linux world, as it is the foundation upon which the GNOME desktop (<www.gnome.org>) is based. Qt is the toolkit that sits behind the other major Linux desktop, KDE.
As you can see, each of these toolkits is layered above the X Window System libraries, the most important of these libraries being Xlib. The reason for this layering is simple. While Xlib could be used to create a GUI application, it is far too primitive for such a task. The Xlib API exposes functions that allow one to, among other things, create simple windows; draw lines, circles, and text; and request and receive events (mouse-button presses and keyboard presses for example). But most application developers would prefer to work with abstractions such as buttons, menus, scrollbars, toolbars, and so forth, and these are what a widget set like Gtk+ provides. A widget is to Xlib what C is to programming in assembly.
For an example of how a toolkit and widget set might be used, lets look at a trivial application such as the one illustrated in Figure 2 (see also Listing 1). This Gtk+ application creates three widgets. A dialog (GtkDialog) provides a window that consists of two separate areas: one for content and the other for controls. A label widget (GtkLabel) is added into the content area, and a pushbutton (GtkButton) is added into the control area. The pushbutton widget, when clicked by the user, will emit a clicked signal. A callback function (MyExitApp) is registered with the button widget, associating it with the clicked signal. This instructs the button widget to call MyExitApp whenever the user clicks on the Quit button. MyExitApp, when invoked, calls exit(2), which is enough for the purposes of this example. Writing such an application in Xlib would be much more difficult.
Widget Types
Before looking at how to write a widget, it is useful to begin with a look at the kind of widgets that Gtk+ supports. There are three kinds to consider: control widgets, container widgets, and composite widgets.
Control Widgets
GtkButton is the canonical example of a control widget. GtkButton is an attempt to model the look, feel, and behavior of buttons as they exist in the real world. Like real buttons, they display markings that convey to the user what will happen when they are pressed. Like real buttons, they provide feedback letting you know that they are being pressed. And, most importantly, they do something interesting when they are pressed, just like real buttons.
A control widget usually is at the bottom of the widget instance hierarchy, as it does not manage or contain other widgets. GtkButton is an example of a control widget that does in fact contain another widget, in this case a label. However, since a button cant be used to arrange arbitrary widgets (like an instance of GtkBox), it is a control widget, not a container widget.
Container Widgets
In contrast to a control widget, a container widget does not represent a real-world object that the user can interact with. Rather, the job of a container widget is to visually arrange a set of control widgets for the user, in some meaningful way, according to some predefined heuristic that is associated with the container widget class. For example, a paned widget (GtkPaned, see Figure 3) splits the area of a window that it manages into two pieces. The developer can add arbitrary widgets to either portion of the window, and the user can (if the application allows it) change the relative size of these areas at run time. A container can manage other containers, allowing the Gtk+ developer to create visual layouts that are of arbitrary complexity, constrained by the capabilities provided by the container widget classes that are being combined and constrained by the good esthetic sense of the developer. GtkBox is perhaps the most flexible and the most widely used container widget provided by Gtk+.
Composite Widgets
A composite widget is a collection of control widgets and container widgets that have been organized in some meaningful way. While a composite widget is visible to the user and can be interacted with, it is different from a control widget in that container widgets generally are higher than control widgets in the widget instance hierarchy of the application. A typical example of a composite widget is GtkColorSelectionDialog (Figure 4), which is an instance of GtkColorSelection, and some instances of GtkButton, all packed into instances of GtkBox, GtkFrame, and GtkButtonBox.
Gtk+ Objects
Gtk+ is an object-oriented toolkit (written in a non object-oriented language, C). Implementing object-oriented worlds in C is nothing new: the Xt Intrinsics did this 15 or so years ago. Many of you, using simple C features like function pointers, have created object-oriented worlds in some of your own program designs. Such is the case with Gtk+.
Classes in Gtk+ become increasingly specialized as one descends down the slopes of the Gtk+ widget class hierarchy. The most abstract Gtk+ class, GtkObject, provides functionality that is important to all of the classes below it. The ability of an object to send a signal to an application (such as when a button is clicked) is rooted in the GtkObject class, as are methods that allow attributes of an object instance to be set or read (either by other objects, or an application). GtkObject also provides methods that are invoked when a widget instance is destroyed and during application shutdown.
Immediately below GtkObject are a few important classes, but the class of most interest to us is GtkWidget. All control and container widgets are instances of GtkWidget (just as widgets are instances of GtkObject). GtkWidget extends the GtkObject class to support features that user-interface widgets need.
GtkWidget
So, what exactly does GtkWidget provide? Lets take a look at the definition of GtkWidget to find out. Each widget class consists of data structures and assorted macros and #defines, as well as an API, all packaged up in an include file. The naming convention for the include file is simple: GtkWidget is defined in gtkwidget.h, GtkButton is defined in gtkbutton.h, and so forth. A C source file implements the functionality of the widget. The name of the C source file is usually the same as the include file, except for the .c suffix. Lets take a look at GtkWidget.h, focusing on two important data structures: the widget instance struct and the widget class struct. Figure 5 illustrates the difference between a widget instance hierarchy, which exists at run time (gray boxes), and a widget class hierarchy, which organizes widget classes based upon their functionality (white boxes).
Widget Instance struct
The first important data structure, struct _GtkWidget (see below), is used to represent an instance of a widget. Instances of a widget are returned by the widget creation function associated with the widget class (e.g., gtk_button_new, gtk_clist_new, gtk_tree_new, etc.). The widget instance struct will vary at run time from widget to widget. For example, each widget that creates a window will have its own value for the window field, its own state (GTK_STATE_NORMAL, GTK_STATE_ACTIVE, etc.), and its own parent widget. Thus, the widget instance struct holds, at run time, what is different among individual instances of a widget class. The style field holds a pointer to a struct of type GtkStyle. Information provided here is used by the widget to determine what fonts and colors it should use in each of its various states and is the basis by which theme support in Gtk+ and GNOME is provided.
struct _GtkWidget { // a widget is an object GtkObject object; // per-widget flags guint16 private_flags; // standard state of widget guint8 state; // state prior to becoming disabled guint8 saved_state; // the name of the widget gchar *name; // style data for this widget GtkStyle *style; // how big the widget wants to be GtkRequisition requisition; // how big the widget is allowed to be GtkAllocation allocation; // the X window, if any, the widget // lives in GdkWindow *window; // the parent of the widget in the // instance hierarchy GtkWidget *parent; };Two fields in the widget instance struct, requisition and allocation, deserve special comment. Here they are:
struct _GtkRequisition { gint16 width; // desired width gint16 height; // desired height }; struct _GtkAllocation { gint16 x; // upper left corner of the widget, // x coordinate gint16 y; // upper left corner of the widget, // y coordinate guint16 width; // actual width of the widget guint16 height; // actual height of the widget };A requisition is passed to each widget by its containing parent, allowing the widget to tell the containing parent how big it would like to be. The width and height values returned by the widget will depend upon the widget and its specific needs. A simple widget might return a constant width and height. A label widget would return the width and height that allows it to display its text using the font defined by the widgets style data. The size requisition process is a recursive one: if the child is a container, it will call down to its children to see how big they want to be and use these results to compute its own size request. So much for requisitions.
An allocation tells a widget how big it actually is, and where it is located on the screen. It defines the bounding box into which the widget draws itself. Events occurring within the bounding box (mouse-button and keyboard presses, expose events, etc.) will be routed to the widget by Gtk+ as they are received.
Notice that the first element of the GtkWidget instance struct is a GtkObject. This is how GtkObject is inherited by GtkWidget. It is like this all the way down the instance hierarchy, with each class including the widget instance struct of its immediate parent as the first element of its instance struct. The GtkToggleButton widget instance struct, shown below, illustrates how it inherits the GtkButton instance struct:
struct _GtkToggleButton { GtkButton button; guint active : 1; guint draw_indicator : 1; GdkWindow *event_window; };Each instance of GtkToggleButton is an instance of GtkButton, plus additional data that is needed by toggle buttons (active, draw_indicator, and event_window in this case).
Widget Class struct
The widget class struct encapsulates the unique functionality of the widget class it represents. A portion of the widget class struct for GtkWidget is shown below. Inclusion of the parent class struct inside the widget class gives the widget class easy access to the parent class when needed. Widget writing, for the most part, amounts to creating your own widget class, including the widget class struct of the parent, overriding methods of the parent class, and adding new methods to the widget class as needed.
struct _GtkWidgetClass { GtkObjectClass parent_class; guint activate_signal; guint set_scroll_adjustments_signal; void (* show) (GtkWidget *widget); void (* show_all) (GtkWidget *widget); void (* hide) (GtkWidget *widget); void (* hide_all) (GtkWidget *widget); void (* map) (GtkWidget *widget); void (* unmap) (GtkWidget *widget); void (* realize) (GtkWidget *widget); void (* unrealize) (GtkWidget *widget); void (* draw) (GtkWidget *widget, GdkRectangle *area); ... void (* size_request) (GtkWidget *widget, GtkRequisition *requisition); void (* size_allocate) (GtkWidget *widget, GtkAllocation *allocation); void (* state_changed) (GtkWidget *widget, GtkStateType previous_state); ... gint (* event) (GtkWidget *widget, GdkEvent *event); gint (* button_press_event) (GtkWidget *widget, GdkEventButton *event); gint (* button_release_event) (GtkWidget *widget, GdkEventButton *event); gint (* motion_notify_event) (GtkWidget *widget, GdkEventMotion *event); ... };GtkToggleButton, for example, is a subclass of GtkButton. The complete widget class struct for GtkToggleButton is as follows:
struct _GtkToggleButtonClass { GtkButtonClass parent_class; // a toggled button is a // button... void (* toggled) (GtkToggleButton *toggle_button); };As you can see, the GtkToggleButton class struct includes as its first element the GtkButtonClass class struct and adds to this a toggled function. You can deduce from this that a toggled button is exactly the same as a button, except it implements a toggled function. (The purpose of this function is outside the scope of this article.) Such a deduction is almost true, however, a widget class also overrides functions in the parent widget class (or higher), as required. This is done by creating new implementations of the functions and replacing the function pointers in the ancestor classes from within the class_init function. Listing 2 illustrates how GtkToggleButton does this. For those of you experienced with object-oriented development, it should be obvious that Gtk+ is cleverly making use of the facilities provided by C to manually implement what object-oriented languages like C++ provide for free. For some of you, this approach is worth serious study; you may someday find the need to build an object-oriented architecture within the constraints of C, and it is good to know that, with a bit of elbow grease, such an endeavor is possible.
Notice how the class argument can be assigned to object_class, widget_class, container_class, and button_class with casts. This is possible since the first member of a class struct is the class struct of the parent. Note that Gtk+ is limited to single inheritance only due to this layout of the class struct, although hacks do exist in GtkWidget so that the GtkWidget class can support a form of multiple inheritance. In the above code, GtkToggledButton overrides the leave, enter, clicked, released, and pressed functions of GtkButton; the unmap, map, unrealize, etc. functions of GtkWidget; and the set_arg and get_arg functions of GtkObject. The rest of the functions that are implemented by these ancestor classes are left untouched.
Creating a Seven-Segment LED Widget
The widget that I created for this article is capable of displaying a single, seven-segment LED. (Figure 6 illustrates a set of GtkSegment widgets packed into a ticker widget, GtkTicker. Source for both widgets and the sample application are available at <www.cuj.com/code> and <www.users.cts.com/crash/s/slogan/gtkbook/widgets.html>). The application can create an instance of GtkSegment, set its value, and set its color to black, red, blue, green, or yellow.
The first step in creating this widget, once I had the concept in mind, was to determine where it belongs in the Gtk+ widget class hierarchy. If it were a container, which it is not, it would have been placed somewhere below GtkContainer. I decided to place it below GtkMisc, which inherits from GtkWidget and adds x and y padding and alignment attributes to the widget instance struct.
The next step was to find a class that inherits from GtkMisc and clone its source code (both the include file and C source). I decided that GtkArrow was the closest match. The next several steps were fairly boilerplate. First, I copied gtkarrow.h to gtksegment.h, and gtkarrow.c to gtksegment.c. Next, I opened gtksegment.h and did the following (case sensitivity is important):
- replaced all instances of ARROW with SEGMENT
- replaced all instances of Arrow with Segment
- replaced all instances of arrow with segment
The above substitutions will rename all functions, macros, and data structures in the include file to be compatible with GtkSegment.
Once this was completed, I turned my attention to the public APIs. As a result of the previous text substitutions, I was left with the following:
GtkType gtk_segment_get_type (void); GtkWidget* gtk_segment_new (GtkSegmentType segment_type, GtkShadowType shadow_type); void gtk_segment_set (GtkSegment *segment, GtkSegmentType segment_type, GtkShadowType shadow_type);Some minor changes in the public API resulted in the changes shown here:
GtkType gtk_segment_get_type(void); GtkWidget* gtk_segment_new(); GtkWidget* gtk_segment_new_with_color(GtkSegmentColor color); int gtk_segment_set_color(GtkSegment *segment, GtkSegmentColor color); int gtk_segment_set_value(GtkSegment *segment, unsigned char value);An enum and a typedef GtkSegmentColor are used to represent supported colors:
enum { GTK_SEGMENT_BLACK, GTK_SEGMENT_RED, // default GTK_SEGMENT_GREEN, GTK_SEGMENT_BLUE, GTK_SEGMENT_YELLOW }; typedef gint16 GtkSegmentColor;The widget instance struct was then modified, with the following results:
struct _GtkSegment { GtkMisc misc; // color of the segment GtkSegmentColor segment_color; // its value, '0', '1', '2', etc. unsigned char segment_value; // GCs for each possible color GdkGC *redGC; GdkGC *greenGC; GdkGC *blueGC; GdkGC *yellowGC; // height of the segment in pixels int height; };Other than the name change from GtkArrowClass to GtkSegmentClass, the widget class struct was left the same as it is for GtkArrow.
The next step was to edit the source file (gtksegment.c). After opening the file, I performed the same text substitutions that I applied to the include file. The source file implements the following core methods:
static void gtk_segment_class_init (GtkSegmentClass *klass); static void gtk_segment_init (GtkSegment *segment); static void gtk_segment_unrealize (GtkWidget *widget); static gint gtk_segment_expose (GtkWidget *widget, GdkEventExpose *event); static void gtk_segment_size_request (GtkWidget *widget, GtkRequisition *requisition);The class_init and init functions are mostly boilerplate. In the class_init function, I commented out support for GtkObject arguments. I also overrode the GtkWidget class size_request and unrealize methods, by adding the following lines:
widget_class->size_request = gtk_segment_size_request; widget_class->unrealize = gtk_segment_unrealize;The size_request function is simple: it checks the requisition it is passed and makes sure that the height and width are at least as large as MIN_SEGMENT_SIZE<D>, a constant I defined in the source. If the value of the segment is '.', then the requisition width is divided in half. Here is the complete source:
static void gtk_segment_size_request(GtkWidget *widget, GtkRequisition *requisition) { g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_SEGMENT (widget)); g_return_if_fail (requisition != NULL); if ( requisition->height < MIN_SEGMENT_SIZE ) requisition->height = MIN_SEGMENT_SIZE; if ( requisition->width < MIN_SEGMENT_SIZE ) requisition->width = MIN_SEGMENT_SIZE; if ( GTK_SEGMENT(widget)->segment_value == '.' ) requisition->width = requisition->width >> 1; }The other function I override, expose, is where all the remaining work is done. The expose function is called whenever the widget is exposed (i.e., it becomes visible for the first time, or when some portion of the widget window becomes unobscured). The expose event is passed to the function by Gtk+, and it indicates the area of the window that is in need of redrawing. (This is used to set clip rectangles for the foreground and background GCs prior to drawing). The only remaining task is to draw the segments value into the window, the size and location of which is determined by the widget allocation:
width = widget->allocation.width; height = widget->allocation.height;The foreground color, as well as the value of the segment, are obtained from the widget instance struct (segment_color is used to determine which GC to use, e.g., redGC, blueGC, etc.). The drawing is done using GDK graphics primitives (which are mostly wrappers above the Xlib graphics functions).
Public Setter Functions
Two functions defined in the public API, namely gtk_segment_set_color and gtk_segment_set_value, are all that remain to be discussed. Both take the value that is passed by the application and store it in the appropriate widget instance struct field (after error checking). Then, if the widget is in a drawable state (meaning it is realized and mapped), a call to gtk_widget_queue_clear is made. This causes Gtk+ to issue a redraw request on the widget, which, in this case, lands us in the gtk_segment_expose method, where the widget contents are then redrawn as explained above.
Summary
Gtk+ provides programmers with a rich set of user-interface widgets. However, some of you will find the need to extend the Gtk+ widget set by creating your own widgets. Gtk+s object system and widget class hierarchy make it relatively easy to create widgets of either type (control or container). In most cases, creating a new widget amounts to applying boilerplate modifications to a carefully selected Gtk+ widget class and replacing widget-specific code with code that implements the behavior of the new widget class. By following the steps outlined above, as well as studying the Gtk+ widget source code provided with this article and the Gtk+ sources that are available at <www.gtk.org>, you should be well on your way towards writing great Gtk+ widget classes of your own.
Acknowledgements
My thanks to Rob Flynn and Eric Warmenhoven (<http://gaim.sourceforge.net>) for providing technical reviews of this article.
Syd Logan is a senior technical manager at AOL/Netscape and has been involved with the Netscape 6 browser project for two years. He enjoys hacking on, and writing about, Unix, C, and X Window System software and has doing so for 15 years. Information on his recent book, Gtk+ Programming in C, can be found at <www.users.cts.com/crash/s/slogan/gtkbook.html>.