Seven steps of Motif programming
When writing a Motif program, there are seven steps that need to be done.
To illustrate each of these steps, you will create a simple Motif application.
Motif Program: Simple PushButton
This application will be a single button that displays the number of times the button was clicked repeatedly to cerr (for example, if double-clicked it will display "2 clicks"). Start a new file that will be your program. Here that file will be called pbExample.cpp.
To use the Motif Toolkit you will need to include the Motif header file. The Motif PushButton widget you will be using requires Xm/PushB.h (each widget's header file includes Xm/Xm.h, but it is good practice to include it anyways). For the display of "pushed", you will need to include iostream. Xm/Protocols.h will be used for the callbacks. You will also need a main function. Here are the initialize contents of pbExample.cpp:
[Motif C++]
#include <iostream>
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Protocols.h>
int main(int argc, char *argv[])
{
return 0;
}
Initialize the Motif toolkit
Your first initialization step is to set the language procedure for Xt. Do so by calling XtSetLanguageProc. Then you will initialize the Motif Toolkit with a call to XtVaAppInitialize. This call does a few things: connects the application to the display; gets any standard command-line arguments; sets up resources; and creates and returns the top-level window widget, which will be the parent of all other widgets in this application.
A deeper understanding of these calls is not necessary to using Motif as it is used in samples. However, if you would like further information on the parameters passed into either function, see the Motif Programming Manual resource listed at the end of this topic.
In ArcGIS Engine applications, you must use AoInitialize as well, placing the call before any ArcObjects usage.
int main(int argc, char *argv[])
{
XtSetLanguageProc(NULL, NULL, NULL);
XtAppContext app_context;
Widget topLevel = XtVaAppInitialize(&app_context, "XApplication", NULL, 0,
&argc, argv, NULL, NULL);
return 0;
}
Some of the code shown has already been entered in previous steps. It is given here to illustrate the accurate placement of the code you are adding in this step.
Create the widgets
With the toolkit initialized, you can create the single widget you are using in this application. There are two ways to create widgets:
- Using a function specific to the particular widget:
XmCreatePushButton() - Usng a function for generic widget creation (and sometimes managing it at the same time):
XtVaCreateWidget()
XtVaCreateManagedWidget()
Although you will see both in the C++ samples for Motif, here you will use the second method, but will not manage the widget at its creation (simply to separate that step for the purpose of this introduction).
[Motif C++]
int main(int argc, char *argv[])
{
XtSetLanguageProc(NULL, NULL, NULL);
XtAppContext app_context;
Widget topLevel = XtVaAppInitialize(&app_context, "XApplication", NULL, 0,
&argc, argv, NULL, NULL);
XmString label = XmStringCreateLocalized("Push the button.");
Widget button = XtVaCreateWidget("button", xmPushButtonWidgetClass, topLevel,
XmNlabelString, label, NULL);
XmStringFree(label);
return 0;
}
The parameters have the following roles:
- button—the name of the widget in the resource database, which can be used for specifications in a resource file. If a label is not provided, it will act as the widget's label.
- xmPushButtonWidgetClass—the class of the widget to be created. For example, to create an ArcGIS control widget, you would give mwCtlWidgetClass.
- topLevel—the parent of the widget, which must be a manager widget that was already created.
- XmNlabelString, label, NULL—resource settings. For more information on the resources available on different widgets and how to use them, see the Motif reference at the end of this topic. Some common resources used in the C++ samples for Motif are those to set the size and placement of the widget. In addition, there is a custom resource that goes with the ArcGIS controls: MwNprogID.
Manage the widgets
For the child to appear in the application, it must be managed. A single call, XtManageChild, will do this for you, placing control of the widget in its parent's hands. This must be done for each widget. Place this line of code after the call to XmStringFree, as shown below:
[Motif C++]
XmStringFree(label);
XtManageChild(button);
Even if a child is managed, it will not appear if its parent is not managed.
Implement event listening and callback functions for widgets
You now have a button, but for that to be useful you must hook it to some functionality. Widgets are attached to behavior at certain events through special callback functions. You can add callbacks to a widget after it is created and either before or after it is managed. As done in most of the C++ samples for Motif, here you add the callback before it is managed.
[Motif C++]
XmStringFree(label);
XtAddCallback(button, XmNactivateCallback, ClickCallback, NULL);
XtManageChild(button);
These parameters have the following roles:
- button—the widget to add the callback to.
- XmNactiveCallback—the callback resource, defined by Motif to correspond to certain events. Here you will pick activation of the button. For other options, see the Motif reference at the end of this topic.
- ClickCallback—pointer to the function to call on the event.
- NULL—Client data to pass into the callback function. Here there is no data the function will need, so NULL is passed.
Callback functions
For the callback to work, you must implement the function that is being called on the event. Callbacks have a specific function signature, as follows:
[Motif C++]
void myCallbackName(Widget w, XtPointer client_data, XtPointer call_data);
The parameters are:
- w—the widget that was activated for this callback to be called
- client_data—any data passed into the function, as indicated in the last parameter of XtAddCallback.
client_data scope: Make sure that the data passed to client_data will be in scope later when the callback routine is executed.
- call_data—a structure containing data specific to the type of widget with which the callback is associated.
For this example, place this function after main in pbExample.cpp, and have it print to cerr the number of repeated clicks on this button (for example, "2 clicks" if double-clicked). Remember to also place a forward declaration of it before main.
To get the number of clicks, you must access the call_data, which has the type XmPushButtonCallbackStruct. To do so, you will need to cast the XtPointer to this callback struct type, then you can get the integer member click_count.
[Motif C++]
void ClickCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
XmPushButtonCallbackStruct *data = (XmPushButtonCallbackStruct*)call_data;
std::cerr << data->click_count << std::endl;
}
Display the widgets
Your application's look and functionality are now written, but to create the actual window for your widget you need to call XtRealizeWidget right before you start the event loop, as you will do in the next step. This call is only needed on the top-level widget, which will then recursively realize the rest of the widgets. This call should be placed after the call to XtManagerChild, as shown:
[Motif C++]
XtManageChild(button);
XtRealizeWidget(topLevel);
Begin the event handling loop
The next step for this application is to turn control of the application over to Xt using XtAppMainLoop, which will manage the events. This code will idle until a user generates an event. The call should be placed after the call to XtRealizeWidget on the topLevel widget, as shown below:
[Motif C++]
XtRealizeWidget(topLevel);
XtAppMainLoop(app_context);
For ArcGIS control programming, MwCtlAppMainLoop must be used in the place of XtAppMainLoop, as you will see in ArcGIS C++ code.
Shutdown the application
As mentioned above, this application will run indefinitely unless it receives an event that tells it to do otherwise. To allow proper shutdown of the application, you will handle the window manager message that the window is going to be closed.
After the button is managed and before the event loop is started in main, you will listen for that window manager message:
[Motif C++]
XtManageChild(button);
Atom wm_delete_window = XmInternAtom(XtDisplay(topLevel), "WM_DELETE_WINDOW",
FALSE);
XmAddWMProtocolCallback(topLevel, wm_delete_window, CloseAppCallback, NULL);
You will also provide the callback that has the application close when that event is heard. As for the other callback, make sure you place a forward declaration before main.
[Motif C++]
void CloseAppCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
exit(0);
}
For ArcGIS Engine C++ programming, you must use AoExit in place of exit. You must call AoUninitialize before shutting down the application with AoExit.
Trying it out
To compile your Motif program, you will need to link against libraries for Motif, Xt, and X11, in that order. If you are programming on Solaris, you will compile with the Sun Workshop (Forte), and if you are programming on Linux you will use GCC.
Solaris:
CC pbExample.cpp -o pbExample -lXm -lXt -lX11
Linux:
g++ pbExample.cpp -o pbExample -L/usr/X11R6/lib -lXm -lXt -lX11
Run the program:
./pbExample
As you click the button, you will see counts appear in your terminal window.
Now that you have a feeling for Motif programming, look at the C++ samples for Motif, and see these steps applied there.
Additional references
- Heller, Dan, Paula M. Ferguson, and David Brennan. Motif Programming Manual (The Definitive Guides to the X Window System, Volume 6A) 2nd Edition. O'Reilly & Associates. 1994.