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, 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.
The internationalization features in X11R5 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 Section #slangproc. The language procedure returns a
language string that is used by XtResolvePathname() to find
locale-specific resource files. See Volume Four, 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. 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. 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 (<OVERSTRIKE<o[[umlaut]]>OVERSTRIKE>).
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 One,
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.
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 widgets. As a result, you cannot use the compound string
techniques for displaying text using multiple fonts. However, in Motif
1.2, 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 Section #stexti18n.
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 a
particular font or font set. 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 now refers to the entity as a font list tag; this change
is strictly semantic. The tag-to-font mapping is done on a per-widget
basis, so the same name can map to different fonts for different
widgets.
An application can create a compound string that
uses multiple fonts by concatenating separate compound strings with
different tags to produce a single compound string. Concatenating
compound strings with different fonts is a powerful way to create
graphically interesting labels and captions. More importantly, because
fonts are loosely bound to compound strings via resources, you can
dynamically associate new fonts with a widget while an application is
running and effectively change text styles on the fly.
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(text) 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() is a new routine
in Motif 1.2; it 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 XmStringCreateLocalized(), 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. All of the examples in this book use
XmStringCreateLocalized() to demonstrate the appropriate technique,
even though the examples are only designed to work in the C locale.
With Motif 1.1, you should use the
XmStringCreateSimple() routine to create a compound string that
uses the default character set and direction. This function is obsolete
in Motif 1.2; it remains for backwards-compatibility purposes only.
With both XmStringCreateLocalized() and
XmStringCreateSimple(), you cannot explicitly specify the tag or
the string direction that is used for the compound string, and the
string cannot have multiple lines.
Both XmStringCreateLocalized() and
XmStringCreateSimple() allocate 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"); XtVaCreateManagedWidget ("widget_name", xmPushButtonGadgetClass, parent, XmNlabelString, str, NULL); 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 be simplified 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 can use this method to convert a C string to a
compound string. The following code fragment has the same effect as the
previous example:
XtVaCreateManagedWidget ("widget_name", xmPushButtonWidgetClass, parent, XtVaTypedArg, XmNlabelString, XmRString, "Push Me", 8, /* or 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. 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 Four, X Toolkit Intrinsics Programming Manual, Motif Edition . The 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 is negligible. The convenience and
elegance of the XtVaTypedArg method may be worth the
performance tradeoff.
The reason most 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.
Motif provides two different compound string
creation routines that allow you to specify a tag used to associate the
compound string with a font or a font set. This tag is a
programmer-specified identifier that enables a Motif widget to pick its
font from a list of fonts at run-time. In Motif 1.1, the font list tag
was referred to as a character set, but strictly speaking, it does not
specify a character set.
The XmStringCreate() and
XmStringCreateLtoR() routines allow you to specify a font list tag.
These routines take the following form:
XmString XmStringCreate(text, tag) char *text; char *tag;
XmString XmStringCreateLtoR(text, tag) char *text; char *tag;Both of these routines create and allocate a new compound string and associate 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 XmNstringDirection resource. This resource
is defined by manager widgets; it specifies the 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.
XmStringCreateLtoR() creates a compound
string in which the direction is hard-coded as left-to-right. Motif
also defines the XmStringLtoRCreate() routine; its
functionality is identical to XmStringCreateLtoR(). This function
is also useful for converting newline-separated strings into compound
strings, as we explain later in this section. Unfortunately, Motif does
not provide a corresponding right-to-left compound string creation
function. If you need such a routine, it is not that difficult to write
one.
The actual 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 XmNfontList
resource. This resource specifies a list of fonts and/or font sets for
the widget; each entry in the list may have an optional tag associated
with it. For example, a resource file might specify a font list as
follows:
*fontList: -*-courier-*-r-*--*-120-*=TAG1, -*-courier-*-r-*--*-140-*=TAG2, -*-courier-*-r-*--*-180-*=TAG3At run-time, the compound string is rendered using the first font or font set in the widget's font list that matches the font list tag specified in the compound string creation function. In Motif 1.2, the compound string rendering functions use the new X11R5 text output functions, so compound strings are displayed appropriately for the current locale. 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. This loose binding between the compound string and the font or font set used to render it is useful in a number of ways:
*XmPushButton.fontList: -*-courier-*-r-*--*-120-*=TAG1 *XmPushButtonGadget.fontList: -*-courier-*-r-*--*-120-*=TAG1 *XmList.fontList: -*-helvetica-*-r-*--*-120-*=TAG1These resource settings indicate that TAG1 maps to a 12-point Courier font for all PushButton widgets and gadgets and maps to a 12-point Helvetica font for all List widgets.
the source code demonstrates how a compound string
can be rendered using different fonts in different PushButton widgets.
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4. XmStringCreateLocalized() is
only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1.
/* 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[] = { "*fontList:9x15=MY_TAG", NULL }; main(argc, argv) int argc; char *argv[]; { Widget toplevel, rowcol; XtAppContext app; XmString text; Display *dpy; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "String", NULL, 0, &argc, argv, fallbacks, NULL); text = XmStringCreate ("Testing, testing...", "MY_TAG"); rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, NULL); XtVaCreateManagedWidget ("pb1", xmPushButtonGadgetClass, rowcol, XmNlabelString, text, NULL); XtVaCreateManagedWidget ("pb2", xmPushButtonGadgetClass, rowcol, XmNlabelString, text, NULL); XtVaCreateManagedWidget ("pb3", xmPushButtonGadgetClass, rowcol, XmNlabelString, text, NULL); 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 font list 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 the figure.
However, the figure shows what happens to the output
when the following resources are specified:
*pb1.fontList: -*-courier-*-r-*--*-120-*=MY_TAG *pb2.fontList: -*-courier-*-r-*--*-140-*=MY_TAG *pb3.fontList: -*-courier-*-r-*--*-180-*=MY_TAG
The font associated with MY_TAG for each of
the PushButtons is different, so the compound string for each one is
rendered in a different font. This case isn't really that exciting,
however, because we could have achieved the same effect without
specifying a font list tag for each font. Since each font list only
contains one font, Motif has no choice but to display the compound
string using that font. The following resource specification creates
the output shown in the figure:
*pb1.fontList: -*-courier-*-r-*--*-120-* *pb2.fontList: fixed,-*-courier-*-r-*--*-140-*=ANOTHER_TAG *pb3.fontList: fixed,-*-courier-*-r-*--*-180-*=MY_TAG
In this case, the compound string in the first
PushButton uses a 12-point Courier font, since that is the only font in
the font list. The second PushButton uses the fixed font
because it is first in the list and neither of the fonts has MY_TAG
associated with it. The third button uses the 18-point Courier font
associated with MY_TAG. In Motif 1.2, 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. Just as with other font list tags, Motif
looks for a font or font set with a matching tag when it renders the
compound string. This font list tag is used to identify the font or
font set that is correct for the encoding of the current locale. If a
font list does not use XmFONTLIST_DEFAULT_TAG, the first item
in the font list is automatically associated with this tag.
An internationalized application should only use
XmFONTLIST_DEFAULT_TAG in font lists to ensure that compound
strings are rendered correctly for the current locale. However, it is
possible to use explicit font list 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 font list associated with it need to
use the same tag, and the tag should 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 essentially
equates XmFONTLIST_DEFAULT_TAG with
XmSTRING_DEFAULT_CHARSET when it cannot find an exact match between
a compound string and a font list. XmFONTLIST_DEFAULT_TAG in a
compound string or font list matches the tag used in creating a
compound string or specifying a font list entry with the tag
XmSTRING_DEFAULT_CHARSET. Some Motif widgets define font list
resources that allow them to provide a consistent appearance for all of
their children. In Motif 1.2, the VendorShell widget defines the
XmNbuttonFontList, XmNlabelFontList, and
XmNtextFontList resources, while the MenuShell defines
XmNbuttonFontList and XmNlabelFontList. These resources
apply 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 is not
set, its value is taken from XmNdefaultFontList.
The BulletinBoard widget defines the
XmNbuttonFontList, XmNlabelFontList, and
XmNtextFontList resources primarily for use in dialog boxes. These
font lists 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 font list 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 XmNfontList resource, as this resource overrides
the more general ones.
A compound string is composed of segments,
where each segment contains a continuous sequence of text with no
change in font list tag or direction. A compound string segment can be
terminated by a separator, which is the equivalent of a newline
in a character string. Separators in compound strings should not be
confused with the Separator widget and gadget class. Segments 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 mutiple fonts and directions.
XmStringSegmentCreate() provides complete
control over the creation of a compound string, as it allows you to
specify the text, a font list tag, and a direction. This routine also
lets you specify whether or not a separator is added to the compound
string. The routine takes the following form:
XmString XmStringSegmentCreate(text, tag, direction, separator) char *text; char *tag; XmStringDirection direction; Boolean separator;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 Manager
XmNstringDirection resource. This resource specifies the direction
for compound strings used by widgets that are immediate children of a
Manager widget, provided that the string direction is not hard-coded in
the compound strings. 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 XmNstringDirection 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
by direction using either XmStringDirectionCreate() or
XmStringSegmentCreate(). 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
or XmSTRING_DIRECTION_ L_TO_R for values of this type.
When using XmStringSegmentCreate(), you
specify the string direction using the direction
parameter. For example, we can change the call to XmStringCreate()
in the source code to the following:
text = XmStringSegmentCreate ("Testing, testing...", "MY_TAG", XmSTRING_DIRECTION_R_TO_L, False);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 the figure.
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(direction) XmStringDirection direction;The routine returns a compound string segment that can be concatenated with another compound string to cause a directional change. 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 the source code to the following:
text = XmStringCreateLtoR ("Testing,0esting...", "MY_TAG");In this case, we use XmStringCreateLtoR() not because we need to specify the left-to-right direction explicitly, but because this function interprets embedded newline characters (\n) as separators. The effect of this change is shown in the figure, where the PushButtons display multiple lines of text.
XmStringCreate() and XmStringSegmentCreate() do not interpret newline characters as separators; they create 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 the figure. XmStringSegmentCreate(), however, can be told to append a separator to the compound string it creates.
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 XmStringCreateLtoR() to
convert the buffer into a compound string that contains separators. the
source code shows a function that reads the contents of a file into a
buffer and then converts that buffer into a compound string.
XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET
in Motif 1.2.
XmString ConvertFileToXmString(filename, &lines) char *filename; int *lines; { struct stat statb; int fd, len, lines; 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 = XmStringCreateLtoR (text, XmFONTLIST_DEFAULT_TAG); XtFree (text); 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 Section #sstringcvt.
Once multiple font list tags are specified in a font
list, 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. the source code
demonstrates the creation of a compound string that uses three fonts.
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4.
/* 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[] = { "multi_font*fontList:-*-courier-*-r-*--12-*=TAG1,-*-courier-bold-o-*--14-*=TAG2,-*-courier-medium-r-*--18-*=TAG3", NULL }; main(argc, argv) int argc; char *argv[]; { Widget toplevel; XtAppContext app; XmString s1, s2, s3, text, tmp; String string1 = "This is a string ", string2 = "that contains three ", string3 = "separate fonts."; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "String", NULL, 0, &argc, argv, fallbacks, NULL); s1 = XmStringCreate (string1, "TAG1"); s2 = XmStringCreate (string2, "TAG2"); s3 = XmStringCreate (string3, "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 = XmStringConcat (s1, s2); text = XmStringConcat (tmp, s3); XtVaCreateManagedWidget ("widget_name", xmLabelWidgetClass, toplevel, XmNlabelString, text, NULL); XmStringFree (s1); XmStringFree (s2); XmStringFree (s3); XmStringFree (tmp); XmStringFree (text); XtRealizeWidget (toplevel); XtAppMainLoop (app); }The output of this program is shown in the figure.
The XmNfontList resource is specified using
three fonts, each with a distinct font list tag. We create each string
using XmStringCreate() with the appropriate text and tag. Then
we concatenate the strings using XmStringConcat(), two at a
time until we have a single compound string that contains all the text.
XmStringConcat() 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. Details of this function and other related functions are
discussed in Section #sstringfunc.
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
font list 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 the source code
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.
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:
XmStringByteCompare() XmStringCompare() XmStringConcat() XmStringCopy() XmStringEmpty() XmStringHasSubstring() XmStringLength() XmStringNConcat() XmStringNCopy()Both XmStringCompare() and XmStringByteCompare() compare two compound strings. These routines take the following form:
Boolean XmStringCompare(string1, string2) XmString string1, string2;
Boolean XmStringByteCompare(string1, string2) XmString string1, 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 font list tags, has the same direction, and contains the same embedded char string internally, the function returns True. The mapping between font list tags and fonts does not happen until a compound string is rendered by a widget, so whether or not the same font list tag actually maps to two different fonts does not affect the results of this function.
XmStringConcat() and XmStringNConcat()
can be used to concatenate compound strings. These functions take the
following form:
XmString XmStringConcat(string1, string2) XmString string1, string2;
XmString XmStringNConcat(string1, string2, n) XmString string1, string2; int n;Both of these routines create a new compound string and copy the concatenation of string1 and string2 into the newly allocated string. XmStringNConcat() copies all of string1, but only n bytes from string2, into the new string. The original strings are preserved, and you are responsible for freeing the string returned by the routines using XmStringFree().
You can copy a compound string using either
XmStringCopy() or XmStringNCopy(), which take the
following forms:
XmString XmStringCopy(string) XmString string;
XmString XmStringNCopy(string, n) XmString string; int n;Both functions copy string into a newly-allocated compound string; XmStringNCopy() only copies n bytes from string.
XmStringHasSubstring() determines whether
or not a compound string contains a particular substring. The routine
has the following form:
Boolean XmStringHasSubstring(string, substring) XmString string, 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 font list tags for the routine to return True.
To get the length of a compound string, use
XmStringLength(), which has the following form:
int XmStringLength(string) XmString string;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(), which takes the
following form:
Boolean XmStringEmpty(string) XmString string;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.
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().
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. If the compound
string only has one tag associated with it and has a left-to-right
orientation, the process is quite simple. In this case, which is quite
common, you can use the following function to make the conversion:
Boolean XmStringGetLtoR(string, tag, text) XmString string; XmStringCharSet tag; char **text;XmStringGetLtoR() takes a compound string and a tag and converts it back into a C character string. If successful, the function returns True, and the text parameter points to a newly-allocated string. Since the routine allocates storage for the character string, you must free this pointer when you are done using it, as shown in the following code fragment:
XmString string; char *text; if (XmStringGetLtoR (string, "MY_TAG", &text)) { printf ("Text = %s0, text); XtFree (text); }
As its name implies, XmStringGetLtoR() only
gets left-to-right oriented text. Additionally, the function only gets
the first text segment from the compound string that is associated with
the specified tag. If the string contains multiple tags
or has a right-to-left direction, you need to traverse the compound
string and retrieve each segment individually in order to obtain the
entire string. Motif defines a new type, XmStringContext, that
is used to identify and maintain the position within the compound
string being scanned. To cycle through a compound string, you need to
use the following sequence of operations:
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(context, string) 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
XmStringGetNextSegment():
Boolean XmStringGetNextSegment(context, text, tag, direction, separator) XmStringContext context; char **text; XmStringCharSet *tag; XmStringDirection *direction; Boolean *separator;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; it stops when it encounters a new tag or a directional change. The values for text, tag, and direction are filled in, and if a separator is found at the end of the segment, separator is set to True. The text parameter points to allocated data that should be freed by the caller using XtFree().
When you are through scanning the compound string,
you need to free the string context using XmStringFreeContext()
, which takes the following form:
void XmStringFreeContext(context) XmStringContext context;the source code shows a routine that uses these functions to print a compound string used as the label for a widget.
void PrintLabel(widget) Widget widget; { XmString str; XmStringContext context; char *text, *tag, buf[128], *p; XmStringDirection direction; Boolean separator; 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 thru buf as text is read */ p = buf; while (XmStringGetNextSegment (context, &text, &tag, &direction, &separator)) { /* copy text into p and advance to the end of the string */ p += (strlen (strcpy (p, text))); if (separator == True) { /* if there's a separator ... */ *p++ = '0; *p = 0; /* add newline and null-terminate */ } XtFree (text); /* we're done with the text; free it */ } XmStringFreeContext (context); XmStringFree (str); printf ("Compound string:0s0, buf); }
As we have demonstrated, font lists can be set in a
resource file. If your application is robust enough to handle any
particular font that the user may specify, you are encouraged to use
fallback resources and the application defaults files for all font list
specifications. This technique simplifies maintenance for your
application, as you do not have to open fonts, maintain handles to
them, and free them. If you are writing an internationalized
application, you should only specify font lists in resource files so
that you can specify different fonts and/or font sets in the resource
files for different locales.
However, if you specifically don't want the user to
override your font specifications, you can hard-code fonts within the
application using various Motif routines to create a font list. In this
case, you are taking on the responsibility of creating, maintaining,
and destroying fonts as necessary. Motif also provides routines that
allow you to retrieve information about a font list.
All of the font list creation functions deal with a
font list object of type XmFontList. This type is intended to
be opaque, so you should not attempt to access the internal fields of
the data structure. If you need information about the fonts in a font
list, you can use the routines for querying a font list that we are
going to describe.
The Motif API for font lists has changed
significantly in Motif 1.2 to support the new XFontSet
abstraction. The Motif 1.1 routines exist for backwards compatibility,
but they are now obsolete. In Motif 1.2, each item in a font list
specifies an XmFontListEntry and an associated tag, while in
Motif 1.1 each item specifies a font and a character set tag. The
XmFontListEntry type is an opaque type that can specify either a
font or a font set.
The process for creating a font list involves
creating individual font list entries and then appending these entries
to a font list. the source code shows a program that produces the same
output as the source code but now the font list is hard-coded in the
program. XtSetLanguageProc() is only available in X11R5; there
is no corresponding function in X11R4. XmFontListEntryCreate()
is only available in Motif 1.2; there is no corresponding function in
Motif 1.1. XmFontListAppendEntry() is only available in Motif
1.2; XmFontListCreate() and XmFontListAdd() are the
corresponding functions in Motif 1.1.
/* fontlist.c -- demonstrate how to create, add to, and destroy * font lists. The fonts and text displayed are hardcoded in * this program and cannot be overriden by user resources. */ #include <Xm/Label.h> main(argc, argv) int argc; char *argv[]; { Widget toplevel; XtAppContext app; XmString s1, s2, s3, text, tmp; XmFontListEntry entry1, entry2, entry3; XmFontList fontlist; String string1 = "This is a string ", string2 = "that contains three ", string3 = "separate fonts."; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); entry1 = XmFontListEntryLoad (XtDisplay (toplevel), "-*-courier-*-r-*--*-120-*", XmFONT_IS_FONT, "TAG1"); entry2 = XmFontListEntryLoad (XtDisplay (toplevel), "-*-courier-bold-o-*--*-140-*", XmFONT_IS_FONT, "TAG2"); entry3 = XmFontListEntryLoad (XtDisplay (toplevel), "-*-courier-medium-r-*--*-180-*", XmFONT_IS_FONT, "TAG3"); fontlist = XmFontListAppendEntry (NULL, entry1); fontlist = XmFontListAppendEntry (fontlist, entry2); fontlist = XmFontListAppendEntry (fontlist, entry3); XmFontListEntryFree (&entry1); XmFontListEntryFree (&entry2); XmFontListEntryFree (&entry3); s1 = XmStringCreate (string1, "TAG1"); s2 = XmStringCreate (string2, "TAG2"); s3 = XmStringCreate (string3, "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 = XmStringConcat (s1, s2); text = XmStringConcat (tmp, s3); XtVaCreateManagedWidget ("label", xmLabelWidgetClass, toplevel, XmNlabelString, text, XmNfontList, fontlist, NULL); XmStringFree (s1); XmStringFree (s2); XmStringFree (s3); XmStringFree (tmp); XmStringFree (text); XmFontListFree (fontlist); XtRealizeWidget (toplevel); XtAppMainLoop (app); }This program creates font list entries for three fonts, appends the entries to a font list, and uses the resulting font list to specify the XmNfontList resource of a Label widget. The compound strings are created using the same tags as the font list entries, so they are displayed in the appropriate fonts.
In the source code we create the font list entries
using XmFontListEntryLoad(), which takes the following form:
XmFontListEntry XmFontListEntryLoad(display, font_name, type, tag) Display *display; char *font_name; XmFontType type; char *tag;This routine loads the font or creates the font set specified by font_name. The function uses Xt resource converters to convert the string name of the font to the appropriate type. The type parameter specifies whether the font name is a font or a font set; it can have the value XmFONT_IS_FONT or XmFONT_IS_FONT_SET . The tag is associated with the font list entry. If the routine can load or create the specified font, it allocates and returns an XmFontListEntry, which the application must free using XmFontListEntryFree(). If the routine cannot find the specified font, it returns NULL.
Once we have created the font list entries, we can
use them to make a font list. XmFontListAppendEntry() appends
a font list entry to a font list. We call this routine three times to
add the three entries. XmFontListAppendEntry() takes the
following form:
XmFontList XmFontListAppendEntry(oldlist, entry) XmFontList oldlist; XmFontListEntry entry;The routine adds the specified entry to the old font list and returns a new font list. If oldlist is NULL, the routine simply creates a font list using the font list entry. Motif caches font lists, so when we add a font list entry to a font list, the routine searches the cache for a font list that matches the new font list. If the routine finds a matching font list, it returns that font list and increments its reference count. Otherwise, XmFontListAppendEntry() allocates space for the new font list and caches it. The routine deallocates the storage for the old font list, but the application is responsible for freeing the storage for the new font list using XmFontListFree().
After we add the font list entries to the font list,
we don't need the individual entries, so we free them using
XmFontListEntryFree(). Notice that this routine takes an address of
a font list entry, not the actual font list entry. When Motif creates a
font list entry, it does not copy the XFontStruct or
XFontSet, so these items must not be freed. If you pass a font list
entry, instead of its address, to XmFontListEntryFree(), you
end up freeing the font or font set, which results in an X protocol
error.
The fontlist.c program creates compound
strings just like our previous examples. The strings are associated
with the same tags as the font list entries, so the strings are
rendered using the appropriate fonts. The program sets the
XmNfontList resource of the Label widget, so the fonts are
hard-coded in the application and cannot be modified using a resource
file. When a font list is assigned to a widget, the widget copies the
list using XmFontListCopy(). After the resource has been
specified, the program no longer needs the font list, so it frees it
using XmFontListFree().
We used XmFontListEntryLoad() to both load
the font and create a font list entry. Alternatively, we could have
loaded the fonts using a routine like XLoadQueryFont() and
then called XmFontListEntryCreate() to create the font list
entries. This routine takes the following form:
XmFontListEntry XmFontListEntryCreate(tag, type, font) char *tag XmFontType type; XtPointer font;The type parameter specifies whether the specified font is an XFontStruct or an XFontSet. You can load a font using XLoadQueryFont(). Use XCreateFontSet() to create a font set. (See Volume One, Xlib Programming Manual, for more information on these routines.) XmFontListEntryCreate() allocates and returns a font list entry; the application is responsible for freeing this entry using XmFontListEntryFree().
It is purely a matter of preference whether you use
XmFontListEntryCreate() or XmFontListEntryLoad(). In the
source code we could replace our calls to XmFontListEntryLoad()
with the following code:
XFontStruct *font1, *font2, *font3; XmFontListEntry entry1, entry2, entry3; font1 = XLoadQueryFont (XtDisplay (toplevel), "-*-courier-*-r-*--*-120-*"); font2 = XLoadQueryFont (XtDisplay (toplevel), "-*-courier-bold-o-*--*-140-*"); font3 = XLoadQueryFont (XtDisplay (toplevel), "-*-courier-medium-r-*--*-180-*"); entry1 = XmFontListEntryCreate ("TAG1", XmFONT_IS_FONT, font1); entry2 = XmFontListEntryCreate ("TAG2", XmFONT_IS_FONT, font2); entry3 = XmFontListEntryCreate ("TAG3", XmFONT_IS_FONT, font3);The functionality of the program is the same in either case, so which method you use really depends on whether you want to load the fonts yourself or let the routine handle it for you.
In Motif 1.1, there are two routines for dealing
with font lists. XmFontListCreate() creates a new font list
with one entry, while XmFontListAdd() adds a font to an
existing font list. These routines take the following form:
XmFontList XmFontListCreate(font, charset) XFontStruct *font; XmStringCharSet charset;
XmFontList XmFontListAdd(oldlist, font, charset); XmFontList oldlist; XFontStruct *font; XmStringCharSet charset;The routines both take an XFontStruct, so you have to load the font yourself using XLoadQueryFont(). The functions both allocate and return a font list that the application must free when it is done using it. These routines exist for backwards compatibility purposes only, so you should not use them with Motif 1.2.
You can retrieve a font list directly from a widget
using XtVaGetValues() no matter whether the font list is
specified in a resource file or created programatically. The following
code fragment demonstrates this technique:
XmFontList fontlist; XtVaGetValues (widget, XmNfontList, &fontlist, NULL);The font list returned by XtVaGetValues() is a pointer to internal data, so it should be considered read-only. You should not alter this font list or free it. This is in direct contrast to how Motif uses XtVaGetValues() for compound strings, where a copy of the string is returned. If you need to manipulate the font list, you can make a copy of it using XmFontListCopy().
Once you have obtained a font list from a widget,
you can use it to specify the font list for another widget, as shown in
the following code:
XtVaSetValues (another_widget, XmNfontList, fontlist, NULL);Since the font list was obtained through a call to XtVaGetValues() , we do not free it after setting the XmNfontList resource.
The XmFontList type is opaque to the
programmer, so if you need to get information about a font list, you
have to use Motif-specific functions that access font list information.
This internal information can be useful if you need the font handles or
tags for any reason. Motif provides a number of routines to cycle
through the font list. The XmFontContext type is used to
identify and maintain the position within the font list being queried.
To query a font list, you need to use the following sequence of
operations:
Boolean XmFontListInitFontContext(context, fontlist) XmFontContext *context; XmFontList fontlist;The routine is passed the address of an XmFontContext variable and a font list. It allocates a new font context structure based on the font list and returns True. If the font list is not valid or there is not enough memory available to allocate a new context, False is returned.
Once the font context has been initialized, the
entries in the font list can be retrieved using
XmFontListNextEntry():
XmFontListEntry XmFontListNextEntry(context) XmFontContext context;This routine cycles through all of the font list entries in the font list. The first call returns the first entry in the font list; repeated calls using the same font context access successive entries. Since the XmFontListEntry type is also opaque, you have to use XmFontListEntryGetFont() and XmFontListEntryGetTag() to retrieve the actual font or font set and tag for the font list entry. These routines take the following form:
XtPointer XmFontListEntryGetFont(entry, type_return) XmFontListEntry entry; XmFontType *type_return;
char * XmFontListEntryGetTag(entry) XmFontListEntry entry;XmFontListEntryGetFont() returns an XFontStruct or an XFontSet depending on the value of type_return . The routine does not copy the data structure, so the application must not free it. XmFontListEntryGetTag() retrieves the tag for the font list entry. This routine allocates storage for the tag, so the application must free it.
In Motif 1.1, you call XmFontListGetNextFont()
to cycle through the fonts in a font list. This routine has the
following form:
Boolean XmFontListGetNextFont(context, charset, font) XmFontContext context; XmStringCharSet *charset; XFontStruct **font;If the function returns True, the character set and font pointers are set to the appropriate values. The charset returned is a pointer to allocated data that must be freed when no longer needed. The value for font points to the actual XFontStruct data used in the font list, so it must not be freed. If the end of the list has been reached, the function returns False . This routine exists for backwards compatibility and should not be used with Motif 1.2.
When you are done querying the font list, you need
to free the font context using XmFontListFreeFontContext(),
which takes the following form:
void XmFontListFreeFontContext(context) XmFontListFontContext context;If you are searching through a font list and need to back up, you must restart the entire process by freeing the current font context and creating a new one.
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()In 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 form:
XmStringDraw(display, window, fontlist, string, gc, x, y, width, alignment, layout_direction, clip); Display *display; Window window; XmFontList fontlist; XmString string; GC gc; Position x, 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 fontlist parameter can be constructed using any of the functions described in Section #sfontlist, or you can retrieve a font list 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 Four, X Toolkit
Intrinsics 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 font list 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_ENDThe 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 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:
XmStringBaseLine() XmStringExtent() XmStringHeight() XmStringWidth()
Each of these functions takes fontlist
(XmFontList) and string (XmString)
parameters. The font list 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 font list manually, as described in Section
#sfontlist.
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
.
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
font list 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
font list tag to ensure that the strings are interpreted and rendered
in the current locale.