Chapter 9. Handling Textual Data

You will probably not find too many reasons to write your own text widget. After all, the standard Motif widget set already provides a variety of editable and noneditable text widgets. However, even if you do not write a text widget, you may end up writing a widget that contains a textual component. For example, perhaps the widget you are writing will display a caption or a title.

This chapter explains how to handle text in Motif widgets. In particular, this chapter will explore the following topics:

For demonstration purposes, the following subsections examine the ExmString widget.

Text Versus Compound Strings

Motif widgets should handle textual information as compound strings (XmString) rather than as simple character strings. Compound strings support multiple fonts, tab lists, and multiple colors; simple character strings do not.

Editable Versus Noneditable Text

When you are writing a widget that displays text, you must decide whether the text will be editable or noneditable. The standard Motif widget set provides two editable text widgets (XmText and XmTextField) and one noneditable text widget (XmLabel).

Editable text is much harder to code than noneditable text. One reason is because widgets with editable text must provide a much larger set of action methods than their noneditable counterparts. For example, a widget with editable text needs to provide actions that allow the user to navigate to different portions of the text. Editable text widgets usually provide actions that allow the user to cut, copy, and paste text. In addition, editable text widgets must support the range selection model. This model allows users to select a contiguous range of elements in a collection. Noneditable widgets need not support any selection model. Consequently, data transfer is much easier to implement in a noneditable text widget than in an editable text widget.

We do not want to discourage you from providing editable text. We merely want to warn you about its complexity.

The ExmString widget demonstrates noneditable text. Motif does not currently provide an Exm demonstration widget that demonstrates editable text.

Recommended Resources for Compound String Widgets

Any Motif widget displaying a compound string should support at least the following resources:

  • A resource whose value holds the render table.

  • A resource whose value holds the compound string itself.

  • A resource whose value specifies the alignment of the text within the widget.

  • A resource whose value determines the layout direction of the text. That is, this resource tells the widget whether the text should read from left to right or from right to left.

It is not a requirement that each widget define all of these resources. In fact, many of these resources will be provided by the widget's superclass. However, each widget displaying text needs to examine the values of these resources and display the text accordingly.

The following subsections examine how the ExmString demonstration widget uses these four resources.

The Render Table Resource

The render table resource holds the value of the render table that is used to display the text. All render table resources should have a representation type of XmRRenderTable.

Some Motif widgets call this resource XmNrenderTable. However, you may wish to provide a somewhat more specialized name for this resource. For example, if you are writing a widget named MyGraph, the render table resource could be called MyNgraphRenderTable.

In order to be consistent with other Motif widgets, the render table resource should set its default value through an XtRCallProc procedure rather than through XmRImmediate data. The XtRCallProc procedure for the ExmString widget is called DefaultFont, and it is defined as follows:

static void
DefaultFont (
        Widget w,
        int offset,
        XrmValue *value
)
{
 ExmStringWidgetClass wc = (ExmStringWidgetClass)XtClass(w);
 static XmRenderTable  f1;

 /* Find the default render table associated with the default
    render table type. */
   f1 = XmeGetDefaultRenderTable(w,
                     wc->string_class.default_render_table_type);

   value->addr = (XtPointer)&f1;
   value->size = sizeof(f1);
}

The XmeGetDefaultRenderTable routine finds the default render table. The second argument to XmeGetDefaultRenderTable specifies the default render table type. This field must contain one of the following three values:

  • XmLABEL_RENDER_TABLE

  • XmBUTTON_RENDER_TABLE

  • XmTEXT_RENDER_TABLE

ExmString stores the default render table type in a field of the String class record. For the ExmString widget, the default render table type is to XmLABEL_RENDER_TABLE. However, subclasses of ExmString may override this value. For example, the ExmCommandButton widget sets its default render table type to XmBUTTON_RENDER_TABLE.

See the reference page for XmeGetDefaultRenderTable in Chapter 17 for more details.

The Compound String Resource

The compound string resource holds the compound string itself. Compound string resources should have a representation type of XmRXmString.

When a user accesses a compound string resource by calling XtGetValue, your widget must return a copy of the compound string resource's value. That is, your widget must make a copy of the string resource value, probably by calling XmStringCopy. Then, your widget must return a pointer to this copy rather than returning the original. Your widget should use Motif's syn_resource mechanism to handle this requirement.

For example, the ExmString widget provides a resource named ExmNcompoundString that holds the value of one compound string. Therefore, ExmString provides the following synthetic resource array:

static XmSyntheticResource syn_resources[] =
{
   {
        ExmNcompoundString,
        sizeof(XmString),
        XtOffsetOf(ExmStringRec, string.compound_string),
        GetValuesCompoundString,
        NULL
   }
};

The preceding declaration tells Motif to call the GetValuesCompoundString routine prior to returning the value of ExmNcompoundString. The GetValuesCompoundString routine is as follows:

GetValuesCompoundString(
        Widget w,
        int resource,   /* unused */
        XtArgVal *value)
{
 ExmStringWidget sw = (ExmStringWidget) w;
 XmString  string;

 /* All Motif widgets are responsible for making a copy of an XmString
    resource whenever an application accesses the resource through a call
    to XtGetValues. */
   string = XmStringCopy(sw->string.compound_string);

   *value = (XtArgVal) string;
}

The Alignment Resource

The alignment resource holds a value that symbolizes the justification of the text. The alignment resource should have a representation type of XmRAlignment. The valid values for this representation type are documented in the XmLabel reference page, under the description of the XmNalignment resource. (See the Motif Programmer's Reference for XmLabel(3x).)

The positioning of text within your widget should depend on a combination of the alignment resource and the layout direction resource. (See the next section for details.)

The Layout Direction Resource

Your widget should examine the layout direction resource provided by XmPrimitive or XmManager. We do not recommend creating your own layout direction resource. The XmManager and XmPrimitive widgets both provide an XmNlayoutDirection resource, and your widget should probably not override this value.

The combination of the layout direction resource and the alignment resource should determine the layout and alignment of the text. For example, suppose the value of the alignment resource is XmALIGNMENT_BEGINNING and the value of the layout direction resource is XmLEFT_TO_RIGHT. In this case, the left sides of the lines of text should be vertically aligned with the left edge of the widget window. On the other hand, if the layout direction value changes to XmRIGHT_TO_LEFT, then the right sides of the lines of text should be vertically aligned with the right edge of the widget window.

The AlignmentDirection method of the ExmString widget demonstrates how to determine where the text starts. The starting location of the text is particularly important for the resize method of ExmString. The text will be positioned in one of the following ways:

  • The text will be centered.

  • The text will start at the left side of the widget.

  • The text will start at the right side of the widget.

The following is the code for the AlignmentDirection method:

static void
AlignmentDirection(
        Widget w)
{
 ExmStringWidget sw = (ExmStringWidget)w;

   if (sw->string.alignment == XmALIGNMENT_CENTER)
   /* The text will be centered. */
     sw->string.text_starts_here = ExmCENTER_STRING;

   else if (
       (XmDirectionMatch(sw->primitive.layout_direction, XmLEFT_TO_RIGHT) &&
        sw->string.alignment == XmALIGNMENT_BEGINNING)
                                ||
       (XmDirectionMatch(sw->primitive.layout_direction, XmRIGHT_TO_LEFT) &&
        sw->string.alignment == XmALIGNMENT_END))
   /* The string will start at the left side of the widget. */
     sw->string.text_starts_here = ExmSTART_STRING_LEFT_SIDE;

   else if (
       (XmDirectionMatch(sw->primitive.layout_direction, XmLEFT_TO_RIGHT) &&
        sw->string.alignment == XmALIGNMENT_END)
                                ||
       (XmDirectionMatch(sw->primitive.layout_direction, XmRIGHT_TO_LEFT) &&
        sw->string.alignment == XmALIGNMENT_BEGINNING))
   /* The string will start at the right side of the widget. */
     sw->string.text_starts_here = ExmSTART_STRING_RIGHT_SIDE;
}

Rendering the Compound String

Assuming that your widget provides code for the four resources described in the previous section, the XmStringDraw function is a good choice for rendering the text onto the screen. The XmStringDraw function takes 11 arguments, 4 of which are the values of the resources described in the previous section.

For example, the ExmString widget calls XmStringDraw inside its DrawVisual method. The call appears as follows:

/* If the compound string is not NULL and if there is enough space in the
   widget to draw at least a little portion of the compound string, then
   render the string with XmStringDraw. */
  if (sw->string.compound_string &&
     (sw->simple.visual.width != 0) &&
     (sw->simple.visual.height != 0)) {
    XmStringDraw (XtDisplay(sw), XtWindow(sw),
                  sw->string.render_table,
                  sw->string.compound_string,
                  wc->simple_class.select_gc(w),
                  sw->simple.visual.x, sw->simple.visual.y,
                  sw->simple.visual.width, sw->string.alignment,
                  sw->primitive.layout_direction, NULL);
    ...

The XmQTaccessTextual Trait

Any widget that displays a primary block of text should install the XmQTaccessTextual trait. On the other hand, if the text in your widget is playing a supporting role, then you should not install the XmQTaccessTextual trait. For example, if the only text in your widget is a pixmap caption, then you would not install this trait on the widget. Furthermore, if the widget displays multiple blocks of text (such as the XmList widget does), then such a widget would not install this trait.

Table 9-1 suggests that this trait is extensively used inside the standard widget set.

Table 9-1. XmQTaccessTextual Access and Use in the Motif Toolkit

WidgetInstallsAccessesUsage Notes
XmComboBoxNoYesCalls the getValue and setValue trait methods
XmLabelYesNoProvides all three trait methods
XmLabelGadgetYesNoProvides all three trait methods
XmNotebookNoYesDoes not call a particular trait method; just examines its children to see if they have this trait installed
XmSelectionBoxNoYesCalls the getValue and setValue trait methods
XmSpinBoxNoYesCalls the setValue trait methods
XmTextYesNoProvides all three trait methods
XmTextFieldYesNoProvides all three trait methods

The XmQTaccessTextual trait provides three methods. These trait methods are called by another widget, typically by the parent of a widget holding the XmQTaccessTextual widget. For example, the parent calls the setValue trait method to set the primary text block of its child. The parent calls the getValue trait method to find out what primary text block its child is displaying. Finally, the parent can call the preferredFormat trait method to find out what text format the child prefers to store its text in.

Different textual widgets hold their text in different formats. Currently, Motif supports three different text formats:

  • XmFORMAT_XmSTRING symbolizes Motif compound string (XmString) format.

  • XmFORMAT_WCS symbolizes a string in the wide-character string format defined by ANSI C.

  • XmFORMAT_MBYTE format symbolizes a string in the multibyte string format defined by ANSI C.

The ExmString widget installs the XmQTaccessTextual trait and defines all three trait methods. The following subsections examine these trait methods.

The getValue Trait Method

The getValue trait method returns a copy of the text currently held by the XmQTaccessTextual widget. Typically, this value will be held inside a resource. For example, the ExmString widget holds its text value inside the ExmNcompoundString resource. Therefore, the current text value of the ExmString widget is easily obtained through the following call:

XtVaGetValues(w, ExmNcompoundString, &value, NULL);

The current text value is now stored in compound string format inside the value variable.

The widget that calls getValue must specify the string format in which it expects to receive the returned value. Therefore, most of the code inside the getValue trait method converts the text from its native format (in this case, XmFORMAT_XmSTRING) to one of the two other supported formats. Converting from XmString format to multibyte or wide-character string format requires a call to XmStringUnparse.

(See the code for ExmString for complete details.)

The setValue Trait Method

The setValue trait method changes the text and/or text format of a widget's primary text block. The setValue trait method takes three arguments:

  • The widget

  • The new text

  • The format in which the caller is passing the new text string

If the new text has the same format as the widget's native format, no conversions are necessary. For example, the native format of the ExmString widget is XmFORMAT_XmSTRING. Therefore, if the caller passes the new text in the XmFORMAT_XmSTRING format, the setValue trait method merely needs to assign the text to the ExmNcompoundString resource. On the other hand, if the caller passes the new text in XmFORMAT_MBYTE (multibyte text) format, setValue must convert the new text from XmString format to XmFORMAT_MBYTE format.

Following is the complete setValue trait method of the ExmString widget:

StringSetValue(
     Widget w,
     XtPointer string,
     int format)
{
 Arg       args[1];
 XmString  temp;
 Boolean   freetemp;
 int       length;
 char     *str;
 wchar_t  *str2;

  /* The caller will pass a new value for ExmNcompoundString. This new
     value will be passed in the "string" argument. However, there is
     no guarantee that the input "string" will be passed in XmString format.
     If the input "string" is passed in WCS or MULTIBYTE format, then we
     must convert the "string" into XmString format. Once the "string"
     is in XmString format, we can use it as the new value of
     ExmNcompoundString. */
    switch (format)    {
        case XmFORMAT_XmSTRING:
                   temp = (XmString) string;
                   freetemp = False;
                   break;

        case XmFORMAT_WCS:
                   str2 = (wchar_t *) string;
                 /* How long is str2? */
                   length = 0;
                   while (str2[length] != 0)
                      length++;
                 /* malloc enough space to hold str */
                   str = (char*) XtMalloc(MB_CUR_MAX * (length+1));
                   wcstombs(str, str2, MB_CUR_MAX * (length+1));
                   XtFree((char*)string);
                   string = str;

                 /* Falling through to XmFORMAT_MBYTE */

        case XmFORMAT_MBYTE:
                   temp = XmStringCreateLocalized(string);
                   freetemp = True;
                   break;

        default:
                   XmeWarning((Widget)w, UNSUPPORTED_FORMAT);
                   return;
    }

 /* Assign the new string to ExmNcompoundString. */
    XtSetArg(args[0], ExmNcompoundString, temp);
    XtSetValues(w, args, 1);

    if (freetemp)
      XmStringFree(temp);
}

The preferredFormat Trait Method

Every textual widget has some native format in which it stores the text itself. The preferredFormat trait method returns the name of this native format. For example, Table 9-2 shows the native format of the XmQTaccessTextual widgets in the standard Motif widget set.

Table 9-2. Native Format of XmQTaccessTextual Widgets

WidgetNative Format
XmLabelXmFORMAT_XmSTRING
XmLabelGadgetXmFORMAT_XmSTRING
XmTextXmFORMAT_MBYTE
XmTextFieldXmFORMAT_MBYTE

The native format of ExmString is XmFORMAT_XmSTRING. Therefore, the entire preferredFormat trait method is simply as follows:

static int
StringPreferredFormat(
     Widget w)
{
   return(XmFORMAT_XmSTRING);
}

If the calling widget knows the textual widget's native format prior to calling getValue or setValue, then needless conversions can be avoided.