Features


X Window Programming

Part 5: X Toolkit Programming

Eric F. Johnson and Kevin Reichard


Johnson and Reichard are authors of X Window Applications Programming Advanced X Window Applications Programming and Power Programming Motif (all MIS:Press). Johnson can be reached at erc@pai.mn.org via UUCP mail. Reichard's Compuserve address is 73670,3422.

Since X Window programming is so tough, the designers of X actively encourage programmers to use toolkits — libraries that sit on top of the low-level X library. We discussed the low-level library in parts 2 and 3 of this series. (see CUJ May 1991 and July 1991). X toolkits provide a nearly object-oriented programming interface that hides most of the messy details of the low-level X library. Unfortunately, X toolkits also introduce their own complexities that are often confusing. The toughest part of using X toolkits is that you must structure your code around the toolkit's idiosyncrasies, which makes difficult porting software from other systems, such as the Macintosh, DOS, or Windows.

The toolkits in X usually entail three layers. At the bottom layer is the X library, mentioned above. Above the X library sits the X Toolkit Intrinsics, often called Xt Intrinsics or simply Xt. Not simple at all, Xt provides a pseudo-object-oriented interface support layer, which handles events for a hierarchy of generic widgets (a term we'll explain below). When you decide to standardize on an Xt-based toolkit, you buy in to the design philosophy we'll describe, whether you want to or not. There's no real middle ground with these toolkits, unfortunately.

Above the Xt Intrinsics sits an actual interface library, also called a widget set. The widget set provides the actual look and feel for the scroll bars, menus, and whatnot, while the Xt layer just supports the widget set under the hood.

This can get confusing really quickly, but bear with us. At least three major X widget sets use the support routines in the Xt Intrinsics:

The Athena widget set is free and comes with the X Window System as released by MIT.

Widgets

Since we've been talking about widget sets, we should explain what a widget is. A widget is a software abstraction for a user interface element, like a scroll bar or menu. Sound too dry and academic for you? Well, a widget is just a data structure that you can create and manipulate. It is usually associated with a window on the screen. This data structure contains pointers to functions for drawing and event-handling. The instance of the data structure exists in a hierarchy of widgets, maintained by Xt. You usually don't have to worry about this, though.

When you create a scroll-bar widget, for example, you can place that scroll bar in a window in your application. The widget set and the Xt Intrinsics take care of the messy part of managing the scroll-bar widget. Your application only needs to worry about the data to scroll, if that. In other words, the act of creating a widget also launches that widget. The X toolkit handles the rest. (Well, most of it, anyway.)

In your programs, you normally don't have to worry about what is in the widget structure. You just pass a pointer to the structure around to all the widget-handling routines. In fact, it's a lot easier to simply ignore what is in the widget structures and treat widgets as opaque data types, at least until you're trying to extend the current widget set by adding your own widgets. That's definitely an advanced topic. One nice thing you'll notice about using X widget sets is that you write a lot less code. (What code you do write, though, is more convoluted.) Just compare the length of this month's sample program to last month's and you'll see what we mean.

Initializing The Xt Intrinsics

The first step in using the Xt Intrinsics is initializing the library. The functions XtInitialize or XtAppInitialize handle this. XtAppInitialize is new with X11 Release 4, so many of you won't have this function available. If so, XtInitialize works just fine. It is, in fact, what we used, so that our code is more compatible across the range of X installations. XtInitialize is considered obsolete, but is the only thing available in older versions of X. Both functions act much the same.

#include <X11/Intrinsic.h>
Widget XtInitialize(
   String shell_name,
   String application_class,
   XrmOptionDescRec xrm_options [],
   Cardinal number_options,
   Cardinal *argc,/* note indirection */
   String argv[]);
If you look at the parameters above, initializing the toolkit looks like a complex task. This is only if you make it so. Here's how we call XtInitialize in the sample program:

#include <stdio.h>
#include <X11/Intrinsic.h>

main(int argc, char *argv[])
   { /* main */
   Widget parent;
   .....
   parent = XtInitialize(
      argv[0], /* shell or app name */
      "Xaw1",  /* application class */
      NULL,
      0,       /* No Xrm Options */
      &argc, argv);
This is much easier. For simplicity, we skip the Xrm (X Resource Manager) options. You'll be able to get away with this in many Athena widget programs. "Xaw1" is the application's class name. The class name is used for finding application-specific customizations (called resources) and is usually the application name with an uppercase first letter. (In this case we call our program "xaw1" — an arbitrary name.) Sometimes the second letter is capitalized, too.

Once you've initialized the Xt Intrinsics, you get back a top-level shell widget, which basically corresponds to your application's top-level window. This widget is the highest parent for all of the widgets you create — and every widget you create needs a parent. The next level of widgets you create will use this as the parent widget. You'll be creating plenty of widgets, or else your Athena widget programs will be pretty boring. We'll create three simple widgets in the sample program. The easiest is a Label widget.

The Label Widget

The Label widget presents a text label, about the simplest widget you can get. To create a Label widget, use XtCreateManagedWidget, which can create widgets of many types, or classes. Pass a widget class of labelWidgetClass. In the code below, XtSetArg is used to change the default text to display "This is a label." XtCreateManagedWidget then creates the widget.

Arg args[10];
int n;
Widget parent_widget;
Widget label;

n = 0;
XtSetArg(
   args [n],
   XtNlabel,
   "This is a label." ); n++;
label = XtCreateManagedWidget(
   "label",         /* name */
   labelWidgetClass,/* widget class */
   parent_widget,   /* parent widget */
   args,            /* changes */
   n );             /* number in args */
More formally, XtCreateManagedWidget looks like:

String name;
WidgetClass widgetclass;
Widget parent;
ArgList args;
Cardinal number_args;

Widget XtCreateManagedWidget(
name, widgetclass,
   parent, args, number_args );

Setting Widget Options

Before you create most widgets, though, you'll need to set a number of options. To start out, it's a lot easier to set all the options in your C code. Later on, you'll want to study resource files, since they allow users to customize your applications a great deal. For now, though, we'll use an odd macro called XtSetArg. It sets one element in an array of Arg types, taking advantage of C's weak type checking. For now, don't worry what's in the Arg structure, since we'll only be using it with XtCreateManagedWidget and XtSetArg.

XtSetArg takes three parameters, an Arg structure, a name for the resource we're setting, and a value. You normally don't worry about their types. Instead, just follow the conventions when calling XtSetArg, even though that makes for some strange-looking code:

Arg args [10];
int n;

n = 0;
XtSetArg( args[n], XtNlabel,
   "This is a label." ); n++;
(Note: Since XtSetArg is a macro, never increment the count variable n inside the parentheses of XtSetArg — hence the n++ at the end of the line. This looks strange, but XtSetArg is used this way all the time in Xt-based programs. Get used to it.)

The XtNlabel is the name of the resource we want to change and is defined to be "label" in <X11/StringDefs.h>. The value part is what we want to set the given resource to. This will be rather confusing at first, but take a long look at the examples in Listing 1 and you'll have a better idea how XtSetArg works.

The basic idea is this. First, you set up all the widget resources you want to change into an array of Arg type, using XtSetArg. Then you create the widget, passing these options (in the Arg array) to the widget-creation function.

Athena Widget Include Files

For most Xt-based programs, you'll need to include at least two files, <X11/lntrinsic.h> and <X11/StringDefs.h>. In addition, each Athena widget has its own include file. Generally, you'll need to include a file for each type (class) of widget you use. The Label widget described above, for example, requires the file <X11/Xaw/Label.h>. Before X11 Release 4, all Athena include files were in /usr/include/X11. Thus, you would use X11/Label.h for the Label widget. With Release 4, these files migrated to /usr/include/X11/Xaw - a subdirectory lower.

We've set up a compile-time option to tell the C compiler where the Athena include files are. Since so many things depend on X11 Release 4, we use the symbol X11R4. If you have not upgraded to at least R4 yet, comment out the following line in the sample program:

#define X11R4    /* define this for
X11 Release 4+  */

The Command Widget

The Label alone isn't very interesting, so we'll add two more widgets. The Command widget is a Label widget that executes a function when you click a mouse button in it. You need to set up this function properly, so that the X Toolkit can call your function back when necessary (hence the term callback function).

But first, we create the Command widget with XtCreateManagedWidget, using a class of Command WidgetClass this time:

#include <X11/Xaw/Command.h>
.....
Arg args[10];
int n;
Widget quit, parent_widget;

n=0;
quit = XtCreateManagedWidget( "quit",
   commandWidgetClass, parent_widget, args, n );
The include file for the command widget is <X11/Xaw/Command. h>.

Widget Callback Functions

XtAddCallback sets up a function you write to be called back whenever the user clicks a mouse button in the Command widget.

Widget widget;
String callback_name;
XtCallbackProc callback_function;
XtPointer client_data;

XtAddCallback ( widget, callback_name,
   callback_function, client_data );
To set up the standard callback for the Command widget, use XtNcallback as the callback name (or type of callback). When the user clicks a mouse button in the widget, the Command widget will call your callback function with three parameters. We named our example function quit_callback.

void quit_callback( widget,
   client_data, call_data )
   Widget widget;
   caddr_t client_data;/* your data */
   caddr_t call_data;  /* data passed
                    from Xt */
   { /* quit_callback */ }
In our code, the quit-callback abruptly quits the program, using exit, so we won't worry about passing any data to the callback function.

Using Container Widgets

When you create more than one widget, you'll need to use some form of "container" to hold your widgets, keep them in place, and figure out what to do when the user changes the size of the application's main window. A very simple container widget is the Athena Paned window widget. The Paned widget divides an area (in this case our whole application window) up into "panes," going across the window. The panes start at the top and work their way down to the bottom. Generally, you have a pane for each child of the paned window widget. The panes also come, by default, with "sashes" — lines with a little box (a place to grab onto with the mouse) near the right side. These sashes allow the user to resize any of the child panes.

To create a paned window container widget, we again use XtCreateManagedWidget, with a class of panedWidgetClass.

#include <X11/Xaw/Paned.h>
.....
   Widget  parent_widget, paned_widget;
   Arg  args[20];
   int  n;

   n = 0;
   paned_widget = XtCreateManagedWidget( "pane",
      panedWidgetClass, parent_widget, args, n );
The include file for the paned widget is <X11/Xaw/Paned. h>.

Since we want the Label and Command widgets to be inside the panes of the Paned widget, we need to create the Paned widget first and make the other two widgets children of the Paned widget. The Paned widget is a child of the parent widget returned by XtInitialize, above. The Command and Label widgets must be children of the Paned widget; that is, the Paned widget is the parent widget passed to XtCreateManagedWidget.

Making Widgets Appear

With X library programs, we've shown that nothing appears when you first create a window. You have to map that window to the screen to get something to appear. The same process is necessary for X Toolkit programs as well, only now we call the process "realizing" rather than mapping. (Don't blame us — we didn't invent the terminology.) Use XtRealizeWidget to realize a widget and all of its managed children. All the children of our first parent widget are managed, since we've been using XtCreateManagedWidget, which creates and manages each widget.

Widget parent;

XtRealizeWidget( parent );

The Widget Event Loop

Once you create and realize all your widgets, call XtMainLoop to handle all loops. This function basically takes over your application and loops forever on events. When each event comes in from the X server (or other input source), XtMainLoop dispatches the event to the proper event handler for whatever widget the event occurred in. If you ever want to quit your program, you'll need to set up at least one callback function, like we did for the Command widget, above. Thus, the main loop forces you to write event-driven programs, whether you'd like to or not.

If you have Release 4, you'll probably want to use the newer XtAppMainLoop, instead of XtMainLoop.

An Athena Widget Program

You'll notice a lot less code in the program in Listing 1 than in the last three month's examples. This is the primary advantage for using an X toolkit. You'll also note that the code looks strange, at least at first glance. We've covered everything the program does, so now you should try it out, to see what the program really does.

To compile and link Athena widget programs, you need to link in the Athena (Xaw), Xt and X (X11) libraries. Under X11, Release 4, you'll also need to link in the X miscellaneous utilities library (Xmu). A UNIX command to compile and link the xaw1.c program would look like:

cc -0 xaw1 xaw1.c -lXaw -lXmu -
lXt -lX11
The key library to check for is the Athena widget library, libXaw.a. Many systems that offer Motif don't include the Athena widgets, including releases from Hewlett-Packard, Data General and SCO. Hopefully, as these vendors upgrade to X11 Releases 4 and 5, then they will think twice and include the Athena widget set.

In the next part, we'll finish our X Window programming series and introduce another toolkit that is based on the Xt Intrinsics, the Motif toolkit from the Open Software Foundation.

References

Asente, Paul J. and Ralph R. Swick, X Window System Toolkit, Digital Press, Bedford, MA, 1990. ISBN (Digital Press) 1-55558-051-3, (Prentice Hall) 0-13-972191-6.

McCormack, Joel, Paul Asente and Ralph R. Swick, X Toolkit Intrinsics: C Language Interface, X11 Release 4 version, 1989, MIT X Consortium, part of the X Window System, Release 4, from MIT.

Nye, Adrian and Tim O'Reilly, X Toolkit Intrinsics Programming Manual, O'Reilly and Associates, Sebastopol, CA, 1990, ISBN 0-937175-34-X.

O'Reilly, Tim (editor), X Toolkit Intrinsics Reference Manual, O'Reilly and Associates, Sebastopol, CA, 1990. ISBN 0-937175-35-8.