-->
by Kamran Husain
IN THIS CHAPTER
This chapter will cover the following topics:
A question you might be asking is "Why include a topic on a development system that you have to pay for, when just about everything for Linux is free?" Well, if you want to develop any applications for the Common Desktop Environment (CDE), you should know how to program Motif applications. Linux is a mature enough system to enable you this luxury of building portable applications. (Plus, the $150 or so you pay for the Motif license will well pay for itself if you can do the work at home on your Linux system rather than commuting!)
This chapter introduces you to writing Motif applications. The information here will not be limited to writing applications for Linux alone, because the concepts in this chapter can be applied to other UNIX systems as well.
In programming Motif applications, 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, the application looks for this information from a source such as a file or the keyboard and (almost always) gets the information as soon as it asks for it. If the information is not available when the application asks for it, the application either waits for it or displays an error message. Also, the order of the incoming data is important for such applications; pieces of data that are out of sequence may cause the application to behave strangely.
In the case of event-driven programming, an application must wait for events on an input queue. The queue orders all incoming events in the order they are received. The first message to come in from one end of a queue is the first one to leave the queue. (Such queues are often called FIFOs, for First In, First Out.) An event can be anything from a mouse click to a timeout notification.
Because events can come in at any time, and in no predefined order, they are referred to as asynchronous events. That is, the order and time of arrival of each event is not deterministic. The application must wait for an event to occur and then proceed based on that event. Thus the term event-driven programming.
In the case of the X Window system, each X Window application has one input queue for all of its incoming events. The application must wait for events on this 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 XtIntrinsics, or Xt for short.
In Xt, an application will typically run in a loop forever. This loop is called an event loop. An application enters the loop by calling a function XtAppMainLoop(). While in this event loop, an application will always wait for an event, When the application receives an event, the application handles the event itself or almost always "dispatches" 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 enable applications to register a function with Xt. An action is called when one or more sequences of specific event types are received. For example, pressing Ctrl-X would call the exit function. The mapping of the action to an event is handled via a translation table within Xt. Functions that handle specific events are referred to as event handlers.
Look at Figure 34.1 to see how the toolkit exists with Motif. As you can see from
the connections in the figure, an application can get to the core Xlib functions
through three means: via Motif, via the Xt library, or directly. This flexible
hierarchy gives you many options when developing Motif applications because you are
at liberty to take full advantage of all functions in all three libraries.
Figure 34.1. The toolkit hierarchy
for X, Xt, and Motif.
By default, most Xlib functions begin with the letter X, but you should not always rely on this being true for all functions. Several macros and functions in the X Window system do not begin with X. For example, the names BlackColor and WhiteColor are not macros. In general, though, if a name in Xlib begins with X, it's probably a function. If a name begins with a capital letter (A through Z), it's a macro.
With Xt, the naming conventions get better, but only slightly. In Xt, macros are not differentiated from functions in any way.
In Motif, almost all declarations begin with Xm. Therefore, XmC refers to a class, XmR refers to a resource, XmN refers to a name, and XtN refers to Xt resources used by Motif. Declarations ending with the words WidgetClass define the base class for a type of Widget. A few conventions to remember about parameters for most Xlib functions are
With practice, you will be able to identify the type of parameters to pass and which toolkit a function belongs to, and be able to make some educated guesses as to what parameters an unknown function might expect.
Let's look at the basic format for a Motif application, shown in Listing 34.1. (I added line numbers for your benefit.) I will discuss this application in detail. You will build other Motif applications based on the structure in this particular application as you progress through this chapter.
The listing shows an application in which a button attaches itself to the bottom
of a form. See Figure 34.2.
Figure 34.2. The output of Listing
34.1 (L34_1.c).
No matter how you resize the window, the button will always be on the bottom. The application does the following things in the order listed:
Let's look at the application in more detail. The include files in the beginning of the listing are required for most applications. Notably, the two files shown in lines 5 and 6 are required for just about any Motif application you'll ever write.
#include <X11/Intrinsic.h> #include <Xm/Xm.h>
These two lines declare the definitions for XtIntrinsics and Motif, respectively. In some systems, you 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 34.1, the Widgets Form and PushButton are included via statements in lines 7 and 8:
#include <Xm/Form.h> #include <Xm/PushB.h>
The variables in the program are declared in lines 12 through 16:
Widget top; XtAppContext app; Widget aForm; Widget aButton; Arg args[5];
The top, aForm, and aButton represent Widgets. Even though their functions 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; only the items that are required for the programmer are visible.
The first executable line of the program calls the XtAppInitialize() function (in line 20) to 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 is used to place other Widgets on top of itself. It is a Manager Widget because it "manages" other Widgets.
There are two steps required for displaying a Widget: First you have to manage it (with XtVaCreateManagedWidget) and then you have to realize it (with RealizeWidget).
Managing a Widget enables 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 the Widget. If the parent Widget is unmanaged, any child Widgets remain invisible, even if they are 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 one. This way the application will only have to realize the topmost Widget when the entire tree has to display only the topmost parent. You have to realize a Widget at least once, but you can manage and "unmanage" Widgets as you want to display or hide them.
You can always create and manage a Widget to call XtCreate and XtManageChild in two separate calls. However, the samples in this chapter will use a single call to create and manage a Widget: XtVaCreateManagedWidget.
Note the parameters in this call to create the Form Widget shown in lines 26 through 30:
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. So, in this case, it is 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.
The next argument is the parent Widget of this new Widget. In this case, top is the parent of Form1. The top Widget is 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 the 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 following three lines (40-42) 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. The hierarchy is as follows:
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 call to XtAddCallback, as shown in the following:
XtAddCallback( aButton, XmNactivateCallback, bye, (XtPointer) NULL);
Here are the parameters for this call:
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 loop that processes all incoming events.
The bye function of this program simply exits the application.
Now comes the tough part of compiling this application into a working application. You will use the gcc compiler that comes with Linux for this purpose.
First, 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 a smaller Motif application; a typical application like the one you've been working on can be up to 1MB in size because of Motif's overhead.
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. Also, shipping a shared library with your application may cause 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 it. 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:
gcc l34_1.c -o list1 -lXm -lXt -lX11
The program can be run from a command line if you create a script file:
$ cat mk gcc $1.c -o $1 -lXm -lXt -lX11
and pass it just the filename without the extension. The best way is to create a makefile, but this script file will work with the examples in this text. So, to compile l34_1.c, you would use the script as follows:
$ mk l34_1
You should see the output shown in Figure 34.2 on your screen. Your application is the one with l34_1 in its frame.
The Motif Widget set is a hierarchy of Widget types. (See Figure 34.3.) 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 34.3. The partial Motif hierarchy.
The Core Widget class provides the basis for all classes. It provides at least the following resources for all Widgets: XmNx,XmNy: A Widget's position on the display.
XmNheight, XmNwidth: A Widget's size.
XmNborderWidth: Set to 1 by default.
XmNsensitive: A Boolean resource that specifies whether this Widget can receive input.
XmNcolorMap: The default color map.
XmNbackground: The background color.
Check the Motif Programmer's reference manual for a complete listing.
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 a displayed area of Widget.
XmNhighlightThickness: If XmNhighlightOnEnter is TRUE, changes the border to this thickness.
XmNhighlightColor: The color a Widget changes to when highlighted.
XmNshadowThickness: This is the thickness to show the pseudo-three-dimensional look for which Motif is famous. The default value is 2.
XmNtopShadowColor and XmNbottomShadowColor: Sets the color for
top and bottom lines around a Widget.
XmNuserData: A pointer available for the programmer's use.
The XmPrimitive Widget also provides the XmNdestroyCallback resource.
This can be set to a function that would do clean-up when a Widget is destroyed.
In Motif 1.2.x or later, the XmPrimitive class also provides a XmNhelpCallback
that is called when the F1 key is pressed in the Widget's window. This is to allow
specific help information for a button, for example.
The XmManager class provides support for all Motif Widgets that contain other Widgets. This is never used directly in an application, and it works in a manner similar to the XmPrimitive class. It provides the following resources: XmNforeground: The color of the pixels in the foreground.
XmNshadowThickness: For the three-dimensional effect.
XmNtopShadowColor: For the three-dimensional effect. This is automatically defaulted to a color derived from the background color. This color is used on the left and top borders of a Widget.
XmNbottomShadowColor: For the three-dimensional effect. This is automatically
defaulted to a color derived from the background color. This color is used on the
right and bottom borders of a Widget.
XmNuserData: For storing user data. Could be used as a void pointer.
The Label Widget is used to display strings or pixmaps. Include the Xm/Label.h file in your source file before using this Widget. Some of the resources for this Widget include these: XmNalignment: Determines the alignment of the text in this Widget. The acceptable values are XmALIGNNMENT_END, XmALIGNMENT_CENTER, and XmALIGNEMENT_BEGIN for right, center, and left justification, respectively.
XmNrecomputeSize: A Boolean resource. If set to TRUE, the Widget will resize should the size of the string or pixmap change 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."
To get acquainted with left and right justification on a label, see Listing 34.2.
The listing also shows how the resources can be set to change Widget parameters programmatically
and via the .Xresource files.
Output is shown in Figure 34.4.
Figure 34.4. Using the Label Widget.
Avoid using the \n in the label name. If you have to create a multistring Widget, use the XmStringCreate calls to create a compound string (see the next section). Another way to set the string is by specifying it in the resource file and then merging 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.
A compound string is Motif's way of representing a string. In 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 function will return an equivalent compound string given a pointer to a NULL- terminated C string and a tag. The tag specifies which fontlist to use and is defaulted to XmFONTLIST_DEFAULT_TAG.
New lines in C strings have to be handled by special separators in Motif. So to create a string and preserve the newlines, use the 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. The definition of a call to free XmString resources is
XmStringFree( XmString s);
You can run similar operations on strings as you would in a C program, except that these string operations are called by different names. Use the function
Boolean XmStringByteCompare( XmString s1, XmString s2);
for a strict byte-for-byte comparison, and for just the text comparison, use
Boolean XmStringCompare( XmString s1, XmString s2);
To check if a string is empty, use
Boolean XmStringEmpty( XmString s1);
To string two strings together, use
XmString XmStringConcat( XmString s1, XmString s2);
XmString Concat() creates a new string by concatenating s2 to s1 and returns it. This returned string 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 becomes corrupted without your performing any direct actions on it, check to see whether the Widget is not making a copy for its use of the passed XmString. Sometimes a Widget might be keeping only a pointer to the XmString. If that string were freed, the Widget might 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 are not the same, it is 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 a copy of such resources for itself.
A similar test could be used to determine whether a Widget returns a copy of its resource or a pointer to it. Use the same listing shown two paragraphs ago, but this time use a getValue to get the same resource twice. Then do the comparison to see whether the address for the original string matches the address of the returned value from getValue(): If the values match, the Widget keeps a pointer. If the values do not match, the Widget keeps an internal copy.
/** *** 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 progress 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 invoke mechanism, the message is lost.
Do not use XtSetArg(arg[n++]... because this will increment n twice.
The XmPushButton is perhaps the most heavily used Widget in Motif. Listings 34.1 and 34.2 show the basic usage for PushButton. When a button is pressed in the PushButton area, the button goes into an armed state (a state between not pressed and going to pressed). The color of the button changes to reflect this state, and you can set this color by using XmNarmColor. This color is shown when the XmNfillOnArm resource is set to TRUE.
The callback functions for a PushButton are the following: XmNarmCallback: Called when a PushButton is armed.
XmNactivateCallback: 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.
In Listing 34.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.
This class is a subclass of the XmLabel Widget class. You can have two
types of buttons: one of many or n of many. When using one of many, the user can
make only one selection from many items. (See Figure 34.5.) When using n of many,
the user can select many options. (See Fig- ure 34.6.) Note the way the buttons are
drawn, with one of many shown as diamonds and n of many shown as boxes.
Figure 34.5.
Using one of many toggle buttons.
Figure 34.6. Using n of many toggle buttons.
The resources for this Widget include the following: XmNindicatorType: Determines the style. 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 functions XmToggleButtonGetState(Widget w)to
get the Boolean state for a Widget, and XmToggleButtonSetState(Widget w, Boolean
b)to set the value for a ToggleButton Widget.
Similar to the PushButton class, the ToggleButton class has three callbacks: XmNarmCallback: Called when the ToggleButton is armed.
XmNvalueChangedCallback: 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 was armed.
For the callbacks, the data passed into the Callback function is a structure of type:
typedef struct { int reason; Xevent *event; int set; } XmToggleButtonCallbackStruct;
The reason for the callback is one of the following events: XmCR_ARM, XmCR_DISARM, or XmCR_ACTIVATE. The event is a pointer to XEvent, which caused this callback. The set value is 0 if the item is not set, or nonzero if set. Look at Listing 34.3, which shows the use of ToggleButton. The ToggleButtons are arranged in one column via the RowColumn Widget, discussed later in this chapter.
Refer to Figure 34.6, shown previously, for the output of this listing with the #define DO_RADIO line commented out. 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. Refer to Figure 34.5, shown previously, for the radio behavior of these buttons.
Usually the time to set resources for a Widget is at the Widget's creation. This is done either with the XtVaCreateManagedWidget call or with the XmCreateYYY call, where YYY is the name of the Widget you are creating.
This test uses the variable argument call to create and manage Widgets. I do it simply because it's easier for me to see what resources I am setting and to create them all in one function call. You might have a personal preference to do it in two steps. Either way is fine. Keep in mind, though, that if you do use the XmCreateYYY call, you have to set the resource settings in a list of resource sets. Listing 34.4 is an example of creating a Label Widget. This is a function that creates a Label Widget on a Widget given the string x.
Or you could use the variable argument lists in creating this label, as shown in Listing 34.5.
In either case, it's your judgment call as to which one to use. The variable list method of creating is a bit easier to read and maintain. But what about setting values after a Widget has been created? This would be done via 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 XtSetValues:
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, let's use the Text Widget. This setting scheme is hard to read, quite clumsy, and prone to typos. For example, when you get a string for a Text Widget, should you use x or address of x?
For this reason, Motif provides convenience functions. For example, in the ToggleButton Widget class, rather than using 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 writing complex applications. In fact, you should write similar convenience functions whenever you can't find one that suits your needs.
This displays a list of items from which the user selects. The list is created from a list of compound strings. Users can select either one or many items from this list. The resources for this Widget include the following: XmNitemCount: 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 for all items in its list when using XtSetValues; however, it returns a pointer to its internal structure when returning values to a XtGetValues call. Therefore, 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: Used to set single or multiple selection capability.
If set to XmSINGLE_SELECT, the user will be able to only select one item.
Each selection will invoke the XmNsingleSelectionCallback. Selecting one
item will deselect a previously selected item. If set to XmEXTENDED_SELECT,
the user will be able to select a block of contiguous items in a list. Selecting
a new item or more will deselect another previously selected item and 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 but will invoke the XmNmultipleSelection
callback. If set to XmBROWSE_SELECT, the user can move the pointer (with
the button pressed) across all the selections, but only one item will be selected.
Unlike the XmSINGLE_SELECT setting, the user does not have to press and
release the button on an item to select it. The XmbrowseSelectionCallback
will be invoked when the button is finally released on the last item browsed.
It is easier to create the List Widget with a call to XmCreateScrolledList()
because this will automatically create a scrolled window for you. However, this method
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.
The following convenience functions will make working with List Widgets easier: XmListAddItem(Widget w, XmString x, int pos)
This will add the compound string x to the List Widget w at the one relative position pos. If pos is 0, the item is added to the back of the list. This function is very slow, so do not use it to create a newlist, 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 newlist.
XmDeleteAllItems(Widget w)
This will delete all the items in a list. It's better to write a convenience function to do
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. Returns 0 if not found.
Let's use the List Widget for a sample application. See Listing 34.6.
The ScrollBar Widget enables the user to select a value from a range. Its resources include the following: XmNvalue: The value representing the location of the slider.
XmNminimum and XmNmaximum: The range of values for the slider.
XmNshowArrows: 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 orientation, 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 Listing 34.7. A point to note in this listing
is that the exit button for the application is offset on the left and right by 20
pixels. This is done via the XmATTACH_FORM value for each side (left or
right) being offset by the value in XmNleftOffset and XmNrightOffset,
respectively. See Listing 34.3 on how to attach items to a form.
In 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 as follows:
XtAddCallback(aScale, XmNvalueChangedCallback, myfunction);
The output is shown in Figure 34.7.
Figure 34.7. Using the Scale Widget.
The callback sends a pointer to the structure of type XmScaleCallbackStruct to a function called myfunction. A sample function, myfunction(), follows:
/** *** 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 and 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 **/ } }
This Widget enables the user to type in text and provides full text-editing capabilities. This text could be multiline or single-line. 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. They include the following: XmNvalue: A character string, just like in C. This is different from Motif 1.1 or older, in which this value used to be a compound string. If you have Motif 1.2 or later, this will be C string.
XmNmarginHeight and XmNmarginWidth: The number of pixels on either side of the Widget. The default is five pixels.
XmNmaxLength: 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: Boolean value. If set to TRUE, enables the user to insert text.
The callbacks for this Widget are XmNactivateCallback: Called when the user
presses the Enter key.
XmNfocusCallback: Called when the Widget receives focus from the pointer.
XmNlosingFocusCallback: Called when the Widget loses focus from the pointer.
This Widget has several convenience functions: 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 Widget's editable string.
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 position p.
XmTextReplace(Widget w, XmTextPosition from, XmTextPosition to, char *s) replaces the string starting from the location from inclusive to the position to, inclusive 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 similar to 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 using the
Text Widget. For the layout of the buttons, you would want to use Widgets of the
XmManager class to manage the layout for you rather than having to do it
in your own application. These Manager Widgets are XmBulletinBoard
XmRowColumn
XmForm
The BulletinBoard class enables the programmer to lay out Widgets on a BulletinBoard by specifying their XmNx and XmNy resources. These values are relative to the top-left corner of the BulletinBoard Widget. The BulletinBoard will not move its child Widgets around by itself. If a Widget resizes, it's the application's responsibility to resize and restructure its Widgets on the BulletinBoard.
The resources for the Widget are as follows: 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 to not see a shadow.
XmNallowOverlap: Enables 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, the default, it will grow or shrink to attempt to accommodate all of its children automatically. If set to XmRESIZE_GROW, it will grow only, 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 BulletinBoard receive focus.
The XmRowColumn Widget class orders its children in a row-and-column (or row major) fashion. This is used to set up menus, menu bars, and radio buttons. The resources provided by this Widget include the following: 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 enables 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 BulletinBoard-like behavior. XmPACK_COLUMN forces all children to be the size of the largest child Widget. This uses the XmNnumColumns resource and places all of 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 left or XmALIGNMENT_END for 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. Defaulted to 0.
XmNresizeWidth: A Boolean variable. If set to TRUE, will enable the RowColumn Widget to resize its width when necessary.
XmNresizeHeight: A Boolean variable. If set to TRUE, will enable the RowColumn Widget to resize its height when necessary.
XmNradioBehaviour: Works with ToggleButtons only. It enables only one ToggleButton of a group of buttons to be active at time. The default is FALSE.
XmNisHomogeneous: If set to TRUE, specifies that only children of the
type of 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 Listing 34.3. To see another example
of the same listing but with two columns, see Listing 34.8.
See Figure 34.8 for the output of Listing 34.8.
Figure 34.8. Using the RowColumn Widget.
The beginning of this chapter introduced you to the workings of a Form Widget. This is the most flexible, but complex, Widget in Motif.
Its resources include these:
These values specify how a child is assigned a position. The following values correspond to the each of the sides of the Widget: XmATTACH_NONE: Does not attach this side to form.
XmATTACH_FORM: Attaches to the corresponding side on form.
XmATTACH_WIDGET: Attaches this side to the opposite side of a reference Widget--for example, 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: Attaches this side to the same side of a reference Widget. This is rarely used.
XmATTACH_POSITION: Attaches a side by the number of pixels shown in XmNtopPosition,
XmNleftPosition, XmNrightPosition, and XmNbottomPosition
resources, respectively.
XmATTACH_SELF: Uses XmNx, XmNy, XmNheight, and
XmNwidth.
These resources are set to the corresponding Widgets for each side for the XmATTACH_WIDGET setting in an attachment:
These 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.
While trying a new layout on a Form Widget, if you get error messages about failing after 10,000 iterations, you have conflicting layout requests to a child or children Widgets. Check the attachments very carefully before proceeding. This error message results from the Form Widget trying different layout schemes to accommodate your request.
When designing layouts, think about the layout before you start writing code. Let's try an order entry example. See Listing 34.9.
The output of this application is shown in Figure 34.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 34.10.
Figure 34.9. The output of Listing
34.9.
The Form Widgets are created to maintain the relative placements of all of the
Widgets that correspond to a type of functionality. The RowColumn Widgets allow the
placement of items on themselves. The best route to take in this example would be
to lay one Text Widget 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.
Figure 34.10. The hierarchy of Listing
34.9.
Here are a few points to note about laying out applications:
Designing a Widget hierarchy is especially important when 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:
The procedure to create a menu is different for each type of menu.
To create a pop-up menu, follow these steps:
Listing 34.10 shows how to set up a simple pop-up menu.
Note three important items about this listing:
A menu bar is a horizontal bar that is always available to the user. Motif uses the RowColumn Widget as a bar with cascading buttons on it for each option.
The procedure for creating a menu bar is as follows:
Listing 34.11 shows you how to set up a simple menu application.
}
Note that the Motif programming style requires you to provide the Help button (if you have any) to be 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 to the right-hand side of the visible bar. See Listing 34.12 to learn how to create pull-down menu items on a menu bar.
}
An Options menu enables the user to select from a list of items while displaying the most recently selected item. The procedure for creating an Options menu is similar to creating menu bars:
An accelerator for a command is the keystroke that invokes the callback for that particular menu item. For example, for opening a file, you could use Ctrl-O. The resource for this accelerator could be set in the resource file as
*Open*accelerator: Ctrl<Key>O
The corresponding menu item should read "Open Ctrl+O" to let the user know about this shortcut. Note the + instead of -. You can also set this resource via the command in the .Xresources file:
*Open*acceleratorText: "Ctrl+O"
Using the .Xresource file is the preferred way of setting these resources.
Mnemonics are a short form for letting the user select menu items without using the mouse. For example, the user could press <meta>F to invoke the File menu. These are also usually set in the .Xresource file. The syntax for the File menu to use the <meta>F key would be this:
*File*mnemonic:F
A dialog box is used to convey information about something to the user and requests a canned response in return. For example, a dialog box might say "Go ahead and Print" and present three buttons--OK, Cancel, and Help. The user must then select one of the three buttons to process the command.
A typical dialog box displays an icon, a message string, and usually three buttons (OK, Cancel, and Help). Motif provides predefined dialog boxes for the following categories:
Each of these dialog box types displays a different icon: a question mark for the Question dialog box, an exclamation mark for an Information dialog box, and so on. The following convenience functions facilitate the creation of dialog boxes:
The infamous "OK to quit?" dialog box can be implemented as shown in Listing 34.13. There is another example in Listing 34.17.
Append this code fragment to the end of any sample listings in this chapter to get instant checking before you actually quit the application. Note that the quitDlg dialog box is set to NULL when the function is first called. It does not have to be re-created on every call after the first one; it is only managed for all subsequent calls to this function.
A dialog box can have four modes of operation, called modality. The mode is set in the XmNdialogStyle resource. These are the possible values:
The dialog boxes provided by Motif are based on the XmMessageBox Widget.
Sometimes it is necessary to get to the Widgets in a dialog box. This is done with
a call to XmMessageBox GetChild( Widget dialog, typeOfWidget); where typeOfWidget
can be one of the following:
XmDIALOG_HELP_BUTTON | XmDIALOG_CANCEL_BUTTON |
XmDIALOG_SEPARATOR | XmDIALOG_MESSAGE_LABEL |
XmDIALOG_OK_BUTTON | XmDIALOG_SYMBOL_LABEL |
XtUnmanageChild(XmMessageBoxGetChild(dlg, XmDIALOG_HELP_BUTTON));
or, in the case of adding a callback, use
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, by replacing the message string Widget with a Form Widget, you have a place for laying out Widgets however you need to.
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, requested 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 to either use or discard it. If an application in the hierarchy does use the event or does not allow upward propagation of events, the message is used at the window itself. Only device events are propagated upward (such as keyboard or mouse)--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 create this connection automatically.
Events contain at least the following information:
Look in the file <X11/Xlib.h> for a description of the union called XEvent, which enables access to these values. The file <X11/X.h> contains the descriptions of constants for the types of events. All event types share the 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 server generates an Expose when a window that was covered by another is brought to the top of the stack, or even partially exposed. The structure for this event type is
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 nonzero, 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 (that is, count == 0).
A Pointer event is generated by a mouse press, release, or movement. The type of event is called XButtonEvent. Recall that the leftmost button is Button1, but it can be changed. This is the structure returned by this button press and release:
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;
A keyboard event is generated when the user presses or releases a key. Both types of events, KeyPress and KeyRelease, are returned in a 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 gives information on whether the key was pressed or released. These constants are defined in <X11/keysymdef.h> and are 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). The call is
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.
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 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 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 color a window on entry and exit to provide feedback to the user as he moves the pointer around.
An application requests events of a particular type by calling the XAddEventHandler() function. The prototype for this function is
XAddEventHandler( Widget , EventMask , Boolean maskable, XtEventHandler handlerfunction, XtPointer clientData);
The handler function is of the form
void handlerFunction( Widget w, XtPointer clientData, XEvent *ev, Boolean *continueToDispatch);
The first two arguments are the clientdata and Widget passed in XtAddEventHandler. The ev argument is the event that triggered this call. The last argument enables 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 );
These are the possible event masks:
Listing 34.14 is a sample application that shows how to track the mouse position.
Managing the X server is critical if you have to handle a large number of incoming events. The XtAppMainLoop() function handles all the incoming events via the following functions:
The loop can do something else between checking and removing messages via the replacement code segment:
while (!done) { while (XtAppPending( applicationContext)) { XtAppNextEvent( applicationContext, &ev)); XtDispacthEvent( &ev)); } done = interEventFunction(); }
There are some caveats with this scheme:
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 after it is called. If TRUE, it wants to be removed; if FALSE, it wants to be called again. For example, you could set up a disk file transfer to run in the background, which will keep returning FALSE until it is done, at which time it will return TRUE.
The work procedures are defined as
Boolean 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 the function XtRemoveWorkProc( XtWorkProcId id);
A timeout is used to perform some task at (almost) regular intervals. Applications set up a timer callback function that is called when a requested time interval has passed. This function is defined as
void thyTimerCallback( XtPointer clientdata, XtInterval *tid);
where clientdata is a pointer to client-specific data. The setup function for the timeout returns the timer ID and is defined as
XtIntervalId XtAddTimeOut ( 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--for example, 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.
Linux is not designed for real-time applications, and you can't 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. Depending on your application, your mileage may vary. See Listing 34.15 for an example of setting up cyclic timers.
}
The XtAddInput function is used to handle inputs from sources other than the event queue. The definition is
XtInputId XtAddInput( XtAppContext app, int LinuxfileDescriptor, 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 via the call
XtRemoveInput( XtInput Id);
The inputHandler function itself is defined as
void InputHandler(XtPointer clientdata, int *fd, XtInputId *id);
Unlike timers, you have to register this function only once. Note that a pointer to a file descriptor is passed in to the function. The file descriptor must be a Linux file descriptor. You do not have support for Linux IPC message queues or semaphores through this scheme. The IPC mechanism is considered dated and is limited to one machine. Consider using sockets instead.
Each Widget draws itself on the screen using its set of drawing parameters called the graphics context (GC). 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 color and thickness of the paintbrush are just some of the factors that determine how the paint is transferred to the canvas.
The function call to create a GC is
GC XCreateGC (Display dp, Drawable d, unsigned long mask, XGCValue *values);
For use with a Widget w, this call would look like this:
GC gc; XGCVvalue gcv; unsigned long mask; gc = XCreate(XtDisplay(w), DefaultRootWindow(XtDisplay(w)), mask, gcv);
Also, you can create a GC for a Widget directly with a call to XtGetGC(). The prototype for this function is
gc = XtGetGC (Widget w, unsigned long mask, XGCValue *values);
The values for the mask parameter are defined as an ORed value of the following definitions:
So, if a call is going to set the Font and Clipping mask, the value of the mask will be (GCFont | GCClipMask). The data structure for setting the graphics context is as follows:
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:
Let's look at the values of the functions in a bit more detail.
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 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 via a call to XSetFunction ( Display *dp, GC gc, int function), where function is set to one of the values just mentioned. 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 enabling the GC to work with all planes on a Widget.
GCForeground and GCBackground
These are the values of the pixels to use for the foreground and background colors, respectively. The call to manipulate these is
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 via the GC. It is defaulted to zero, which is the signal to the server to draw the thinnest line possible.
GCLineStyle GCDashOffset GCDashList
This determines the style of the line drawn on-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 does not include, the endpoint pixels of a line; CapButt draws up to the endpoints of a line (inclusive); CapRound tries to round off the edges of a thick line (three or more pixels wide); and CapProjecting extends the endpoint a little.
GCJoinStyle
This is used to draw the endpoints 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, GCStipple
The fill style can be set to FillSolid, which specifies the fill color to be the foreground color; FillTiled specifies a pattern of the same in the Tile attribute; and FillStipple specifies a pattern in the Stipple attribute. FillStipple uses the foreground color where a bit is set to 1 and nothing when a bit is set to 0, whereas FillOpaqueStippled uses the foreground color when a bit is set to 1 and the background color when a bit is set to 0.
GCFont
This specifies the fontlist to use. (See the section "Using Fonts and FontLists," later in this chapter.)
GCArcMode
This defines the way an arc is drawn on-screen (see the next section).
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 a drawable. Widgets have a window after they are realized. 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 via a call to XtIsRealized() on the Widget, which will return TRUE if it is realized and FALSE if it is not. Use the XmDrawingArea Widget's callbacks for rendering your graphics, because it is designed for this purpose. The callbacks available to you are:
XmNinputCallback: Invoked when a button or key is pressed on the Widget.
All three functions pass a pointer to the XmDrawingAreaCallbackStruct.
To draw a line on-screen, use the XDrawLine or XDrawLines function call. Consider the example shown in Listing 34.16.
The output from Listing 34.16 is shown in Figure 34.11.
Figure 34.11. Drawing points and lines.
This 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 as follows:
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
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);
will draw a rectangle at (x,y) of geometry (width, height). To draw more than one box at a time, use the XDrawRectangles() function. This function is declared as
XDrawRectangles( Display *display, Window dwindow, GC gc, XRectangle *xp, int number);
where xp is a pointer to an array of number rectangle definition structures.
For filled rectangles, use the XFillRectangle and XFillRectangles calls, respectively.
To draw a point on-screen, use the XDrawPoint or XDrawPoints function call. This is similar to line-drawing functions. (Refer to Listing 34.16.)
To draw circles, arcs, and similar shapes, 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 an angle a1 starting from the 3 o'clock position to angle a2. The units for angles are in one sixty-fourths (1/64) of a degree. The arc is drawn counterclockwise. The largest value is 64x360 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. The prototype for this function is
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.
Fonts may be the trickiest aspect of Motif to master. See the section on fonts in Chapter 23, "Using Motif," before reading 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
XDrawString ( Display *dp, Drawable dw, GC gc, int x, int y, char *str, int len);
which uses only the foreground color. To draw with the background and foreground colors, use
XDrawImageString ( Display *dp, Drawable dw, GC gc, int x, int y, char *str, int len);
The X Color Model is based on an array of colors called a colormap. Applications refer to a color by its index into this colormap. The indices are placed in the application's frame buffer, which contains an entry for each pixel of the display. The number of bits in the index defines the number of bitplanes. The number of bitplanes defines the number of colors that can be displayed on-screen at one time. For example, one bit per pixel gives two colors, four bits per pixel gives 16 colors, and eight bits per pixel gives 256 colors.
Applications generally inherit the colormap of their parent. They can also create their own colormap using the XCreateColormap call. The call is defined as
Colormap XCreateColormap( Display *display, Window dwindow, Visual *vp, int requested);
This allocates the number of requested color entries in the colormap for a window. Generally, the visual parameter is derived from the macro
DefaultVisual (Display *display, int screenNumber);
where screenNumber = 0 in almost all cases. (See the previous chapter on 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 via the call XFreeColormap(Display *display, Colormap c);.
Applications can get the standard colormap from the X server via the XGetStandardColormap() call, and set it via 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, they have to follow two steps:
For setting or 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 );
See Listing 34.17 for a sample function to set the color of a Widget.
The default white and black pixels are defined as
Pixel BlackPixel( Display *dpy, int screen); Pixel WhitePixel( Display *dpy, int screen);
and 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 requesting colors in different orders. Each entry is allocated on a next-available-entry basis. Sometimes, if you overwrite an existing entry in a cell, you might actually see a change in a completely different application. So be careful.
Applications can query the RGB components of a color by calling the function
XQueryColor( Display *display, Colormap *cmp, Xcolor *clr);
For many colors at one time, use
XQueryColors( Display *display, Colormap *cmp, Xcolor *clr, int number);
At this time the application can modify the RGB components. Then you can store them in the colormap with the call
XStoreColor( Display *display, Colormap *cmp,XColor *clr);
Recall that X11 has some strange names for colors in the /usr/lib/rgb.txt file. Applications can get the RGB components of these names with a call to
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 the call
XAllocNamedColor( Display *display, Colormap cmp, char *name, Xcolor *clr, XColor *exact);
A Pixmap is like a window, but is off-screen and therefore invisible to the user. This is usually the same depth of the screen. You create a Pixmap with the 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 of 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 via the call to XCopyArea. The prototype for this call is
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 XCopyArea is that the depth of the Src and Dst drawables have to be of the same depth. To show a bitmap on a screen with depth greater than 1 pixel, you have to copy the bitmap one plane at a time. This is done via the 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);
where the plane specifies the bit plane to which this one-bit-deep bitmap must be copied. 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 that have three defined values for a sample file called gumby.h:
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);
where 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.
This chapter covered the following topics:
This chapter could easily expand into a book. (Please, do not tempt me!) I have only covered the basics of writing Motif applications. However, given the vast number of tools in Linux, you can see how you can port any existing Motif application code to and from a Linux machine. Similarly, a Linux machine can also prove to be a good development platform for developing Motif applications.