2.4.1 Programming Style



The application has an OSF/Motif style application interface. It utilizes some prebuilt widgets that are supplied from Inventor as well as augmenting the overall functionality with specialized widgets created directly from Motif and Xt.

The calls to build the interface occupy most of the main function. As it should be, the user input and output is the most indispensable part of the program. Without the ability to receive and handle events, the program would be limited to simple serial simulation. The first Xt call is in wrapped in an SoXt object method called init(); The call just binds Inventor with Xt so that they may work together. It calls SoDB::init() to initialize the Inventor database and calls XtAppInitialize to request an application context from X.

From main() there is one central call to build the application interface after the application initialization has taken place -- buildMainWindow(Widget parent). This call encapsulates all of the subsequent calls that build the interface widgets.

BuildmainWindow(Widget parent) first creates a form widget to manage all of the rest of the widgets that make up the Single Document Interface (or SDI). It is known as a manager widget and is meant to parent other widgets.

The current implementation adheres to the common Motif programming style as follows:


...where MAX_NUM_PARGUMENTS is the maximum number of arguments to be loaded into the args array during the life of the variable; XmNfield is the type of argument to be passed to Xt during the widget creation; and XmVALUE is the value of the XmNfield type argument to be passed to Xt during the call to create the widget. XtSetArg is used to set attributes such as the location of the widget relative to its parent; the attachment points for the widget to its parent and other widgets; the color, size, and font attributes; and any other attribute that Xt or Motif defines for that type of widget. It is within these calls that the alterable look and feel is introduced to the widgets.


Figure 10 -- Widget Attachments

XtCreateWidget (as the name suggests) is a call to X and returns an unmanaged widget of the type XmWidgetClass, of parent, with the unique handle "uniqueNameString", and the attributes as set forth in the array args of length n. The call to Xt is avoided when there exists a fitting higher level Motif call that is specialized for the desired widget. An unmanaged widget differs from a managed one in that its existance is not initialized in the special tables and structures that Xt creates for event handling. Motif cannot know how yet to display an unmanaged widget. When the call XtManageChild(childName) or XtManageChildren(childrenArray, cardinality) is made, the Xt event handling structures learn about the widget, display it and begin to automate its reaction to predefined events.

XtAddCallback(widgetName, XmNcallBackListName, callbackProcedure, clientData) tells the widget widgetName to add the callback procedure callbackProcedure to the callback list XmNcallBackListName and pass the callback procedure the data specified by clientData. In effect, this call tells the widget to respond to a given event generated within its boundaries by calling the procedure callbackProcedure and passing clientData to it. Motif, and underlying Xt, use the entries in the callback lists to mask for events as they are passed from child to parent up through the heirarchy until either a widget responds to the event, or all widgets in the heirarchy up to the root have declined to react to it. At that point, normal execution resumes.

2.4.2 Callback Functions



In Motif, callbacks are registered with Xt to respond to user events. In Xt a widget can be made to respond to any user generated event. Motif is no different. Xt maintains several callback lists that are responsible for different kinds of events. After creation, when a Motif widget is managed and effectively placed in the widget tree it is eligible to register for events. At this time the widget has not explicitly registered any events into the callback lists, so any event generated within its boundaries will be ignored. When an event is generated within the boundaries of a widget, X suspends normal execution, determines what kind of event has been generated, and searches tha appropriate callback list for the widget's unique identifier. If the identifier is not found, Xt next searches its parent and repeats this behavior until a it finds a widget in the tree registered for the event. If Xt finds a widget registered for the event it calls the corresponding callback function. If no widget in the tree has been registered to respond to the event it is simply ignored and regular processing continues. A widget is registered for an event with the call XtAddCallback(Widget widget, String callbackName, XtCallBackProc callback, XtPointer clientData). Widget is the name of the registering widget; callbackName is the name of the callback list; callback is the name of the procedure to be invoked by the event, and clientData is the value to be passed to the callback procedure.

A callback procedure is defined to look like the following:

Widget is the identifier of the widget in which the event was generated (or the widget that responded to the event); clientData is a pointer to the value that was defined to be passed to the callback when the event was registered with XtAddCallback; and callData is a pointer to the Xt or Motif defined structure that corresponds to the type of widget that responded to the event. CallData is usually where the data of interest is communicated to the callback procedure. All relevant processing of the passed data for the event is defined in the body of the procedure. The procedure must not attempt to return a value.

Xt normally checks for events by invoking a call to XtAppMainLoop. XtAppMainLoop is simply an infinite loop that calls XtAppNextEvent to grab the next event from the event queue and then calls XtDispatchEvent to search the callback lists to dispatch the event to the appropriate procedure. The call to XtAppmainLoop must be the last call in the main body of the application because it is infinite and any subsequent calls will not be reached during normal program execution. Inventor simply wraps this call in an SoXt object method appropriately called SoXt:: XtAppMainLoop.

Normally the loop initiated by XtAppMainLoop is sufficient for any event handling. An exception is encountered when a significant ammount of processing is encountered in a callback procedure. When a callback procedure is computationally intensive and demands a large ammount of time to run to completion, the application will not respond to any events. The application appears frozen to user events. This is expected behavior when one considers what is actually going on behind the scenes. The event queue is normmally checked during the infinite loop initiated by XtAppMainLoop. When the application responds to an event, the processor state and program counter are pushed upon the stack as with any procedure call and the corresponding callback routine is initiated. During the execution of the callback routine, the application never checks the event queue again to process pending events. It is only when the callback routine returns that the event queue is considered by the application. There are two ways to correct for the seeming inappropriate behavior. Both have been used where they are appropriate.


Figure 11 -- Normal Motif Event Processing

The first way to avoid the application "freeze" during expensive callbacks is by explicitly embedding calls to check the event queue during callback execution. If events are found, they are processsed immediately and then normal execution resumes. A simple loop can inspect the event queue with XtAppPending to determine if there are events pending; and use XtAppNextEvent and XtDispatchEvent to process the event. Strategic placement of these calls in an expensive callback routine will correct for the "freezing" effect caused by the absence of event checking. The routine invoked to begin the numerical simulation is an example of a proceedure that benefits from this method. To speed execution of the callback procedures, the call can be made every nth iteration instead of every time step.

The second way to avoid the application "freeze" is supplied by Xt, and appears to be more elegant. Xt has anticipated this need by incorporating work procedures into its library. Work procedures use the same underlying methods as described above but they are implemented slightly differently. A call to:

adds the procedure proc for the application appContext (the unique application identifier supplied at initialization) and passes clientData as described above to the procedure. Multiple procedures can be added but the last one added is always the active one. The procedure should iterate n times before returning (Boolean)FALSE. When FALSE is returned, Xt checks the event queue, processes pending events, and calls the procedure again until TRUE is returned. When TRUE is returned the work procedure is removed and the previously added procedure (if it exists) is processed in an exactly similar way. The drawback of using Xt work procedures is that the procedure must be specially designed to work correctly. It was determined that it was advantageous to use work procedures in only a limited number of cases.


Figure 12 -- Control Flow for a Work Procedure

Back to the Table of Contents
Next Section ->