Unix Free Tutorial

Web based School

Previous Page Main Page

    • 47 — UNIX Graphical User Interfaces for Programmers
    • 47 — UNIX Graphical User Interfaces for Programmers

      By Karman Husain

      Writing Motif Applications

      This chapter will serve as an introduction to event-driven programming. After reading this chapter, you will have enough information to write your own application. Keep in mind, though, that writing Motif applications is perhaps not the easiest task in the world, nor is it the most complex. As with any other system, you will learn new ways of doing things. In Motif, you have to get used to programming in an event-driven environment. A typical C application runs from start to finish at its own pace. When it needs information, it looks for it from a source such as a file or the keyboard, and gets the information almost as soon as it asks for it. However, in event-driven programming, applications are executed on an asynchronous basis. That is, the order and time of arrival of each event is not deterministic. The application waits for an event to occur and then proceeds based on that event. Thus the term "event-driven programming."

      In the case of X Window programming, an application must wait for events on the input queue. Similarly, a server waits for an event from a client and then responds based on the type of event received. This event handling and other aspects of X programming are handled by a toolkit called the Xt ToolkitIntrinsics, or Xt for short.

      In Xt, an application typically runs in a loop forever. This loop is called an event loop, and an application enters it by calling the function XtAppMainLoop. While in this event loop, the application will always wait for an event, and will either handle it itself or more likely "dispatch" the event to a window or widget.

      A widget registers functions that it wants called when a type of event is received. This function is called a callback function. In most cases, a callback function is independent of the entire application. For example, some widgets will redraw themselves when a Pointer button is clicked in their display area. In this case, they would register a redraw callback function on a button click. Xt also supports "actions," which allow applications to register a function with Xt. An action is called when one or more sequences of specific event types are received. For example, Ctrl+X would call the exit function. The mapping of the action to an event is handled through a translation table within Xt. Functions which handle specific events are referred to as "event handlers."

      Naming Conventions

      By default, most X lib functions begin with the letter "X", but sadly this is not a rule to rely on. Several macros and functions do not begin with X, such as BlackColor, WhiteColor, etc. In general, if a name in Xlibrary begins with X, it's a function. If a name begins with any other capital letter, it's a macro.

      With Xt, the naming conventions get better, but only slightly. In Xt, macros are not differentiated from functions.


      TIP: Do not rely on the name of a toolkit function to tell you whether it's a macro or not. Read the manual.

      In Motif, almost all declarations begin with Xm. XmC refers to a class. XmR refers to a resource. XmN refers to a name. XtN refers to Xt resources used by Motif.

      In Motif, declarations ending with the words WidgetClass define the base class for a type of widget. Other conventions to remember about parameters passed in most X library function calls are: Width is always to the left of height. x is to the left of y. Source is to the left of destination. Display is usually the first parameter.

      With practice, you will be able to identify the types of parameters to pass and which toolkit a function belongs to, and you'll be able to "guess" what parameters an unknown function might expect.

      Writing Your First Motif Application

      See 47_1.c on the CD-ROM for a complete listing showing the basic format for a Motif application.

      The listing shows an application in which a button attaches itself to the bottom of a form. No matter how you resize the window, the button will always be on the bottom. The application does the following things:

      • Initializes the toolkit to get a Shell widget.

      • Makes a Form widget.

      • Manages all widgets as they are created.

      • Makes a Button widget and puts it on top of the Form widget.

      • Attaches a callback function to the button.

      • Realizes the widget (that is, makes the hierarchy visible).

      • Goes into its event loop.

      Let's look at the application in more detail. The #include files in the beginning of the file are required for most applications. Note the following files:

      #include <X11/Intrinsic.h>
      
      #include <Xm/Xm.h>

      These declare the definitions for XtIntrinsics and Motif, respectively. Some systems may not require the first inclusion, but it's harmless to put it in there because multiple inclusions of Intrinsic.h are permitted. In addition, each Motif widget requires its own header file. In Listing 47.1, the two widgets Form and PushButton require the following header files:

      #include <Xm/Form.h>
      
      #include <Xm/PushB.h>

      The variables in the program are declared in the following lines:

      Widget top;
      
      XtAppContext app;
      
      Widget aForm;
      
      Widget aButton;
      
      int    n;

      The top, aForm, and aButton represent widgets. Even though their widget types are different, they can all be referred to as widgets.

      The XtAppContext type is an "opaque" type, which means that a Motif programmer does not have to be concerned about how the type is set up. Widgets are opaque types as well, because only the items that are required by the programmer are visible.

      The first executable line of the program calls the XtAppInitialize() function. This will initialize the Xt toolkit and create an application shell and context for the rest of the application. This value is returned to the widget "top" (for top level shell). This widget will provide the interface between the window manager and the rest of the widgets in this application.

      The application then creates a Form widget on this top-level widget. A Form widget places other widgets on top of itself. It is a Manager widget because it "manages" other widgets.

      There are two steps for displaying a widget: Managing it and realizing it.

      Managing a widget allows it to be visible. If a widget is unmanaged, it will never be visible. By managing a widget, the program gives the viewing control over to the windowing system so it can display it. If the parent widget is unmanaged, any child widgets remain invisible even if managed.

      Realizing a widget actually creates all the subwindows under an application and displays them. Normally only the top-level widget is realized after all the widgets are managed. This call will realize all the children of this widget.

      Note that realizing a widget takes time. A typical program will manage all the widgets except the topmost widget. This way the application will only call XtRealizeWidget on the topmost parent when the entire tree has to be displayed. You have to realize a widget at least once, but you can manage and unmanage widgets as you want to display or hide them.

      In the past, the way to create and manage a widget was to call XtCreate and XtManageChild in two separate calls. However, this text will use a single call to create and manage a widget: XtVaCreateManagedWidget.

      Note the parameters to this call to create the Form widget:

      aForm = XtVaCreateManagedWidget("Form1", xmFormWidgetClass, top, XmNheight,90, XmNwidth,200,
      
      NULL);

      The first parameter is the name of the new widget. The second parameter describes the class of the widget being created. Recall that this is simply the widget name sandwiched between xm and WidgetClass. In this case it's xmFormWidgetClass. Note the lowercase x for the class pointer. This class pointer is declared in the header files included at the beginning of the file, Form.h.


      TIP: As another example, a label's class pointer would be called xmLabelWidgetClass and would require the Label.h file. Motif programmers have to be especially wary of the case-sensitivity of all variables.

      The next argument is the parent widget of this new widget. In this case top is the parent of Form1. The top widget was returned from the call to XtAppInitialize.

      The remaining arguments specify the parameters of this widget. In this case you are setting the width and height of this widget. This list is terminated by a NULL parameter.

      After the form is created, a button is placed on top of it. A Form widget facilitates placement of other widgets on top of it. In this application you will cause the button to "attach" itself to the bottom of the form. The three lines attach themselves to the form.

      XmNleftAttachment,XmATTACH_FORM,
      
      XmNrightAttachment,XmATTACH_FORM, XmNbottomAttachment,XmATTACH_FORM,

      The class of this button is included in the PushB.h file and is called xmPushButtonWidgetClass. The name of this widget is also the string that is displayed on the face of the button. Note that the parent of this button is the aForm widget. Thus the hierarchy is: top is the parent of aForm is the parent of aButton.

      The next step is to add a callback function when the button is pressed. This is done with the following call:

      XtAddCallback( aButton, XmNactivateCallback, bye, (XtPointer) NULL);

      In this call:

      • aButton is the pushbutton widget.

      • XmNactivateCallback is the action that will trigger this function.

      • bye is the name of the function called. You should declare this function before making this function call.

      • NULL is a pointer. This pointer could point to some structure meaningful to function bye.

      This will register the callback function bye for the widget. Now the topmost widget, top, is realized. This causes all managed widgets below top to be realized. The application then goes into a forever loop while waiting for events.

      The bye function of this program simply exits the application.

      Compiling This Application

      Read the compiler documentation for your machine. Almost all vendor supplied compilers now conform to the ANSI C standard. If your compiler is not ANSI compatible, get an upgrade—you'll need it.

      Next, check the location of the libraries in your system. Check the /usr/lib/X11 directory for the following libraries: libXm.a, libXt.a, and libX11.a. If possible, use the shared library versions of these libraries with .so extensions followed by some numbers. The advantage of using shared libraries is that it results in a smaller Motif application. A typical application like the preceding one can be up to 1M in size because of the overhead of Motif.

      The disadvantage of shared libraries is that your end user may not have the correct version of the library in his path. This does annoy some end users, especially if no fast method of acquiring this resource is available to them. Also, shipping a shared library with your application may require you to pay some licensing fees to the original library vendor. From a programmer's perspective, shared libraries are sometimes impossible to use with your debugger. Of course, if your debugger supports them, use them. Check your compiler documentation.

      In most cases, if you intend to use shared libraries, use the static versions to do your debugging and testing and then compile the shared version. Always check your vendor's licensing agreement for details on how to ship shared libraries.

      The application can be compiled with this command:

      CC list1.c -o list1 -lXm -lXt -lX11

      CC is your version of the ANSI compiler: gcc, acc, cc, or whatever. The program can be run from a command line; create a script file:

      CC $1.c -o $1 -lXm -lXt -lX11

      Now pass this script file; just the filename without the extension. The best way is to create a makefile, although this command will work with the examples in this text.

      The Widget Hierarchy

      The Motif widget set is a hierarchy of widget types. (See Figure 47.1.) Any resources provided by a widget are inherited by all its derived classes. Consider the three most important base classes: Core, XmPrimitive, and XmManager.


      Figure 47.1. The widget hierarchy.

      Core

      The Core widget class provides the basis for all classes. It provides at least the following variables:

      • XmNx, XmNy: This is a widget's position on the display.

      • XmNheight, XmNwidth: This is a widget's size.

      • XmNborderWidth: This is set to 1 by default.

      • XmNsensitive: A Boolean resource that specifies whether this widget can receive input.

      • XmNcolorMap: The default colormap.

      • XmNbackground: The background color.

      Check the Motif Programmer's reference manual for a complete listing.

      XmPrimitive

      The XmPrimitive widget class inherits all the resources from Core and adds more functionality.

      • XmNforeground: The foreground color.

      • XmNhighlightOnEnter: Changes color when the pointer is within the window of the widget.

      • XmNhighlightThickness: If XmNhighlightOnEnter is True, changes the border to this thickness.

      • XmNhighlightColor: The color to use when drawing the highlighted border.

      • XmNshadowThickness: The number of pixels used to draw the psuedo-3D look that Motif is famous for. This is defaulted to 2.

      • XmNtopShadowColor and XmNbottomShadowColor: Sets the color for top and bottom lines around a widget.

      • XmNuserData: A pointer available for use to the programmer.

      The XmPrimitive widget also provides the XmNdestroyCallback resource. This can be set to a function that does clean-up when a widget is destroyed. In Motif 1.2 or later, the XmPrimitive class also provides a XmNhelpCallback resource that is called when the F1 key is pressed in the widget's window. This is to allow specific help information for a widget.

      XmManager

      The XmManager class provides support for all Motif widgets that contain other widgets. This is never used directly in an application and works in a similar manner to the XmPrimitive class.

      The Label Widget

      The Label widget is used to display strings or pixmaps. Include the Xm/Label.h file in your source file before you use this widget.

      Some of the resources for this widget include:

      • XmNalignment: This resource determines the alignment of the text in this widget. The allowed values are XmALIGNNMENT_END, XmALIGNMENT_CENTER, and XmALIGNMENT_BEGIN, for right-, center-, and left- justification, respectively.

      • XmNrecomputeSize: A Boolean resource. If set to TRUE, the widget will be resized when the size of the string or pixmap changes dynamically. This is the default. If set to FALSE, the widget will not attempt to resize itself.

      • XmNlabelType: The default value of this type is XmSTRING to show strings. However, it can also be set to XmPIXMAP when displaying a pixmap specified in the XmNpixmap resource.

      • XmNlabelPixmap: This is used to specify which pixmap to use when the XmNlabelType is set to XmPIXMAP.

      • XmNlabelString: This is used to specify which XmString compound string to use for the label. This defaults to the name of the label. See the section "Strings in Motif: Compound Strings" later in this chapter.

      To get acquainted with left- and right-justification on a label, see file 47_2 on the CD-ROM. This listing also shows how the resources can be set to change widget parameters, programmatically and through the .Xresource files.

      Avoid using the \n in the label name. If you have to create a multi-string widget, use the XmStringCreate call to create a compound string. (See the next section, "Strings in Motif: Compound Strings.") Another way to set the string is to specify it in the resource file and then merge the resources.

      The listing shows the label to be right-justified. You could easily center the string horizontally by not specifying the alignment at all and letting it default to the center value. Alternatively, try setting the alignment parameter to XmALIGNMENT_BEGINNING for a left-justified label.

      Strings in Motif Compound Strings

      A compound string is Motif's way of representing a string. For a typical C program, a null-terminated string is enough to specify a string. In Motif, a string is also defined by the character set it uses. Strings in Motif are referred to as compound strings and are kept in opaque data structures called XmString.

      In order to get a compound string from a regular C string, use this function call:

      XmString XmStringCreate( char *text, char *tag);

      This returns an equivalent compound string, given a pointer to a null-terminated C string and a tag. The tag specifies which font list to use and is defaulted to XmFONTLIST_DEFAULT_TAG.

      New lines in C strings have to be handled by special separators in Motif. To create a string and preserve the new lines, use this call:

      XmString XmStringCreateLtoR( char *text, char *tag);

      The compound strings have to be created and destroyed just like any other object. They persist long after the function call that created them returns. Therefore, it's a good idea to free all locally used XmStrings in a function before returning, or else all references to the strings will be lost.

      Here's the definition of a call to free XmString resources:

      XmStringFree( XmString s);

      You can run operations on strings similar to those you would under ASCII programming, except that they are called by different names. Use Boolean XmStringByteCompare( XmString s1, XmString s2); for a strict byte-for-byte comparison. For just the text comparison, use XmStringCompare( XmString s1, XmString s2);.

      To check if a string is empty, use the following:

      Boolean XmStringEmpty( XmString s1);

      To concatenate two strings together, use the following:

      XmString XmStringConcat( XmString s1, XmString s2);

      It creates a new string by concatenating s2 to s1. This new resource has to be freed just like s1 and s2.

      If you want to use sprintf, use it on a temporary buffer and then create a new string. For example:

      char str[32];
      
      XmString xms;
      
      ......
      
      sprintf(str," pi = %lf, Area = %lf", PI, TWOPI*r);
      
      xms =  XmStringCreateLtoR( str,  XmFONTLIST_DEFAULT_TAG); ......
      
      n = 0;
      
      XtSetArg(arg[n],XmNlabelString,xms); n++; 
      XtSetValues(someLabel, arg, n); XmStringFree(xms);

      If a string value is corrupted by itself over time, check to see if the widget is not making a copy of the passed XmString for its own use. Sometimes a widget may only be keeping a pointer to the XmString. If that string was "freed," the widget may wind up pointing to bad data.

      One good way to check is to set an XmString resource. Then use the XtGetValues function to get the same resource from the widget. If the values of the XmStrings are the same, the widget is not making a copy for itself. If they aren't the same, it's safe to free the original because the widget is making a local copy. The default course of action is to assume that a widget makes copies of such resources for itself.

      A similar test could be used to determine if a widget returns a copy of its resource to a pointer to it. Use the preceding listing, but this time use XTgetValues to get the same resource twice. Then do the comparison.

      /**
      
      *** This is a sample partial listing of how to check if the
      
      *** data returned on an XtGetValues and an XtSetValues
      
      *** call is a copy or a reference.
      
      ***/
      
      #include "Xm/Text.h"
      
      ..
      
      Widget w;
      
      XmString x1, x2, x3;
      
      x3 = XmStringCreateLtoR("test", XmFONTLIST_DEFAULT_TAG); XmTextSetString(w,x3);
      
      ...
      
      x1 = XmTextGetString(w);
      
      x2 = XmTextGetString(w);
      
      XtWarning(" Checking SetValues");
      
      if (x1 != x3)
      
           XtWarning("Widget keeps a copy ! Free original!");
      
      else
      
      XtWarning("Widget does not keep a copy! Do NOT free original");
      
      XtWarning(" Checking GetValues");
      
      if (x1 == x2)
      
           XtWarning("Widget returns a copy! Do NOT free");
      
      else
      
      XtWarning("Widget does not return a copy! You should free it ");

      The XtWarning() message is especially useful for debugging the execution of programs. The message is relayed to the stderr of the invoking application. If this is an xterm, you will see an error message on that terminal window. If no stderr is available for the invoker, the message is lost.


      TIP: The XtSetArg macro is defined as:

      #define XtSetArg(arg,n,d) \
      ((void)((arg).name = (n).(arg).value = (XtArgVal)))

      Do not use XtSetArg(arg[n++], ... because this will increment n twice.

      The PushButton Widget

      XmPushButton is perhaps the most heavily used widget in Motif.

      Listings 1 and 2 showed the basic usage for this class. When a button is pressed in the pushbutton area, the button goes into an "armed" state. The color of the button changes to reflect this state. This color can be set by using XmNarmColor. This color is shown when the XmNfillOnArm resource is set to TRUE.


      TIP: If the XmNarmcolor for a pushbutton does not seem to be working, try setting the XmNfillOnArm resource to TRUE.

      The callback functions for a pushbutton are:

      • XmNarmCallback: Called when a pushbutton is armed.

      • XmNactivateCallback: Called when a button is released in the widgets area while the widget is armed. This is not invoked if the pointer is outside the widget when the button is released.

      • XmNdisarmCallback: Called when a button is released with the pointer outside the widget area while the widget is armed.


      TIP: If a callback has more than one function registered for a widget, all the functions will be called but not necessarily in the order they were registered. Do not rely on the same order being preserved on other systems. If you want more than one function performed during a callback, sandwich them in one function call.

      In Listing 47.2, you saw how a callback function was added to a pushbutton with the XtAddCallback function. The same method can be used to call other functions for other actions, such as the XmNdisarmCallback.

      The Toggle Button Widget

      The toggle button class is a subclass of the XmLabel widget class. There are two types of buttons: N of many and one of many. When using N of many, users can select many options. When using one of many, the users must make one selection from many items. Note the way the buttons are drawn; N of many buttons are shown as boxes and one of many buttons are shown as diamonds.

      The resources for this widget include:

      • XmNindicatorType: This determines the style and can be set to XmN_OF_MANY or XmONE_OF_MANY (the default).

      • XmNspacing: The number of pixels between the button and its label.

      • XmNfillOnSelect: The color of the button changes to reflect a "set" when the XmNfillOnArm resource is set to TRUE.

      • XmNfillColor: The color to show when "set."

      • XmNset: A Boolean resource indicating whether the button is set or not. If this resource is set from a program, the button will automatically reflect the change.

      It's easier to use the convenience function XmToggleButtonGetState(Widget w) to get the Boolean state for a widget, and to use XmToggleButtonSetState(Widget w, Boolean b) to set the value for a toggle button widget.

      Like the pushbutton class, the toggle button class has three similar callbacks:

      • XmNarmCallback: Called when the toggle button is armed.

      • XmNvalueChangedCallback: Called when a button is released in the widget area while the widget is armed. This is not invoked if the pointer is outside the widget when the button is released.

      • XmNdisarmCallback: Called when a button is released with the pointer outside the widget area while the widget is armed.

      For the callbacks, the callback data is a structure of type:

      typedef struct {
      
           int  reason;
      
           XEvent  *event;
      
           int  set;
      
      } XmToggleButtonCallbackStruct;

      The reason for the callback is one of the following: XmCR_ARM, XmCR_DISARM, or XmCR_ACTIVATE. The event is a pointer to XEvent that caused this callback. The set value is 0 if the item is not set and non-zero if it's set. The buttons are arranged in one column through the RowColumn widget discussed later in this chapter. See file 47_3c on the CD-ROM for an example of how to use the toggle button.

      By defining the DO_RADIO label, you can make this into a radio button application. That is, only one of the buttons can be selected at one time.

      Convenience Functions

      Usually, the way to set resources for a widget is to do it when you create the widget. This is done with either the XtVaCreateManaged call or the XmCreateYYY call, where YYY is the widget you're creating. The text uses the variable argument call to create and manage widgets. If you use the XmCreateYYY call, you have to set the resource settings in a list of resource sets. An example of creating a Label widget is shown in file 47_4c on the CD-ROM. This is a function that creates a Label widget on a widget given the string x.

      Or you could use the variable argument lists to create this label, as shown in file 47_5c.

      In either case, it's your judgment call as to which one to use. The label created with the variable lists is a bit easier to read and maintain. But what about setting values after a widget has been created? This would be done through a call to XtSetValue with a list and count of resource settings. For example, to change the alignment and text of a label, you would use the following:

      n = 0;
      
      XtSetArg(arg[n], XmNalignment, XmALIGNMENT_BEGIN); n++;
      
      XtSetArg(arg[n], XmNlabelString, x); n++;
      
      XtSetValues(lbl,arg,n);

      Similarly, to get the values for a widget you would use XtGetValues:

      Cardinal n; /* usually an integer or short... use Cardinal to be safe
      
      */ int align;
      
      XmString x;
      
      ...
      
      n = 0;
      
      XtSetArg(arg[n], XmNalignment, &align); n++;
      
      XtSetArg(arg[n], XmNlabelString, &x); n++; XtGetValues(lbl,arg,n);

      In the case of other widgets, such as the Text widget, this setting scheme is hard to read, quite clumsy, and prone to typos. For example, to get a string for a Text widget, do you use x or address of x?

      For this reason, Motif provides convenience functions. In the ToggleButton widget class, for example, rather than use the combination of XtSetValue and XtSetArg calls to get the state, you would use one call, XmToggleButtonGetState(Widget w), to get the state. These functions are valuable code savers when you're writing complex applications. In fact, you should write similar convenience functions whenever you cannot find one that suits your needs.

      The List Widget

      The List widget displays a list of items from which the user can select. The list is created from a list of compound strings. Users can select either one item or many items from this list. The resources for this widget include:

      • XmNitemCount: This determines the number of items in the list.

      • XmNitems: An array of compound strings. Each entry corresponds to an item in the list. Note that a List widget makes a copy of all items in its list when using XtSetValues; however, it returns a pointer to its internal structure when returning values to an XtGetValues call. So do not free this pointer from XtGetValues.

      • XmNselectedItemCount: The number of items currently selected.

      • XmNselectedItems: The list of selected items.

      • XmNvisibleItemCount: The number of items to display at one time.

      • XmNselectionPolicy: This is used to set single or multiple selection capability. If set to XmSINGLE_SELECT, the user will be able to select only one item. Each selection will invoke XmNsingleSelectionCallback. Selecting one item will deselect another previously selected item. If set to XmEXTENDED_SELECT, the user will be able to select a block of contiguous items in a list. Selecting one or more new items will deselect other previously selected items. Each selection will invoke the XmNmultipleSelection callback.

      If set to XmMULTIPLE_SELECT, the user will be able to select multiple items in any order. Selecting one item will not deselect another previously selected item. Each selection will invoke the XmNmultipleSelection callback.

      If the resource is set to XmBROWSE_SELECT, the user can move the pointer across all the selections with the button pressed, but only one item will be selected. This will invoke XmbrowseSelectionCallback when the button is finally released on the last item browsed. Unlike with the XmSINGLE_SELECT setting, the user does not have to press and release the button to select an item.

      It is easier to create the List widget with a call to XmCreateScrolledList(), because this will automatically create a scrolled window for you. Also, the following convenience functions will make working with List widgets easier. However, they may prove to be slow when compared to XtSetValues() calls. If you feel that speed is important, consider using XtSetValues(). You should create the list for the first time by using XtSetValues.

      • XmListAddItem(Widget w, XmString x, int pos): This will add the compound string x to the List widget w at the 1-relative position pos. If pos is 0, the item is added to the back of the list. This function is very slow. Do not use it to create a new list, because it rearranges the entire list before returning.

      • XmListAddItems(Widget w, XmString *x, int count, int pos): This will add the array of compound strings, x, of size count, to the List widget w from the position pos. If pos is 0, the item is added to the back of the list. This function is slow too, so do not use it to create a new list.

      • XmDeleteAllItems(Widget w): This will delete all the items in a list. It's better to write a convenience function:

        n = 0;
        XtSetArg(arg[n], XmNitems, NULL); n++;
        XtSetArg(arg[n], XmNitemCount, 0); n++;
        XtSetValues(mylist,arg,n);
      • XmDeleteItem(Widget w, XmString x): Deletes the item x from the list. This is a slow function.

      • XmDeleteItems(Widget w, XmString *x, int count): Deletes all the count items in x from the list. This is an even slower function. You might be better off installing a new list.

      • XmListSelectItem(Widget w, XmString x, Boolean Notify): Programmatically selects x in the list. If Notify is TRUE, the appropriate callback function is also invoked.

      • XmListDeselectItem(Widget w, XmString x): Programmatically deselects x in the list.

      • XmListPos( Widget w, XmString x): Returns the position of x in the list. 0 if not found.

      See file 47_6c on the CD-ROM.

      The Scrollbar Widget

      The Scrollbar widget allows the user to select a value from a range. Its resources include:

      • XmNvalue: The value representing the location of the slider.

      • XmNminimum and XmNmaximum: The range of values for the slider.

      • XmNshowArrows: The Boolean value if set shows arrows at either end.

      • XmNorientation: Set to XmHORIZONTAL for a horizontal bar or XmVERTICAL (default) for a vertical bar.

      • XmNprocessingDirection: Set to either XmMAX_ON_LEFT or XmMAX_ON_RIGHT for XmHORIZONTAL, or XmMAX_ON_TOP or XmMAX_ON_BOTTOM for XmVERTICAL orientation.

      • XmNincrement: The increment per move.

      • XmNpageIncrement: The increment if a button is pressed in the arrows or the box. This is defaulted to 10.

      • XmNdecimalPoint: Shows the decimal point from the right. Note that all values in the Scrollbar widget's values are given as integers. Look at the radio station selection example in file 47_8c on the CD-ROM. Note that the Push to Exit button for the application is offset on the left and right by 20 pixels. This is done by offsetting the XmATTACH_FORM value for each side (left or right) through the value in XmNleftOffset and XmNrightOffset. See the "Forms" section for more details.

      For the case of FM selections, you would want the bar to show odd numbers. A good exercise for you would be to allow only odd numbers in the selection. Hint: Use XmNvalueChangedCallback:

      XtAddCallback(aScale, XmNvalueChangedCallback, myfunction);

      The callback will send a pointer to the structure of type XMScaleCallbackStruct. where myfunction is defined as:

      /**
      
      *** Partial listing for not allowing even numbers for FM selection.
      
      **/
      
      #define MAX_SCALE 1080
      
      #define MIN_SCALE 800
      
      static void
      
      myfunction(Widget w, XtPointer dclient,  XmScaleCallbackStruct *p)
      
      {
      
      int k;
      
      k = p->value;
      
      if ((k & 0x1) == 0)  /** % 2  is zero ** check limits & increase **/
      
           {
      
           k++;
      
           if (k >= MAX_SCALE) k = MIN_SCALE + 1;
      
           if (k <= MIN_SCALE) k = MAX_SCALE - 1;
      
      XmScaleSetValue(w,k);  /** this will redisplay it too
      
      **/
      
      }
      
      }

      The Text Widget

      The Text widget allows the user to type in text. This text can be multi-line, and the Text widget provides full text editing capabilities. If you are sure you want only single-line input from the user, you can specify the TextField widget. This is simply a scaled-down version of the Text widget. The resources for both are the same unless explicitly stated. These include:

      • XmNvalue: A character string, just like in C. This is different from Motif 1.1 or older, where this value was a compound string. If you have Motif 1.2 or later, this will be C string.

      • XmNmarginHeight and XmNmarginWidth: The number of pixels between the widget border and the text. The default is 5 pixels.

      • XmNmaxLength: This sets the limit on the number of characters in the XmNvalue resource.

      • XmNcolumns: The number of characters per line.

      • XmNcursorPosition: The number of characters at the cursor position from the beginning of the text file.

      • XmNeditable: The Boolean value that, if set to TRUE, will allow the user to insert text.

      The callbacks for this widget are:

      • XmNactivateCallback: Called when the user presses the Return key.

      • XmNfocusCallback: Called when the widget receives focus from the pointer.

      • XmNlosingFocusCallback: Called when the widget loses focus from the pointer.

      There are several convenience functions for this widget:

      • XmTextGetString(Widget w) returns a C string (char *).

      • XmTextSetString(Widget w, char *s) sets a string for a widget.

      • XmTextSetEditable(Widget w, Boolean trueOrFalse) sets the editable string of the widget.

      • XmTextInsert(Widget w, XmTextPosition pos, char *s) sets the text at the position defined by pos. This XmTextPosition is an opaque item defining the index in the text array.

      • XmTextShowPosition(Widget w, XmTextPosition p) scrolls to show the rest of the string at the position p.

      • XmTextReplace(Widget w, XmTextPosition from, XmTextPosition to, char *s) replaces the string starting from the location from inclusive to the position to, with the characters in string s.

      • XmTextRemove(Widget w) clears the text in a string.

      • XmTextCopy(Widget w, Time t) copies the currently selected text to the Motif clipboard. The Time t value is derived from the most recent XEvent (usually in a callback), which is used by the clipboard to take the most recent entry.

      • XmTextCut(Widget w, Time t) is like XmTextCopy, but removes the selected text from the text's buffer.

      • XmTextPaste(Widget w) pastes the contents of the Motif clipboard onto the text area at the current cursor (insertion) position.

      • XmTextClearSelection(Widget w, XmTextPosition p, XmTextPosition q, Time t) selects the text from location p to location q.

      In the following example, you could construct a sample editor application with the Text widget. For the layout of the buttons, you would use widgets of the XmManager class to manage the layout for you. These manager widgets are:

      • XmBulletinBoard

      • XmRowColumn

      • XmForm

      The Bulletin Board Widget

      The Bulletin Board widget allows the programmer to lay out widgets by specifying their XmNx and XmNy resources. These values are relative to the top left corner of the Bulletin Board widget. The Bulletin Board widget will not move the children widget around on itself. If a widget resizes, it's the application's responsibility to resize and restructure its widgets on the Bulletin Board.

      The resources for the widget are:

      • XmNshadowType: Specifies the type of shadow for this widget. It can be set to XmSHADOW_OUT (the default), XmSHADOW_ETCHED_IN, XmSHADOW_ETCHED_OUT, or XmSHADOW_IN.

      • XmNshadowThickness: The number of pixels for the shadow. This is defaulted to 0 (no shadow).

      • XmNallowOverlap: Allows the children to be overlapped as they are laid on the widget. This is a Boolean resource and is defaulted to TRUE.

      • XmNresizePolicy: Specifies the resize policy for managing itself. If set to XmRESIZE_NONE, it will not change its size. If set to XmRESIZE_ANY, it will grow or shrink to attempt to accommodate all its children automatically. This is the default. If set to XmRESIZE_GROW, it will grow, but never shrink, automatically.

      • XmNbuttonFontList: Specifies the font for all XmPushButton children.

      • XmNlabelFontList: Specifies the default font for all widgets derived from XmLabel.

      • XmNtextFontList: Specifies the default font for all Text, TextField, and XmList children.

      It also provides the callback XmNfocusCallback, which is called when any children of the Bulletin Board receives focus.

      The RowColumn Widget

      The RowColumn widget class orders its children in a row or columnar fashion. This is used to set up menus, menu bars, and radio buttons. The resources provided by this widget include:

      • XmNorientation: XmHORIZONTAL for a row major layout of its children; XmVERTICAL for a column major layout.

      • XmNnumColumns: Specifies the number of rows for a vertical widget and the number of columns for a horizontal widget.

      • XmNpacking: Determines how the children are packed. XmPACK_TIGHT allows the children to specify their own size. It fits children in a row (or column if XmHORIZONTAL), and then starts a new row if no space is available. XmPACK_NONE forces Bulletin Board-like behavior. XmPACK_COLUMN forces all children to be the size of the largest column. This uses the XmNnumColumns resource and places all its children in an organized manner.

      • XmNentryAlignment: Specifies which part of the children to use in its layout alignment. Its default is XmALIGNMENT_CENTER, but it can be set to XmALIGNMENT_BEGINNING for the left side or XmALIGNMENT_END for the right side. This is on a per column basis.

      • XmNverticalEntryAlignment: Specifies the alignment on a per row basis. It can be assigned a value of XmALIGNMENT_BASELINE_BOTTOM, XmALIGNMENT_BASELINE_TOP, XmALIGNMENT_CONTENTS_BOTTOM, XmALIGNMENT_CONTENTS_TOP, or XmALIGNMENT_CENTER.

      • XmNentryBorder: The thickness of a border drawn around all children, and is defaulted to 0.

      • XmNresizeWidth: A Boolean variable that, if set to TRUE, will allow the RowColumn widget to resize its width when necessary.

      • XmNresizeHeight: A Boolean variable that, if set to TRUE, will allow the RowColumn widget to resize its height when necessary.

      • XmNradioBehaviour: Works with toggle buttons only. It allows only one toggle button in a group of buttons to be active at a time. The default is FALSE.

      • XmNisHomogeneous: If set to TRUE, this specifies that only children of the type Class in XmNentryClass can be children of this widget. The default is FALSE.

      • XmNentryClass: Specifies the class of children allowed in this widget if XmNisHomogeneous is TRUE. A sample radio button application was shown in file 47_5c. To see another example of the same listing but with two columns, see file 47_8c on the CD-ROM.

      The Form Widget

      The beginning of the chapter introduced you to the workings of the Form widget. This is the most flexible and most complex widget in Motif. Its resources include:

      • XmNtopAttachment

      • XmNleftAttachment

      • XmNrightAttachment

      • XmNbottomAttachment

      These values specify how a child is placed. The following values correspond to each side of the widget:

      
      
      XmATTACH_NONE: Do not attach this side to Form.
      XmATTACH_FORM: Attach to corresponding side on Form.
      XmATTACH_WIDGET: Attach this side to opposite side of a reference widget. For example, attach the right side of this widget to the left side of the reference widget. A reference widget is another child on the same form.
      XmATTACH_OPPOSITE_WIDGET: Attach this side to same side of a reference widget. This is rarely used.
      XmATTACH_POSITION: Attach a side by the number of pixels shown in XmNtopPosition, XmNleftPosition, XmNrightPosition, and XmNbottomPosition resources, respectively.
      XmATTACH_SELF: Use XmNx, XmNy, XmNheight, and XmNwidth.

      The following resources are set to the corresponding widgets for each side for the XmATTACH_WIDGET setting in an attachment:

      • XmNtopWidget

      • XmNleftWidget

      • XmNrightWidget

      • XmNbottomWidget

      The following resources are the number of pixels a side of a child is offset from the corresponding Form side. The offset is used when the attachment is XmATTACH_FORM.

      • XmNtopOffset

      • XmNleftOffset

      • XmNrightOffset

      • XmNbottomOffset

      Sometimes it is hard to get the settings for a Form widget just right, or the Form widget does not lay out the widgets in what seems to be the proper setting for a child widget. In these cases, lay the children out in ascending or descending order from the origin of the Form widget. That is, create the top left widget first and use it as an "anchor" to create the next child, then the next one to its right, and so on. There is no guarantee that this will work, so try using the bottom right, bottom left, or top right for your anchor positions.

      If this technique does not work, try using two forms on top of the form you're working with. Forms are cheap, and your time is not. It's better to just make a form when two or more widgets have to reside in a specific layout.

      When you're trying a new layout on a Form widget, if you get error messages about failing after 10,000 iterations, it means you have conflicting layout requests to one or more child widgets. Check the attachments very carefully before proceeding. This error message results from the Form widget trying different layout schemes to accommodate your request.


      TIP: At times, conflicting requests to a form will cause your application to slow down while it's trying to accommodate your request, not show the form, or both.

      Designing Layouts

      When you're designing layouts, think about the layout before you start writing code. Let's try an album search front-end example. See file 47_9c on the CD-ROM.

      The application is shown in Figure 47.9. Notice how the labels do not line up with the Text widget. There is a problem in the hierarchy of the setup. See the hierarchy of the application in Figure 47.10.


      Figure 47.9. The output of Listing 47.9.


      Figure 47.10. The hierarchy of Listing 47.9.

      The Form widgets are created to maintain the relative placements of all widgets that correspond to a type of function. The RowColumn widgets allow items to be placed on them. The best route to take in this example is to lay one text and one label on one RowColumn widget and have three RowColumn widgets in all, one for each instance up to NUM_ITEMS. This will ensure that each label lines up with its corresponding Text widget.

      A couple of points to note about laying out applications:

      • Think about what you want the form or dialog to do. Draw it on paper if you have to. Coding is the easy part; determining what to do is much harder.

      • Be consistent. Users will love you for it. If Alt+x is a shortcut for "Exit" in one screen, do not make it a cut operator in another. Keep controls on the same side of all dialog boxes and forms. Use separators to separate different functions on the same window.

      • Choose a color scheme for your end users. What may be cool to you may be grotesque to the end user. They may not even be using a color monitor in some rare cases. A combination of white, gray, and black may be your best bet if you don't want to deal with different color schemes in your code.

      • Colors on your monitor may not be the same on the end user's monitor.

      • Do not assume that the user's monitor has the same resolution as yours. Keep fonts (and buttons) big enough for a large cursor. Allow windows to be resizeable as much as possible to allow the user to customize his desktop.

      • Assume nothing. If the user can size your window to an unworkable size, either limit the size in resizeCallback to the lowest size or don't allow sizing at all.

      • Offer some help for the user. In the future, Help will be required as a standard option on menu bars, so plan ahead.

      • Avoid clutter. Too many options and entries on one huge form tend to confuse and baffle the user. Consider two tiers or more. Default everything as much as possible.

      • Make the program more forgiving. Sometimes an "Are you sure?" dialog with an option to change a list of parameters can be endearing to a user. On the other hand, some users hate this type of checking.

      Menus

      The way you design widget hierarchies is especially important when you're working with Motif menus. Motif menus are a collection of widgets, so there is no "menu" widget for a menu. You create menus using a hierarchy of different types of widgets: RowColumn, PushButton, CascadeButton, ToggleButton, Label, and Separator.

      There are three kinds of menus in Motif:

      • Popup: This appears as a list of items when a pointer button is pressed on a widget.

      • Pulldown: This appears when a button on an existing menu is pressed.

      • Option: This allows the user to select from a list of options, with the current selection visible at all times.

      The procedure to create a menu is different for each type of menu.

      Popup

      To create a Popup menu, do the following:

      1. Include the correct header files. You will need the header files for the menu:

        Label.h

        RowColumn.h

        PushB.h

        Separator.h

        BulletinB.h

        CascadeB.h

      2. Create the menu pane with a call to XmCreatePopupMenu. This is a convenience call to create a RowColumn widget and a MenuShell widget with the proper settings.

      3. Create the buttons on the menu pane. Use XmPushbuttons, XmToggleButtons, XmSeparator, and XmCascadeButtons.

      4. Attach callback functions to the widgets.

      See file 47_10c on the CD-ROM for a listing that sets up a pop-up menu.

      Note three important items about this listing: You can use printf functions within Motif applications. The output goes to the controlling terminal by default. This is invaluable in debugging. The menu is not visible by itself. An event handler on the parent of the menu is registered before the menu can be displayed. This allows the menu to be displayed any time a button is pressed. The XmMenuPosition call sets the position of the Popup menu. It is then managed (after placement).

      The Menu Bar

      A menu bar is a horizontal bar that is continually available to the user. Motif uses the RowColumn widget as a bar, with cascading buttons for each option.

      The procedure for creating a menu bar is as follows:

      1. Include the correct header files. You will need the header files for the menu:

        Label.h RowColumn.h

        PushB.h Separator.h

        BulletinB.h CascadeB.h

      2. Create the menu bar with a call to XmCreateMenuBar().

      3. Create the pull-down menu panes with a call to XmCreatePulldownMenu().

      4. For each pull-down pane, create a cascade button on the menu bar. Use the menu bar as the parent. A cascade button is used to link the items in a menu with the menu bar itself.

      5. Attach the menu pane to its corresponding cascade button. Use the XmNsubMenuId resource of the cascade button on the appropriate menu pane.

      6. Create the menu entries in the menu panes.

      File 47_1k on the CD-ROM shows how to set up a menu bar and pull-down menus.

      Note that the Motif programming style requires you to make the Help button (if you have one) right-justified on the menu bar. This Help cascade button should then be set to the XmNmenuHelpWidget of a menu bar. The menu bar will automatically position this widget at the right-hand side of the visible bar.

      File 47_12c on the CD-ROM is another example of setting up a menu bar and pull-down menus.

      The Options Menu

      The Options menu allows the user to select from a list of items, and displays the most recently selected item. The procedure for creating an Options menu is similar to creating a menu bar.

      1. Include the correct header files. You will need the header files for the menu:

        Label.h Separator.h

        RowColumn.h BulletinB.h

        PushB.h CascadeB.h

      2. Create the menu bar with a call to XmCreateOptionMenu().

      3. Create the pull-down menu panes with a call to XmCreatePulldownMenu().

      4. For each pull-down pane, create a cascade button on the menu bar.

      5. Attach the menu pane to its corresponding cascade button. Use the XmNsubMenuId resource of the cascade button on the appropriate menu pane.

      6. Create the menu entries in the menu panes.

      Accelerators and Mnemonics

      A menu item's accelerator is a keystroke that invokes the callback for that particular item. For example, to open a file you could use Ctrl+O. The resource for this accelerator could be set in the resource file as the following:

      *Open*accelerator: Ctrl<Key>O

      The corresponding menu item should read "Open Ctrl+O" to make the user aware of this shortcut. You can also set this resource through the following command in the .Xresources file:

      *Open*acceleratorText: "Ctrl+O"

      Using the .Xresource file is the preferred way of setting these resources.

      Mnemonics are a shorthand for letting users select menu items without using the mouse. For example, you could use <meta>F for invoking the File menu. These are usually set in the .Xresources file as well. The syntax for the File menu to use the <meta>F key would be as follows:

      *File*mnemonic: F

      Dialog Boxes

      A dialog box conveys information about something to the user, and receives one of a limited number of responses. For example, a dialog box could read "Go Ahead and Print" with three buttons—OK, Cancel, and Help. The user would then select one of the three buttons.

      A typical dialog box displays an icon, a message string, and (usually) three buttons. Motif provides predefined dialog boxes for the following categories: Errors, information, warnings, working, and question.

      Each of the above dialog box types displays a different icon: a question mark for the Question dialog box, an exclamation mark for the Information dialog box, and so on. Convenience functions ease creation of dialog boxes:

      • XmCreateErrorsDialog

      • XmCreateInformationDialog

      • XmCreateWarningDialog

      • XmCreateWorkingDialog

      • XmCreateQuestionDialog

      The infamous "Really Quit?" dialog box can be implemented as shown in Listing 47.13. There is another example in file 47_17c on the CD-ROM.

      Append this to the end of any listing to get instant verification before you actually quit the application.

      Note that the quitDlg dialog box is set to NULL when the function is first called. It is only managed for subsequent calls to this function.

      Modes of a Dialog Box

      A dialog box can have four modes of operation, called modalities. The mode is set in the XmNdialogStyle resource. The possible values are as follows:

      • Non-Modal: The user can ignore the dialog box and work with any other window on the screen. The resource value is XmDIALOG_MODELESS.

      • Primary Application Modal: All input to the window that invoked the dialog box is locked out. The user can use the rest of the windows in the application. The resource value is XmDIALOG_PRIMARY_APPLICATION_MODAL.

      • Full Application Modal: All input to all the windows in the application that invoked the dialog box is locked out. The user cannot use the rest of the windows in the application. The resource value is XmDIALOG_FULL_APPLICATION_MODAL.

      • System Modal: All input is directed to the dialog box. The user cannot interact with any other window in the system. The resource value is XmDIALOG_SYSTEM_MODAL.

      The dialog boxes provided by Motif are based on the XmMessageBox widget. Sometimes it is necessary to get to the widgets in a dialog. This is done by a call to the following:

      Widget XmMessageBoxGetChild( Widget dialog, typeOfWidget);

      Here, typeOfWidget can be one of these:

      XmDIALOG_HELP_BUTTON    XmDIALOG_CANCEL_BUTTON
      
      XmDIALOG_SEPARATOR      XmDIALOG_MESSAGE_LABEL
      
      XmDIALOG_OK_BUTTON      XmDIALOG_SYMBOL_LABEL

      The dialog box may have more widgets that can be addressed. Check the man pages for the descriptions of these widgets.

      For example, to hide the Help button in a dialog box, use this call:

      XtUnmanageChild(XmMessageBoxGetChild(dlg, XmDIALOG_HELP_BUTTON));

      In the case of adding a callback, use this call:

      XtAddCallback(XmMessageBoxGetChild(dlg, XmDIALOG_OK_BUTTON),
      
      XmNactivateCallback, yourFunction);

      A typical method of creating custom dialog boxes is to use existing ones. Then, using the XmMessageBoxGetChild function, you can add or remove any function you want. For example, replace the Message String widget with a Form widget and you have a place to lay out widgets however you need.

      Events

      An event is a message sent from the X server to the application that some condition in the system has changed. This could be a button press, a keystroke, a request for information from the server, or a timeout. An event is always relative to a window and starts from the bottom up. It propagates up the window hierarchy until it gets to the root window, where the root window application makes the decision whether to use or discard it. If an application in the hierarchy does use the event or does not allow propagation of events upwards, the message is used at the window itself. Only device events (keyboard or mouse) are propagated upwards, not configuration events.

      An application must request an event of a particular type before it can begin receiving events. Each Motif application calls XtAppInitialize to make this request automatically.

      Events contain at least the following information:

      • The type of event

      • The display where it happened

      • The event window

      • The serial number of the last event processed by the server

      Look in the file <X11/Xlib.h> for a description of the union called XEvent, which allows access to these values. The file <X11/X.h> contains the descriptions of constants for the types of events.

      All event types share this header:

      typedef struct {
      
           int type;
      
      unsigned long serial;   /* # of last request processed by server */
      
      Bool send_event;        /* true if this came from a SendEvent request */ Display
       *display;/* Display the event was read from */
      
           Window window;  /* window on which event was requested
            in event mask */ } XAnyEvent;

      The types of events include:

      KeyPress

      KeyRelease

      ButtonPress

      ButtonRelease

      MotionNotify

      EnterNotify

      LeaveNotify

      FocusIn

      FocusOut

      KeymapNotify

      Expose

      GraphicsExpose

      NoExpose

      VisibilityNotify

      CreateNotify

      DestroyNotify

      UnmapNotify

      MapNotify

      MapRequest

      ReparentNotify

      ConfigureNotify

      ConfigureRequest

      GravityNotify

      ResizeRequest

      CirculateNotify

      CirculateRequest

      PropertyNotify

      SelectionClear

      SelectionRequest

      SelectionNotify

      ColormapNotify

      ClientMessage

      MappingNotify

      Expose

      The server generates an Expose when a window that has been covered by another is brought to the top of the stack, or even partially exposed.

      The structure for this event type is as follows:

      typedef struct {
      
           int type;     /* Type of event */
      
           unsigned long serial;     /* # of last request processed by server */
      
           Bool send_event;     /* true if this came from a SendEvent request */
      
           Display *display;     /* Display the event was read from */
      
           Window window;
      
           int x, y;
      
           int width, height;
      
           int count;     /* if non-zero, at least this many more */
      
      } XExposeEvent;

      Note how the first five fields are shared between this event and XAnyEvent. Expose events are guaranteed to be in sequence. An application may get several Expose events from one condition. The count field keeps a count of the number of Expose events still in the queue when the application receives this one. Thus, it can be up to the application to wait to redraw until the last Expose event is received (count == 0).

      Pointer Events

      A pointer event is generated by a mouse button press or release, or by any mouse movement. This type of event is called XButtonEvent. Recall that the leftmost button is Button1, but it can be changed. See the section "Left-Handed Users" in the previous chapter. The structure returned by a button press and release is the following:

      typedef struct {
      
           int type;     /* of event */
      
           unsigned long serial;     /* # of last request processed by server */
      
           Bool send_event;     /* true if this came from a SendEvent request */
      
           Display *display;     /* Display the event was read from */
      
           Window window;     /* "event" window it is reported relative to */
      
           Window root;     /* root window that the event occured on */
      
           Window subwindow;     /* child window */
      
           Time time;     /* milliseconds */
      
           int x, y;     /* pointer x, y coordinates in event window */
      
           int x_root, y_root;     /* coordinates relative to root */
      
           unsigned int state;     /* key or button mask */
      
           unsigned int button;     /* detail */
      
           Bool same_screen;     /* same screen flag */
      
      } XButtonEvent;
      
      typedef XButtonEvent XButtonPressedEvent;
      
      typedef XButtonEvent XButtonReleasedEvent;

      The event for a movement is called XMotionEvent, with the type field set to MotionNotify.

      typedef struct {
      
           int type;     /* MotionNotify */
      
           unsigned long serial;     /* # of last request processed by server */
      
           Bool send_event;     /* true if this came from a SendEvent request */
      
           Display *display;     /* Display the event was read from */
      
           Window window;     /* "event" window reported relative to */
      
           Window root;     /* root window that the event occured on */
      
           Window subwindow;     /* child window */
      
           Time time;     /* milliseconds */
      
           int x, y;     /* pointer x, y coordinates in event window */
      
           int x_root, y_root;     /* coordinates relative to root */
      
           unsigned int state;     /* key or button mask */
      
           char is_hint;     /* detail */
      
           Bool same_screen;     /* same screen flag */
      
      } XMotionEvent;
      
      typedef XMotionEvent XPointerMovedEvent;

      Keyboard Events

      A keyboard event is generated when the user presses or releases a key. Both types of events, KeyPress and KeyRelease, are returned in an XKeyEvent structure.

      typedef struct {
      
           int type;     /* of event */
      
           unsigned long serial;     /* # of last request processed by server */
      
           Bool send_event;     /* true if this came from a SendEvent request */
      
           Display *display;     /* Display the event was read from */
      
           Window window;     /* "event" window it is reported relative to */
      
           Window root;     /* root window that the event occured on */
      
           Window subwindow;     /* child window */
      
           Time time;     /* milliseconds */
      
           int x, y;     /* pointer x, y coordinates in event window */
      
           int x_root, y_root;     /* coordinates relative to root */
      
           unsigned int state;     /* key or button mask */
      
           unsigned int keycode;     /* detail */
      
           Bool same_screen;     /* same screen flag */
      
      } XKeyEvent;
      
      typedef XKeyEvent XKeyPressedEvent;
      
      typedef XKeyEvent XKeyReleasedEvent;

      The keycode field presents the information on whether the key was pressed or released. These constants are defined in <X11/keysymdef.h> and may be vendor-specific. These are called KeySym and are generic across all X servers. For example, the F1 key could be described as XK_F1.

      The function XLookupString converts a KeyPress event into a string and a KeySym (a portable key symbol). Here's the call:

      int XLookupString(XKeyEvent *event,
      
                     char *returnString,
      
                     int max_length,
      
                     KeySym  *keysym,
      
                     XComposeStatus *compose);

      The returned ASCII string is placed in returnString for up to max_length characters. The KeySym contains the key symbol. Generally, the compose parameter is ignored.

      Window Crossing Events

      The server generates crossing EnterNotify events when a pointer enters a window, and LeaveNotify events when a pointer leaves a window. These are used to create special effects for notifying the user that the window has focus. The XCrossingEvent structure looks like this:

      typedef struct {
      
           int type;               /* of event */
      
           unsigned long serial;     /* # of last request processed by server */
      
           Bool send_event;     /* true if this came from a SendEvent request */
      
           Display *display;     /* Display the event was read from */
      
           Window window;          /* "event" window reported relative to */
      
           Window root;          /* root window that the event occured on */
      
           Window subwindow;     /* child window */
      
           Time time;          /* milliseconds */
      
           int x, y;               /* pointer x, y coordinates in event window */
      
           int x_root, y_root;     /* coordinates relative to root */
      
           int mode;               /* NotifyNormal, NotifyGrab, NotifyUngrab */
      
           int detail;
      
           /*
      
                * NotifyAncestor, NotifyVirtual, NotifyInferior,
      
                * NotifyNonlinear,NotifyNonlinearVirtual
      
                */
      
           Bool same_screen;     /* same screen flag */
      
           Bool focus;          /* boolean focus */
      
           unsigned int state;     /* key or button mask */
      
      } XCrossingEvent;
      
      typedef XCrossingEvent XEnterWindowEvent;
      
      typedef XCrossingEvent XLeaveWindowEvent;

      These are generally used to change a window's color when the user moves the pointer in and out of it.

      Event Masks

      An application requests events of a particular type by calling a function XAddEventHandler.

      XAddEventHandler( Widget ,
      
                          EventMask ,
      
                          Boolean maskable,
      
      XtEventHandler handlerfunction,
      
                          XtPointer clientData);

      The handler function is of this form:

      void handlerFunction( Widget w, XtPointer clientData,
      
                               XEvent *ev, Boolean *continueToDispatch);

      The first two arguments are the client data and widget passed in XtAddEventHandler. The ev argument is the event that triggered this call. The last argument allows this message to be passed to other message handlers for this type of event. This should be defaulted to TRUE.

      You would use the following call on a widget (w) to be notified of all pointer events of the type ButtonMotion and PointerMotion on this widget.

      extern void handlerFunction( Widget w, XtPointer clientData,
      
                     XEvent *ev, Boolean *continueToDispatch); ..
      
      XAddEventHandler( w, ButtonMotionMask | PointerMotionMask, FALSE, handlerFunction, NULL );

      The possible event masks are the following:

      NoEventMask

      KeyPressMask

      KeyReleaseMask

      ButtonPressMask

      ButtonReleaseMask

      EnterWindowMask

      LeaveWindowMask

      PointerMotionMask

      PointerMotionHintMask

      Button1MotionMask

      Button2MotionMask

      Button3MotionMask

      Button4MotionMask

      Button5MotionMask

      ButtonMotionMask

      KeymapStateMask

      ExposureMask

      VisibilityChangeMask

      StructureNotifyMask

      ResizeRedirectMask

      SubstructureNotifyMask

      SubstructureRedirectMask

      FocusChangeMask

      PropertyChangeMask

      ColormapChangeMask

      OwnerGrabButtonMask


      File 47_14c on the CD-ROM is a sample application that shows how to track the mouse position.

      Managing the Queue

      The XtAppMainLoop() function handles all the incoming events through the following functions:

      • XtAppPending, which checks the queue to see if any events are pending.

      • XtAppNextEvent, which removes the next event from the queue.

      • XtDispatchEvent, which passes the message to the appropriate window.

      The loop can do something else between checking and removing messages through the replacement code segment:

      while (!done)
      
                {
      
                while (XtAppPending( applicationContext))
      
                     {
      
      XtAppNextEvent( applicationContext, &ev));
      
                     XtDispatchEvent( &ev));
      
                     }
      
                done = interEventFunction();
      
                }

      There are some caveats with this scheme:

      • This is a non-blocking function. It must be fed at all times with events or it will take over all other applications' time.

      • There is no guarantee when your interevent function will be run if the queue is flooded with events.

      • Note the while loop for checking messages. It's more efficient to flush the queue first and then call your function, rather than calling it once per check for messages.

      • The interevent function must be fast or you will see the user interface slow down. If you want to give your user feedback about what's going on during a long interevent function, you can handle just the Expose events through a call to XmUpdateDisplay( Display *). This will handle only the Expose events in the queue so that you can update a status display.


      Caution: Consider using the select call to handle incoming events of file descriptors. This is a call that allows an application to wait for events from various file descriptors (in AIX, on UNIX message queues) on read-ready, write-ready, or both. This is done by setting the bits in 32-bit wide integer for up to 16 files (and 16 more message queues in AIX) to wait on input from. The setup scheme for select calls is different on different UNIX systems. Check the man pages for the select function on your system. The pseudo-code to handle select calls follows.

      Check your system's man pages for this code.

      Open all the files with an open call.

      Get the file descriptor for the event queue. Use the Select macros to set up the parameters for select call ret = return from the select function.

      switch (ret)
      case0:

      process the event queue.
      case 1: ...
      process the file descriptor

      Work Procedures

      These are functions called by the event handler loop whenever no events are pending in the queue. The function is expected to return a Boolean value indicating whether it has to be removed from the loop once it is called. If TRUE, it will be removed. If FALSE, it will be called again. For example, you could set up a disk file transfer to run in the "background" that will keep returning FALSE until it is done, at which time it will return TRUE.

      The work procedures are defined as

      XtWorkProc yourFunction(XtPointer clientdata);

      The way to register a work procedure is to call

      XtWorkProcId  XtAppAddWorkProc ( XtAppContext app,
      
      XtWorkProc   functionPointer, XtPointer    clientData);

      The return ID from this call is the handle to the work procedure. It is used to remove the work procedure with a call to

      XtRemoveWorkProc( XtWorkProcId id);

      Using Timeouts

      A timeout is used to perform some task at (almost) regular intervals. Applications set up a timer callback function, which is called when a requested time interval has passed. This function is defined as the following:

      XtTimerCallbackProc thyTimerCallback( XtPointer clientdata, XtInterval *tid);

      Here, clientdata is a pointer to client-specific data.

      The setup function for the timeout returns the timer ID and is defined as the following:

      XtIntervalId XtAppAddTimeOut ( XtAppContext app,
      
      int milliseconds, XtTimerCallback TimerProcedure, XtPointer clientdata);

      This call sets up a timer to call the TimerProcedure function when the requested milliseconds have passed. It will do this only once. If you want cyclic timeouts, say, in a clock application, you have to explicitly set up the next function call in the timer handler function itself. So generally the last line in a timer handler is a call to set a timeout for the next time the function wants to be called.

      UNIX was not originally designed for real-time applications and you cannot expect a deterministic time interval between successive timer calls. Some heavy graphics updates can cause delays in the timer loop. For user interface applications, the delays are probably not a big drawback. However, consult your vendor before you attempt to write a time-critical control application. Your mileage may vary depending on your application.

      File 47_15c on the CD-ROM is a program that sets a cyclic timer.

      Other Sources

      The XtAddInput function is used to handle inputs from sources other than the event queue. Here is the definition:

      XtInputId XtAddInput( XtAppContext app,
      
           int UNIXfileDescriptor,
      
       XtPointer  condition,
      
      XtInputCallback inputHandler,
      
      XtPointer clientdata);

      The return value from this call is the handle to the inputHandler function. This is used to remove the call through the call:

      XtAppAddInput( XtInput Id);

      The input Handler function itself is defined as:

      XtImportCallbackProc InputHandler(XtPointer clientdata, int *fd,
      
      XtInputId *id);

      Unlike timers, you must register this function only once. Note that a pointer to the file descriptor is passed into the function. The file descriptor must be a UNIX file descriptor. You do not have support for UNIX IPC message queues or semaphores through this scheme. The IPC mechanism is considered dated, and is limited to one machine. Consider using sockets instead.

      Handling Output

      The Graphics Context

      Each widget draws itself on the screen using its set of drawing parameters, called the graphics context. For drawing on a widget, you can use the X primitive functions if you have its window and its graphics context. It's easier to limit your artwork to the DrawingArea widget, which is designed for this purpose. You can think of the GC as your paintbrush and the widget as the canvas. The colors and the thickness of the paintbrush are just two of the factors that determine how the paint is transferred to the canvas. The GC is your paintbrush.

      Here is the function call to create a GC:

      GC XCreateGC (Display dp, Drawable d, unsigned long mask, XGCValue *values);

      For use with a widget, w, this call would look like the following:

      GC gc;
      
      XGCVvalue gcv;
      
      unsigned long mask;
      
      gc = XCreate(XtDisplay(w), XtWindow(w),
      
           mask, gcv);

      Also, you can create a GC for a widget directly with a call to XtGetGC:

      gc = XtGetGC (Widget w, unsigned long mask, XGCValue *values);

      The values for the mask are defined as follows:

      GCFunction

      GCPlaneMask

      GCForeground

      GCBackground

      GCLineWidth

      GCLineStyle

      GCCapStyle

      GCJoinStyle

      GCFillStyle

      GCFillRule

      GCTile

      GCStipple

      GCTileStipXOrigin

      GCTileStipYOrigin

      GCFont

      GCSubWindowMode

      GCGraphicsExposures

      GCClipXOrigin

      GCClipYOrigin

      GCClipMask

      GCDashOffset

      GCDashList

      GCArcMode

      The data structure for setting graphics context is shown here:

      typedef struct {
      
           int function;     /* logical operation */
      
           unsigned long plane_mask;/* plane mask */
      
      unsigned long foreground;/* foreground pixel */ unsigned long background;/* 
      background pixel */ int line_width;     /* line width */
      
           int line_style;     /* LineSolid, LineOnOffDash, LineDoubleDash */
      
           int cap_style;     /* CapNotLast, CapButt,
      
                     CapRound, CapProjecting */
      
           int join_style;     /* JoinMiter, JoinRound, JoinBevel */
      
           int fill_style;     /* FillSolid, FillTiled,
      
                     FillStippled, FillOpaeueStippled */
      
           int fill_rule;     /* EvenOddRule, WindingRule */
      
           int arc_mode;     /* ArcChord, ArcPieSlice */
      
           Pixmap tile;     /* tile pixmap for tiling operations */
      
           Pixmap stipple;     /* stipple 1 plane pixmap for stipping */
      
           int ts_x_origin;     /* offset for tile or stipple operations */
      
           int ts_y_origin;
      
           Font font;     /* default text font for text operations */
      
           int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
      
      Bool graphics_exposures;/* boolean, should exposures be generated */ 
      int clip_x_origin;     /* origin for clipping */
      
           int clip_y_origin;
      
           Pixmap clip_mask;     /* bitmap clipping; other calls for rects */
      
           int dash_offset;     /* patterned/dashed line information */
      
           char dashes;
      
      } XGCValues;

      If you want to set a value in a GC, you have to take two steps before you create the GC:

      1. Set the value in the XGCValue structure.

      2. Set the mask for the call GCFunction. This determines how the GC paints to the screen. The dst pixels are the pixels currently on the screen, and the src pixels are those that your application is writing by using the GC.

        GXclear dst = 0
        GXset dst = 1
        GXand dst = src AND dst
        GXor dst = src OR dst
        GXcopy dst = src

        GXnoop dst = dst
        GXnor dst = NOT(src OR dst)
        GXxor dst = src XOR dst

        GXinvert dst = NOT dst
        GxcopyInverted dst = NOT src

      The function for a GC is changed through a call XSetFunction (Display *dp, GC gc, int function), where function is set to one of the above values. The default value is GXcopy.

      There are several other masks that you can apply. They are listed in the <X11/X.h> file.

      • GCPlaneMask: The plane mask sets which planes of a drawable can be set by the GC. This is defaulted to AllPlanes, thereby allowing the GC to work with all planes on a window.

      • GCForeground and GCBackground: These are the values of the pixels to use for the foreground and background colors, respectively. Here is the call to manipulate these:

        XSetForeground(Display *dp, GC gc, Pixel pixel); XSetBackground(Display *dp, GC gc, Pixel pixel);

      • GCLineWidth: This is the number of pixels for the width of all lines drawn through the GC. It is defaulted to 0, which is the signal to the server to draw the thinnest line possible.

      • GCLineStyle, GCDashOffset, and GCDashList: This determines the style of the line drawn on the screen. LineSolid draws a solid line using the foreground color, LineOnOffDash draws an intermittent line with the foreground color, and LineDoubleDash draws a line that is composed of interlaced segments of the foreground and background colors. The GCDashOffset and GCDashList values determine the position and length of these dashes.

      • GCCapStyle: This determines how the server draws the ends of lines. CapNotLast draws up to, but not including, the end point pixels of a line. CapButt draws up to the end points of a line (inclusive). CapRound tries to round off the edges of a thick line (3 or more pixels wide). CapProjecting projects the end point out a little.

      • GCJoinStyle: This is used to draw the end points of a line. It can be set to JointMiter for a 90-degree joint, JoinBevel for a beveled joint, or JoinRound for a rounded joint.

      • GCFillStyle, GCTile, and GCStipple: The fill style can be set to FillSolid, which specifies the foreground color as the fill color. FillTiled specifies a pattern set in the tile attribute. FillStipple specifies a pattern in the stipple attribute. It uses the foreground color when a bit is set to 1 and nothing when a bit is set to 0, whereas FillOpaqueStippled uses the foreground when a bit is set to 1 and the background when a bit is set 0.

      • GCFont: This specifies the font list to use. See the section "Using Fonts and FontLists" later in this chapter.

      • GCArcMode: This defines the way an arc is drawn on a screen. See the next section, "Drawing Lines, Points, Arcs, and Polygons."

      Drawing Lines, Points, Arcs, and Polygons

      Motif applications can access all the graphics primitives provided by Xlib. All Xlib functions must operate on a window or a pixmap; both are referred to as drawable. A widget has a window after it is realized, and you can access this window with a call to XtWindow(). An application can crash if Xlib calls are made to a window that is not realized. The way to check is through a call to XtIsRealized() on the widget, which returns TRUE if it's realized and FALSE if it's not. Use the XmDrawingArea widget's callbacks for rendering your graphics, because it is designed for this purpose. The following callbacks are available to you:

      • XmNresizeCallback: Invoked when the widget is resized.

      • XmNexposeCallback: Invoked when the widget receives an Expose event.

      • XmNinputCallback: Invoked when a button or key is pressed on the widget.

      All three functions pass a pointer to XmDrawingAreaCallbackStruct.

      Drawing a Line

      To draw a point on a screen, use the XDrawLine or XDrawLines function call. Consider the example shown on the CD-ROM in file 47_16c.

      The following code is an example of the primitives required to draw one line on the widget. Note the number of GCValues that have to be set to achieve this purpose. The XDrawLine function definition is shown here:

      XDrawLine( Display *dpy,
      
           Drawable d,
      
           GC gc,
      
           int x1,
      
           int y1,
      
           int x2,
      
           int y2);

      It's more efficient to draw multiple lines in one call. Use the XDrawLines function with a pointer to an array of points and its size.

      The mode parameter can be set to:

      • CoorModeOrigin: Use the values relative to the drawable's origin.

      • CoorModePrevious: Use the values as deltas from the previous point. The first point is always relative to the drawable's origin.

      To draw boxes, use the XDrawRectangle function:

      XDrawRectangle( Display *display,
      
      Drawable dwindow,
      
                GC       gc,
      
                int      x,
      
                int      y,
      
                unsigned int width,
      
                unsigned int height);

      This will draw a rectangle at (x, y) of geometry (width, height). To draw more than one box at one time, use the XDrawRectangles() function. This is declared as the following:

      XDrawRectangles( Display *display,
      
                     Window  dwindow,
      
                     GC      gc,
      
                     XRectangle *xp,
      
      int     number);

      Here, xp is a pointer to an array of "number" rectangle definition structures.

      For filled rectangles, use the XFillRectangle and XFillRectangles calls, respectively.

      Drawing a Point

      To draw a point on a screen, use the XDrawPoint or XDrawPoints function call. These are similar to line-drawing functions. Look at Listing 47.16.

      Drawing Arcs

      To draw circles, arcs, and so on, use the XDrawArc function:

      XDrawArc(Display *display,
      
                Window  dwindow,
      
                GC   gc,
      
                int  x,
      
                int  y,
      
      unsigned int    width; unsigned int    height; int     a1,
      
                int  a2);

      This function is very flexible. It draws an arc from angle a1, starting at the 3 o'clock position, to angle a2. The unit of measurement for angles is 1/64 of a degree. The arc is drawn counterclockwise. The largest value is 64´360 units because the angle arguments are truncated. The width and height define the bounding rectangle for the arc.

      The XDrawArcs() function is used to draw multiple arcs, given pointers to the array.

      XDrawArcs (Display *display,
      
                Window  dwindow,
      
                GC   gc,
      
                XArc *arcptr,
      
                int  number);

      To draw polygons, use the call:

      XDrawSegments( Display *display, Window dwindow,
      
                GC   gc,
      
                XSegment *segments,
      
                int     number);

      The XSegment structure includes four "short" members, x1, y1, x2, and y2, which define the starting and ending points of all segments. For connected lines, use the XDrawLines function shown earlier. For filled polygons, use the XFillPolygon() function call.

      Using Fonts and Fontlists

      Fonts are perhaps the trickiest aspect of Motif to master. See the section on Fonts in the previous chapter before you read this section to familiarize yourself with font definitions. The function XLoadQueryFont(Display *dp, char *name) returns an XFontStruct structure. This structure defines the extents for the character set. This is used to set the values of the Font field in a GC.

      To draw a string on the screen, use the following:

      XDrawString ( Display *dp, Drawable dw, GC gc,
      
           int x, int y, char *str, int len);

      This only uses the foreground color. To draw with the background and foreground, use this:

      XDrawImageString ( Display *dp, Drawable dw, GC gc,
      
                int x, int y, char *str, int len);

      The X Color Model

      The X color model is based on an array of colors called a colormap. Applications refer to a color by its index in this colormap. The indices are placed in an application's frame buffer, which contains an entry for each pixel of the display. The number of bits in the index define the number of bitplanes. The number of bitplanes define the number of colors that can be displayed on a screen at one time. For example, one bit per pixel displays two colors, four bits per pixel displays 16 colors, and eight bits per pixel displays 256 colors.

      An application generally inherits the colormap of its parent. It can also create its own colormap by using the XCreateColormap call. The call is defined as:

      Colormap XCreateColormap( Display *display,
      
                     Window   dwindow,
      
                     Visual   *vp,
      
                     int       allocate);

      This allocates the number of allocate color entries in a window's colormap. Generally the visual parameter is derived from this macro:

      DefaultVisual (Display *display, int screenNumber);

      Here screenNumber = 0 in almost all cases. See the previous chapter, "Screens, Displays, and Windows," for a definition of screens.

      Colormaps are a valuable resource in X and must be freed after use. This is done through this call:

      XFreeColormap(Display *display, Colormap c);

      Applications can get the standard colormap from the X server by using the XGetStandardColormap() call, and can set it through the XSetStandardColormap() call. These are defined as

      XGetStandardColormap( Display *display,
      
                Window  dwindow,
      
      XStandardColormap *c, Atom      property);

      and

      XSetStandardColormap( Display *display,
      
      Window  dwindow, XStandardColormap *c, Atom      property);

      Once applications have a colormap to work with, you have to take two steps:

      1. Define the colormap entries.

        The property atom can take the values of RGB_BEST_MAP, RGB_GRAY_MAP, or RGB_DEFAULT_MAP. These are names of colormaps stored in the server. They are not colormaps themselves.

      2. Set the colormap for a window through this call:

        XSetWindowColormap ( Display *display,
        Window dwindow,
        Colormap c );

        For allocating a color in the colormap, use the XColor structure defined in <X/Xlib.h>.

        To see a bright blue color, use the segment:

        XColor color;
        color.red = 0;
        color.blue = 0xffff;
        color.green = 0;

        Then add the color to the colormap using the call to the function:

        XAllocColor(Display *display,
        Window dwindow,
        XColor *color );

      A sample function that sets the color of a widget is shown in file 47_17c on the CD-ROM.

      The default white and black pixels are defined as the following:

      Pixel BlackPixel( Display *dpy, int screen); Pixel WhitePixel( Display *dpy, int screen);

      These will work with any screen as a fallback.

      The index (Pixel) returned by this function is not guaranteed to be the same every time the application runs. This is because the colormap could be shared between applications that each request colors in a different order. Each entry is allocated on the basis of next available entry. Sometimes if you overwrite an existing entry in a cell, you may actually see a change in a completely different application. So be careful.

      Applications can query the RGB components of a color by calling this function:

      XQueryColor( Display *display,
      
                Colormap *cmp,
      
                XColor  *clr);

      For many colors at one time, use this:

      XQueryColors( Display *display,
      
                Colormap *cmp,
      
                XColor  *clr,
      
                int number);

      At this time the application can modify the RGB components and then store them in the colormap with this call:

      XStoreColor( Display *display,
      
                Colormap *cmp,
      
                XColor  *clr);

      Recall that X11 has some strange names for colors in /usr/lib/rgb.txt file. Applications can get the RGB components of these names with a call to this:

      XLookupColor( Display *display,
      
                Colormap cmp,
      
                char     *name,
      
                XColor  *clr
      
                XColor  *exact);

      The name is the string to search for in the rgb.txt file. The returned value clr contains the next closest existing entry in the colormap.

      The exact color entry contains the exact RGB definition in the entry in rgb.txt. This function does not allocate the color in the colormap. To do that, use this call:

      XAllocNamedColor( Display *display,
      
                Colormap cmp,
      
                char    *name,
      
                XColor  *clr
      
                XColor  *exact);

      Pixmaps, Bitmaps, and Images

      A pixmap is like a window but is off-screen, and is therefore invisible to the user. It is usually the same depth as the screen. You create a pixmap with this call:

      XCreatePixmap (Display *dp,
      
      Drawable dw, unsigned int width, unsigned int height, unsigned int depth);

      A drawable can be either a window (on-screen) or a pixmap (off-screen). Bitmaps are pixmaps with a depth of one pixel. Look in /usr/include/X11/bitmaps for a listing of some of the standard bitmaps.

      The way to copy pixmaps from memory to the screen is through this call:

      XCopyArea( Display dp,
      
           Drawable Src,
      
           Drawable Dst,
      
           GC   gc,
      
           int  src_x,
      
           int  src_y,
      
           unsigned int width,
      
           unsigned int height,
      
           int  dst_x,
      
           int  dst_y);

      The caveat with this call is that the Src and Dst drawables have to be of the same depth. To show a bitmap with a depth greater than one pixel on a screen, you have to copy the bitmap one plane at a time. This is done through the following call:

      XCopyPlane( Display dp,
      
           Drawable Src,
      
           Drawable Dst,
      
           GC   gc,
      
           int  src_x,
      
           int  src_y,
      
           unsigned int width,
      
           unsigned int height,
      
           int  dst_x,
      
           int  dst_y,
      
           unsigned long plane);

      The plane specifies the bit plane that this one-bit-deep bitmap must be copied to. The actual operation is largely dependent on the modes set in the GC.

      For example, to show the files in the /usr/include/bitmaps directory, which have three defined values for a sample file called gumby.h:

      • gumby_bits: Pointer to an array of character bits

      • gumby_height:Integer Height

      • gumby_width: Integer width

      First create the bitmap from the data using the XCreateBitmapFromData() call. To display this one-plane-thick image, copy the image from this plane to plane 1 of the display. You can actually copy to any plane in the window.

      A sample call could be set for copying from your pixmap to the widget's plane 1 in the following manner:

      XCopyPlane( XtDisplay(w), yourPixmap, XtWindow(w), gc,
      
      0,0, your_height, your_width, 0,0,1);

      It copies from the origin of the pixmap to the origin of plane 1 of the window.

      There are other functions for working with images in X. These include the capability to store device-dependent images on disk and the Xpm format.

      Xpm was designed to define complete icons and is complicated for large pixmaps. The format for an Xpm file is as follows:

      char *filename[] =
      
      {
      
      "Width Height numColors CharacterPerPixel",
      
      "character colortypes"
      
      ..PIXELS..
      
      };

      A string of "8 8 2 1" defines a 8´8 icon with two colors and one character per color. The PIXELS are strings of characters: the number of strings equals the number of rows. The number of characters per string equals the number of columns.

      The character represents a color. Colortypes are a type followed by a color name. So "a c red m white" would show a red pixel at every "a" character on color screens, and a white pixel on monochrome screens. See the following example:

      char *someFig[ ] = {
      
      "8 8 2 1",
      
      "a c red m white",
      
      ". c blue m black",
      
      "aa....aa",
      
      "aa....aa",
      
      "aa....aa",
      
      "aaaaaaaa",
      
      "aaaaaaaa",
      
      "aa....aa",
      
      "aa....aa",
      
      "aa....aa"
      
      };

      See the man pages for more details on using Xpm files. Look for the functions XpmReadFileToPixmap and XpmWriteFileToPixmap for information on reading these images from or storing them to disk.

      GUI Builders and Management Tools

      The difference between a GUI interface builder and a GUI interface management tool is that if you generate new code from a GUI interface builder, it will not save any previous changes that you have made to previous versions of code. No backups are kept. A GUI interface management tool, however, will allow you to keep all your changes in the file.

      Here are some ideas on selecting and using GUI builders:

      They do save you time even if you don't intend to use the code generated by the builder. They can help you lay out all the widgets and set the appropriate placements to get the desired effect (colors, X,Y positions, and so on).

      One of the failings of such software packages is that no backups are kept of the code that a developer has done to the callback stubs. Refer to the sections on using and writing Motif widgets for more information about callbacks. This software simply generates code from the interface that the user has designed. This code includes all stubs for the widgets that the user has designated. Therefore, regenerating code from an edited interface overwrites any modifications to any previously edited stubs. Some builders do this, some don't. Check this with your vendor.

      Environments tend to lock you into a specific programming mode. For some developers, this may equate to lack of freedom, and may turn them away from what might well mean time to market. The time to try an environment out and test its flexibility is before you buy.

      Code generated by GUI builders may not be the most efficient for your particular application. You should be able to easily modify the generated code.

      Check to see if functionality can be added without going through special hoops (such as precompilers). For example, how easy is it to add your own C++ classes?

      Does the builder generate native code, or do you have to use her libraries? If you have to ship shared libraries, check the licensing agreements or see if static versions are available.

      A Few Commercial GUI Builders

      This is a list of some of the GUI builders and environments on the market today. This list is by no means complete, and exists only as a guide to what's available. Contact your local vendor for more information.

      • Imperial Software Technology Ltd., Reading, England, through VI Corporation (800-732-3200), offers a GUI builder, X-Designer, which has built-in OpenLook-to-Motif conversion.

      • Kinesix (713-953-8300) provides Sammi, an integrated GUI building environment.

      • LIANT (800-237-1873) offers a C++/Views visual programming tool that ports Motif applications to DOS text, OS/2, Windows, and so on.

      • Neuron Data (800-876-4900) lists an amazing 40 platforms that you can port your GUI application to.

      • XVT Design (800-678-7988) offers an Interactive Design Tool and the XVT Portability Toolkit, which will port Motif applications to DOS text, OS/2, Windows, and so on.

      • Zinc Software (801-785-8900) offers Zinc Designer and Applications Framework to build and port Motif applications to DOS text, OS/2, Windows, and so on.

      What You Have Learned in this Chapter

      This chapter covered the following topics:

      • The basics of writing Motif applications

      • Special naming conventions in Motif and X

      • Writing and compiling your first Motif application

      • Revisiting widget hierarchy

      • Working with various common widgets

      • Introduction you to designing layouts

      • Creating pop-up menus and menu bars

      • Creating simple dialog boxes

      • Learning how to use the mouse in event handling

      • Colors in X

      • Drawing lines and points

      • Introduction to GUI builders and management tools

      Acknowledgements

      I am indebted to Metro Link software for providing me with their version of Motif 1.2, which I used to develop all the routines and test the sources in this guide. Their software installed cleanly with no hassles on a linux (1.02) system running on a 386DX. All libraries worked great at the time and presented no compatibility problems in going porting sources to Sun and AIX. There was no reason to call their support line, so I could not evaluate it. The price for all binaries and the development system is $208, which includes overnight shipping and my choice of Volume 3 or Volume 6 from the O'Reilly X Window System User's Guide manual set.

      You can contact Metro at (305) 938-0283.

      References

      
      
      Quercia, Valerie and O'Reilly, Tim. The Definitive Guides to the X Window System, X Window System User's Guide, Volume Three, Motif Edition. O'Relly, March 1992.
      Johnson, Eric F. and Reichard, Kevin. Advanced X Window Applications Programming. MIS:Press, 1990.
      Johnson, Eric F. and Reichard, Kevin. Power Programming ... Motif Second Edition. MIS:Press, 1993.
      OSF/Motif Programmers Guide. Prentice Hall, 1993.
      OSF/Motif Style Guide. Prentice Hall, 1990.
      Taylor, Dave. Teach Yourself UNIX in a Week. Sams Publishing, 1994.
      Rost, Randi J. X and Motif Quick Reference Guide. Digital Press, 1990.
      Young, Doug. The X Window System Programming and Applications with Xt, OSF/Motif Edition, 1994.

      Previous Page Main Page