This article will introduce the basic concepts in building a graphical user interface in X and Motif. I'll go into a quick introduction to the X Window System and its programming model, then introduce Motif and illustrate some concepts with a sample program. Finally, we'll go through the basic Motif programming principles.
The X Window System, or simply X, was originally developed at the Massachusetts Institute of Technology with support from Digital Equipment Corporation. X was developed for Project Athena to fulfill the project's need for a distributed, hardware-independent user interface platform.
X is a graphics system used primarily on UNIX systems. It provides an inherently client/server-oriented framework for displaying windowed graphics. It provides a way of writing device-independent graphical and windowing software that can be ported easily from one machine to another.
X provides functionality via a vast set of subroutine libraries. These may be called from a variety of high-level languages. However, they are most easily called from C programs.
Since X is network-oriented, the applications developed for it do not need to run on the same system as the one supporting the display. The programmer doesn't need to worry much about the practicality of this, as X normally makes this transparent to the user.
The server is the program that controls the display. It acts as a bridge between user programs (i.e., clients or applications) and the resources of the local system. These run on either local or remote systems. The server performs the following duties:
Allows access by multiple clients.
Interprets network messages from clients.
Allows two-dimensional graphics display.
Maintains local resources such as windows, cursors, fonts and graphics.
The client in X usually consists of two parts. One, the graphical user interface, which is written in one or more of Xlib, Xt or Motif. Two, the algorithmic or functional part of the application where the input is received from the interface and processing tasks are defined.
In this section, I will briefly describe the main tasks of the three levels of the X/Motif programming model. The X/Motif programming model consists of Xlib, Xt Intrinsics and Motif.
Figure 1. User Interface Library Model
Xlib handles the interface between the client application and the network. It is part of the X software architecture. The main task of Xlib is to translate C data structures and procedures into the special form of X protocol messages, which are then sent off. The converse, receiving messages and converting them to C structures, is performed by Xlib as well.
X Toolkit Intrinsics (Xt Intrinsics) is a toolkit that allows programmers to create and use widgets. Toolkits, such as the Xt Intrinsics, implement a set of user interface features or application environments such as menus, buttons or scroll bars (referred to as widgets). Since Motif is built upon Xt, we'll need to call some Xt functions. However, we do not need to understand the workings of Xt, as Motif takes care of most things for us.
The client and server are connected by a communication path called the connector. This is performed by a low-level C language interface known as Xlib. It is true that many applications can be written solely using Xlib, but in general, it will be difficult and time-consuming to write complex GUI programs only in Xlib. Many higher-level subroutine libraries, called toolkits, have been developed to remedy this problem. One of these toolkits is Motif.
Motif is a widely accepted set of user interface guidelines developed by the Open Software Foundation and its member companies around 1989, and supported since. These rules specify how an X Window System application should “look and feel”. Motif includes the Motif Toolkit (Xm), which enforces a policy on top of the X Toolkit Intrinsics (Xt). Xt is really a “mechanism, not policy” layer, and Xm provides the specific look and feel. For example, Xt does not insist that windows have titlebars or menus, but it provides hooks for developers of specific toolkits (Motif, OpenLook, Athena widgets) to take advantage of. Motif also includes the Motif Style Guide document which details how a Motif user interface should look and behave to be “Motif compliant”.
As illustrated in Figure 1, the application may interact with all layers of the windowing system, the operating system and other libraries as needed. On the other hand, the user interface portion of the application should restrict itself to the Motif, Xt and Xlib libraries whenever possible.
The widget is the basic building block for the Graphical User Interface (GUI). It is common and beneficial for most GUIs assembled in Motif to look and behave in a similar fashion. Each widget in Motif is provided by default actions. Motif also prescribes certain other actions that should, whenever possible, be adhered to. Information regarding Motif GUI design is provided in the Motif Style Guide.
Each widget is defined to be of a certain class. All widgets of that class inherit the same set of resources and callback functions. Motif also defines a whole hierarchy of widget classes. There are two broad Motif widget classes that concern us. The Primitive widget class contains actual GUI components, such as buttons and text widgets. The Manager widget class defines widgets that hold other widgets.
A Motif widget may be regarded as a general abstraction for user-interface components. Motif provides widgets for almost every common GUI component, including buttons, menus and scroll bars. Motif also provides widgets whose only function is to control the layout of other widgets, enabling fairly advanced GUIs to be easily designed and assembled.
Widgets are designed to operate independently of the application except through well-defined interactions, known as callback functions. This takes much of the mundane GUI control and maintenance away from the application programmer. Widgets know how to redraw and highlight themselves and how to respond to certain events such as a mouse click. Some widgets go further than this; the Text widget, for example, is a fully functional text editor with built-in cut and paste as well as other common text-editing facilities.
Widgets are very useful because they simplify the X programming process and help preserve the look and feel of the application so it is easier to use.
The Motif Reference Manual provides definitions on all aspects of widget behavior and interaction. Basically, each widget is defined as a C data structure whose elements define a widget's data attributes, or resources and pointers to functions, such as callbacks.
The general behavior of each widget is defined as part of the Motif (Xm) library. In fact, Xt defines certain base classes of widgets which form a common foundation for nearly all Xt-based widget sets. Motif provides a widget set, the Xm library, which defines a complete set of widget classes for most GUI requirements on top of Xt.
The helloworld.c program explains the steps to follow in writing Motif programs.
helloworld.c creates a window with a single pushbutton in it. The button contains the string “Hello World!”. When the button is pressed, a string (Hello to you too!) is printed to standard output. This program illustrates a simple interface between the Motif GUI and the application code.
The program also runs forever! This is a key feature of event-driven processing. For now, we will have to quit our programs using the operating system:
Use CTRL-c to quit from the command line.
Use the Window Menu “quit” option.
Depress right mouse button down around the top perimeter of the window, and choose the “quit” option from menu.
The complete program listing for the helloworld.c program is in Listing 1. The display of helloworld.c on screen will look like Figure 2.
Figure 2. helloworld.c Display
To compile a Motif program, we have to link it with the Motif, Xt and Xlib libraries. The compile command I used for helloworld.c is
gcc helloworld.c -o helloworld -lXm -lXt -lXext\ -lICE -lSM -lX11
Note that this compile line is required in my environment. It may be different in yours. You should check your local system documentation for the exact compilation directives.
Having successfully compiled your Motif program, the command
will run it and display the PushButton on the screen as shown in Figure 2.
When writing a Motif program, you will be calling upon Motif and Xt functions and data structures explicitly. In order to distinguish the various toolkits, X adopts the following convention:
Motif function and data structure names begin with Xm, such as XmStringCreateSimple and XmStringFree.
Xt Intrinsics functions and most data structures begin with Xt, such as XtVaAppInitialize and XtVaCreateManagedWidget. The widget data structure is an exception to this rule.
Xlib functions and most data structures begin with X. There are no Xlib functions used in helloworld.c. However, an example of an Xlib function call is XDrawString or XDrawLine.
Any application that uses the Motif toolkit must include a header file for each widget it uses. Every Motif widget has its own header file, so we have to include the Xm/PushB.h file for the pushbutton widget, the Xm/DrawingA.h for the drawing widget and so on. However, we do not have to explicitly include the Xt header file, since Xm/Xm.h does this automatically. Every Motif program will include Xm/Xm.h, the general header for the motif library.
We'll now analyze the helloworld.c program in detail. There are six basic steps that nearly all Motif programs have to follow. These are:
Initializing the toolkit
Setting up events and callback functions
Displaying the widget hierarchy
Entering the main event-handling loop
The first step in a Motif program is to initialize the Xt Intrinsics toolkit. Before an application creates any widget, it must initialize the toolkit. There are several ways to initialize the toolkit. The most common is XtVaAppInitialize. When the XtVaAppInitialize function is called, the following tasks are performed:
The application is connected to the X display.
The command line is parsed for the standard X command-line arguments.
Resources are created using the app-default file, if any.
The top-level window is created.
XtVaAppInitialize takes several arguments.
The Application context: the first argument to XtVaAppInitialize is the address of an application context, which is a structure that Xt uses to manage some internal data associated with an application. For the Motif program we are considering, we need not know anything about this except that we need to set it in our program.
Application class name: the second argument is a string which defines the class name of the application. It is used to reference and set resources common to the application or even to a collection of applications.
Command-line arguments: the third and fourth arguments specify a list of objects as special X command-line arguments. The third argument is the list and the fourth, the number in the list. This is advanced X use and will not be considered further in this article. Just set the third argument to NULL and the fourth to 0. The fifth and sixth arguments, &argc and argv, contain the values of any command-line arguments given. These arguments may be used to receive command-line input of data in standard C fashion (e.g., file names for the program to read). Note that the command line may be used to set certain resources in X. However, these will have been removed from the argv list if they have been correctly parsed and acted upon before being passed on to the remainder of the program.
Fallback resources provide security against errors in other setting mechanisms. They are ignored if resources are set by any other means (i.e., using the app-default file). A fallback resource is a NULL-terminated list of strings. For now, we will simply set it to NULL since no fallback resources have been specified.
Additional parameters: the eighth parameter is the start of a NULL-terminated list of resource,value pairs that are applied to the top-level widget returned by XtVaAppInitialize. For now, it's a NULL-terminated list since there is no resource setting.
Creating a widget is referred to as instantiating it. You ask the toolkit for an instance of a particular widget class, which can be customized by setting its resources. A widget in Motif can be created by using a specific function for creating each widget or by using convenience functions for generic widget creation and even by creating and managing widgets with a single function call to XtVaCreateManagedWidget.
In general, we create a widget using the function XmCreatewidgetname. To create a pushbutton widget, we use XmCreatePushButton. Similarly, to create a menu bar, we use XmCreateMenuBar.
Most XmCreatewidgetname functions take four arguments:
The parent widget (topWidget in helloworld.c)
The name of the created widget, a string (“Hello World! Push me” in helloworld.c)
Command line/resource list (NULL in helloworld.c)
The number of arguments in the list
The argument list can be used to set widget resources such as the widget's initial height and width.
Once a widget is created, it must be managed. XtManageChild is a function that performs this task. A widget's parent manages the child's size and location, determines whether the child is visible, and may also control input to the child.
When this happens, all aspects of the widget are placed under the control of its parent. The most important aspect of this is that if a widget is left unmanaged, it will remain invisible even when the parent is displayed. This provides a mechanism with which we can control the visibility of a widget. Note that if a parent widget is not managed, a child widget will remain invisible even if the child is managed.
However, one function actually creates and manages a widget. This function is called XtVaCreateManagedWidget, which can be used to create and manage any widget.
An event is defined to be any mouse action (such as clicking on a button or a menu bar option) or keyboard action such as pressing ENTER or any input device action. The effects of an event are numerous, and include window resizing, window repositioning and invoking functions available from the GUI.
When a widget is created, it will automatically respond to certain internal events, such as a window manager's request to change size or color and changing its appearance when pressed. This is because Xt and Motif frees the application program from the burden of having to intercept and process most of these events. However, in order to be useful to the application programmer, a widget must be easily attached to application functions. Widgets must be hooked up to application functions via callback resources.
X handles events asynchronously. It basically takes a continuous stream of events and then dispatches them to applications, which then take appropriate actions.
If you write programs in Xlib, there are many low-level functions available for handling events. Xt, however, simplifies the event-handling task, since widgets are capable of handling many events for us, such as automatic redraw and response to mouse presses.
The functionality of a widget encompasses its behavior in response to user events. Each widget defines a table of events, called the translation table, to which it responds. The translation table maps each event, or sequence of events, to one or more actions. Full details of each widget's response can be found in the Motif Reference material.
Translations and actions allow a widget class to define associations between events and widget functions. For any application program, Motif will provide only the GUI. The main body of the application will be attached to the GUI and functions called from various events within the GUI.
To do this in Motif, we have to add our own callback functions. In helloworld.c, we have a function pushButton which prints to the standard output.
The function XtAddCallback is the most commonly used function to attach a function to a widget. XtAddCallback has four arguments:
The widget in which the callback is to be installed (button in helloworld.c).
The name of the callback resource. In our example, we set XmNactivateCallback.
The pointer to the function to be called.
Client data may get passed to the callback function. Here, we do not pass any data; it is set to NULL.
In addition to performing a job like highlighting the widget, each event action can also call a program function.
Callback functions have the following format:
void functionNameCallback (Widget w, XtPointer client_data, XmPushButtonCallbackStruct *cbs)
The callback function parameters are:
The first parameter of the function is the widget associated with the function (button in our case).
The second parameter is used to pass client data to the function. It is not used in our sample program.
The third parameter is a pointer to a structure that contains data specific to the particular widget that called the function and information on the event that triggered the call. The structure we have used is a XmPushButtonCallbackStruct, since we are using the PushButton Widget.
Ibrahim Haddad (firstname.lastname@example.org) is a Ph.D. student in the computer science department at Concordia University in Montréal, Canada. Ibrahim was first introduced to Linux (0.99) and Motif at the Lebanese American University. Among his interests are e-commerce, web applications, distributed objects and helping his friends at LinuxLeb.com (Linux Lebanon).