These tutorials are excerpted from the book "Motif Programming: The Essentials and More" , by Marshall Brain.
In order to understand callbacks, we are going to have to change the program we have been using so that it contains a widget capable of producing a callback in response to a user event. A label widget doesn't produce any callbacks in response to user events, but like all other widgets it does produce a callback when it is destroyed. A pushbutton widget is probably the simplest widget that does respond to user events, so we will use it. The description of a PushButton widget is given in the Motif PRM, or in the PRM summary available on the FTP site as described at the end of the previous tutorial. Take a second to quickly look through the description. Notice that you have to use the PushB.h include file for this widget. Also notice that the superclass is a label widget, so the pushbutton inherits all label resources (and callbacks, if there were any).
To try the pushbutton, type in and execute the following code. When you execute it, try pushing the push button labeled "push me" a few times.
#include <Xm/Xm.h> #include <Xm/PushB.h> XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET; Widget toplevel, button; void main(argc,argv) int argc; char *argv[]; { Arg al[10]; int ac; /* create the toplevel shell */ toplevel = XtAppInitialize(&context,"",NULL,0, &argc,argv,NULL,NULL,0); /* create the button widget */ ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate("Push Me",char_set)); ac++; button=XmCreatePushButton(toplevel,"button",al,ac); XtManageChild(button); XtRealizeWidget(toplevel); XtAppMainLoop(context); } This code is very similar to the very first program that we used, except that the label widget is replaced by a pushbutton widget. When you run it, clicking on the pushbutton should cause the button to highlight. In order to DO anything with the pushbutton, we need to make use of its callbacks.
The idea behind a callback is simple: you create a function, and tell the widget to call that function whenever an event triggers the callback. An event might be, in this case, the user pushing the button. To demonstrate this, we will modify the above code so that it will print the words "button pushed" to stdout whenever the button is pushed. Here's the new code:
#include <Xm/Xm.h> #include <Xm/PushB.h> XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET; Widget toplevel, button; void handle_button(w,client_data,call_data) Widget w; XtPointer client_data; XmPushButtonCallbackStruct *call_data; /* handles the pushbutton's activate callback. */ { printf("button pushed\n"); } void main(argc,argv) int argc; char *argv[]; { Arg al[10]; int ac; /* create the toplevel shell */ toplevel = XtAppInitialize(&context,"",NULL,0, &argc,argv,NULL,NULL,0); /* create the button widget */ ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate("Push Me",char_set)); ac++; button=XmCreatePushButton(toplevel,"button",al,ac); XtManageChild(button); XtAddCallback(button,XmNactivateCallback,handle_button,NULL); XtRealizeWidget(toplevel); XtAppMainLoop(context); } This is an extremely simple example. We will look at why this works, and then look at extensions to the idea.
This program starts by setting up the pushbutton widget, as in the first program. The new XtAddCallback line is the only difference. This tells Motif to cause the button widget, whenever its "activate" callback is triggered, to call the function named handle_button. Now when the user clicks the button, handle_button is called. The handle_button callback function simply prints out the words "button pushed" to stdout.
Any callback function receives three pieces of information when it is called. The first parameter is the widget that triggered the callback, the second is a peice of programmer-defined data (it can be anything that fits in 4 bytes - an integer, a pointer, etc.), and the third is a pointer to a "callback structure", which contains an integer holding the reason for the callback and the complete event structure describing the event (an XEvent structure). Here's the description of theXmAnyCallbackStruct:
typedef struct { int reason; Xevent *event; } XmAnyCallbackStruct; Generally you ignore this whole thing, but sometimes it contains useful information. The "reason" integer is sometimes useful if multiple callbacks are activating the same procedure.
The client_data field can be extremely useful at times, either to differentiate multiple callbacks to the same procedure, or to send in a pointer to a structure that contains a bunch of infomation that the callback will need. The code below demonstrates the "differentiation" capability of the client_data field, and will also help you to learn the difference between an arm, disarm and activate events.
#include <Xm/Xm.h> #include <Xm/PushB.h> XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET; Widget toplevel, button; void handle_button(Widget w, int client_data, XmAnyCallbackStruct *call_data) { switch (client_data) { case 1: printf("activate\n"); break; case 2: printf("arm\n"); break; case 3: printf("disarm\n"); break; } } void main(argc,argv) int argc; char *argv[]; { Arg al[10]; int ac; /* create the toplevel shell */ toplevel = XtAppInitialize(&context,"",NULL,0,&argc, argv,NULL,NULL,0); /* create the button widget */ ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate("Push Me",char_set)); ac++; button=XmCreatePushButton(toplevel,"button",al,ac); XtManageChild(button); XtAddCallback(button,XmNactivateCallback, handle_button, 1); XtAddCallback(button,XmNarmCallback, handle_button, 2); XtAddCallback(button,XmNdisarmCallback, handle_button, 3); XtRealizeWidget(toplevel); XtAppMainLoop(context); } This code is very similar to the previous code, except that now an integer piece of data is being passed to the callback routine (yes, I should have #defined constants, but this makes it more obvious). In the callback, the client_data parameter is used to guide a switch statement. The "arm" callback gets called when the pushbutton gets clicked down, and the "disarm" callback gets called when the button is released. The "activate" callback gets called only if the button is disarmed with the cursor inside the button, while "arm" and "disarm" are always called.
You can declare client_data to be any type. For example, say you have a struct called data, of type "struct data_type data", and you want to pass the thing as client_data. You can use the following line when creating the callback:
XtAddCallback(button,XmNactivateCallback,handle_button,&data);
The client_data parameter in the callback would be declared as "struct data_type *client_data", and the client data would be used in the callback by referring to "client_data->fieldname". If you are using an ANSI C compiler, the compiler will complain about type problems when you try to pass client data in this manner. To hush the complaints, define the type of the client_data parameter to be XtPointer in the parameter list to the callback function, and then cast that parameter to the necessary type inside the callback function itself. In the call to XtAddCallback, cast the value you pass to the type XtPointer as well.