Chapter 14. Miscellaneous Toolkit Programming Techniques

This chapter describes various Xt functions that have not been treated elsewhere in the book.

This chapter discusses various Xt facilities that didn't fit neatly into any other chapter. Many of them are functions provided mostly because Xt uses them internally, and they are unlikely to be useful in application or widget code. Some of them are quite important for accomplishing certain tasks. You should scan the contents of this chapter to familiarize yourself with these facilities so that you will be aware of them.

The topics covered are errors and warning messages, objects, a description of all of Xt's macros and functions for getting information, the Core accept_focus method, how to interpret key events, Xt's facilities for memory management, making global grabs, file finding and internationalization, multiple application contexts, multiple top-level shells, and connecting to multiple servers.

Errors and Warnings

There are several broad categories of errors that may occur in Xt applications. One is the X server error, which is a form of event that tells the client that some parameter in an earlier request was illegal, or that no more server memory is available. A second is the connection failure error generated by Xlib when the connection with the server fails (usually due to a system crash or network interruption). Xlib provides the XSetErrorHandler() and XSetIOErrorHandler() functions to allow the application to provide a routine to handle these two types of errors. Xt provides no interface to these routines--Toolkit applications must use the Xlib routines to customize these error handlers (Xlib uses default error handlers when the application does not use these routines to specify them). For a description of these error handlers and the routines for changing them, see Volume One, Xlib Programming Manual.

A third category is made up of error and warning messages that Xt reports when function parameters are specified improperly, when a translation is incorrectly specified, and for many other reasons. For a complete listing of all errors and warnings that can be generated by Xt, see Volume Five, X Toolkit Intrinsics Reference Manual, Appendix D, Standard Errors and Warnings. Xt provides separate parallel routines for errors and for warnings. The difference between Xt errors and Xt warnings is that errors are fatal and the application exits after printing the error, while warnings are nonfatal and the application continues. The main purpose of these facilities is to generate consistent messages.

Two levels of interface are provided:

  • A high-level interface that takes an error name and class and looks the error up in an error resource database. The high-level fatal error handler is invoked by a call to XtAppErrorMsg(); the high-level nonfatal error handler is invoked by a call to XtAppWarningMsg(). The high-level functions construct a string and pass it to the lower-level interface.

  • A low-level interface that takes a simple string, which is printed out as the error message. The low-level fatal error handler is invoked by a call to XtAppError(); the low-level nonfatal error handler is invoked by a call to XtAppWarning().

These error-reporting interfaces are used internally by Xt, but widget or application code can also use them. For example, when a resource is given an illegal value in a resource file, the widget or application can report the error or warning to the user (which depends on whether the widget or application can continue after the error--most widgets issue only warnings and then fall back on their default value).

The low-level handlers are much easier to use, but they do not support internationalization (alternate languages) at all since the messages are hardcoded in the application. The high-level handlers can potentially support internationalization, but not as elegantly as the normal resource database since Xt searches for the error database file in a fixed location, not using the language string. If you want your application or widget to run in more than one language, you should use the high-level handlers, but only one database for one language can be installed on a system at a time. This is likely to be improved in later releases of Xt.

To use the low-level handlers, you specify the string message as the sole argument to XtAppError() or XtAppWarning().

Contrary to what you might expect, the high-level handlers XtAppErrorMsg() and XtAppWarningMsg() are actually harder to use than the low-level handlers. You must pass six arguments to the calls that generate the errors or warnings, and then to take advantage of their benefits you must set up an error resource database. The first three arguments are the name, type, and class of the error. The use of these three arguments is not yet standardized since they are not widely used. However, in Xt itself, the name identifies the error message, and the type identifies the task that was in progress when the error occurred (or the section of code). The class, within Xt, is always XtToolkitError. The three remaining arguments of XtAppErrorMsg() and XtAppWarningMsg() are a default message, a parameter list, and the number of parameters. The default message will be printed only if no matching message is found in the database. Because Xt does not define or install any error database, it uses these default messages only, and ignores the name, type, and class information. (Xt uses the high-level handlers so that an error resource file can be installed to print all the errors in a foreign language.) The parameter list is used together with the message in the database. The message may be in standard printf format, and the parameters are used to fill in any variable fields.

Example 14-1 shows one of the rare cases where XtAppErrorMsg() is invoked in the Athena widgets.

Example 14-1. How to invoke XtAppErrorMsg (from AsciiSrc.c)

    if (src->ascii_src.string == NULL)
        XtAppErrorMsg(XtWidgetToApplicationContext(src),
                "NoFile", "asciiSourceCreate", "XawError",
                "Creating a read only disk widget and no file \
                specified.", NULL, 0);


The error resource database is stored in a file, /usr/lib/X11/XtErrorDB, under most UNIX-based operating systems. The MIT distribution does not include an XtErrorDB database, but you can define one. Since this database is made up of one file, you must append the resource settings you need to this file rather than replacing it. The resource name searched for in the database is the concatenation of the name and type arguments specified in the calls to XtAppErrorMsg() or XtAppWarningMsg().

You can redefine the routine that prints the message in order to change the fixed part of the message or to add features like logging of errors and warnings. Use XtAppSetErrorMsgHandler() and XtAppSetWarningMsgHandler() (if you are using the high-level handlers) or XtAppSetErrorHandler() and XtAppSetWarningHandler() (if you are using the low-level handlers). See the reference pages for XtErrorMsgHandler(2) and XtErrorHandler(2) in Volume Five, X Toolkit Intrinsics Reference Manual, for a description of how to define a new error or warning handler. The default error and warning messages printed are:

X Toolkit Error:   message.            (for errors)
X Toolkit Warning: message.            (for warnings)

Remember that Xt itself uses these messages (not just your widget code), so that they must remain appropriate when called from anywhere in the Xt, widget, or application code. If you want the message to identify the name of the widget set or widget, you must include this information in the part of the message filled in from the string you pass or from the resource database.

Table 14-1 summarizes Xt's calls for issuing errors and warnings and for modifying the messages issued.

Table 14-1. Xt Error and Warning Message Utilities

Message

Low Level

High Level

Issue Error

XtAppError

XtAppErrorMsg

Issue Warning

XtAppWarning

XtAppWarningMsg

Set Error Handler

XtAppSetErrorHandler

XtAppSetErrorMsgHandler

Set Warning Handler

XtAppSetWarningHandler

XtAppSetWarningMsgHandler


Note, however, that for the high-level routines that use the error and warning resource database, there is only one database common to all application contexts, at least in the sample implementation of Xt provided by MIT in R4 and R5.

When writing a high-level error or warning handler you will need to call XtGetErrorDatabase() to get a pointer to the error resource database and XtGetErrorDatabaseText() to get the message for a particular set of arguments passed to XtAppErrorMsg() or XtAppWarningMsg(). For details on how to use these functions, see the reference pages in Volume Five, X Toolkit Intrinsics Reference Manual.

XtDisplayStringConversionWarning() is a convenience routine to be used in resource type converters that convert from XmRString to any representation type. It calls XtAppWarningMsg() with the appropriate arguments to issue a suitable warning. Note however, that the class used is XtToolkitError. It may be better to use a class that describes the widget or widget set that defines the converter.

Objects

Most of this book describes how to use widgets, which are subclasses of the Core widget class. Chapter 13, Menus, Gadgets, and Cascaded Popups, also discusses using gadgets, which are subclasses of the RectObj widget class. As described there, gadgets are windowless widgets that are especially useful for menu panes, because they cut back on the overhead that would be required to implement the same thing using a widget for each pane.

The third and final Xt class you can subclass is the Object widget class. Objects are windowless widgets like gadgets, but they lack geometry resources, sensitivity resources, and the expose method. They provide support for resources and callbacks, and that is all.

The primary use of objects is to create replaceable subparts of a widget. For example, the R4 Athena Text widget uses objects to implement its source and sink, which control the storage and the display of the data respectively. By replacing the source and sink, you could develop a multi-color or multi-font editor without having to rewrite the central widget which implements all the editing commands.

An object has the following methods: class_initialize, class_part _initialize, set_values, and get_values_hook. All these methods have the same purposes as in widgets.

While a gadget uses part of its parent's window, an object shares its parent's entire window, and is not managed at all by its parent since it has no geometry. The parent of an object is normally a widget that is a subclass of Core but not Composite or Constraint (unlike gadget parents).

But like gadgets, objects depend on cooperation by their parent, and require a specially designed parent. The object parent's methods make calls to functions defined by the object child instead of doing the work themselves. The object child's functions can be either semi-public functions or they can be class methods defined by the object. The latter is done so that subclasses of the object can replace the functions.

As an example, let's look at how the Athena Text widget handles drawing. The drawing is done by the TextSink object, and its subclass AsciiSink. Although an object has no expose method, it provides an equivalent function that draws on the parent. This function is either a semi-public function (public to its parent but not to the application writer), or it is a class method. The Text widget uses both techniques. TextSink provides a semi-public function XawTextSinkDisplayText(), which is called by Text whenever drawing is needed.

XawTextSinkDisplayText() calls one of TextSink's class methods called DisplayText. DisplayText is a class method so that subclasses of TextSink can replace it or inherit it. AsciiSink does replace this method with code that draws the text in a single constant-width font, in the foreground and background colors. So in order to write a Text widget that draws in more than two colors, you would just have to write an object that is a new subclass of TextSink.

When Text calls XawTextSinkDisplayText(), this function calls one of TextSink's class methods called DisplayText. DisplayText is a class method so that subclasses of TextSink can replace it or inherit it. AsciiSink does replace this method with code that draws the text in a single constant-width font, in the foreground and background colors. So in order to write a Text widget that draws in more than two colors, you would just have to write an object that is a new subclass of TextSink.

An object may assume that it should draw on its parent, but this is not necessarily the case, since an object could have an object as its parent. An object also has no idea of its own size, since it has no geometry data in its instance structure, and therefore doesn't by itself know what size window it is drawing into. To solve both these problems, XawTextSinkDisplayText() passes the parent's widget ID and the parent's size into the objects drawing code.

Macros For Getting Information

Xt provides several macros and functions for getting information about widgets. Some of these, such as XtIsRealized() and XtIsManaged(), you have seen before in the context of widget methods.

Some of these are macros and some are functions, and some are macros when used in widget code and functions when used in application code. This does not affect how they can be used, so we won't bother to specify which can be both functions and macros. We will use the term “macro” for all of these informational routines. In Volume Five, X Toolkit Intrinsics Reference Manual, they are listed alphabetically, together with all of the Intrinsics functions.

Xt provides two basic macros for determining the class of a widget: XtIsComposite() and XtIsSubclass(). These are primarily used internally by Xt to implement geometry management, but you may find a use for them. For example, you might write a composite widget that uses XtIsComposite() to treat composite children differently than simple children, or uses XtIsSubclass() to treat constraint children or one of your own classes uniquely. There are lots of convenience functions for XtIsSubclass() that determine if a widget is a subclass of a particular class. These are XtIsObject(), XtIsRectObj(), XtIsWidget(), XtIsComposite(), XtIsConstraint(), XtIsShell(), XtIsOverrideShell(), XtIsWMShell(), XtIsVendorShell(), XtIsTransientShell(), XtIsTopLevelShell(), and XtIsApplicationShell().

You have already seen XtIsManaged() used in composite widgets. See Chapter 12, Geometry Management.

You have also already seen XtIsRealized() used in various methods to make sure a widget has a window before operations are attempted on the window. For example, the expose method calls XtIsRealized() before drawing into the window.

XtIsSensitive() checks the value of the XmNsensitive resource for a widget and its ancestors. If any of them is False, it returns False. Remember that sensitivity controls whether a widget responds to user events.

XtHasCallbacks() lets you tell whether a widget class has a callback of a certain resource name, and whether any callback functions have actually been added to it. It returns the enum value XtCallbackNoList if there is no callback list, XtCallbackHasNone if there is a callback list with no functions on it, and XtCallbackHasSome if there is a callback list containing functions pointers.

XtNameToWidget() searches a hierarchy for the widget ID of the specified widget instance name. Its primary use from the application is to get the IDs of the child widgets of a compound widget such as Dialog, so that their resources can be set directly. This is a violation of the rules of data hiding, however, and is not recommended. In widget code, XtNameToWidget() is used to provide a layer of abstraction so that widgets can be identified using string names. For example, it is used by the converter defined by Form that allows widget names to be specified in resource files. The opposite function, which returns a widget instance name given the widget ID, is XtName() (which perhaps should have been called “XtWidgetToName”).

XtWindowToWidget() gives you the Widget which corresponds to the specified X window ID. This is used mainly by Xt, but you may find a use for it.

XtDisplayOfObject(), XtScreenOfObject(), and XtWindowOfObject() search the parental hierarchy of an object to discover the closest windowed ancestor and then return a pointer to a Display structure, a pointer to a Screen structure, or a Window ID. These macros are useful for making Xlib calls from within code that implements a subclass of Object.

XtGetApplicationNameAndClass() returns the name and class strings of an application. The name is usually argv[0] stripped of any directories, while the class is the string passed as the second argument of XtAppInitialize(). These are the name and class used by Xt to look up resources for the application and its widgets. You are not likely to need this function.

The accept_focus Method and the Keyboard Focus

The keyboard focus is the window to which the server sends keyboard events. The window manager controls which top-level window gets the keyboard focus (using either a click-to-type or pointer-following model). Once an application's top-level window gets the focus, it can set the focus to one of its children; that's the way Motif's keyboard traversal code works. As described in Chapter 5, More About Motif, within a Motif application, you can have either a click-to-type or pointer-following model, although keyboard operation is only possible if the click-to-type model is used.

The Core class part structure includes a field for the accept_focus method. Theoretically, this method lets a widget set the keyboard focus to one of its children when it gets the keyboard focus. A typical example is an application that wants to set the keyboard focus to the text entry child of a dialog box whenever the dialog box is given the keyboard focus by the window manager. This would be done so that the user can type with the pointer anywhere in the dialog widget instead of just with the pointer in the text entry widget.

To implement this example, the text entry child would need an accept_focus method that would set the keyboard focus to itself using the Xlib call XSetInputFocus(). The dialog box would need an accept_focus method that called XtCallAcceptFocus() on the text entry widget child. The application can call XtCallAcceptFocus() on the dialog widget in response to FocusIn events to start this process, and set the focus back to PointerRoot on FocusOut events. For details on these events, see Volume Two, Xlib Reference Manual. This procedure for giving the child of a dialog the keyboard focus is necessary because the application can't find out the name of the child that should have the focus without breaking widget encapsulation rules.

In reality, none of the Motif widgets (and none of the Athena widgets, for that matter) use the accept_focus method. The keyboard traversal code is implemented separately.

An accept_focus method should return a Boolean value to report whether it succeeded in setting the keyboard focus, and XtCallAcceptFocus() returns this same value.

The XtSetKeyboardFocus() function redirects keyboard events that occur within an application to any widget within that application. This is different from XSetInputFocus() in that no request to the server is made, and the redirection of keyboard events is handled entirely by Xt's event dispatching mechanism. However, XtSetKeyboardFocus() should not be used in Motif applications, since it interferes with the keyboard traversal code.

Keyboard Interpretation

Keyboard handling in X is designed so that you can write a program that will operate on systems with widely different physical keyboards. To accomplish this, there are several layers of mappings:

  • The first mapping is between physical keys and keycodes (a number for each key), and varies between servers. A KeyPress event includes only the keycode and information about what other keys and buttons were being held at the time of the keypress. Programs that interpret keycodes directly will operate on only one type of system.

  • The next mapping is between keycodes and keysyms, which are symbolic constants beginning with XK_ that represent the meaning of a key press. This mapping takes into account whether Shift or other modifier keys were being held during the press. Xlib provides the routine XLookupString() that converts the keycode in a key event to the appropriate keysym. Internationalized programs use the XmbLookupString or XwcLookupString versions which can figure out the keysym resulting from key sequences entered into an input method. Portable programs use keysyms to interpret key events. The keycode to keysym mapping is server-wide. It can be changed, but this is normally done only to accomplish radical changes in the placement of keys such as changing a QWERTY style keyboard to DVORAK.

  • The final mapping is between keysyms and strings. For printing characters, XLookupString() also returns a string representation of the interpretation of the key pressed. For example, if the key marked A was pressed with no other keys held down, the string returned would be a. A text entry widget, for example, would append this string to the string being displayed, but modify the string in other ways to handle keysyms that do not have a string representation such as XK_Backspace. The values of keysyms are arranged logically so that all printing characters have a particular range.

Motif adds virtual key bindings to this model, as described in Section 5.9, "Drag and Drop."

When you write an action that accepts key events, you will usually need to interpret the meaning of the key pressed. Xt provides its own interface to XLookupString(): XtTranslateKeycode(). You pass several fields of the key event to XtTranslateKeycode(), and it returns the keysym. However, XtTranslateKeycode() does not return the string interpretation of the key event that would be returned by XLookupString(). If you need that string, you will have to call XLookupString().

Xt provides XtTranslateKeycode() because Xt also provides routines for changing the way the translation returned by XtTranslateKeycode() is done. XtSetKeyTranslator() allows you to specify your own procedure to convert from the key event information to a keysym. The default key event translation procedure is XtTranslateKey(), and so you can restore the default translator if necessary, and so that you can call it from your own translator to get default translations (you need to add only the code that makes the translations not done by the default translator). See XtKeyProc in Volume Five, X Toolkit Intrinsics Reference Manual, for details on providing a key event translation procedure.

Among these routines for modifying the interpretation of key events is a facility for changing the handling of capitalization. For example, most keyboards have the question mark (?) symbol over the slash (/) symbol on one key. The standard case converter converts a press of this key with the Shift key held down to the XK_question keysym. In rare cases a keyboard may have a different symbol over / and put ? somewhere else. Also, some keyboards have two or more symbols on a single key, some of which are not represented at all by standard keysyms. The case converter handles these situations. The case converter is usually called from the key translator described above. To call the case converter, use XtConvertCase(), and to change the case converter, call XtRegisterCaseConverter(). See XtCaseProc in Volume Five, X Toolkit Intrinsics Reference Manual, for details on writing a case converter procedure.

Note that the translation manager uses these same key translation and case converter routines to interpret translation tables. Therefore, make sure that you add features only to them, keeping existing features.

Xt also provides two routines that may help in interpreting key events: XtGetKeysymTable() and XtKeysymToKeycodeList(). The former returns the entire mapping of keycodes to keysyms for the server, while the latter tells you what keycodes are listed for the specified keysym. Neither function is necessary for routine keyboard handling.

From within an action routine, you can call XtGetActionKeysym() to get the keysym that resulted in the action being called. This can be very useful, since the event passed to the action contains only the keycode of the key that was pressed. However, there is another way to achieve a similar result. You can use string parameters of actions to pass in the keysym that you specified in the translation table. For example, if you provide the translation :<Key>q : Quit(q), the Quit action will be passed the string “q” in its params argument. However, XtGetActionKeysym() is very useful if you translate a wide range of key events to one action, and then want to distinguish between them in the action.

Memory Allocation

Xt provides routines for performing routine memory allocation and deallocation. The routines XtMalloc(), XtCalloc(), XtRealloc(), and XtFree() are equivalents of the standard C routines malloc, calloc, realloc, and free but they add error checking and reporting. The allocation routines make sure the allocation succeeded, and if it did not, they print a (fatal) error message. XtFree() makes sure the passed pointer is not NULL before calling free.

XtNew() is a macro which allocates storage for one instance of the passed type and returns a pointer. For example, XtNew(XtCallbackList) allocates storage for one callback list structure. XtNewString() is a macro that allocates storage for a string, copies the string into the new storage, and returns the pointer. For example, a string can be copied into new storage using the following:

    static String buf[] = "How do you do?";
    String p;
    p = XtNewString(buf);

After this sequence, p points to a separate string that contains “How do you do?” Then buf can be changed without affecting p.

Action Hooks and Calling Actions Directly

Xt allows you to register any number of functions to be called whenever any action in an application context is invoked. This is done with XtAppAddActionHook(). Note that there is just one “action hook” in the application context, so that all the action hook functions registered for that application context are called whenever any of the actions in that application context are invoked. The registration does not specify any particular action or any particular widget.

The main reason for registering an action hook is to record all the actions that were invoked in an application, so that they can be played back later using XtCallActionProc(). An action hook function is called with all the same arguments that are passed to an action, plus the string name of the action. The action hook function would store this information as a unit each time it was called. When it comes time to play back the recorded actions, it would pass all the information in each unit to XtCallActionProc().

An action hook can be removed with XtRemoveActionHook().

Xt Grabbing Functions

Grabs are used mostly for popup menus and dialog boxes. As described in Chapter 13, Menus, Gadgets, and Cascaded Popups, Xt has its own grab mode that controls the distribution of events within one application, which can be used to restrict events to one popup in a cascade or allow events to go to any popup in a cascade. As also described there, popup menus in particular need a passive global grab of the pointer in order to detect button releases that occur completely outside the application so that menus can be popped down properly. A passive global grab actually instructs the server to redirect events to a certain window. All the grabbing needs of popups are satisfied by the built-in action XtMenuPopup(), or by the function XtPopupSpringLoaded(), which can be used in a callback function. If necessary, you can write your own version of XtMenuPopup() and register it with XtRegisterGrabAction() so that the appropriate passive global grab is in effect.

The above facilities should be quite sufficient for your needs in the area of popups. However, it is possible that you may need an global grab for some other purpose. Perhaps you need all keyboard events in one window for a short time and don't want to change existing translations or accelerators for keyboard events in other widgets. This is only one plausible scenario.

The Xlib functions for grabbing are XGrabKey(), XGrabButton(), XGrabKeyboard(), and XGrabPointer(). Each of these functions has an analogue for ungrabbing. But Xt provides its own version of all eight of these functions. You should use the Xt versions because they are integrated into Xt and take care of things like making sure the widget that is to get the grab has been realized. In other words, if you do need the server to redirect events for you using a grab, use the following functions: XtGrabKey(), XtGrabButton(), XtGrabKeyboard(), XtUngrabKeyboard(), and XtUngrabPointer(). For more on global grabs, see Chapter 8 in Volume One, Xlib Programming Manual.

File Finding and Internationalization

Xt's facilities for allowing an application to run in different languages are mostly built into the resource database mechanism. Where Xt searches for resource files depends on a language string, which can be set within resources at run time or with an environment variable to affect one user's environment. This is described in Chapter 10, Resource Management and Type Conversion. In R5 and later, Xt also uses the value of the XtNcustomization resource to set the path it uses when looking for application defaults and other files. XtNcustomization is normally used to supply separate sets of resources for color and monochrome screens.

The functions that Xt uses to implement its search for resource files are also available to you for searching for application files. This allows you to provide separate data files for each language for a help system, for example. The primary function you would use to do this is XtResolvePathname(), since it searches directories in a standard order based on X/Open Portability Guide conventions.

If you want to find a file but are not interested in internationalization, you can use XtFindFile(). This function can simplify code that reads files, since it helps handle differences in file systems.

Application Contexts

The introduction to application contexts in Section 3.9, "More About Application Contexts" described their use in 99 percent of applications. As you may recall, their purpose is chiefly to attain portability to certain systems that do not provide a separate address space for each process.

Xt provides parallel versions of many routines--one set that uses the default application context, and one set that has an explicit application context argument. The routines that use the default application context are remnants of an earlier release when application contexts did not work properly. To achieve the desired portability, you must now use the versions with the explicit argument. We have done this throughout this book. In Volume Five, X Toolkit Intrinsics Reference Manual, the reference pages for all the functions that use the default application context note the fact that they should no longer be used.

Table 14-2 shows the complete list of routines that have two versions.

Table 14-2. Xt Routines That Use Default and Explicit Application Contexts

Default

Explicit

(registering functions)

XtAddActions()

XtAppAddActions()

XtAddConverter()

XtAppAddConverter()

XtAddInput()

XtAppAddInput()

XtTimeOut()

XtAppTimeOut()

XtWorkProc()

XtAppWorkProc()

(creating shells)

XtCreateApplicationShell()

XtAppCreateShell()

(event dispatching)

XtMainLoop()

XtAppMainLoop()

XtNextEvent()

XtAppNextEvent()

XtPeekEvent()

XtAppPeekEvent()

XtPending()

XtAppPending()

XtProcessEvent()

XtAppProcessEvent()

(error and warning messages)

XtError()

XtAppError()

XtErrorMsg()

XtAppErrorMsg()

XtGetErrorDatabase()

XtAppGetErrorDatabase()

XtGetErrorDatabaseText()

XtAppGetErrorDatabaseText()

XtSetErrorHandler()

XtAppSetErrorHandler()

XtSetErrorMsgHandler()

XtAppSetErrorMsgHandler()

XtSetWarningHandler()

XtAppSetWarningHandler()

XtSetWarningMsgHandler()

XtAppSetWarningMsgHandler()

XtWarning()

XtAppWarning()

XtWarningMsg()

XtAppWarningMsg()

(selection timeouts)

XtGetSelectionTimeout()

XtAppGetSelectionTimeout()

XtSetSelectionTimeout()

XtAppSetSelectionTimeout()

(action hooks)

(no equiv)

XtAppAddActionHook()

(toolkit initialization)

(no equiv)

XtAppInitialize()

(resources)

(no equiv)

XtAppSetFallbackResources()

XtSetTypeConverter()

XtAppSetTypeConverter()

(no equiv)

XtAppReleaseCacheRefs()


Note that XtCreateApplicationShell() and XtAppCreateShell() have names that don't follow the example set by all the rest.

Multiple Application Contexts

The use of more than one application context in a single program presents possibilities that you might wish to explore. Having more than one application context in the same program allows you to have one program that when run looks like two or more independent programs. This approach saves disk space and memory on systems that don't provide shared libraries, since the grouped programs can share a single copy of the libraries.[100]

Having two application contexts makes each sub-application more separate than if they were just under different top-level Shell widgets. Each widget class has an action list, and each application context has a separate context-wide action list. When the translation manager looks for an action, it looks in the widget class action list first, and then the application context action list. Therefore, each sub-application could add an action to its application context without conflict with another sub-application adding a different action of the same name.

On parallel processing machines, each separate application context could run in parallel. However, it is difficult to write portable code to take advantage of this, since each architecture has different conventions for indicating parallelisms in C code.

Rewriting XtAppMainLoop for Multiple Application Contexts

To use multiple application contexts, you need to write your own equivalent of XtAppMainLoop() to dispatch events to your multiple application contexts. This is necessary because XtAppMainLoop() dispatches events only from one application context, and it never returns so you can't call it again for the other.

The available tools are XtAppNextEvent(), XtAppPeekEvent(), XtAppPending(), XtAppProcessEvent(), and the Xlib functions XFlush() and XSync(). Rewriting XtAppMainLoop() for two or more application contexts is tricky, because you don't want to let the dispatching of any one application context get behind. It is not as simple as dispatching events from each application context alternately, since the events might not occur alternately. It is easy to get stuck waiting for events in one application context while events queue up at the other. There is little experience in how this should be done properly, and no examples in the distribution from MIT. However, hypothetically, the following describes how it could work.

To do this properly, you have to understand how Xlib's network optimization works. Xlib buffers up many types of requests and sends (flushes) them to the server as a group.[101] A flush is most commonly caused by a routine such as XtAppNextEvent() that waits for an event if none are available. It is because XtAppNextEvent() waits forever for an event that the routine could get locked waiting for events in one application context while the user types frantically in the other.

The answer is to use XtAppPending() to determine whether an event is available on a particular application context, and then call XtAppProcessEvent() if there is an event to process. Then continue to do the same on each other application context. However, this alone is not enough. Neither XtAppPending() nor XtAppProcessEvent() called in this manner cause Xlib's buffer of requests to be sent to the server. Therefore, periodic calls to XSync() or XFlush() are necessary to flush the output buffer. The difficult part is to call these enough to flush the buffer when necessary, but not so much as to eliminate the advantages of the buffering. There is no ideal solution to this problem.

On multi-tasking systems it is perhaps possible to fork so that each application context runs in a separate process.

Functions Used with Multiple Application Contexts

XtWidgetToApplicationContext() and XtDisplayToApplicationContext() could be useful if you use more than one application context in an application. XtWidgetToApplicationContext() is also useful in widget code, to call error-issuing routines that require an application context argument, such as XtAppWarning() and XtAppWarningMsg().

Multiple Top-level Shells

A single application can have more than one top-level window. In other words, you are not restricted to containing your application's entire user interface in a single rectangle. If you have one section of the application that is most appropriate as a long, thin vertical window that looks like a long, permanent menu, and another section that is a long, horizontal bar such as a ruler, each of these could be a separate top-level window. That way, not only is less screen space wasted than if these two windows were placed within a single rectangle, but the user can move the two windows around separately using the window manager. The user can also iconify them separately when not needed.

To create additional top-level application shell widgets, you call XtAppCreateShell() or XtVaAppCreateShell(). (XtCreateApplicationShell() is now superceded.) The class specified in the call should be topLevelShellWidgetClass.

As you may know, a single server may have several screens attached. At present, all shells created will appear on the default screen. There is no way for the application to specify that a shell be created on a particular screen, but then again, doing this is usually unwise anyway because not many users actually have more than one screen. The user can specify which screen is considered the default screen using the -display command-line option.

Connecting to Multiple Servers

One of the great features of the server-client model is that a single program can connect to several servers to display on the screens of several users. For example, this would allow you to create an X-based version of the UNIX utility wall in which one user can make a message appear on all user's screens throughout a network. You could also create a conferencing program in which each user has a window on their screen in which they can type and view typing by others in real time, and their typing will appear on all the other user's screens.

The Xt application opens a connection with a server using XtOpenDisplay() (this routine requires an explicit application context argument). Once you have opened the connection, you will want to create a shell widget on the server's default screen using XtCreateApplicationShell() or XtAppCreateShell(). Then, you create widgets for each server simply by using the appropriate Shell widget as parent. Thereafter, XtAppMainLoop() dispatches events from all the connections to the appropriate widget.

Class Extension Structures

X Consortium standards, once adopted, can only be changed in ways that are binary and source compatible with the original standard specification. Xt became an X consortium standard in Release 3. Therefore, Release 4 and later releases are required to be source and binary compatible with Release 3. Source compatibility means that properly coded applications and widgets written to the R3 specification should compile and work under R4 and later releases. Source compatibility is maintained by keeping all programming interfaces intact while adding features with new interfaces. Binary compatibility means that widgets written and compiled with R3 must be able to be linked with R4 or R5 Xt and Xlib libraries and still run. Source compatibility is necessary but not sufficient for binary compatibility. The major addition requirement of binary compatibility is that fields added to structures must be added to the end of the structure.

In Chapter 6, Inside a Widget, you saw how a class structure for a widget class is built by nesting the class part structures of all its superclasses into one big structure, the class record. The X consortium could not simply add fields to the class part structures of the basic Xt classes without breaking binary compatibility with existing subclasses because the class parts of basic Xt classes appear in the middle of the class record of subclasses. Therefore, they used extension structures to add these fields.

Class extension structures allowed the X consortium to add to the class structures of basic Xt classes. The last field of each basic Xt class is called extension. This field can be set to a pointer to the class's extension structure, which contains its added fields. New features that required additional class structure fields were added to the Composite and Constraint classes in R4. Therefore, these two classes now have extension structures.

Some classes, such as Core, do not have an extension structure because no additional class fields have yet been necessary. They still have the extension field, but it is not used.

A class extension structure is defined in the private header file of a widget class. The extension structure for Composite is called CompositeClassExtensionRec as shown in Example 14-2.

Example 14-2. Common fields in a class extension record

typedef struct {
    XtPointer next_extension;/* 1st 4 mandated for all ext rec */
    XrmQuark record_type;    /* NULLQUARK; on CompositeClassPart */
    long version;            /* must be XtCompositeExtensionVersion */
    Cardinal record_size;    /* sizeof(CompositeClassExtensionRec) */
    Boolean accepts_objects;
} CompositeClassExtensionRec, *CompositeClassExtension;

All extension structures start with the same four fields. The only field in this structure actually used by Composite is accepts_objects, the use of which is described in Section 12.4.5, "The class_part_init Method." The accepts_objects field is only used by Composite widgets that have the code necessary to accept gadget children. When a widget class needs an extension feature, the widget class initializes the extension structure in its .c file, and then in the class_ initialize method sets the extension field to point to the extension structure.

When a widget class does not need an extension feature--for example, a Composite widget that does not accept gadget children--the widget class does not provide code in the class_initialize method to set the extension field.

The .c file of all widget classes should initially set all extension fields to NULL. The difference between classes that use extension features and those that don't is only the presence or absence of code in class_initialize to set the extension fields.

The four required fields in an extension structure are:

next_extension 

Specifies the next record in the list, or NULL.

record_type 

Specifies the particular structure declaration to which each extension record instance conforms.

version 

Specifies a symbolic constant supplied by the definer of the structure.

record_size 

Specifies the total number of bytes allocated for the extension record.

When you initialize an extension structure of a given class in the .c file, you always set these four fields to the same values. Example 14-3 shows how to initialize the Composite extension structure. The first two fields are usually NULL and NULLQUARK. The reference page for each class that has an extension structure will document how to initialize the third and fourth fields.

CompositeClassExtensionRec extension_rec = {
    /* next_extension */  NULL,
    /* record_type */     NULLQUARK,
    /* version */         XtCompositeExtensionVersion,
    /* record_size */     sizeof(CompositeClassExtensionRec),
    /* accepts_objects */ True,  /* only new field */
};

The next_extension field implies correctly that you can nest extension structures. Perhaps a later release of Xt will require the addition of more Composite class fields. These could be added to the end of the existing extension structure, or a new extension structure could be defined and a pointer to it placed in next_extension. This allows additions to be made to a class structure without breaking binary compatibility.

You may be wondering whether you should use extension structures when extending your own widgets. Probably not. Because you are likely to release your set of widgets as a package, you have no need for binary compatibility with previous releases (of your own). Binary compatibility would only be an advantage if you wanted to replace a few widgets from a former release and save users who may have subclassed the widgets you have replaced from recompiling their widgets. For the trouble involved, the benefit is very small.

Using Editres in Xt Programming[102]

editres is a tool for viewing the structure of X Toolkit applications, finding and setting resources, and dynamically see the results of such settings. editres can help programmers create an app-default file and fallback resources and test custom Xt widgets. This section describes how to add support to Motif applications, and how to use editres as an aid in debugging widgets and applications.

A Tour of editres

The editres application is part of the core distribution in Release 5. This section is a brief tour of the editres resource editor. It will illustrate how to use editres to customize an X Toolkit application. When the user starts editres it looks like Figure 14-1.

Figure 14-1. editres immediately after startup

The editres application contains four areas; The Menu Bar, the Panner, the Message Area, and the Tree Display (which is initially empty). You begin by choosing Get Widget Tree from the Command menu and, following the instructions in the Message Area, select an X Toolkit application by clicking a pointer button. Figure 14-2 shows the result of selecting the xman application. (Note that the application must have support for editres. All applications written using the Athena widget set have that support built in as of R5. Applications written using Motif need to have support added, as shown later.

Figure 14-2. editres with widget tree displayed

The Tree Display now contains the widget hierarchy of xman, displayed graphically. You can now see the name of each widget in the application, and the parent-child relationship of each of these widgets. By using the Tree menu or a keyboard accelerator the user can also see the Class names, Widget ID's, or Window ID's. Figure 14-3 shows the same xman application, but with the Window ID's displayed instead of the widget names.

Figure 14-3. editres display window IDs of widget tree

By selecting “Select Widget in Client” from the Tree menu you can click a pointer button anywhere in the application, and editres will highlight the corresponding widget in the Tree Display. Figure 14-4 shows the result of clicking in the topLabel widget of the xman application. Editres can also do the inverse of this; selecting the “Flash Active Widgets” command from the Tree menu will show you (by flashing widgets in the application) which widgets are currently selected in the Tree Display.

Figure 14-4. Flashing widget in application by selecting it in editres

With these two commands you can easily determine the correspondence between the application itself and the Tree Display representation of it. Because a resource specification is simply a description of the widget's locations in the Tree Display and can be constructed by concatenating the names of the widget and it ancestors, editres can easily construct valid resource specifications that apply to the widgets selected. For example, the fully specified resource name of the topLevel widget is xman.topBox.form.topLabel.

Once you find the widget you want to modify you can select the Show Resource Box command to bring up a dialog box that will allow you to set the resource of that widget. A Resource Box is shown in Figure 14-5, as it would look after changing the topLabel widget's resource to “Testing, Testing.”

Figure 14-5. Resource box for the selected Label widget

At the top of the Resource Box is the resource line currently being edited. This is the actual string that will be inserted into the resource file if you decide to save a change. Below this is an area that allows you to modify the widgets to be affected by this change. This area allows you to substitute class names for instance names, and asterisks for periods in the left hand side of the resource specification. These changes will loosen the binding of the resource specification allowing this one resource line to apply to more and more widgets (see Figure 14-6). Note that no matter how the bindings are modified (by changing periods to asterisks) the original widget will always match the specification.

Figure 14-6. Generalizing the widgets affected by the resource specification

When taken to the extreme, the resource specification can apply to every widget in the application. As you change the bindings of the resource specification, the Tree Display is updated to highlight all the widgets that will be affected by the resource line displayed. When this is combined with the Show Highlighted Widgets command, which flashes the widgets in the application that correspond to those highlighted in the Tree Display, you can quickly determine which widgets will match a given resource specification.

The next area contains a complete list of normal and constraint resources that are available for the originally selected widget. This area is used to select the name of the resource that the user wishes to modify. Foreground, for example, will set the foreground color. Below this area is simply a text field to allow the user to enter the string that will represent the resource value. The resource value that is entered here should be exactly what would be entered into a resource file, thus all new lines should be escaped by a backslash (\) and backslash-n (\n) should be used to add a new line to the resource value.


Note: Since editres specifies all resources as strings, it can only set those resources that can be set from resource files. Motif 1.1 and 1.2 do not provide support for certain resources to be set in resource files. One example is the XmNleftWidget resource of the Form widget.)

The commands at the bottom of the Resource Box allows the user to select the file the resources will be written into, write the resources to the file, apply the resource immediately to the application, or just popdown the resource box.

Note that applying the resource to the application will not always have the same effect as restarting the application with the resource added to the user's customization file. This is a design constraint of the X Toolkit. Some resources are designed to be read once at startup, and trying to coerce them into a dynamic resource editor is problematic. The basic problem is that the editres support in the application uses XtSetValues() to change the resource of the selected widgets. An XtSetValues() call after startup will override any hard-coded application defaults (but not application defaults specified in files or as fallback resources), whereas the same resource specification in a resource file will not. Therefore, the results of applying a resource setting using editres are only a guide.

Those are the basic features of editres. You can see there is much work to be done, but the basic task of finding and setting resource values is much simpler with this tool. This brief tour should give you an idea of the capabilities of the resource editor, and hopefully stir your imagination a bit. I will next describe the method editres uses to communicate with the applications.

editres as a Programmer's Tool

The programmer can use editres to help understand the structure of the application or develop resource settings to be used as the app-defaults file or fallback resources.

The editres tool can also be useful for testing newly written or modified widgets. The test program can be written generically and then editres lets you see the results of various resource settings. The tests allow the widget's geometry_manager and set_values methods to be debugged more quickly and with much less effort than is possible without editres.

Before you can do debugging or testing with editres, you need to find out if the application you will be using has editres support, and if not, add it.

Adding editres Support to an Xt Application

editres support is native to the R5 Athena widget set, but is not native in Motif 1.1 or 1.2. When the support is not available, only a few lines of code need to be added to an Xt application to link in the editres communication routines. Also note that if you use custom widgets with a widget set that supports editres, the custom widgets are automatically included and no code needs to be added. If you know the widget set you use already supports editres, you can skip to the next section.

First check that editres is not supported by the widget set you are using, by invoking the “Get Widget Tree” command from the editres command menu, and then clicking on your application. If editres is not supported, the command will fail resulting in no tree display and the message “It appears that this client does not understand the Editres Protocol.” This message will be displayed only after the editres command has timed out, a process that may take up to sixty seconds.

The R5 Xmu library provides an event handler that can send and receive the editres protocol. To add editres support to an Xt based application, you need only to register this event handler on at least one shell widget in the application. But to allow the user to select any window in the application and activate editres, you must add it to every shell widget in the application. Here is the code needed to add the event handler to one shell widget:

Example 14-3. Adding editres support in an application

   #include <X11/Xmu/Editres.h>


   XtAddEventHandler(shell, (EventMask) 0, True, _XEditResCheckMessages, NULL);

This code can be added anywhere after the shell widget has been created. The X11R5 Xmu library must be linked into the application to provide the routines that actually decode the protocol and process the editres events. Because Xmu depends upon Xt and X11, the order of linking is important. The link command for the application should be:

	% cc <objects> -lwidget set library -lXmu -lXt -lX11 -lXext

Once the application has been recompiled and relinked editres support should be available. If the _XEditResCheckMessages() handler was added to only one shell in the application, be sure to select that shell or one of its normal (non-popup) children when editres prompts you to “Click the mouse pointer on any Xaw client.”

Using editres to Understand the Structure of an Application

Picking up maintenance of an X application that was written by someone else can be an exercise in patience. Because of the event driven nature of X applications there is no single flow of control. In order to find out what is happening it is often necessary to locate the place where a widget on the screen is created in the code and then use this information to find its widget ID and display the data contained in that widget. editres can make this task much simpler.

To find the place where a widget is created, use the “Select widget in client” command, and click the pointer on the widget of interest within the target application. This will highlight that widget in editres's tree display. The tree display will show the name of the widget, which makes finding where it was created easier, usually a simple matter of using grep(1). You can also view the widget's class name, widget ID and window ID by selecting various commands from the Tree menu, or using the keyboard accelerators described below.

Table 14-3. editres accelerators

Accelerator

Description

N

Show widget name

C

Show widget class

I

Show widget ID (in hex)

W

Show widget's window

If the pointer is over a widget when the accelerator is used then only that one widget is affected. If the pointer is over the tree background then all widgets in the application are affected.

When viewing the widget windows, the programmer is also informed if the widget is unrealized or if the widget in question is a non-windowed object, such as an Object or a Motif Gadget. This information is often useful when attempting to decipher an X protocol error, since it contains the XID of the offending widget. The class names can be useful to help understand the behavior, and geometry constraints of the current application's window layout. Widget ID's are mainly useful when used in concert with a symbolic debugger such as dbx or CodeCenter (formerly Saber-C), since the values in the widget structure can be viewed in the debugger by de-referencing this widget pointer.

To provide a permanent hard copy of the widget hierarchy of an application, either for future reference, or for inclusion in the application's documentation, editres provides a command called “Dump Widget Tree to a File.” The output is dumped in an outline format and includes both the name and class of each widget currently in the application. Note that this command will only show those widgets that had been created the last time a “Get Widget Tree”, or “Refresh Current Widget Tree” command was executed.

In addition to allowing the programmer to understand the structure of an application, editres can be used to add entries to an application defaults resource file. To use editres as an aid simply use the “Set Save File” button at the bottom of the Resource Box, and then use the “Save and Apply” button to add entries to the application defaults file. editres immediately applies the resource change to the application.

Using editres to Test or Debug a Widget

The programmer can also use editres to test some of the dynamic characteristics of a widget. Since a widget's most common interface to the outside world is through the setting of resources, the “Apply” action in editres's resource box window provides a very convenient mechanism for testing and debugging widget resources. This mechanism can be used to test the widget's set_values and Constraint set_values procedures, as well as the widget's geometry_manager.

editres gets its list of resources for a widget by sending a message to the application. The editres support code in the application that receives the message then calls the Xt functions XtGetResourceList() and XtGetConstraintResourceList() to get the list of resources supported. Xt does not provide functions for getting the list of resources for application resources, objects that are not Xt children or Xt popup children, and subresources, so they are not visible to editres. This limitation appears in most Text widgets, because they use either objects or subparts that have subresources, and editres cannot find these resources.

Editres passes only string values across to the application to set resources. Therefore, the editres support code in the application uses a StringTo<something> converter to actually set the widget resource. If no such converter is available, editres will not be able to set the resource. Since these converters are required to allow the resource to be set in the app-defaults file or user resource file, your widget should have them anyway, so you should not need to add any special code to your widget in order to debug it using editres.

Testing a Widget's set_values Procedures

To test a widget's set_values procedure you must first pop up the resource box associated with that widget. The resource box is popped up by selecting the widget in question and selecting the “Show Resource Box” command. This will pop up a window that shows all the resources available to this widget. Selecting a resource, entering a value, and hitting the Apply button at the bottom of the Resource Box window allows the programmer to test each resource of this widget to verify that it is responding to them correctly.

Note that each widget must register a type converter from String to each new type of resource it created, since editres sends all values as strings. Writing and registering these converters is a good idea in any case, since it allows each widget resource to be specified in the app-defaults file or another resource file as well as through the set_values mechanism.

It is important to note that changes to a resource defined by the superclass may often require support from the subclass. If your new widget, which is a subclass of an existing widget, has added some additional functionality to a resource that is not new to this widget, but was defined by its superclass, you must test this resources as well (e.g., a Label widget will need to test the sensitivity resource, since it added support for stippling the text when it becomes insensitive). Programmers should be sure to modify the value of each resource, not just those new resources added to this widget,

To test the Constraint set_values procedure of a widget the Resource Box of a child of this widget must be popped up. This Resource Box window will list the constraint resources separately from the normal resources. These resources can be tested with the apply button in the same fashion as normal resources.

Testing a Widget's geometry_manager

Although it is not obvious at first, editres can be used to test a widget's geometry manager. Since the size and location of every widget are stored in resources they can be modified on-the-fly with editres.

First identify the widget whose geometry manager is to be tested. I will call this widget the “parent.” To test its geometry manager we must make a request to change the geometry of one of its children. Therefore, use editres to select one of the children of the “parent” widget and pop up its Resource Box. This will allow the child's size and location to be changed by modifying the x, y, width and height resources. Changing these attributes will in turn call upon the geometry manager of the parent widget, allowing the programmer to see if the proper behavior is occurring. By placing several different kinds of children in the parent and seeing how changes to the geometry of each child affects the others, extensive testing can be performed in a short period of time.

Since two resources cannot be modified in a single editres command there is no direct way to make a simultaneous width and height change to a widget. But if a Xaw Label or XmLabel widget is used as a child then a change to the font will often cause a change to both the width and height of the widget. The effects of such a change can then be tested in the parent's geometry manager.

Editres may be expanded to include additional features.

Internationalization in the X Toolkit

In X11R5, Xt was modified to better support internationalization. An internationalized application reads the user's language (called a locale) from the environment or a resource file and operates in that language without changes to its binary. X internationalization is based on the ANSI-C internationalization model. The concepts and implementation of X internationalization are described in the Third Edition of Volume One, Xlib Programming Manual. Xt support of internationalization is trivial in most applications: the only code needed is a call to XtSetLanguageProc() just before the call to XtAppInitialize(). However, if your program directly manipulates text from widgets, or you need to write widgets that accept text input or draws text, you'll need to understand Xlib's internationalization features.

String Encoding and Locale Dependencies in Xt

The X Toolkit specification has not been so thoroughly revised as the Xlib spec to make explicit the expected encodings of all strings. Generally, it will be true that Xt variables or arguments of type String should be in the encoding of the locale, but caution is necessary: the widget name passed to XtCreateWidget() is of type String, for example, but since it may be used in resource specifications it should be in the Host Portable Character Encoding if the application is to operate in more than a single locale. Neither is the specification explicit about such things as the encoding of translation tables or the localized behavior of resource converters like XtCvtStringToBoolean. None of these are critical problems; the Xt programmer who wishes to write internationalized applications should be aware, however, that there are internationalization issues in Xt that remain to be worked out.

Establishing Locale in an Xt Application

Resource specifications in X11R5 are parsed in the current locale, which means that an application should have established its locale before reading its app-defaults file and creating its resource database. However, it should be possible to specify the locale for an application using a resource, which means that the resources should be read before the locale is set. The Intrinsics as of X11R5 resolves this catch-22 by parsing resources in two steps: first it scans the command line and per-display resource string for the setting of the locale, then, once the locale is set, it fully parses all the resource specifications.

The locale and resource initialization sequence is as follows:

  1. The application starts up in the default locale. The programmer does not call setlocale, but registers a language procedure by calling XtSetLanguageProc(). This language procedure will later be called to set the locale.

  2. The application then typically calls XtInitialize() or XtAppInitialize() or XtOpenDisplay(). These initialization routines call XtDisplayInitialize() which scans (but doesn't actually parse--this scan is done in a way that is independent of the initial locale of the process) the command line and the RESOURCE_MANAGER property on the root window of the default screen for the value of the xnlLanguage resource. (The class of this resource is XnlLanguage, and it can be set from the command line using the -xnlLanguage option. The “nl” stands for “native language.”) The language string obtained from this resource (or the empty string if the resource doesn't exist) is passed to the language procedure registered with XtSetLanguageProc(). If no language procedure was registered, XtDisplayInitialize() continues exactly as it did in X11R4.

  3. The language procedure uses the passed string to set the locale. Note that the language procedure can pass the empty string directly to setlocale which will take it as a signal to set the locale based on the value of the appropriate operating system environment variable. The language procedure should also call XSupportsLocale() to verify that the locale is supported and XSetLocaleModifiers() to set any locale modifiers. Finally, the language procedure must return the name of the locale it set (which is the return value of setlocale).

  4. Now XtDisplayInitialize() saves the return value of the language procedure for use by XtResolvePathname() as a pathname substitution when searching for the appropriate application defaults file for the locale.

Note that XtDisplayInitialize() could make the call to setlocale itself, but that this was deemed inappropriate by the designers. If no language procedure is registered, XtDisplayInitialize() behaves as it did in X11R4, and the locale is never set. For most applications the default language procedure will be sufficient: it calls setlocale, XSupportsLocale(), and XSetLocaleModifiers() and it returns the name of the locale it set. Note that the default procedure is not actually registered by default; you must explicitly register it or you will get X11R4 behavior. To register it, call XtSetLanguageProc() with a procedure argument of NULL. Example 14-4 shows how to initialize Xt using the default language procedure to correctly set the locale.

Example 14-4. Establishing the locale in an Xt application

main(argc, argv)
int argc;
char **argv;
{
    Widget toplevel;
    /* register the default language proc */
    /* no app-context, no function, no tag */
    XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    /* this function invokes XtDisplayInitialize which
     * will call the language procedure. */
    toplevel = XtAppInitialize(...);
      .
      .
      .


XFontSet Resources

The XFontSet abstraction defined by X11R5 Xlib for internationalized text output will be an important resource for any internationalized widget that draws text. The purpose of the XFontSet is as follows. An application needs more than one X font in order to operate in a language like Japanese. In Japanese, any useful text-based application needs a Kanji font (final symbols), a Katakana font (phonetic font used in typing), and an English font (for specialized terms) at the same time. So these are grouped together and called one XFontSet.

The X11R5 Xt Intrinsics give XFontSet almost the same support they give the XFontStruct. They define:

  • XtNfontSet, a name for an XFontSet resource.

  • XtCFontSet, a class for an XFontSet resource.

  • XtRFontSet, the representation type for an XFontSet resource.

  • A pre-registered String-to-XFontSet converter.

  • XtDefaultFontSet, a string constant guaranteed to work with the pre-registered String-to-XFontSet converter.

When the constant XtDefaultFontSet is passed to the pre-registered String-to-XFontSet converter, it queries the resource database for the value of a resource with name xtDefaultFontSet and class XtDefaultFontSet. If this resource exists and has a valid value, the XFontSet is created using the resource value as the base font name list. If the resource doesn't exist or no font set can be created from it, the converter falls back onto an implementation-defined default font set list.

Unfortunately there is no standard command-line option analogous to -font defined by the Xt Intrinsics which will set this xtDefaultFontSet resource. Applications can provide this command-line option themselves, or a user can specify a font set with the -xrm option:

imail -xrm "*xtDefaultFontSet: -*-*-*-R-*-*-*-140-100-100-*-*-*-*"

Other Xt Changes for Internationalization

The three other changes to Xt for internationalization are as follows:

  • To allow the localization of error messages, the high-level Xt error and warning handling routines are no longer required to use the single file /usr/lib/X11/XtErrorDB (on POSIX systems) as the error message database. Unfortunately, the app-context based design of the error and warning handlers means that these handers are not passed a handle to the Display that the error occurred on. This implies that they cannot use XtResolvePathname() (which requires the display to look up the language string to use in path substitutions) to find an appropriate error database for the locale. For this reason X11R5 does not specify a standard error database search path, but simply states that the source of the error and warning message text is implementation-dependent. What this means to the programmer of internationalized applications is that you cannot portably rely on the default high-level Xt warning and error handlers to find the localized text of your error messages. You should register an error handler of your own or display your error messages through some entirely different mechanism.

  • X11R4 specified that the language string obtained from the xnlLanguage resource should be in the form:

    language[_territory][.codeset]

    This was deemed an unnecessary restriction, and for X11R5 the specification has been changed to state that the language string (obtained from the xnlLanguage resource or returned by the new language procedure) has a “language part,” a “territory part,” and a “codeset part,” but that the format of the string is implementation-dependent.

  • The internationalization of X text input with input methods (see Xlib) requires that an input method have a way to intercept X events before they are processed by the application. An input method does this by registering an event filter, and all applications that perform internationalized text input are required to call the function XFilterEvent() each time they receive an event. To support internationalized input, XtDispatchEvent() has been modified to make this required call to XFilterEvent(). Furthermore, if an event arrives that triggers a grab registered by XtGrabButton() or XtGrabKey(), and that event is filtered by XFilterEvent(), then XtDispatchEvent() breaks the grab by calling XtUngrabPointer() or XtUngrabKeyboard() with the timestamp of the event. This is done because when an input method filters an event, the application should behave as if that event never arrived.

Internationalization in Motif 1.2

Since Motif 1.2 is built upon the R5 version of the Xt Intrinsics, it has the same internationalization features. In addition, however, Motif widgets that handle text have been reimplemented to support wide character and multi-byte strings.

Text Output

The Motif 1.2 XmFontList can contain XFontSets as well as XFontStructs. The 1.2 XmString drawing functions use the new X11R5 internationalized Xlib text drawing functions when they encounter an XFontSet in an XmFontList. In this way, text display in all Motif widgets except Text and TextField has been internationalized. Note that XmStrings can only be created from multi-byte strings; there is no wide-character XmString creation function.

The XmString and XmFontList API have changed; see below.

Text Input

The Text and TextField widgets have been extensively re-written in Motif 1.2 to support internationalized text input and output based on the X11R5 mechanisms. Fortunately, the complexity of Xlib internationalization is almost entirely hidden by these widgets.

The text widgets have a new resource XmNvalueWcs which expects a wide-character string value, and also support a number of new callbacks which return text in wide-character form. The existing resources and callbacks remain to support multi-byte strings. Programmers may use whichever style is more convenient for their application.

Much of the interaction with the X Input Method is handled by the Motif VendorShell widget. It supports two new resources, XmNinputMethod and XmNpreeditType, which specify locale modifiers with which to select the input method and the preedit interaction style desired. When input method preedit or status information must be displayed in “off-the-spot” style at the bottom of the application window, the VendorShell widget provides and manages this space.

Compound Text Conversion

Compound Text is the X Consortium standard for inter-client communication of internationalized text. When performing cut-and-paste or drag-and-drop in an internationalized application, it may be necessary to convert between XmStrings and Compound Text. Motif 1.2 provides 2 new functions to do this: XmCvtXmStringToCT() and XmCvtCTToXmString().

_XmGetLocalized()

Motif 1.2 contains a new function, _XmGetLocalized() intended for use by widget writers to obtain localized equivalents for common strings such as “Ok”, “Cancel”, and “Help”.

Summary

Motif 1.2 makes internationalization nearly transparent. To write an internationalized application with Motif, do the following:

  • Place all strings and pixmaps in resource or UIL files.

  • Specify a generic font set in your XmFontLists.

  • Do not use multiple charset tags in your XmStrings.

  • Call XtSetLanguageProc(NULL, NULL, NULL); to establish locale, before calling XtAppInitialize().

  • Don't expect internationalization to work or to work well on existing generic US platforms. Operating system support for locales and internationalization is also required.



[100] In SunView, many of the basic applications were grouped in a single binary probably for this reason. The Xlib and Xt libraries are quite large. For example, on a Sony NWS-841 workstation, the executable image of a “hello, world” application written with Xt and the Athena widget set uses 450K of disk space. One of the most complicated existing X applications, xterm, uses 600K of disk space on this system. Therefore, the various libraries account for about three-quarters of the disk space used, even for a fairly large program.

[101] For a detailed discussion of Xlib's network optimization and its effects, see the introduction to Volume Zero, X Protocol Reference Manual.

[102] This section was written by Chris Peterson, and originally appeared in The X Resource, Issue 0.