Motif Programming Manual (Volume 6A)

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer

X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation





In this chapter:



Chapter: 25

Compound Strings



This chapter describes Motif's technology for encoding font changes and character directions in the strings that are used by almost all of the Motif widgets.

Compound strings are designed to address two issues frequently encountered by application designers: the use of foreign character sets to display text in other languages and the use of multiple fonts to render text. With the addition of internationalized string rendering capabilities in X11R5 onwards, the use of compound strings for internationalization purposes is theoretically no longer necessary. However, the Motif widget set still uses compound strings extensively, so applications have no choice but to create them to display text.

From Motif 2.0 onwards, the XmFontList is obsolete, and is replaced by the XmRenderTable. RenderTables are fully described in Chapter 24; briefly, a RenderTable describes a complete style by which a compound string can be rendered. In Motif 2.0 onwards, not only can we associate different fonts with a compound string, we can also render the various segments of the string in different colors, underline styles, and so forth. These and other aspects of rendering compound strings are described in the following sections.

Internationalized Text Output

The internationalization features in X11R5 onwards are based on the ANSI-C locale model. Under this model, an application uses a library that reads a customization database at run-time to get information about the user's language environment. An Xt-based application can establish its language environment (or locale) by registering a language procedure with XtSetLanguageProc(), as described in Chapter 18, Text Widgets. The language procedure returns a language string that is used by XtResolvePathname() to find locale-specific resource files. See Volume 4, X Toolkit Intrinsics Programming Manual, for more information on the localization of the resource database.

One of the important characteristics of a language environment is the encoding that is used to represent the character set for the particular language. In X, character set simply refers to a set of characters, while an encoding is a numeric representation of these characters.1 A charset (not the same as a character set) is an encoding in which all of the characters have the same number of bits. Charsets are often defined by standards bodies such as the International Standards Organization (ISO). For example, the ISO Latin-1 charset (ISO8859-1) defines an encoding for the characters used in all Western languages. The first half of Latin-1 is standard ASCII, while the second half (with the eighth bit set) contains accented characters needed for Western languages other than English. Character 65 in ISO Latin-1 is an uppercase "A", while 246 is a lowercase "o" with an umlaut (ö).

However, not all languages can be represented by a single charset. Japanese text commonly contains words written using the Latin alphabet, as well as phonetic characters from the katakana and hirigana alphabets, and ideographic kanji characters. Each of these character sets has its own charset; the phonetic and Latin charsets are 8-bits wide, while the ideographic charset is 16-bits wide. The charsets must be combined into a single encoding for Japanese text, so the encoding uses shift sequences to specify the character set for each character in a string.

Strings in an encoding that contains shift sequences and characters with non-uniform width can be stored in a standard NULL-terminated array of characters; this representation is known as a multibyte string. Strings can also be stored using a wide-character type in which each character has a fixed size and occupies one array element in the string. The text output routines in X11R5 support both multibyte and wide-character strings. To support languages that use multiple charsets, X developed the XFontSet abstraction for its text output routines. An XFontSet contains all of the fonts that are needed to display text in the current locale. The new text output routines work with font sets, so they can render text for languages that require multiple charsets. See Volume 1, Xlib Programming Manual, for more information on internationalized text output.

With the addition of these features in X, a developer can write an internationalized application without using the internationalization features provided by compound strings. In an internationalized application, strings are interpreted using the encoding for the current locale. To support a number of locales, the application needs to store string data in separate files from the application code. The application must provide a separate file for each of the locales supported, so that the program can read the appropriate file during localization.

However, since most Motif widgets use compound strings for representing textual data, a Motif application has to use compound strings to display text. As we describe compound strings in this chapter, we'll discuss how to use them so as not to interfere with the lower-level X internationalization features. However, since Rendition objects in Motif 2.x only refer to fonts in the form of an X FontStruct or X FontSet, the toolkit is rather more compatible with the X11R5 internationalization features than previous versions of the toolkit, and so this is less of an issue.

Creating Compound Strings

Almost all of the Motif widgets use compound strings to specify textual data. Labels, PushButtons, and Lists, among others, all require their text to be given in compound string format, whether or not you require the additional flexibility compound strings provide. The only widgets that don't use compound strings are the Text and TextField widgets2. As a result, you cannot use the compound string techniques for displaying text using multiple fonts. However, these widgets do support internationalized text output, so they can display text using multiple character sets. For information on the internationalization capabilities of the Text and TextField widgets, see the Text Widget Internationalization Section of Chapter 18.

A compound string (XmString) is made of three components: a tag, a direction, and text. The tag is an arbitrary name that the programmer can use to associate a compound string with particular rendition data. In Motif 1.1, the tag was referred to as a character set. Since the tag doesn't necessarily specify a character set, Motif 1.2 referred to the entity as a font list tag. In Motif 2.0 and later, the font list is obsolete, and has been replaced with the render table. A render table consists of rendition objects: the tag now refers to the name assigned to an individual rendition object. One of the things which a rendition object contains is a font. Renditions and Render Tables can be shared between, and inherited from, other widgets.

An application can create a compound string that uses multiple fonts by concatenating separate compound strings with different rendition tags to produce a single compound string. Concatenating compound strings with different renditions is a powerful way to create graphically interesting labels and captions. More importantly, because renditions and render tables are loosely bound to compound strings via resources, you can dynamically associate new renditions with a widget while an application is running and effectively change text styles on the fly.


The Simple Case

Many applications only need to use compound strings to specify various textual resources. In this case, all that is needed is a routine that converts a standard C-style NULL-terminated text string into a compound string. The most basic form of conversion can be done using the XmStringCreateLocalized() function, as demonstrated in examples throughout this book. This routine takes the following form:

XmString XmStringCreateLocalized (char *text)
The text parameter is a common C char string. The value returned is of type XmString, which is an opaque type to the programmer.

XmStringCreateLocalized() creates a compound string in the current locale, which is specified by the tag XmFONTLIST_DEFAULT_TAG. This routine interprets the text string in the current locale when creating the compound string. If you are writing an internationalized application that needs to support multiple locales, you should use XmStringCreateLocalized() to create compound strings. The routine allows you to take advantage of the lower-level internationalization features of X.

Most applications specify compound string resources in resource files. This technique is appropriate for an internationalized application, as there can be a separate resource file for each language environment that is supported. Motif automatically converts all strings that are specified in resource files into compound strings using the tag XmFONTLIST_DEFAULT_TAG, so the strings are handled correctly for the current locale. If an application needs to create a compound string programmatically, it should use XmStringCreateLocalized() to ensure that the string is interpreted in the current locale. The examples in other chapters of this book use XmStringCreateLocalized() to demonstrate the appropriate technique, even though the examples are only designed to work in the C locale.

With XmStringCreateLocalized(), you cannot explicitly specify the tag or the string direction that is used for the compound string, and in Motif 1.2, the string cannot have multiple lines.

XmStringCreateLocalized() allocates memory to store the compound string that is returned. Widgets that have compound string resources always allocate their own space and store copies of the compound string values you give them. When you are done using a compound string to set widget resources, you must free it using XmStringFree(). The following code fragment demonstrates this usage:

XmString   str = XmStringCreateLocalized ("Push Me");
Widget     push_b;
Arg        args[...];
int        n = 0;
...
XtSetArg (args[n], XmNlabelString, str); n++;
push_b = XmCreatePushButton (parent, "widget_name", args, n);
XmStringFree (str);
...
The process of creating a compound string, setting a widget resource, and then freeing the string is the most common use of compound strings. However, this process involves quite a bit of overhead, as memory operations are expensive. Memory is allocated by the string creation function and again by the internals of the widget for its own storage, and then your copy of the string must be deallocated.

The programmatic interface to the string creation process can also be achieved by using the XtVaTypedArg feature in Xt. This special resource can be used in variable argument list specifications for functions such as XtVaCreateManagedWidget() and XtVaSetValues(). It allows you to specify a resource using a convenient type and have Xt do the conversion for you. In the case of compound strings, we could use this method to convert a C string to a compound string. The following code fragment has the same logical effect as the previous example:

push_b = XtVaCreateManagedWidget ( "widget_name",
                                   xmPushButtonWidgetClass,
                                   parent,
                                   XtVaTypedArg, XmNlabelString,
                                   XmRString, "Push Me", strlen ("Push Me") + 1,
                                   NULL);
XtVaTypedArg takes four additional parameters: the name of the resource, the type of the value specified for the resource, the value itself, and the size of the value. We set the XmNlabelString resource. We want to avoid converting the character string to a compound string, so we specify a char * value and XmRString as its type.3The string "Push Me" is the string value; the length of the string, including the NULL-terminating byte, is 8.

The XtVaTypedArg method for specifying a compound string resource is only a programmatic convenience; it does not save time or improve performance. The three-step process of creating, setting, and freeing the compound string still takes place, but it happens within Motif's compound string resource converter. Using automatic conversion is actually slower than converting a string using XmStringCreateLocalized(). However, unless you are creating hundreds of strings, the difference may well be negligible.4

The reason none of the examples in this book do not make use of the feature is that we are trying to demonstrate good programming techniques tuned to a large-scale, production-size, and quality application. Using the XtVaTypedArg method for compound strings is painfully slow when repeated over hundreds of Labels, PushButtons, Lists, and other widgets. The XtVaTypedArg method is perfectly reasonable for converting other types of resources, however. If you are converting a lot of values from one type to another, it is in your own best interest to evaluate the conversion process yourself by testing the automatic versus the manual conversion methods.


Rendition Tags

Motif provides compound string creation routines that allow you to specify a tag used to associate the compound string with a rendition. This tag is a programmer-specified identifier that enables a Motif widget to pick its rendition from a render table at run-time5.

The XmStringCreate() routine allows you to specify a rendition tag. The routine takes the following form:

XmString XmStringCreate (char *text, char *tag)
This routine creates and allocates a new compound string and associates the tag parameter with that string. As with any compound string, be sure to free it with XmStringFree() when you are done using it.

XmStringCreate() creates a compound string that has no specified direction. The default direction of a string may be taken from the XmNlayoutDirection resource6. This resource is defined by manager widgets; it specifies the layout and string direction for all the children of the manager. If the default direction is not adequate, XmStringDirectionCreate() can be used to create a compound string with an explicit direction, as we'll discuss shortly.7

As of Motif 2.0, XmStringGenerate() is now the preferred routine for creating simple compound strings. This routine can convert either single or multi-byte character strings to compound string format. The routine is defined as follows:

XmString XmStringGenerate ( XtPointer        text,
                            XmStringTag      tag,
                            XmTextType       type,
                            XmStringTag      rendition)
The text parameter is the input string to be converted. This can be either single or multi-byte data, the type parameter will inform the function of the supplied text type. type can be either XmCHARSET_TEXT or XmMULTIBYTE_TEXT. The tag parameter is simply the name to be associated with the created compound string: if this is NULL, the default value XmFONTLIST_DEFAULT_TAG is used. If the rendition parameter is not NULL, the compound string which is returned is enclosed within matching XmSTRING_RENDITION_BEGIN and XmSTRING_RENDITION_END string components: this is the way that a compound string is created such that it is associated with a particular named rendition from the widgets' render table. The following code fragment is the simple case: convert a string to a compound string without explicitly hard-coding a rendition association:

XmString xms = XmStringGenerate ( (XtPointer) "Hello World",
                                  NULL,
                                  XmCHARSET_TEXT,
                                  NULL);
The actual rendition, and hence the font or font set, that is associated with the compound string is dependent on the widget that renders the string. Every Motif widget that displays text has an XmNrenderTable resource. This resource specifies a list of rendition objects for the widget; each entry in the list may have an optional tag associated with it. For example, a resource file might specify a render table as follows:

*renderTable: renditionA, renditionB, renditionC
*renditionA.fontName: -*-courier-*-r-*--*-120-*
*renditionA.fontType: FONT_IS_FONT
*renditionB.fontName: -*-courier-*-r-*--*-140-*
*renditionB.fontType: FONT_IS_FONT
*renditionC.fontName: -*-courier-*-r-*--*-180-*
*renditionC.fontType: FONT_IS_FONT
At run-time, the compound string is rendered using the rendition in the widget's render table that matches the tag specified in the compound string creation function. In Motif 1.2 onwards, the compound string rendering functions use the X11R5 text output functions, so compound strings are displayed appropriately for the current locale.

In Motif 1.2, if Motif cannot find a match, the compound string is rendered using the first item in the widget's font list, regardless of its tag. In Motif 2.0 and later, if no matching rendition can be found, the XmDisplay object XmNnoRenditionCallback is invoked. This callback, if specified, can be used to supply the missing rendition information. The XmNnoRenditionCallback is discussed in Chapter 24, Render Tables. After invocation of this callback, if no matching or supplied rendition can still be found, the first rendition in the render table which has the default tag XmFONTLIST_DEFAULT_TAG is used to render the compound string, provided it has a font. If this default rendition also has no font, the compound string is not drawn, and a warning message is displayed.

This loose binding between the compound string and the rendition (and hence the font or font set) used to render it is useful in a number of ways:

The same compound string can be rendered using different fonts in different widgets simply by specifying a different render table for each widget. For example:

*XmPushButton.renderTable: renditionA
*XmPushButton*renditionA.fontName: -*-courier-*-r-*--*-120-*
*XmList.renderTable: renditionA
*XmList*renditionA.fontName: -*-helvetica-*-r-*--*-120-*
Compound strings rendered in different fonts can be concatenated to create a multi-font compound string. The font for each segment is selected from the renditions within the widget's render table by means of a unique tag.

Compound strings can be language-independent, with the tag used to select between fonts with different character set encodings. This is the least common use for compound strings, and as of X11R5, it is no longer needed to support internationalized text output.

Example 25-1 demonstrates how a compound string can be rendered using different fonts in different PushButton widgets.8

Example  25-1 The string.c program

/* string.c -- create a compound string with the "MY_TAG" tag.
** The tag defaults to the "9x15" font. Create three pushbuttons:
** pb1, pb2, and pb3. The user can specify resources so that each of the
** widgets has a different font associated with the "MY_TAG" tag
** specified in the compound string.
*/

#include <Xm/RowColumn.h>
#include <Xm/PushBG.h>

String fallbacks[] = { "*MY_TAG.fontName:9x15", NULL };

main (int argc, char *argv[])
{
    Widget         toplevel, rowcol, push_b;
    XtAppContext   app;
    XmString       text;
    Display        *dpy;
    Arg            args[2];

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtVaOpenApplication (&app, "String", NULL, 0, &argc, argv, 
                                fallbacks, sessionShellWidgetClass, NULL);

    rowcol = XmCreateRowColumn (toplevel, "rowcol", NULL, 0);

    text = XmStringGenerate ( (XtPointer) "Testing, testing...",
                              XmFONTLIST_DEFAULT_TAG,
                              XmCHARSET_TEXT,
                              "MY_TAG");

    XtSetArg (args[0], XmNlabelString, text);
    push_b = XmCreatePushButtonGadget (rowcol, "pb1", args, 1);
    XtManageChild (push_b);
    push_b = XmCreatePushButtonGadget (rowcol, "pb2", args, 1);
    XtManageChild (push_b);
    push_b = XmCreatePushButtonGadget (rowcol, "pb3", args, 1);
    XtManageChild (push_b);
    XmStringFree (text);

    XtManageChild (rowcol);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
This simple program creates three PushButton gadgets that all use the same compound string for their labels. The rendition tag MY_TAG is associated with the 9x15 font in the fallback resources. By default, all of the buttons look the same, as shown in Figure 25-1.

Figure  25-1 Output of string program

However, Figure 25-2 shows what happens to the output when the following resources are specified:

*.renderTable: MY_TAG
*.fontType: FONT_IS_FONT
*pb1.*MY_TAG.fontName: -*-courier-*-r-*--*-120-*
*pb2.*MY_TAG.fontName: -*-courier-*-r-*--*-140-*
*pb3.*MY_TAG.fontName: -*-courier-*-r-*--*-180-*

Figure  25-2 Output of string program with rendition resources set

The rendition associated with MY_TAG for each of the PushButtons is different, so the compound string for each one is rendered in a different font. Since each render table only contains one rendition, Motif has no choice but to attempt to display the compound string using the font associated with the single rendition. The following resource specification creates the output shown in Figure 25-3:

*.fontType: FONT_IS_FONT
*.renderTable: MY_TAG

*pb1*.fontName: -*-courier-*-r-*--*-120-*
*pb2*.renderTable: fixed_rendition, courier_rendition
*pb2.fixed_rendition.fontName: fixed
*pb2.courier_rendition.fontName: -*-courier-*-r-*--*-140-*
*pb3*MY_TAG.fontName: fixed,-*-courier-*-r-*--*-180-*

Figure  25-3 Output of string program with multiple rendition resources set

In this case, the compound string in the first PushButton uses a 12-point Courier font, since that is the only rendition in the render table, it has the implicit tag _MOTIF_DEFAULT_LOCALE assigned to it, and this is used as the default. The third button uses the 18-point Courier font associated with MY_TAG. The second PushButton does not render the compound string label because there is no rendition with a matching tag, and no default unnamed rendition. Motif prints out a warning message:

Warning: No font found.
This behavior is different to that of Motif 1.2. In Motif 1.2, the toolkit would use the first font if the compound string contained a tag which did not match any of the fonts in the XmNfontList resource of the widget concerned. In Motif 2.0 and later, the programmer must supply a default font; the XmDisplay object XmNnoFontCallback is called whenever this situation arises, and the programmer can supply the missing font information in the callback. The XmNnoFontCallback procedure is discussed in Chapter 24, Render Tables.


The Default Tag

The constant XmFONTLIST_DEFAULT_TAG is used to tag compound strings that are created in the encoding of the current locale. When a compound string is created using XmStringCreateLocalized(), this tag is used. The equivalent compound string can also be created using XmStringCreate() with the tag explicitly set to XmFONTLIST_DEFAULT_TAG. In Motif 2.0 and later, the compound string can also be created using XmStringGenerate(), with the tag either explicitly set to XmFONTLIST_DEFAULT_TAG, or implicitly by setting the tag to NULL. Motif looks for a rendition with a matching tag when it renders the compound string. In Motif 2.0 and later, a rendition which is created with a NULL tag is assigned the tag _MOTIF_DEFAULT_LOCALE, and this will match with a compound string component which has the tag XmFONTLIST_DEFAULT_TAG.

An internationalized application can use XmFONTLIST_DEFAULT_TAG to ensure that compound strings are rendered correctly for the current locale. However, it is possible to use explicit rendition tags for locale-specific text. Explicit tags are necessary when an application wants to display compound strings using different point sizes or type styles. In this case, the compound string and the renditions associated with it need to use the same tag; the tag can be mapped to XmFONTLIST_DEFAULT_TAG using XmRegisterSegmentEncoding().

In Motif 1.1, the first font in widget's font list is the default character set for that widget. If the widget does not have a font list, it uses a default character set referred to by the constant XmSTRING_DEFAULT_CHARSET. If the user has set the LANG environment variable, its value is used for this character set. If this value is invalid or its associated font cannot be used, Motif uses the value of XmFALLBACK_CHARSET, which is vendor-defined but typically set to "ISO8859-1".

For backwards compatibility, Motif 1.2 equated XmFONTLIST_DEFAULT_TAG with XmSTRING_DEFAULT_CHARSET when it could not find an exact match between a compound string and a font list. XmFONTLIST_DEFAULT_TAG in a compound string or font list matched the tag used in creating a compound string or specified in a font list entry with the tag XmSTRING_DEFAULT_CHARSET.

Again for backwards compatibility, in Motif 2.0 and later, renditions with the tag _MOTIF_DEFAULT_LOCALE match against compound string components whose tag is XmFONTLIST_DEFAULT_TAG. A rendition created with a NULL tag is assigned the default tag _MOTIF_DEFAULT_LOCALE.


RenderTable Resources

Some Motif widgets define rendition resources that allow them to provide a consistent appearance for all of their children.

In Motif 1.2, the VendorShell widget defined the XmNbuttonFontList, XmNlabelFontList, and XmNtextFontList resources, while the MenuShell defined XmNbuttonFontList and XmNlabelFontList. These resources referred to XmFontList data, and were applied to all of the buttons, Labels, and Text widgets that are descendents of the widget. In Motif 1.1, the VendorShell and MenuShell only defined the XmNdefaultFontList resource; this resource applied to all of the children of the widget. For backwards compatibility, if one of the more specific font list resources was not set, its value was taken from XmNdefaultFontList.

In Motif 2.0 and later, the resources XmNbuttonFontList, XmNlabelFontList, XmNtextFontList, and XmNdefaultFontList are deprecated, and have been replaced by the resources XmNbuttonRenderTable, XmNlabelRenderTable, and XmNtextRenderTable. There is no putative XmNdefaultRenderTable defined by the MenuShell or VendorShell classes.

The BulletinBoard widget defines the resources XmNbuttonRenderTable, XmNlabelRenderTable, and XmNtextRenderTable primarily for use in dialog boxes. These render tables apply to the buttons, Labels, and Text widgets that descend from a BulletinBoard. For more information on the use of the resources in dialog boxes, see Chapter 5, Introduction to Dialogs.

All of these render table resources are designed to help you maintain a consistent interface. However, you can always specify the font for a particular button, Label, or Text widget using the widget's XmNrenderTable resource, as this resource overrides the more general ones.


Compound String Segments

A compound string is composed of segments, where each segment contains a continuous sequence of text with no change in rendition tag or direction. A compound string segment can be terminated by a separator, which is the equivalent of a newline in a character string.9Segments can be concatenated with other segments or compound strings to create longer strings; each segment can specify a different tag and direction to make a string that uses multiple fonts and directions.

XmStringComponentCreate() provides complete control over the creation of a compound string, as it allows you to specify individual text, rendition tag, tab, and direction components which can be concatenated together into a whole10. In Motif 2.0 and later, Tab segments can also be added to a compound string; these can be used to create a multi-columnar arrangement, and is typically used by the List widget. The routine takes the following form:

XmString XmStringComponentCreate ( XmStringComponentType     type,
                                   unsigned int              length,
                                   XtPointer                 value)

String Directions

Compound strings are rendered either from left-to-right or from right-to-left. If you are going to use left-to-right strings uniformly in your applications, you really don't need to read this section.There are several ways to build a compound string that is rendered from right-to-left; the best method is dependent on the nature of your application.

If your application uses right-to-left strings for all of its widgets, you may want to use the XmNlayoutDirection resource11. This resource specifies the layout of the component in general terms: for Primitive widgets, it also specifies the direction for compound strings, provided that the string direction is not hard-coded in the compound strings themselves. If you use this resource, you can continue to use XmStringCreate() or XmStringCreateLocalized() to create compound strings.

Most right-to-left languages display certain things, like numbers, from left-to-right, so it is not always possible to use the XmNlayoutDirection resource. In this case, you have to create compound string segments that hard-code their directional information.You can create individual string segments with a specific direction by using either XmStringDirectionCreate() or XmStringComponentCreate(). Both of these routines take an argument of type XmStringDirection, which is defined as an unsigned char. You can specify either XmSTRING_DIRECTION_R_TO_L, XmSTRING_DIRECTION_L_TO_R, or XmSTRING_DIRECTION_DEFAULT for values of this type.

When using XmStringComponentCreate(), you specify the string direction using the value parameter. For example, we can change the call to XmStringCreate() in Example 25-1 to the following:

XmStringDirection direct = XmSTRING_DIRECTION_R_TO_L;
XmString dir = XmStringComponentCreate ( XmSTRING_COMPONENT_DIRECTION,
                                         sizeof (XmStringDirection),
                                        (XtPointer) &direct);
char *cptr = "Testing, testing";
XmString text = XmStringComponentCreate ( XmSTRING_COMPONENT_TEXT,
                                          strlen (cptr),
                                          (XtPointer) cptr);
XmString xms = XmStringConcatAndFree (dir, text);
Obviously, you would normally do this only if you were using a font that was meant to be read from right-to-left, such as Hebrew or Arabic. The output that results from this change is shown in Figure 25-4.

Figure  25-4 Output of string program using a right-to-left direction

You can also use the function XmStringDirectionCreate() to create a compound string segment that contains only directional information. This routine takes the following form:

XmString XmStringDirectionCreate (XmStringDirection direction)
The routine returns a compound string segment that can be concatenated with another compound string to cause a directional change.


String Separators

Separators are used to break compound strings into multiple lines, in much the same way that a newline character does in a character string. To demonstrate separators, we can change the string creation line in Example 25-1 to the following:

text = XmStringGenerate ( (XtPointer) "Testing,\nTesting...",
                           XmFONTLIST_DEFAULT_TAG,
                           XmCHARSET_TEXT,
                           "MY_TAG");
We can use XmStringGenerate() because this function interprets embedded newline characters (\n) as separators. The effect of this change is shown in Figure 25-5, where the PushButtons display multiple lines of text.

Figure  25-5 Output of string program with multiple lines

XmStringCreate() does not interpret newline characters as separators; it creates a single compound string segment in which the '\n' is treated just like any other character value in the associated font or font set, as shown in Figure 25-6. XmStringComponentCreate(), however, can be told to create a separator which can be embedded into a larger compound string.

Figure  25-6 Output of string program with \n not interpreted as a separator

Most applications need newline characters to be interpreted as separators. For example, if you are using fgets() or read() to read the contents of a file, and newlines are read into the buffer, you should use XmStringGenerate() to convert the buffer into a compound string that contains separators. Example 25-2 shows a function that reads the contents of a file into a buffer and then converts that buffer into a compound string.12

Example  25-2 The ConvertFileToXmString() routine.

XmString ConvertFileToXmString (char *filename, int *lines)
{
    struct stat    statb;
    int            fd, len;
    char           *text;
    XmString       str;

    *lines = 0;

    if (!(fd = open (filename, O_RDONLY))) {
        XtWarning ("Internal error -- can't open file");
        return NULL;
    }

    if (fstat (fd, &statb) == -1 ||
                    !(text = XtMalloc ((len = statb.st_size) + 1)))
    {
        XtWarning ("Internal error -- can't show text");
        close (fd);
        return NULL;
    }

    (void) read (fd, text, len);
    text[len] = 0;

    str = XmStringGenerate ((XtPointer) text,
                            XmFONTLIST_DEFAULT_TAG,
                            XmCHARSET_TEXT,
                            NULL);
    XtFree (text);
    (void) close (fd);

    *lines = XmStringLineCount (str);
    return str;
}
Since separators are considered to be line breaks, we can count the number of lines in the compound string using the function XmStringLineCount(). However, this does not imply that separators terminate compound strings or cause font changes. As we have shown, a separator can be inserted into the middle of a compound string without terminating it. The fact that separate segments are created has little significance unless you need to convert compound strings back into C strings, which we discuss in the Compound String Conversion Section later in this chapter.


Multiple-font Strings

Once multiple renditions with distinctive tags are specified in a render table, you can use the list to display more than one font or font set in a single compound string. You can create a multi-font string in one of two ways: create the compound text in segments or create separate compound strings. Either way, once the segments or strings have been created, they must be concatenated together to form a new compound string that has font-change information embedded in it. Example 25-3 demonstrates the creation of a compound string that uses three fonts.13

Example  25-3 The multi_font.c program

/* multi_font.c -- create three compound strings using 12, 14 and 18
** point fonts. The user can specify resources so that each of the strings
** use different fonts by setting resources similar to that shown
** by the fallback resources.
*/

#include <Xm/Label.h>

String fallbacks[] = {
    "*.renderTable: TAG1, TAG2, TAG3",
    "*.fontType: FONT_IS_FONT",
    "*TAG1.fontName: -*-courier-*-r-*--*-120-*",
    "*TAG2.fontName: -*-courier-bold-o-*--*-140-*",
    "*TAG3.fontName: -*-courier-medium-r-*--*-180-*",
    NULL
};

main (int argc, char *argv[])
{
    Widget         toplevel, label;
    XtAppContext   app;
    XmString       s1, s2, s3, text, tmp;
    Arg            args[2];
    String         string1 = "This is a string ",
                   string2 = "that contains three ",
                   string3 = "separate fonts.";

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtVaOpenApplication (&app, "String", NULL, 0, &argc, argv, 
                                fallbacks, sessionShellWidgetClass, NULL);
    s1 = XmStringGenerate ((XtPointer) string1, NULL,
                                            XmCHARSET_TEXT, "TAG1");
    s2 = XmStringGenerate ((XtPointer) string2, NULL,
                                            XmCHARSET_TEXT, "TAG2");
    s3 = XmStringGenerate ((XtPointer) string3, NULL,
                                            XmCHARSET_TEXT, "TAG3");

    /* concatenate the 3 strings on top of each other, but we can only
    ** do two at a time. So do s1 and s2 onto tmp and then do s3.
    */
    tmp = XmStringConcatAndFree (s1, s2);
    text = XmStringConcatAndFree (tmp, s3);
    XtSetArg (args[0], XmNlabelString, text);
    label = XmCreateLabel (toplevel, "widget_name", args, 1);
    XtManageChild (label);

    XmStringFree (text);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
The output of this program is shown in Figure 25-7.

Figure  25-7 Output of multi_font program

The XmNrenderTable resource is specified using three renditions, each with a distinct tag. We create each string using XmStringGenerate() with the appropriate text and tag. Then we concatenate the strings using XmStringConcatAndFree(), two at a time until we have a single compound string that contains all the text. XmStringConcatAndFree() does not work like strcat() in C. The Motif function creates a new compound string that is composed of the two existing strings, rather than appending one string to the other string.

It is possible to specify compound string resource values, such as the XmNlabelString resource of the Label widget, in a resource file as normal C strings. Motif provides a resource converter that converts the character string into a compound string. However, this resource converter does not allow you to specify rendition tags in the character string. If you need font changes within a compound string, you need to create the compound strings explicitly in your application as we have done in Example 25-3.

Manipulating Compound Strings

Most C programmers are used to dealing with functions such as strcpy(), strcmp(), and strcat() to copy, compare, and modify strings. However, these functions do not work with compound strings, as they are not based on a byte-per-character format, and they may have NULL characters as well as other types of information embedded in them. In order to perform these common tasks, you can either convert the compound string into a character string, or you can use the compound string manipulation functions provided by Motif. The method you choose depends largely on the complexity of the compound strings you have and/or the complexity of the manipulation you need to do.


Compound String Functions

Motif provides a number of functions that allow you to treat compound strings in much the same way that you treat C-style character arrays. The toolkit provides the following routines:

Boolean XmStringByteCompare (XmString string1, XmString string2)
Boolean XmStringCompare (XmString string1, XmString string2)
XmString XmStringConcat (XmString string1, XmString string2)
XmString XmStringConcatAndFree (XmString string1, XmString string2)
XmString XmStringCopy (XmString string)
Boolean XmStringEmpty (XmString string)
Boolean XmStringHasSubstring (XmString string, XmString substring)
Boolean XmStringIsVoid (XmString string)
int XmStringLength (XmString string)
Both XmStringCompare() and XmStringByteCompare() compare two compound strings, string1, and string2. XmStringCompare() simply checks if the strings have the same text components, directions, and separators; it returns True if they do. This routine is simpler and more frequently used than XmStringByteCompare(), which performs a byte-by-byte comparison of the two compound strings. If each string uses the same rendition tags, has the same direction, and contains the same embedded char string internally, the function returns True. The mapping between rendition tags and fonts does not happen until a compound string is rendered by a widget, so whether or not the same rendition tag actually maps to two different fonts does not affect the results of this function.

XmStringConcat() and XmStringConcatAndFree() can be used to concatenate compound strings. Both of these routines create a new compound string and copy the concatenation of string1 and string2 into the newly allocated string. With XmStringConcat(), the original strings are preserved, but with XmStringConcatAndFree() the original parameter strings are internally freed. In both cases, you are responsible for freeing the compound string returned by the routines using XmStringFree()14.

You can copy a compound string using XmStringCopy(), which copies the parameter string into a newly-allocated compound string.15

XmStringHasSubstring() determines whether or not a compound string contains a particular substring. For this function, substring must be a single-segment compound string. If its text is completely contained within any single segment of string, the function returns True. The two strings must use the same rendition tags for the routine to return True.

To get the length of a compound string, use XmStringLength(). This function returns the number of bytes in the compound string including all tags, direction indicators, and separators. If the structure of string is invalid, the routine returns zero. This function cannot be used to get the length of the text represented by the compound string; it is not the same as strlen()).

You can determine whether or not a compound string contains any segments using XmStringEmpty(). This function returns True if there are no segments in the specified string and False otherwise. If the routine is passed NULL, it returns True. The function XmStringIsVoid() is similar, except that it checks only for text, separator, and tab components16. Therefore testing a compound string which contains only direction indicators using XmStringEmpty() will return False, whereas XmStringIsVoid() will return True.


Compound String Retrieval

You can retrieve a compound string from a Motif widget using XtVaGetValues(). However, the way XtVaGetValues() is used for compound string resources is different than how it is used for most other resources. The value returned by XtVaGetValues() for a compound string resource is a copy of the internal data, so the compound string must be freed by the application, as shown in the following code fragment:

XmString         str;
extern Widget    pushbutton;
char             *text;

XtVaGetValues (pushbutton, XmNlabelString, &str, NULL);
...
/* do whatever you want with the compound string */
...
XmStringFree (str); /* must free compound strings from GetValues */
To avoid memory leaks in your application, you must remember to free any compound strings that you retrieve from a widget using XtVaGetValues(). You free a compound string using the routine XmStringFree().


Compound String Conversion

If the Motif routines described in the previous section are inadequate for your needs, you can convert compound strings back into C strings and manipulate them using the conventional C functions. This process can be simple or complicated depending on the complexity of the compound string to be converted.

In Motif 1.2, there were two basic methods of conversion. The simplest method was to use the routine XmStringGetLtoR(), which is sufficient if the compound string has a single tag associated with it, and has a left-to-right orientation. The more complex method is to walk along the compound string one segment at a time. This technique is rather low level, and requires the creation of a type, XmStringContext, that is used to identify and maintain the position within the compound string being scanned. This is described below.

In Motif 2.0 and later, XmStringGetLtoR() is deprecated. In its place is the notion of a Parse Table, whereby compound strings can be converted using a table-driven description of the requisite conversion process. This is covered in the Parse Tables Section later in this chapter.


The XmStringContext

To cycle through a compound string, you need to use the following sequence of operations:

  1. Initialize a string context for the compound string using XmStringInitContext().
  2. Iterate through the string by calling XmStringGetNextTriple() to get the type, length, and value associated with each segment17.
  3. Free the string context using XmStringFreeContext().
XmStringInitContext() initializes a string context that allows an application to read the contents of a compound string segment by segment. This routine takes the following form:

Boolean XmStringInitContext (XmStringContext *context, XmString string)
The function allocates a new XmStringContext type and sets the pointer that is passed by the calling function in the context parameter to this data. If the allocation is successful and the compound string is valid, the function returns True.

Once the string context has been initialized, the contents of the string can be scanned using XmStringGetNextTriple():

XmStringComponentType XmStringGetNextTriple ( XmStringContext    context,
                                              unsigned int       *length,
                                              XtPointer          *value)
The routine does not take an XmString parameter because the context parameter is used to keep track of the compound string. The function reads the next segment; the values for length, value are filled in, and the function returns the type of the new segment. The XmStringComponentType parameter is an enumerated type, and has the following possible values:18

XmSTRING_COMPONENT_CHARSET
XmSTRING_COMPONENT_TEXT
XmSTRING_COMPONENT_LOCALE_TEXT
XmSTRING_COMPONENT_WIDECHAR_TEXT
XmSTRING_COMPONENT_DIRECTION
XmSTRING_COMPONENT_SEPARATOR
XmSTRING_COMPONENT_TAB
XmSTRING_COMPONENT_LAYOUT_PUSH
XmSTRING_COMPONENT_LAYOUT_POP
XmSTRING_COMPONENT_RENDITION_BEGIN
XmSTRING_COMPONENT_RENDITION_END
XmSTRING_COMPONENT_UNKNOWN
XmSTRING_COMPONENT_END
When you are through scanning the compound string, you need to free the string context using XmStringFreeContext(), which takes the following form:

void XmStringFreeContext (XmStringContext context)
Example 25-4 shows a routine that uses these functions to print a compound string used as the label for a widget.

Example  25-4 The PrintLabel() routine

void PrintLabel (Widget widget)
{
    XmString                 str;
    XmStringContext          context;
    char                     *text, *p, buf[256];
    XtPointer                data;
    unsigned int             length;
    XmStringComponentType    type;

    XtVaGetValues (widget, XmNlabelString, &str, NULL);

    if (!XmStringInitContext (&context, str)) {
        /* compound strings from GetValues still need to be freed! */    
        XmStringFree (str);
        XtWarning ("Can't convert compound string.");
        return;
    }

    /* p keeps a running pointer through buf as text is read */
    p = buf;
    while ((type = XmStringGetNextTriple (context, &length, &data)) 
                                            != XmSTRING_COMPONENT_END) {
        switch (type) {
            case XmSTRING_COMPONENT_TEXT :
                text = (char *) data;
                (void) strcpy (p, text);
                p += length;
                XtFree ((char *) data);
                break;
            case XmSTRING_COMPONENT_TAB :
                *p++ = '\t';
                *p = 0;
                break;
            case XmSTRING_COMPONENT_SEPARATOR :
                *p++ = '\n';
                *p = 0;
                break;
        }
    }

    XmStringFreeContext (context);
    XmStringFree (str);

    printf ("Compound string:\n%s\n", buf);
}

Parse Tables

From Motif 2.0, strings can be converted to and from compound strings by means of table-driven mappings. A Parse Table consists of Parse Mapping objects. What a Parse Mapping defines is a small piece of the conversion process in the form of a match pattern, and a substitution method. Whenever a sequence of input corresponds to the match pattern it is replaced using the substitution specified in the mapping object.

To be more concrete, a string containing newline characters can be mapped to a compound string by supplying a parse mapping which has a match pattern of the newline character, and a substitution pattern that is a compound string consisting of a segment of type XmSTRING_COMPONENT_SEPARATOR. The internal implementation of the toolkit convenience function XmStringGenerate() does precisely this: it uses the lower level parse mapping routines, supplying a default parse table that has mappings for newlines and tabs.

You dont need to specify a large Parse Table for most operations. The default mechanisms convert text directly into textual compound string components without the need for the programmer to specify Parse Mappings on a character-by-character basis for all of the input. Typically, you only need to create a Parse Mapping when you wish to handle a particular input character specially.

For many conversions, the default internal mappings are entirely sufficient, and so XmStringGenerate() is more than adequate for most tasks: it hides the lower level details of the table-driven parsing process from the programmer. There are however cases where a bespoke converter makes sense. Consider reading a file where the separation between fields is not by tab characters. A typical example of this is the UNIX /etc/passwd file, where fields are separated by colons. If we wanted to read lines of this file into a List widget, where each field is in a distinct column, we have to define a parse mapping which converts colons into compound string tab components because the default internal parse tables do not handle this specific conversion: they simply treat the colon like any other character, so if you use the default mechanisms the colon will appear untranslated in the converted compound string.

Thee two most important procedures for performing table-driven string to compound string conversion (and vice-versa) are XmStringParseText(), and XmStringUnparse(). XmStringParseText() converts a single or multi-byte character stream into a compound string; XmStringUnparse() converts a compound string back into the single or multi-byte character representation.

Where the conversion involves arrays of strings or compound strings, the routines XmStringTableParseStringArray() and XmStringTableUnparse() are provided. These are in fact simple convenience routines which do little more than call XmStringParseText() and XmStringUnparse() iteratively across the array of strings or compound strings.

Examples of all of the above routines will be given in due course. But first, we must define exactly what Parse Mappings and Parse Tables are, and how to create them.


The XmParseMapping Object

A Parse Mapping object is a pseudo-widget; although not a true widget, it is an object which has resources and a resource-style interface. A mapping is created through the routine XmParseMappingCreate(), which takes the following form:

XmParseMapping XmParseMappingCreate (Arg *argList, Cardinal argCount)
The argList parameter is an array of parse mapping resources, argCount being the number of resources in the array. This is directly analogous to the Arg array passed as a parameter to standard widget creation routines. The function returns an opaque handle, an XmParseMapping, used to designate the object created. To create a Parse Table, we simply create an array of these, one for each particular piece of bespoke conversion, and pass the array to the XmStringParseText() or XmStringUnparse() routines as required. An XmParseTable is nothing more than an array of XmParseMapping objects.

Once created, the resources associated with an XmParseMapping object can be modified using the XmParseMappingSetValues() routine. This routine is defined as follows:

void XmParseMappingSetValues ( XmParseMapping     map,
                               Arg                *argList,
                               Cardinal           argCount)
Conversely, we can retrieve the values associated with an XmParseMapping object using the XmParseMappingGetValues() routine:

void XmParseMappingGetValues ( XmParseMapping     map,
                               Arg                *argList,
                               Cardinal           argCount)
When we have finished using the XmParseMapping object, we free it using XmParseMappingFree():

void XmParseMappingFree (XmParseMapping map)

XmParseMapping Resources

The most important attributes of an XmParseMapping object are the XmNpattern, XmNpatternType, XmNsubstitute, and the XmNinvokeParseProc resources. XmNpattern represents the input associated with this XmParseMapping object: it is the data against which the input stream is matched. You specify this to the parse mapping object in the form of a pointer to a character (multi-byte character or wide character, depending on what it is you are trying to convert to a compound string), even though any given parse mapping object concerns itself with converting a single piece of input and not a sequence. If the current input matches the XmNpattern resource, it is replaced in the output stream by either the value of the XmNsubstitute resource, or dynamically constructed by the procedure specified by the XmNinvokeParseProc resource. Which of the two methods is adopted depends upon the value of the XmNincludeStatus resource. If XmNincludeStatus is XmINSERT, the XmNsubstitute resource is used for the transformation; if XmNincludeStatus is XmINVOKE, the XmNinvokeParseProc procedure is used; lastly, if XmNincludeStatus is XmTERMINATE, the conversion process is terminated at that point in the input stream. The XmNinvokeParseProc resource is considered later in this chapter. Remember that the XmNpattern resource requires a pointer to an array as its value, although only the first item in the array is used when performing matching. XmNpattern can specify more than just character arrays: we can parse multi-byte or wide-character input as well. The type of input is specified using the XmNpatternType resource, and it takes the following values:

XmCHARSET_TEXT          XmWIDECHAR_TEXT          XmMULTIBYTE_TEXT
To make this clear, the following code fragment creates a parse mapping for converting colon characters in a simple char * input stream into compound string tab components. Since it is only the colon that we are treating specially, we only need one parse mapping object to convert the entire input to a compound string: everything except the colon is converted using default internal algorithms:

XmParseMapping     map;
XmString           tab;
Arg                args[4];
Cardinal           n = 0;

/* Match against the colon character */
/* Note we specify the string ":" not the character ':' */
XtSetArg (args[n], XmNpattern, ":"); n++;

/* XmNpattern contains a simple character array */
/* so we specify the pattern type as XmCHARSET_TEXT */
XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;

/* Convert to a tab compound string component */
tab = XmStringComponentCreate (XmSTRING_COMPONENT_TAB, 0, NULL);
XtSetArg (args[n], XmNsubstitute, tab); n++;

/* Use the XmNsubstitute resource for conversion */
/* Not the XmNinvokeParseProc resource */
XtSetArg (args[n], XmNincludeStatus, XmINSERT); n++;

/* Create the Parse Mapping object */
map = XmParseMappingCreate (args, n);
The way the parse system works is as follows: each character in the input stream is matched in turn against the XmNpattern resources for each of the mappings in a given XmParseTable. Where there is a match, that mapping is used to perform the translation, either by directly replacing the input with the XmNsubstitute value, or by calling the XmNinvokeParseProc routine to provide the output value. The input stream is advanced by one character (wide-character or multi-byte character, depending on the input stream type), and the process reiterates until either the end of the input is reached, or some parse mapping object specifies XmTERMINATE as the required match action. The concatenation of each chunk of output is performed automatically by the parse process: the parse mapping objects only concern themselves with supplying the individual chunks for a given piece of input.

It is important to stress that we only need to provide mapping objects for special processing, because the default action in the absence of a supplied mapping for some input is a direct conversion to the output. For example, if we wanted to convert lines of /etc/passwd into a compound string, we only need to supply a mapping that specifies the colon character is a logical tab separator. Everything else gets converted implicitly on a simple character-by-character basis.


Converting to Compound Strings

Example 25-5 illustrates a simple string to compound string conversion. It loads lines read from the /etc/passwd file into a multi-column List widget. For details of TabLists used to provide the multi-column layout, you are referred to Chapter 24, Render Tables.

Example  25-5 The parse_file.c program

/* parse_file.c: converts a file into
** a multi-column list format. Assumes that
** the file is colon-separated fields.
*/

#include <stdio.h>
#include <Xm/Xm.h>
#include <Xm/List.h>

/* construct an array of compound strings 
** from loading a file using colon as the field separator.
**
** A more generic routine would pass the field separator in.
*/
XmString *load_file (Widget list, char *file, int *count)
{
    XmParseMapping     map[2];
    FILE               *fptr;
    char               buffer[256];
    Arg                args[8];
    char               *cptr;
    XmString           tab;
    XmString           *xms_array = (XmString *) 0;
    int                xms_count = 0;
    int                n;

    *count = 0;

    if ((fptr = fopen (file, "r")) == (FILE *) 0) {
        return NULL;
    }

    /* Map colons to tabs */
    n = 0;
    tab = XmStringComponentCreate (XmSTRING_COMPONENT_TAB, 0, NULL);
    XtSetArg (args[n], XmNpattern, ":");                n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNsubstitute, tab);             n++;
    XtSetArg (args[n], XmNincludeStatus, XmINSERT);     n++;
    map[0] = XmParseMappingCreate (args, n);

    /* Throw away newlines by terminating the parse for this line */
    n = 0;
    XtSetArg (args[n], XmNpattern, "\n");               n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNincludeStatus, XmTERMINATE);  n++;
    map[1] = XmParseMappingCreate (args, n);

    while ((cptr = fgets (buffer, 255, fptr)) != (char *) 0) {
        xms_array = (XmString *) XtRealloc ((char *) xms_array,
                                (xms_count + 1) * sizeof (XmString));

        xms_array[xms_count] = XmStringParseText (cptr, NULL, NULL,
                                        XmCHARSET_TEXT, map, 2, NULL);

        xms_count++;
    }

    (void) fclose (fptr);

    XmParseMappingFree (map[0]);
    XmParseMappingFree (map[1]);

    *count = xms_count;

    return xms_array;
}

main (int argc, char *argv[])
{
    Widget           toplevel, list;
    XtAppContext     app;
    Arg              args[8];
    XmTabList        tlist = NULL;
    XmRenderTable    rtable;
    XmRendition      rendition;
    XmString         *data = (XmString *) 0;
    int              lines = 0;
    int              n, i;

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtVaOpenApplication (&app, "Demo", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    n = 0;
    XtSetArg (args[n], XmNvisibleItemCount, 10); n++;
    /* For TabList separation calculations */
    XtSetArg (args[n], XmNunitType, XmINCHES);   n++;
    list = XmCreateScrolledList (toplevel, "list", args, n);
    XtManageChild (list);

    /* Load the data from file as an array of compound strings */
    data = load_file (list, ((argc > 1) ? argv[1] : "/etc/passwd"), &lines);

    XtVaSetValues (list, XmNitems, data, XmNitemCount, lines, NULL);

    /* Use the toolkit to propose a tab list for the items */
    /* This isn't ideal but will do for this example */
    tlist = XmStringTableProposeTablist (data, lines, list,
                                        (float) 0.1, XmRELATIVE);
    
    /* Create a render table for the List using the tab list */
    n = 0;
    XtSetArg (args[n], XmNtabList, tlist);                        n++;
    XtSetArg (args[n], XmNfontName, "-*-courier-*-r-*--*-120-*"); n++;
    XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);              n++;
    rendition = XmRenditionCreate (list, XmFONTLIST_DEFAULT_TAG, args, n);
    rtable = XmRenderTableAddRenditions (NULL, &rendition, 1,
                                        XmMERGE_REPLACE);
    XtVaSetValues (list, XmNrenderTable, rtable, NULL);

    /* Free up the temporarily allocated memory */
    XmRenditionFree (rendition);
    XmRenderTableFree (rtable);
    XmTabListFree (tlist);

    for (i = 0; i < lines; i++)
        XmStringFree (data[i]);

    XtFree ((char *) data);

    /* Display the interface */
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
Sample output from the program is given in Figure 25-8.

Figure  25-8 Output of the parse_file program

The interesting part of the program is the routine load_file(). This parses a file into an array of compound strings by constructing two XmParseMapping objects. The first object converts colons into tab components:

    /* Map colons to tabs */
    n = 0;
    tab = XmStringComponentCreate (XmSTRING_COMPONENT_TAB, 0, NULL);
    XtSetArg (args[n], XmNpattern, ":");                n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNsubstitute, tab);             n++;
    XtSetArg (args[n], XmNincludeStatus, XmINSERT);     n++;
    map[0] = XmParseMappingCreate (args, n);
The second XmParseMapping object is used to throw away the trailing newline character returned by the fgets() system call. We could just as easily have truncated the returned string from fgets() to remove the newline, but it was more interesting to demonstrate how to terminate parsing of an input sequence using an XmParseMapping object. Here we simply specify the XmNincludeStatus as XmTERMINATE to throw the newline away:

    /* Throw away newlines by terminating the parse for this line */
    n = 0;
    XtSetArg (args[n], XmNpattern, "\n");               n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNincludeStatus, XmTERMINATE);  n++;
    map[1] = XmParseMappingCreate (args, n);
Once the parse table is assembled, we only need to call the right routine to convert the input. This is the function XmStringParseText(), which takes the following form:

XmString XmStringParseText ( XtPointer        input,
                             XtPointer        *input_end,
                             XmStringTag      tag,
                             XmTextType       input_type,
                             XmParseTable     parse_table,
                             Cardinal         parse_table_size,
                             XtPointer        client_data)
The data to be converted to a compound string is given by input: this can be an ordinary C string as in Example 25-5, or a wide-character or multi-byte array. Which of these various input types is the case must be specified through the input_type parameter, which can be one of XmCHARSET_TEXT, XmWIDECHAR_TEXT, or XmMULTIBYTE_TEXT. The input_end parameter specifies a point within the input where parsing is to terminate. If NULL, parsing continues to the end of the input unless terminated by an XmParseMapping object. If parsing does terminate before the end of the input, and input_end is not NULL, the routine resets the input_end pointer to where parsing actually finished. The tag parameter is used to name the created compound string: if the value is NULL, the compound string is created with the default tag _MOTIF_DEFAULT_LOCALE. The parse_table and parse_table_count parameters identify the array of XmParseMapping objects used to control the conversion process. If parse_table is NULL, a default internal parse table is used which simply converts newlines to compound string separator components, and tabs to tab components (and thus is equivalent to XmStringGenerate()). The client_data parameter is used to pass application-specific data to any parse procedures within the parse_table. In particular, the client_data is passed to any XmNinvokeParseProc routines specified for the XmParseMapping objects. The XmNinvokeParseProc routines are discussed in the Parse Procedures Section later in this chapter.

Another routine for converting strings to compound strings is XmStringTableParseStringArray(). This works using an array of input strings rather than a single input. We could have used this routine in Example 25-5 if we loaded the contents of the file into an array of C strings before performing the conversion process, instead of converting each line as it is encountered. The convenience routine XmStringTableParseStringArray() is defined as follows:

XmStringTable
XmStringTableParseStringArray ( XtPointer        *input_array,
                                Cardinal         input_array_count,
                                XmStringTag      tag,
                                XmTextType       input_type,
                                XmParseTable     parse_table,
                                Cardinal         parse_table_size,
                                XtPointer        client_data)
The parameters to XmStringTableParseStringArray() are similar to those of XmStringParseText(), except that the data to be converted is specified using the input_array and input_array_count parameters. The main difference between the two routines is that XmStringTableParseStringArray() does not have an input_end parameter. Example 25-6 is a reworking of the load_file() routine to use XmStringTableParseStringArray().

Example  25-6 The load_file_array() routine.

/* construct an array of compound strings 
** from loading a file using colon as the field separator.
**
** A more generic routine would pass the field separator in.
*/
XmString *load_file_array (Widget list, char *file, int *count)
{
    XmParseMapping     map[2];
    FILE               *fptr;
    char               buffer[256];
    Arg                args[8];
    char               *cptr;
    XmString           tab;
    XmString           *xms_array = (XmString *) 0;
    int                lines = 0;
    char               **data = (char **) 0;
    int                n;

    *count = 0;

    if ((fptr = fopen (file, "r")) == (FILE *) 0) {
        return NULL;
    }

    /* Map colons to tabs */
    n = 0;
    tab = XmStringComponentCreate (XmSTRING_COMPONENT_TAB, 0, NULL);
    XtSetArg (args[n], XmNpattern, ":"); n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNsubstitute, tab); n++;
    XtSetArg (args[n], XmNincludeStatus, XmINSERT); n++;
    map[0] = XmParseMappingCreate (args, n);

    /* Throw away newlines by terminating the parse for this line */
    n = 0;
    XtSetArg (args[n], XmNpattern, "\n"); n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNincludeStatus, XmTERMINATE); n++;
    map[1] = XmParseMappingCreate (args, n);

    while ((cptr = fgets (buffer, 255, fptr)) != (char *) 0) {
        data = (char **) XtRealloc ((char *) data,
                                    (lines + 1) * sizeof (char *));
        data[lines] = XtMalloc ((unsigned) strlen (cptr) + 1);
        (void) strcpy (data[lines], cptr);

        lines++;
    }

    (void) fclose (fptr);

    xms_array = XmStringTableParseStringArray ((XtPointer *) data, lines, NULL, 
                                    XmCHARSET_TEXT, map, 2, NULL);

    for (n = 0; n < lines; n++) {
        XtFree (data[n]);
    }

    XtFree ((char *) data);

    XmParseMappingFree (map[0]);
    XmParseMappingFree (map[1]);

    *count = lines;

    return xms_array;
}

Converting From Compound Strings

Converting compound strings back into a character array, whether to ordinary C or wide/multi-byte strings, is very similar to the process for converting strings to compound strings. We construct an XmParseTable with appropriate bespoke mappings if the toolkit default maps are insufficient, then call the relevant parsing procedure. In this case, we call either XmStringUnparse() or XmStringTableUnparse(), depending on whether it is a simple compound string or an array we need to convert. XmStringUnparse() is defined as follows:

XtPointer XmStringUnparse ( XmString         input,
                            XmStringTag      tag,
                            XmTextType       tag_type,
                            XmTextType       output_type,
                            XmParseTable     parse_table,
                            Cardinal         parse_table_count,
                            XmParseModel     parse_model)
The compound string to be converted is specified by input. How we want the compound string to be converted depends upon the output_type parameter. If we want an ordinary C string, we specify output_type as XmCHARSET_TEXT. For wide character text or multi-byte array, specify XmWIDECHAR_TEXT or XmMULTIBYTE_TEXT respectively. The routine returns a generic pointer: cast the result to the appropriate data type depending on the value of output_type. For a normal C style string, the return value of XmStringUnparse() should be simply cast to char *. XmStringUnparse() returns dynamically allocated memory, and thus the return value should be freed when no longer in use. Only those compound string components which match the designated tag are converted: if tag is NULL, all components of input are converted. tag_type specifies the representation of tag, which could be in ordinary, wide character, or multi-byte format. tag_type takes the same range of values as output_type described above. The parse_table and parse_table_count parameters specify the parse mappings for controlling the conversion process. If parse_table is NULL, the default internal parse table is used, which converts compound string separators to newlines and tab components to the tab character. The parse_model parameter specifies how non-textual compound string components are to be converted. The possible values are:

XmOUTPUT_ALL                 XmOUTPUT_BEGINNING
XmOUTPUT_END                 XmOUTPUT_BOTH
XmOUTPUT_BETWEEN
The value XmOUTPUT_ALL attempts to convert everything. The value XmOUTPUT_BETWEEN will convert a non-text component if and only if it appears between two text components. This can be used as a filter to map multiple adjoining tab components to a single tab character, for instance. XmOUTPUT_END only converts non-text components if they follow a textual one, so this could be used to strip tab components off the front. XmOUTPUT_BEGINNING converts non-text if it precedes text. XmOUTPUT_BOTH is a combination of XmOUTPUT_BEGINNING and XmOUTPUT_END.

As an example, consider the reverse of Example 25-5, where the contents of a multi-column List is output to file, using the colon character as a field separator. The routine to perform this task is given in Example 25-7:

Example  25-7 The dump_file() routine.

/* Dump the contents of a List to file,
** using colon as the field separator.
**
** A more generic routine would pass the field separator in.
*/
void dump_file (Widget list, char *file)
{
    XmParseMapping     map;
    FILE               *fptr;
    Arg                args[8];
    XmString           tab;
    XmString           *xms_array = (XmString *) 0;
    int                xms_count = 0;
    char               *output;
    int                n, i;

    if ((fptr = fopen (file, "w")) == (FILE *) 0) {
        return;
    }

    XtVaGetValues (list, XmNitems, &xms_array,
                                    XmNitemCount, &xms_count, NULL);

    /* Map tabs to colons */
    n = 0;
    tab = XmStringComponentCreate (XmSTRING_COMPONENT_TAB, 0, NULL);
    XtSetArg (args[n], XmNpattern, ":"); n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT); n++;
    XtSetArg (args[n], XmNsubstitute, tab); n++;
    XtSetArg (args[n], XmNincludeStatus, XmINSERT); n++;
    map = XmParseMappingCreate (args, n);

    for (i = 0; i < xms_count; i++) {
        output = (char *) XmStringUnparse (xms_array[i], NULL,
                                        XmCHARSET_TEXT, XmCHARSET_TEXT,
                                        &map, 1, XmOUTPUT_ALL);
        (void) fprintf (fptr, "%s\n", output);
    }

    (void) fclose (fptr);

    XmParseMappingFree (map);
}
The important thing to note is that for converting compound strings to strings, an XmParseMapping is set up exactly as though we are converting to a compound string. That is, XmNpattern is the C string we get from the conversion, XmNsubstitute is the compound string segment to match against. Since we are substituting a compound string for a C string, we might reasonably expect XmNsubstitute to contain the required C string value, but this is not the case. XmNsubstitute always contains a compound string, XmNpattern contains the equivalent character, wide character or multi-byte value, irrespective of the direction of conversion.

The alternative when converting an array of compound strings is to use XmStringTableUnparse(), which takes the following form:

XtPointer *XmStringTableUnparse ( XmStringTable    table,
                                  Cardinal         table_count,
                                  XmStringTag      tag,
                                  XmTextType       tag_type,
                                  XmTextType       output_type,
                                  XmParseTable     parse_table,
                                  Cardinal         parse_table_count,
                                  XmParseModel     parse_model)
This routine differs from XmStringUnparse() only in that it takes XmStringTable and counter parameters, and returns an array. As for XmStringUnparse(), XmStringTableUnparse() returns allocated memory: you need to free each of the elements in the returned array, as well as the array itself. We could equally have written the dump_file() routine to use XmStringTableUnparse() as follows:

char **output;

output = (char **) XmStringTableUnparse (xms_array, xms_count,
                                    NULL, XmCHARSET_TEXT, XmCHARSET_TEXT,
                                    &map, 1, XmOUTPUT_ALL);
...
for (i = 0; i < xms_count; i++) {
    (void) fprintf (fptr, "%s\n", output[i]);
    XtFree (output[i]);
}

XtFree ((char *) output);

Parse Procedures

The problems with an XmParseMapping as described so far are that the mapping between the input and output sequences are static, the parse state as far as the application is concerned is context free, and there is no opportunity to dynamically move the current input pointer around if we decide to skip some sequence of data. For example, we might want to skip a particular field when converting a formatted line read from a file because we don't want to show it to the user. For this kind of task, the simple XmNsubstitute resource is insufficient. We need an XmParseProc. The specification of an XmParseProc is as follows:

typedef XmIncludeStatus (*XmParseProc) ( XtPointer        *in_out,
                                         XtPointer        text_end,
                                         XmTextType       text_type,
                                         XmString         locale_tag,
                                         XmParseMapping   entry,
                                         int              pattern_length,
                                         XmString         *str_include,
                                         XtPointer        call_data)
The in_out parameter points to the current location within the input stream when the procedure is invoked. The procedure can move the pointer to reset the location where parsing is to continue after the routine finishes. text_end initially points to the end of the in_out string, but again the procedure can move the location to indicate where parsing is to continue from after the mapping has been applied to the current input. The text_type parameter is the type of the input stream, and is one of XmCHARSET_TEXT, XmWIDECHAR_TEXT, XmMULTIBYTE_TEXT. The locale_tag parameter specifies the tag to be used in creating the result of the mapping. entry is the XmParseMapping associated with the current matched input. pattern_length is the number of bytes remaining to be parsed at the address specified by in_out. If the XmParseProc wishes to insert a compound string into the output at this juncture, it returns it at the str_include address. Lastly, the application can arrange to pass data to the routine through the client_data parameter, thus providing whatever context the function needs to perform its task. The call_data is specified as a parameter to the convenience routine (XmStringParseText() or XmStringTableParseStringArray()) which is currently controlling the parsing process.

As an example, consider converting a UNIX /etc/passwd file to an array of compound strings, except that the password field is to be hidden. As before, we map colon characters to tab components, except that this time we skip the second field in each line of the file. We add a second parse mapping object to handle newlines - not because we want to map newlines to anything, but because it is a convenient place to reset the field counter as the end of the line is reached. The address of the field counter is passed as client_data to the parse routines so that they can inspect and manipulate the value. Example 25-8 shows how this can be done using XmParseProc routines.

Example  25-8 The load_filtered_passwd() routine.

/* load_filtered_passwd(): converts the /etc/passwd file into
** a multi-column list format, skipping the password field.
*/

#include <stdio.h>
#include <Xm/Xm.h>
#include <Xm/List.h>

/* reset_field(): used to reset the field context
** when a newline is encountered in the input
*/
XmIncludeStatus reset_field ( XtPointer          *in_out,
                              XtPointer          text_end,
                              XmTextType         text_type,
                              XmStringTag        locale_tag,
                              XmParseMapping     parse_mapping,
                              int                pattern_length,
                              XmString           *str_include,
                              XtPointer          call_data)
{
    /* client data is a pointer to the field counter */
    int *field_counter = (int *) call_data;

    /* A newline encountered.
    **
    ** Trivial: we reset the field counter for this line
    ** and terminate the parse sequence 
    */

    *field_counter = 0;

    return XmTERMINATE;
}

/* filter_field(): throws away the second (password) field
** and maps colon characters to tab components.
*/
XmIncludeStatus filter_field ( XtPointer          *in_out,
                               XtPointer          text_end,
                               XmTextType         text_type,
                               XmStringTag        locale_tag,
                               XmParseMapping     parse_mapping,
                               int                pattern_length,
                               XmString           *str_include,
                               XtPointer          call_data)
{
    /* client data is a pointer to the field counter */
    int *field_counter = (int *) call_data;
    char *cptr = (char *) *in_out;

    /* Skip this colon */
    cptr += pattern_length;

    /* If this is the first colon
    ** then skip the input until after the second.
    ** Otherwise, we return a TAB component
    */

    if (*field_counter == 0) {
        /* Skip over the next colon */
        while (*cptr != `:') cptr++;
            cptr++;
    }

    *str_include = XmStringComponentCreate (XmSTRING_COMPONENT_TAB,
                                            0, NULL);

    *in_out = (XtPointer) cptr;
    *field_counter = *field_counter + 1;

    return XmINSERT;
}

XmString *load_filtered_passwd (Widget list, char *file, int *count)
{
    XmParseMapping     map[2];
    FILE               *fptr;
    char               buffer[256];
    Arg                args[8];
    char               *cptr;
    XmString           *xms_array = (XmString *) 0;
    int                xms_count = 0;
    /* Used as client data to the XmParseProc routines */
    int                field_count = 0;
    int                n;

    *count = 0;

    if ((fptr = fopen (file, "r")) == (FILE *) 0) {
        return NULL;
    }

    /* Set up an XmParseProc to handle colons */
    n = 0;
    XtSetArg (args[n], XmNpattern, ":");                  n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT);   n++;
    XtSetArg (args[n], XmNincludeStatus, XmINVOKE);       n++;
    XtSetArg (args[n], XmNinvokeParseProc, filter_field); n++;
    map[0] = XmParseMappingCreate (args, n);

    /* Set up an XmParseProc to handle newlines */
    n = 0;
    XtSetArg (args[n], XmNpattern, "\n");                n++;
    XtSetArg (args[n], XmNpatternType, XmCHARSET_TEXT);  n++;
    XtSetArg (args[n], XmNincludeStatus, XmINVOKE);      n++;
    XtSetArg (args[n], XmNinvokeParseProc, reset_field); n++;
    map[1] = XmParseMappingCreate (args, n);

    while ((cptr = fgets (buffer, 255, fptr)) != (char *) 0) {
        xms_array = (XmString *) XtRealloc ((char *) xms_array,
                            (xms_count + 1) * sizeof (XmString));

        xms_array[xms_count] = XmStringParseText (cptr, 
                                NULL, NULL, XmCHARSET_TEXT,
                                map, 2, &field_count);

        xms_count++;
    }

    (void) fclose (fptr);

    XmParseMappingFree (map[0]);
    XmParseMappingFree (map[1]);

    *count = xms_count;

    return xms_array;
}
Figure 25-9 shows the result of replacing the load_file() routine of Example 25-5 with the load_filtered_passwd() function:

Figure  25-9 Output of the load_filtered_passwd() routine

Rendering Compound Strings

Motif always renders compound strings automatically within its widgets, so you should never find yourself in a situation where you need to render a compound string manually. However, if you are writing your own widget, you may need to incorporate the same type of functionality. Motif provides three functions that render compound strings:

XmStringDraw()
XmStringDrawImage()
XmStringDrawUnderline()
From Motif 1.2, all of these routines use the X11R5 text output routines when necessary, to ensure that the text is rendered correctly for the current locale.

The most basic rendering function is XmStringDraw(), which takes the following form19:

void XmStringDraw ( Display            *display,
                    Window             window,
                    XmRenderTable      renderTable,
                    XmString           string,
                    GC                 gc,
                    Position           x,
                    Position           y,
                    Dimension          width,
                    unsigned char      alignment,
                    unsigned char      layout_direction, 
                    XRectangle         *clip)
As you can see, the function requires a great deal of information to actually render the string. If you are rendering into a widget, you can specify the display and window using XtWindow() and XtDisplay(). Since a gadget does not have a window, you must use XtWindowOfObject() with a gadget. The renderTable parameter can be constructed using any of the functions described in Chapter 24, Render Tables, or you can retrieve a render table from a widget using XtVaGetValues().

The function also requires a graphics context (GC) so that certain rendering attributes such as color can be applied. A graphics context is generally not available through a widget, so you have to get one at the Xlib level. If you are writing your own widget, you can probably use a GC that is cached by Xt and returned by XtGetGC() (see Volume 4, Xlib Programming Manual). Also, if you are writing your own widget, you may want to consider exposing the GC to the programmer in the form of a resource.

The x, y, and width parameters specify the coordinates and width of the rectangle that contains the compound string. The width parameter is used only for alignment purposes. There is no height parameter because the render table may specify fonts that are unknown in size and whose heights are too variable. The clip parameter defines the drawing boundary; you can pass NULL to indicate that the rendering should not be clipped.

The alignment parameter can be set to one of the following values:

XmALIGNMENT_BEGINNING    XmALIGNMENT_CENTER     XmALIGNMENT_END
The value identifies the justification for the text. The effect of the value is modified by the value of the layout_direction parameter, which can be set to either XmSTRING_DIRECTION_L_TO_R or XmSTRING_DIRECTION_R_TO_L.

The function XmStringDrawImage() is to XmStringDraw() as XDrawString() is to XDrawImageString().The difference is that the image routines overwrite the background even in places where the font does not set bits in the character image, while the other routines only render foreground pixels.

The XmStringDrawUnderline() routine takes the same parameters as XmStringDraw() with one addition. The last parameter specifies the portion of the compound string that should be underlined. A compound string can be wholly or partially underlined depending on whether the last parameter specifies the entire compound string or only a substring of the string parameter.

It may be necessary to get dimensional information about a compound string in order to know where to place it within the window when it is drawn. You may also want this data to determine the optimal or desired width and height of a widget in case you have to provide a geometry callback method. When a call to XtQueryGeometry() is made, a widget that contains compound strings may need to tell the calling function the dimensions it needs to render its compound strings adequately. Motif provides the following routines to help you determine compound string dimensions:

Dimension   XmStringBaseline (XmRenderTable renderTable, XmString string)
void        XmStringExtent (XmRenderTable renderTable, XmString string,
                            Dimension *width, Dimension *height)
Dimension   XmStringHeight (XmRenderTable renderTable, XmString string)
Dimension   XmStringWidth (XmRenderTable renderTable, XmString string)
Each of these functions takes renderTable (XmRenderTable) and string (XmString) parameters. The render table is dependent on the widget associated with the string, but there is no requirement that you must use a string that is associated with a widget. If you just want to get the dimensions of a particular compound string rendered using an arbitrary font or font set, you can create a render table manually, as described in Chapter 24, Render Tables.

XmStringBaseline() returns the number of pixels between the top of the character box and the baseline of the first line of text in the compound string. XmStringWidth() and XmStringHeight() return the width and height, respectively, for the specified compound string. XmStringExtent() takes two additional parameters, width and height. These arguments return the width and height in pixels of the smallest rectangle that encloses the compound string specified in string.

Summary

Compound strings can be useful for creating multi-line or multi-font text for widgets such as Labels, PushButtons, and ToggleButtons. Compound strings were also designed to help in making internationalized applications, but this functionality has basically been made obsolete by the addition of internationalization features in X11R5. Since Motif applications have to use compound strings to display most textual data, the trick to developing an internationalized application is to use compound strings without interfering with lower-level X internationalization functionality.

The best practice is to specify compound string and rendition resources in resource files, so that you can have a separate file for each language that is supported by your application. If you have to create compound strings in an application, you should use XmStringCreateLocalized() or specify the XmFONTLIST_DEFAULT_TAG rendition tag to ensure that the strings are interpreted and rendered in the current locale.



1 Both of these terms are different from the definition of a font, which is a collection of glyphs used to represent the characters in an encoding.

2 The Motif 2.0 CSText widget, which did support compound strings, was removed from Motif 2.1.

3 This terminology may be confusing to a new Motif programmer. Xt uses the typedef String for char *. The representation type used by Xt resource converters for this type is XtRString (XmRString in Motif). A compound string, on the other hand, is of type XmString; its representation type is XmRXmString. You just have to read the symbols carefully. Resource converters are described in detail in Volume 4, X Toolkit Intrinsics Programming Manual, Motif Edition.

4 There is some evidence that the XtVaTypedArg method outlined here for compound strings results in memory leakage, so this should be viewed with some suspicion.

5 Prior to Motif 2.0, the tag was associated with a font list rather than a rendition. In Motif 1.1, the font list was referred to as a character set, but strictly speaking, it did not specify a character set.

6 The XmNstringDirection resource is deprecated as of Motif 2.0, and is subsumed into the XmNlayoutDirection resource. XmNlayoutDirection is available only from Motif 2.0 onwards.

7 XmStringCreateLtoR() is deprecated as of Motif 2.0.

8 XtVaAppInitialize() is considered deprecated in X11R6. XtVaOpenApplication() is only available in X11R6. XmStringGenerate() is only available from Motif 2.0 onwards.

9 Separators in compound strings should not be confused with the Separator widget and gadget class.

10 In Motif 2.0 and later, XmStringSegmentCreate() is deprecated. XmStringComponentCreate() is only available in Motif2.0 and later.

11 The resource XmNstringDirection is considered deprecated in Motif 2.0 and later.

12 XmStringCreateLtoR() is deprecated in Motif 2.0 and later. XmStringGenerate() is only available from Motif 2.0.

13 XtVaAppInitialize() is considered deprecated in X11R6. XmStringGenerate() and XmStringConcatAndFree () are only available in Motif 2.0 and later.

14 XmStringNConcat() is deprecated from Motif 2.0.

15 XmStringNCopy() is deprecated from Motif 2.0.

16 XmStringIsVoid() is only available in Motif 2.0 and later.

17 XmStringGetNextComponent() is deprecated as of Motif 2.0. XmStringGetNextTriple() is only available in Motif 2.0 or later.

18 For backwards compatibility, the values XmSTRING_COMPONENT_TAG and XmSTRING_COMPONENT_ FONTLIST_ELEMENT_TAG are mapped onto XmSTRING_COMPONENT_CHARSET. The Layout push/pop, Rendition push/pop, and Tab component values are only available from Motif 2.0 onwards.

19 In Motif 1.2, XmStringDraw() takes an XmFontList parameter. In Motif 2.0, the XmFontList is obsolete, and has been replaced with an XmRenderTable. For backwards compatibility, the XmFontList type is implemented as a skeleton XmRenderTable.






X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer




Copyright © 1991, 1994, 2000, 2001, 2002 O'Reilly & Associates, Inc., Antony Fountain and Jeremy Huxtable. All Rights Reserved.