In this Chapter we will develop our first Motif program. The main purpose of the program is to illustrate and explain many of the fundamental steps of nearly every Motif program.
Our program, push.c , will create a window with a single push button in it. The button contains the string, ``Push Me''. When the button is pressed (with the left mouse button) a string is printed to standard output. We are not yet in a position to write back to any window that we have created. Later Chapters will explore this possibility. However, this program does illustrate a simple interface between 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 by either:
In forthcoming Chapters (see also Exercise 5.1) we will see how to quit the program from within our programs.
The display of push.c on screen will look like this:
Fig. 5.1 Push.c display
As was previously stated, the main purpose of studying the program is to gain a fundamental understanding of Motif programming. Specifically the lessons that should be clear before embarking on further Motif programs are:
We now list the complete program code and then go on to study the code in detail in the remainder of this Chapter.
The complete program listing for the push.c program is as follows:
#include <Xm/Xm.h> #include <Xm/PushB.h> /* Prototype Callback function */ void pushed_fn(Widget , XtPointer , XmPushButtonCallbackStruct *); main(int argc, char **argv) { Widget top_wid, button; XtAppContext app; top_wid = XtVaAppInitialize(&app, "Push", NULL, 0, &argc, argv, NULL, NULL); button = XmCreatePushButton(top_wid, "Push_me", NULL, 0);
/* tell Xt to manage button */ XtManageChild(button); /* attach fn to widget */ XtAddCallback(button, XmNactivateCallback, pushed_fn, NULL); XtRealizeWidget(top_wid); /* display widget hierarchy */ XtAppMainLoop(app); /* enter processing loop */ } void pushed_fn(Widget w, XtPointer client_data, XmPushButtonCallbackStruct *cbs) { printf("Don't Push Me!!\n"); }
When writing a Motif program you will invariably call upon both Motif and Xt functions and data structures explicitly. You will not always call Xlib functions or structures explicitly (but recall that Motif and Xt are built upon Xlib and they may call Xlib function from their own function calls).
In order to distinguish between the various toolkits, X adopts the following convention:
In order to be able to use various Motif, Xt Intrinsics or Xlib data structures we must include header files that contain their definitions.
The X system is very large and there are many header files. Motif
header files are found in #include<Xm/...>
subdirectories, the Xt
and Xlib header files in #include<X11/...>
subdirectories (E.g.
the Xt Intrinsic definitions are in #include<X11/Intrinsics.h>
).
Every Motif widget has its own header file, so we have to include the
<Xm/PushB.h>
file for the push button widget in push.c.
We do not have to explicitly include the Xt header file as <Xm/Xm.h>
does
this automatically. Every Motif program will include <Xm/Xm.h>
-- the
general header for the motif library.
To compile a Motif program we to link in the Motif, Xt and Xlib
libraries. To do this use: -lXm -lXt -lX11
from the compiler command
line. NOTE: The order of these is important.
So to compile our push.c program we should do:
cc push.c -o push -lXm -lXt -lX11
The exact compilation of your Motif programs may require other compiler directives that depend on the operating system and compiler you use. You should always check your local system documentation or check with your system manager as to the exact compilation directives. You should also check your C compiler documentation. For example you may need to specify the exact path to a nonstandard location of include (-I flag) or library (-L flag) files. Also our push.c program is written with ANSI style function calls and some compilers may require this knowledge explicitly. Some implementations of X/Motif do not strictly adhere to the ANSI C standard. In this case you may need to turn ANSI C function prototyping etc. off.
Having successfully complied you Motif program the command:
push
should successfully run the program and display the PushButton on the screen.
Let us now analyse the push.c in detail. There are six basic steps that nearly all Motif programs have to follow. These are:
We will now look at each of these steps in detail.
The initialisation of the Xt Intrinsics toolkit must be the first stage of any basic Motif program.
There are several ways to initialise the toolkit. XtVaAppInitialize() is one common method. For most of our programs this is the only one that need concern us.
When the XtVaAppInitialize() function is called, the following tasks are performed:
XtVaAppInitialize() has several arguments:
&argc
and argv
contain the values of any command line argument
given. These arguments may be used to receive command line input of data in
standard C fashion (e.g. filenames for the program to read). Note that
the command line may be used (Section ) 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.
There are several ways to create a widget in Motif:
We will introduce the convenience functions shortly but for now we will continue with the simpler first method of widget creation.
In general we create a widget using the function:
XmCreate<widget name>()
.
So, to create a push button widget we use
XmCreatePushButton()
Most XmCreate<widget name>()
functions take 4 arguments:
"Push_Me"
- in
push.c.
The argument list can be used to set widget resources (height, width etc.) at creation. The name of the widget may also be important when setting a widget's resources. The actual resources set depend on the class of the widget created. The individual Chapters and reference pages on specific widgets list widget resources. Chapter deals with general issues of setting resources and explores the methods described here further.
Once a widget has been created it will usually want to be
managed.
XtManageChild() is a function that
performs this task.
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, then it will remain invisible even when the parent is displayed. This provides a mechanism with which we can control the on screen visibility of a widget -- we will look at this in more detail in Chapter 10. Note that if a parent widget is not managed, then a child widget will remain invisible even if the child is managed.
Let us leave this topic by noting that we can actually create and manage a widget in one function called XtVaCreateManagedWidget(). This function can be used to create any widget. We will meet this function later in the Chapter .
When a widget is created it will automatically respond to certain internal events such as a window manager request to change size or colour and how to change 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 able to be easily attached to application functions.
Widgets have special callback functions to take care of this.
An event is defined to be any mouse or keyboard (or any input device) action. The effect of an event is numerous including window resizes, window repositioning and the invoking functions available from the GUI.
X handles events asynchronously , that is, events can occur in any order. X basically takes a continuous stream of events and then dispatches them according to the appropriate applications which then take appropriate actions (remember X can run more than one program at a time).
If you write programs in Xlib then there are many low level functions for handling events. Xt, however, simplifies the event handling task since widgets are capable of handling many events for us (e.g. widgets are automatically redrawn and automatically respond to mouse presses). How widgets respond to certain actions is predefined as part of the widget's resources. Chapter 17 gives a practical example of changing a widget's default response to events.
Every widget has a translation table that defines how a widget will respond to particular events. These events can enable one or more actions. Full details of each widgets response can be found in the Motif Reference material and manuals[Hel94b].
An example of part of the translation table for the push button is:
bBtn1downn: Activate(), Disarm()BSelect Press:
Arm()
BSelect Click: Activate(), Disarm()
BSelect Press
corresponds to a left mouse pressed down and the action is
the Arm() function being called which cause the display of the button to
appear as it was depressed. If the mouse is clicked (pressed and released), then
the Activate() and Disarm() functions are called, which will cause
the button to be reactivated.
Keyboard events can be listed in the table as well to provide facilities such as hot keys, function/numeric select keys and help facilities. These can provide short cuts to point and click selections.
Examples include: KActivate
-- typically the return key,
KHelp
-- the HELP or F1 key.
The function Arm(), Disarm() and Activate() are examples of predefined callback functions.
For any application program, Motif will only provide 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 push.c we have a function pushed_fn() which prints to standard output.
The function XtAddCallback()
is the most commonly
used function to attach a function to a widget.
It has four arguments:
In addition to performing a job like highlighting the widget, each event action can also call a program function. So, we can also hang functions off the arm, disarm etc. actions as well. We use XmNarmCallback, XmNdisarmCallback names to do this.
So, if we wanted to attach a function quit() to a disarm for the button widget, we would write:
XtAddCallback(button, XmNdisarmCallback, quit, NULL);
Let us now look at the declaration of the application defined callback function. All callback functions have this form.
void pushed_fn(Widget w, XtPointer client_data, XmPushButtonCallbackStruct *cbs)
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. We will see how to attach client data to a callback later. We do not use it in this example so just leave it defined as above for now.
The third parameter is a pointer to a structure that contains data specific to the particular widget that called the function and also information about the event that triggered the call.
The structure we have used is a XmPushButtonCallbackStruct . A Callback Structure has the following general form:
typedef struct { int reason; XEvent *event; .... widget specifics ... } Xm<widget>CallbackStruct;
The reason element contains information about the callback such as whether arm or disarm invoked the call and the event element is a pointer to an (Xlib) XEvent structure that contains information about the event.
We have nearly finished our first program. We have two final stages to perform which every Motif program has to perform. That is to tell X to: