The Athena widget set was the first major free widget set for the X Window System. It is based on Xt, the X Toolkit Intrinsics, so it uses the Xt interface functions, like Motif and Openlook. Athena is sometimes referred to as Xaw. The Athena widget set is packaged with the X Window System distribution and is used to build the basic X applications such as xterm, xedit and xload.

The look and feel of Athena is extremely flat and boring, but there are 3d alternatives. People who use Athena programs regularly have the option of a derivative with a 3D appearance, such as Xaw-3d. While the flat variety is not as pretty, it is fully functional and is maintained as part of the X Window System distribution directly, rather than as a possibly incompatible outside project.

Since Athena is directly built on Xt and Xlib, it is tied closely to the X. This puts it at a disadvantage to toolkits like GTK+ and QT that are more loosely bound to the windowing system (GTK+ through GDK, QT through it's own internals), and are thus portable to other platforms.

Using Athena in 2 quick example programs

Here's a small tutorial to let you see the essentials of Athena, and get you on the way to building larger things in Athena. If you've already used Motif or some other Xt based widget set, you should just look at the Athena documentation. Along those lines, experience in Athena will transfer well to Motif and OpenLook.

Program 1: Hello, World!

athena1.c:


#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/Xaw/Label.h>

String app_resources[] =
	{ "*label.Label: Testing Athena Label Widget", NULL };
// This is where you set initial widget properties


XrmOptionDescRec options[] = {
	{ "-label", "*label.Label", XrmoptionSepArg, NULL };
}
// This lets us give -label "label text" on the command line

int main(int argc, char **argv)
	{
	XtAppContext app_context;
	Widget top_level, label;
	// You will need 1 XtAppContext for any Xt/Athena application
	// You will need a Widget for each widget in your program

	top_level = XtOpenApplication( &app_context, "test", NULL, 0,
			&argc, argv,
			shellWidgetClass,
			app_resources,
			NULL, 0);
	// Magic for now; read the function specification if you want.

	label = XtCreateManagedWidget("label", labelWidgetClass, 
			top_level, NULL, 0);
	// Most widgets are created like this.

	XtRealizeWidget(top_level);

	XtAppMainLoop(app_context);
	return (0);
	}

Lesson one: Yes, it's complicated. Sorry.

The first example is not quite as minimal as it could have been. A simpler program would use NULL and 0 in the place of app_resources and options, and 1, respectively, and had those parts left out. I just thought it would be nice to showcase Xt right from the start. I've provided a Makefile at the bottom for building the examples.

#include ...

In any Athena program, you will need the Xt header file, as well as headers for any Athena widgets you plan on using, in this case just the Label widget. The Shell.h file is for the the top level shell widget. For any Athena experts reading this, I read that XtAppInitialize was deprecated in favor of XtOpenApplication, so I used that instead, but X11R6 programs still use it, so it's not a big deal I suppose.

top_level = XtOpenApplication(...);

This line initializes all typical Xt programs. It sets up the low-level Xlib stuff, and takes typical command like arguments such as "-geometry WxH+x+y", which are indispensable to people who use .xsession and suchlike heavily. You will need an XtAppContext to pass to this function and the main loop function. It returns a Widget type which for practical purposes represents the top level window of the application (shellWidgetClass type, in this case). It also takes app_resources, which I will ponder in a minute.

label = XtCreateManagedWidget("label", labelWidgetClass,
top_level, NULL, 0);

The XtCreateManagedWidget function will be used to create widgets, which can be visible widgets like text boxes and non-visible things like containers. The widget is given an Xt-internal name, "label", which is used in other places. The next argument is the class of widget we are using, which is a symbol from the Label.h header file. Next, we need the widget which contains the one we are creating, which in this case is the top level widget. The next two arguments have to do with passing arguments to the widget. We'll use those later.

XtRealizeWidget(top_level); XtAppMainLoop(app_context);

XtRealizeWidget is the function that makes the widget and it's children active and visible, in the case of the top level widget, makes the window appear. XtAppMainLoop is the function in which Xt actually sets up and draws all the widgets we've set up, and then loop forever waiting for events. Programs will not typically get past this function except by explicitly stopping Xt which is unneccesary in basic examples, so for now we won't put any useful code after it.

String app_resources[] =
{ "*label.Label: Testing Athena Label Widget", NULL };
XrmOptionDescRec options[] = {
{ "-label", "*label.Label", XrmoptionSepArg, NULL } }

These two lists are common in any Xt program. The first, a String array, is a list of resources that are added to the X resource database, which is beyond the scope is this tutorial. For the time being, it's an easy way of setting options on your widgets, in this case the text that goes on the label. There is another way (The real way, actually) to set and get properties of widgets, but I'll cover that later.

The second list, which is a list of XrmOptionDescRec structs, allows you to specify certain properties of widgets to be set from the command line. In this case, this allows us to optionally set the text of the label.

These two lists do nothing unless they are passed as parameters to XtOpenApplication.

Compile and run

"make athena1" should make this program, if your box is set up approximately like mine. If not, you may have to poke around. As for running it, try "atthena1 -label 'MY LABEL HERE' ", or try giving it geometry options.

Program 2: Callbacks and multiple widgets


#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Box.h>
// We need StringDefs.h for the symbol XtNlabel;
// When in doubt, include more headers than necessary

String app_resources[] =
	{ "*label.Label: Button not clicked yet", 

	"*command.Label: Click this button",
	NULL };
// Properties for our label and button

static int click_count = 0;
// I wouldn't normally use a global variable, but in this case
// I didn't want to overcomplificate things by passing a struct
// to the callback function.

// This is the callback function. It is set up to be called when the
// 'command' button is pressed.
void button_clicked(Widget w, XtPointer client_data, XtPointer call_data)
	{
	Widget label = (Widget) client_data;

	char string[50] = "Button clicked this many times: ";

	click_count++;

	sprintf(string + strlen(string), "%d", click_count);

	XtVaSetValues( label, XtNlabel, string, NULL);
	// This is a simple way of changing or setting values on
	// a widget. A more complicated and general way involves the
	// XtSetArg macro and the XtSetValues function.
	}

int main(int argc, char **argv)
	{

	XtAppContext app_context;
	Widget top_level, label, command, box;

	top_level = XtOpenApplication( &app_context, "test", options, 1,
			&argc, argv,
			app_resources,
			shellWidgetClass,
			NULL, 0);
	box = XtCreateManagedWidget("box", boxWidgetClass, top_level, 
		NULL, 0);
	// 'box' is a container widget to hold 'label' and 'command'

	label = XtCreateManagedWidget("label", labelWidgetClass, box, 
		NULL, 0);
	command = XtCreateManagedWidget("command", commandWidgetClass, 
		box, NULL, 0);
	// Now we put both or our widgets in the box.

	XtAddCallback(command, XtNcallback, button_clicked, label);
	// Add a callback function (button_clicked) to the
	// command widget, and pass that function 'label' as an argument.

	XtRealizeWidget(top_level);

	XtAppMainLoop(app_context);
	return (0);
	}

Lesson 2: Yes, it's still complicated

This example adds to the last bloodbath two of the underlying widget concepts that you see in any library: Containing widgets, and callbacks.


box = XtCreateManagedWidget("box", boxWidgetClass, 
	top_level, NULL, 0);
label = XtCreateManagedWidget("label", labelWidgetClass, 
	box, NULL, 0);
command = XtCreateManagedWidget("command", commandWidgetClass, 
	box, NULL, 0);

This part is pretty simple. We can't just pop as many widgets as we want into the top level widget; it's a limited monster, and rightfully so. So we use a special widget (a container widget, or a composite widget) that is built to contain and arrange multiple widgets. I've intentionally used the simplest of these, which doesn't require any special tampering to work.

Note the way we put the other widgets in the box; instead of naming top_level as the parent, we name 'box' as the parent and they appear in the box. The terminology for this is that 'box' is a child of top_level, and 'command' and 'label' are both children of 'box'. The moral of the story: Put your children in a box. There. I said it.


// In main:
XtAddCallback( command, XtNcallback, button_clicked, label);

// Elsewhere:
void button_clicked(Widget w, XtPointer client_data, XtPointer call_data)

Callbacks are the program's way of doing things after entering the main loop phase. What we are doing, in this case, is telling Xt to call the function "button_clicked", with 'label' as one of the arguments, whenever the 'command' button is pressed. Different types of widgets have different types of callback information, but you can set all of them up with this function.

All functions set up to be Athena callbacks must take 3 arguments: The calling widget, a client data pointer, and a call data pointer. We don't need to worry about the calling widget or the call data pointer, because we know where the call came from; but in some other instances, we might not. The type XtPointer looks funny, but it's just a void* in this case, so we can use it however we want.


XtVaSetValues( label, XtNlabel, string, NULL);

This is another way to set the resources of a widget. The XtVaSetValues function takes a variable number of arguments, starting with the widget you want to change, and ending with a NULL. In between are pairs of the name of the resource (XtNlabel, the text of the label), and the value we want to give it (here given as string).

There is a more complicated way of doing this that doesn't require us to put it all in one function call. I may address that way in the future.

Not satisfied? Read more code, get the specs

If you want to browse some of the most commonly used Athena source code, get the source of the most recent X Window System distribution. There are a whole wealth of programs (Under xc/programs) that use Athena and Xt exclusively. Some of the simpler programs to look up might be xload and xclock; there are more complicated beasts like xedit and xterm, if you're feeling frisky.

Obviously, you will have no idea what you're looking at; that's why you will need the specifications for both Xt and for Athena if you're planning on getting anywhere, and even then it's going to be tough sledding. See the link at the bottom for where you can find these in PostScript format.

Wrapping up: Makefile and links

No, this isn't a typical fancy makefile. I just needed something to work at the time. Poke around other makefiles to see how things are generally done, if you want. This makefile may or may not work for you; it was written for Debian Linux. YMMV.

Makefile:

CC = gcc
LIBS = -L/usr/X11R6/lib -lXaw -lXt -lX11

athena1:
	$(CC) -o athena1 athena1.c $(LIBS)

athena2:
	$(CC) -o athena2 athena2.c $(LIBS)

Last notes: I plan to add to this tutorial, but I'm just in a rush to get it posted and I'm tired of having it sitting around waiting. If you want to reserve judgement until I feel I'm completely finished, give it a few days and I may come through for you.

Links and resources: