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: 24

Render Tables



This chapter describes the new features introduced in Motif 2.0 for rendering compound strings.

In Motif 1.2, compound strings were rendered with respect to an XmFontList, which encapsulates a list of X fonts and font sets. By separating the contents of a compound string from the fonts with which it is associated, it was possible to display the same compound string in different ways in distinct widget contexts, simply by changing the XmFontList for each context.

In Motif 2.0 and later, the separation of content from rendition information is continued and extended. The XmFontList is however now obsolete. In its place is the notion of a Render Table, represented by the XmRenderTable type. Throughout Motif, all the places which used to contain an XmFontList resource also now support an XmRenderTable equivalent.

A Render table consists of a sequence of XmRendition objects. Unlike an XmFontList, however, a Rendition object describes rather more than simply the fonts with which a compound string is drawn. A Rendition also describes color, line style, and columnar information. In Motif 2.0 and later, we can have multi-color compound strings inside a multi-column List. A Rendition object is also optimized: fonts can be loaded dynamically at the point of rendition rather than having to be pre-loaded at or before widget creation.

Render Tables and Rendition objects are shareable across contexts, and independently reference-counted. They are also inherited within the widget hierarchy, thus enabling a degree of consistent appearance for the application.

For backwards compatibility, the XmFontList is maintained as a type, although internally it is re-implemented as a skeleton XmRenderTable containing only font information. Any specified XmRenderTable resource for the widget concerned takes precedence.

Renditions

An XmRendition object is a pseudo-widget. Although not a true widget, it has many of the properties of one, namely resource attributes and a resource-style interface. Rendition attributes can also be specified in a resource file.


Creating Renditions

An XmRendition object is created through the routine XmRenditionCreate(), defined as follows:

XmRendition XmRenditionCreate ( Widget           widget,
                                XmStringTag      tag,
                                Arg              *argList,
                                Cardinal         argCount)
The widget parameter does not have to be related in any way to the place where the new XmRendition object is to be applied: it is simply used to find a connection to the X server, so that font and color resources of the XmRendition can be loaded. The tag parameter identifies the XmRendition object: compound strings which contain embedded tags are matched against this name when deciding whether to apply the XmRendition to the rendering process of the string. In effect, the XmRendition tag takes the place of the deprecated XmFontList tag. If tag is NULL, the value _MOTIF_DEFAULT_LOCALE is assigned. The argList and argCount parameters specify the resources of the XmRendition object, and are in exactly the same format as the usual argument lists supplied to widget creation routines.


Rendition Resources

XmRendition resources fall mainly into three groups: those which specify the font, those which specify the color, and those which specify the tab or multi-columnar data.

The font is specified through either the XmNfontName of the XmNfont resource. The XmNfontName resource is specified using a standard XLFD font description string. The XmNfont resource can be specified either as an X font (XFontStruct *) or an X font set (XFontSet); whichever you supply, you also need to set the XmNfontType resource to either XmFONT_IS_FONT or XmFONT_IS_FONTSET respectively. Specifying the XmNfont resource means of course that you need to load the font beforehand using XLoadFont(), XLoadQueryFont() or similar, described in Volume 1, Xlib Programming Manual. An alternative is to arrange to load the font when it is actually needed for rendering. This is done by specifying the XmNfontName in conjunction with the XmNloadModel resource. If the load model is XmLOAD_IMMEDIATE, the font name is loaded when the rendition is created. Otherwise, with a load model of XmLOAD_DEFERRED, the font is loaded when actually required. The following code fragment creates an XmRendition object which is configured with a deferred font.

extern Widget widget;
XmRendition rendition;
Arg args[4];
Cardinal n = 0;

XtSetArg (args[n], XmNfontName, "-*-courier-bold-o-*--*-140-*"); n++;
XtSetArg (args[n], XmNloadModel, XmLOAD_DEFERRED);               n++;
XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);                 n++;
rendition = XmRenditionCreate (widget, "my_bold_font", args, n);
If an XmNfont resource is specified, it takes precedence over any XmNfontName which is also specified. In addition to specifying the font, it is also possible to specify an underline or strike-through style for the font rendition. The resource XmNunderlineType controls any underlining. It has the following possible values:

XmDOUBLE_DASHED_LINE          XmDOUBLE_LINE
XmSINGLE_DASHED_LINE          XmSINGLE_LINE
XmNO_LINE
Similarly, a strike-through line can be specified using the XmNstrikethruType resource1. This resource has exactly the same range of values as the XmNunderlineType resource.

There are two color resources which can be specified for a rendition. These are XmNrenditionBackground and XmNrenditionForeground, which are Pixel-valued.

Multi-column specification is performed using the XmNtabList resource. Since this subject requires knowledge of other object types, the XmTab and XmTabList, each of which really require a section to themselves, discussion of this resource is reserved for the Tab Lists Section later in the chapter.

At this point something needs to be said about the way in which Render Tables work in order to understand the default values associated with each of the resources of an XmRendition object. When a particular compound string component is rendered, any tag associated with the component is matched against renditions in the current render table, starting at the head of the table. Any given rendition in the table may only specify a portion of the rendition information, for example just the foreground color. However, we cannot just draw a color, we also need to draw using a font, and maybe also a line style. There may indeed be renditions in the table which specify these, but they can all have different rendition tags which do not match the current component tag. This is where the notion of inheritance comes in. If any resource in a rendition has the reserved value XmAS_IS, its actual value is calculated by moving back up through the render table. The order of renditions in a render table is therefore important because it determines the order of inheritance. The default value for all resources is indeed XmAS_IS, except for the color resources, which default to XmUNSPECIFIED_PIXEL.

The last XmRendition attribute to mention is the XmNtag resource. This is simply the name passed through from the XmRenditionCreate() routine, and it defaults to _MOTIF_DEFAULT_LOCALE. The value NULL is therefore never applied to this resource, although an empty string can be used as the tag. This resource should not be manipulated by the programmer, who should treat the attribute as private to the toolkit.

The following code fragment fully specifies every resource for an XmRendition object. The rendition is unnamed when created, so that it defaults to _MOTIF_DEFAULT_LOCALE. Again, discussion of the XmTabList is reserved until later in the chapter.

extern Widget    widget;
XmRendition      rendition;
Arg              args[10];
Cardinal         n = 0;
Pixel            fg = ...; /* Whatever */
Pixel            bg = ...; /* Whatever */
XmNtabList       tlist = ...; /* Discussed later */

XtSetArg (args[n], XmNfontName, "fixed");          n++;
XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);   n++;
XtSetArg (args[n], XmNloadModel, XmLOAD_DEFERRED); n++;
XtSetArg (args[n], XmNunderlineType, XmNO_LINE);   n++;
XtSetArg (args[n], XmNstrikethruType, XmNO_LINE);  n++;
XtSetArg (args[n], XmNrenditionForeground, fg);    n++;
XtSetArg (args[n], XmNrenditionBackground, bg);    n++;
XtSetArg (args[n], XmNtabList, tlist);             n++;
rendition = XmRenditionCreate (widget, NULL, args, n);

Retrieving Rendition Resources

The attributes of an XmRendition object can be fetched using the routine XmRenditionRetrieve(). This routine has the following signature:

void XmRenditionRetrieve ( XmRendition      rendition,
                           Arg              *argList,
                           Cardinal         argCount)
Juts as we need to pass the address of a variable when fetching resources using XtGetValues(), so we need to pass an address to fetch a rendition resource. The following code fragment outlines the scheme:

extern XmRendition   rendition;
Arg                  args[4];
Cardinal             n = 0;
Pixel                fg;
unsigned char        underline;

XtSetArg (args[n], XmNunderlineType, &underline); n++;
XtSetArg (args[n], XmNrenditionForeground, &fg);  n++;
XmRenditionRetrieve (rendition, args, n);
All resources for an XmRendition object can be fetched at any time, except for the XmNtag resource, which should not be touched.


Updating Rendition Resources

Updating the rendition resources is also straightforward, and involves the routine XmRenditionUpdate(), which is defined as follows:

void XmRenditionUpdate ( XmRendition    rendition,
                         Arg            *argList,
                         Cardinal       argCount)
Again, all rendition resources can be dynamically changed except XmNtag. If the XmNfontName resource is changed, the XmNfont value is immediately set to NULL internally irrespective of whether the load model is XmLOAD_DEFERRED or XmLOAD_IMMEDIATE.


Freeing a Rendition

When a rendition object is no longer required, it should be freed using the routine XmRenditionFree(). This function has the prototype:

void XmRenditionFree (XmRendition rendition)
Note that rendition objects are shareable: we can add the same rendition object to multiple render tables, and remove the rendition from a table, freeing as and when required, because the rendition is reference counted: XmRenditionFree() does not actually free the object until the count is zero. How we add or remove a rendition from a render table is covered in the next section.

Render Tables

A render table, represented by the opaque type XmRenderTable, is a set of rendition objects. An XmRenderTable is not simply an array of XmRendition objects, but a distinct opaque type into which rendition objects must be explicitly merged.


Creating Render Tables

Rendition objects are added to a render table using the function XmRenderTableAddRenditions(), which is defined thus:

XmRenderTable
XmRenderTableAddRenditions ( XmRenderTable    old_table,
                             XmRendition      *merge_renditions,
                             Cardinal         new_rendition_count,
                             XmMergeMode      merge_mode)
The old_table parameter is the table into which we want to add the renditions merge_renditions. If old_table is NULL, a new render table is formed from the merge_renditions. Otherwise the merge_renditions are merged into the old_table. Clearly it is possible to have potential conflicts, because a rendition in the old_table and in the merge_renditions may have the same tag. How to resolve conflicts is determined by the merge_mode parameter. If merge_mode is XmMERGE_REPLACE, any rendition in old_table with the same tag as a rendition in the merge_renditions is ignored: merge_renditions take precedence. If merge_mode is XmMERGE_SKIP, the old_table takes precedence, and renditions are merged from merge_renditions if and only if old_table does not contain a rendition with a matching tag. These two cases are straightforward: use only the rendition in the old_table, or in the new merge list. More complex however are the cases described by the merge_mode values XmMERGE_NEW and XmMERGE_OLD. The value XmMERGE_NEW gives precedence to renditions in the merge_renditions list, except that if any resources associated with a rendition in the merge_renditions list have the value XmAS_IS, the value is copied from any rendition in the old_table with a matching tag. XmMERGE_OLD is similar: old_table renditions take precedence, but any resource in a rendition in old_table which is XmAS_IS takes its value from any rendition in merge_renditions with the same tag. The degree of control which can be exercised over the creation and manipulation of render tables using the various merge_mode values is quite complex. The simple case, however, is straightforward: the following specimen code creates a new render table by merging in two newly allocated rendition objects, then applies it to an unspecified widget:

extern Widget      widget;
XmRendition        renditions[2];
XmRenderTable      rtable;
Arg                args[4];
Cardinal           n;

n = 0;
XtSetArg (args[n], XmNfontName, "fixed");           n++;
XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);    n++;
XtSetArg (args[n], XmNloadModel, XmLOAD_IMMEDIATE); n++;
renditions[0] = XmRenditionCreate (widget, NULL, args, n);

n = 0;
XtSetArg (args[n], XmNfontName, "-*-courier-bold-o-*--*-140-*"); n++;
XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);                 n++;
XtSetArg (args[n], XmNloadModel, XmLOAD_DEFERRED);               n++;
renditions[1] = XmRenditionCreate (widget, "bold", args, n);

rtable = XmRenderTableAddRenditions ( NULL, renditions,
                                      XtNumber (renditions),
                                      XmMERGE_NEW);

XtVaSetValues (widget, XmNrenderTable, rtable, NULL);

Freeing Render Tables

An XmRenderTable is a dynamically allocated object. We must arrange to free the memory ourselves when we are finished using the table. This is done through the function XmRenderTableFree(), which is simply defined as follows:

void XmRenderTableFree (XmRenderTable table)
Render tables, like the rendition objects which they contain, are reference-counted internally by Motif. The table may not in fact be freed after calling XmRenderTableFree() unless the reference count becomes zero. XmRenderTableFree() does not free the constituent renditions: these need to be deallocated separately in their own right. Note that if we apply a render table to a widget through the XmNrenderTable resource, the widget takes a copy (or rather, updates the reference count) of the table and the renditions which it contains, and so the following code outlines the correct scheme when creating widgets using render tables:

extern Widget      parent;
Widget             new_widget;
XmRenderTable      render_table;
XmRendition        renditions[MAX_RENDITIONS];
Arg                args[MAX_ARGS];
int                i, n;

/* Create the renditions we require */
for (i = 0; i < MAX_RENDITIONS; i++) {
    ...
    renditions[i] = XmRenditionCreate (parent, "some_tag", args, n);
    ...
}

/* Create the Render Table */
render_table = XmRenderTableAddRenditions ( NULL, renditions,
                                            XtNumber (renditions),
                                            XmMERGE_NEW);

/* Create a new widget with the given render table */
/* Or indeed apply to an existing widget using XtSetValues() */
/* Either way, the widget "takes a copy" */
n = 0;
XtSetArg (args[n], XmNrenderTable, render_table); n++;
...
new_widget = XmCreatePushButton (parent, "name", args, n);

/* Free the allocated space */
/* Firstly, the constituent renditions */
for (i = 0; i < MAX_RENDITIONS; i++) {
    XmRenditionFree (renditions[i]);
}

/* Now the render table itself */
XmRenderTableFree (render_table);

Copying Render Tables

A copy of an existing render table can be achieved using the function XmRenderTableCopy(), which is defined thus:

XmRenderTable XmRenderTableCopy ( XmRenderTable    old_table,
                                  XmStringTag      *tags,
                                  Cardinal         num_tags)
The routine allocates storage for, and returns, a new render table based upon old_table. The tags parameter can be used as a filter: if tags is not NULL, only renditions within old_table whose tag is contained within the tags array are copied over. Otherwise, all renditions within the old_table are cloned. We could, for example, create a new render table which only contains the "fixed" rendition from the code fragment in the Creating Render Tables Section earlier in this chapter, using this routine:

XmRenderTable    fixed_table;
XmStringTag      tag = "fixed";
...
fixed_table = XmRenderTableCopy (new_table, &tag, 1);

Retrieving Renditions from Render Tables

Supposing we want to modify a rendition (or indeed multiple renditions) contained within an arbitrary render table. If we know the tag (or tags) associated with the rendition, we can fetch the renditions using either of the following routines:

XmRendition XmRenderTableGetRendition ( XmRenderTable    table,
                                        XmStringTag      tag)
XmRendition *XmRenderTableGetRenditions ( XmRenderTable    table,
                                          XmStringTag      *tags,
                                          Cardinal         num_tags)
Fetching a single rendition using XmRenderTableGetRendition() is straightforward enough. The complexity arises where we need to fetch multiple renditions. The array of renditions returned by XmRenderTableGetRenditions() is sized to the list of requested tags, and it may contain NULL entries if a given tag does not match anything in the render table. There is a one-to-one correspondence between the position of a given tag in the tags array and the returned render table list. In other words, the returned renditions are not necessarily in the order in which they appear in table, but most certainly are in the order in which the tags data appears. Therefore if, say, the first tag in tags does not match anything in table, the first element in the returned rendition array will be NULL. Both XmRenderTableGetRendition() and XmRenderTableGetRenditions() allocate memory which must be freed at an appropriate point by the programmer. The following code clarifies the situation:

extern XmRendertable   render_table;
XmStringTag            *tags[3];
XmRendition            *renditions;
int                    i;

tags[0] = (XmStringTag) "bold";
tags[1] = (XmStringTag) "red";
tags[2] = (XmStringTag) "no such rendition";

renditions = XmRendertableGetRenditions ( render_table,
                                          tags,
                                          XtNumber (tags));

if (renditions != (XmRendition *) 0) {
    /* The returned renditions array is the same size as the tags array */
    /* Furthermore, the renditions are in tag-array order */
    for (i = 0; i < XtNumber (tags); I++) {
        /* But an entry can be NULL if a given tag does not match */
        if (renditions[i] == (XmRendition) 0) {
            printf ("Warning: no such rendition %s\n", tags[i]);
        }
        else {
            /* Process the rendition, then free the allocated space */
            ...
            XmRenditionFree (renditions[i]);
        }
    }
    /* Free the allocated array pointer */
    XtFree ((char *) renditions);
}
This all assumes that we know the names of the tags in an arbitrary render table. Where this is not the case, we need to query the set of tags in a render table using the routine XmRenderTableGetTags(), which has the following functional prototype:

int XmRenderTableGetTags ( XmRenderTable    table,
                           XmStringTag      **tags)
The routine returns the number of renditions in the parameter table, and places the list of tags at the address specified by the tags parameter. The returned array is placed in dynamically allocated memory which the programmer should free at the appropriate point. The tags are in the order in which the renditions occur in the render table. The following routine simply lists all the tags in a given render table:

void ListRenditionTags (XmRenderTable table)
{
    int            count, i;
    XmStringTag    *tags;

    count = XmRenderTableGetTags (table, &tags);

    for (i = 0; i < count; i++) {
        printf ("Tag %d is %s\n", i, tags[i]);
        XtFree (tags[i]);
    }
    XtFree ((char *) tags);
}

Removing Renditions

If we know the tag or tags associated with a group of rendition objects in a render table, we can remove the renditions from the table through the routine XmRenderTableRemoveRenditions(), defined as follows:

XmRenderTable XmRenderTableRemoveRenditions ( XmRenderTable    old_table,
                                              XmStringTag      *tags,
                                              int              tag_count)
The function returns a new render table formed by copying all renditions in old_table which do not have a matching tag in the tags array. A side effect of calling XmRenderTableRemoveRenditions() is that the reference count associated with old_table is decremented. An exception to this is where the tags array is NULL: the old_table is returned unmodified in any way, so that in effect the routine does nothing. Any renditions which are removed from old_table also have their reference counts decremented by the routine. Which means that if we remove the last rendition from a table, and the rendition is only referenced by this table, both the rendition and the table are freed by the toolkit.

Tab Lists

A TabList is the means by which Motif implements multi-column layout for compound strings. TabLists consist of Tab objects; a Tab is simply an offset across the widget where the compound string is rendered. The Tab represents a location at which to start drawing a compound string segment.

There are four aspects to achieving a multi-column layout.

  1. The Tab objects themselves, describing specific locations across a widget. Each Tab specifies a single logical column starting point.
  2. The TabList, which is an ordered set of Tabs.The TabList taken as a whole provides the multi-column appearance.
  3. Compound string components of type XmSTRING_COMPONENT_TAB; these can be embedded into a compound string, informing the toolkit that the following compound string text component is to be drawn dependent upon the current Tab information.
  4. A Render Table, which contains a TabList as a constituent Rendition resource.
Tabs and TabLists are inoperative unless the compound string to be drawn contains the special XmSTRING_COMPONENT_TAG component.


Tabs

Tabs are implemented through the XmTab object. The XmTab object is an opaque handle onto a structure which describes an offset across the widget where a compound string is rendered. Each XmTab is a shareable, reference counted resource which can be used in multiple tablists.


Creating an XmTab

An XmTab is created using the function XmTabCreate(), which is defined as follows:

XmTab XmTabCreate ( float              value,
                    unsigned char      units,
                    XmOffsetModel      offset_model,
                    unsigned char      alignment,
                    char               *decimal)
The value parameter is interpreted in terms of units, which can be one of the following:

XmPIXELS
XmMILLIMETERS                Xm100TH_MILLIMETERS
XmINCHES                     Xm1000TH_INCHES
XmCENTIMETERS
XmPOINTS                     Xm100TH_POINTS
XmFONT_UNITS                 Xm100TH_FONT_UNITS
The offset_model parameter determines whether the tab position is an absolute distance across the widget where rendering is to take place (XmABSOLUTE), or whether the tab position is calculated relative to the previous tab stop (XmRELATIVE). The alignment parameter specifies how text is aligned with respect to the tab location. Only XmALIGNMENT_BEGINNING is implemented as of Motif 2.1.10. The decimal parameter specifies the multi-byte character in the current locale which is used as a decimal point. This is currently unused.

The following code fragment creates a tab stop 1.5 inches from the start of compound string rendering:

XmTab tab;

tab = XmTabCreate ( (float) 1.5,
                    XmINCHES,
                    XmABSOLUTE,
                    XmALIGNMENT_BEGINNING,
                    ".");

Freeing an XmTab

When an XmTab is no longer required, the memory associated with the object should be freed using the routine XmTabFree(), which is defined as follows:

void XmTabFree (XmTab tab)

Fetching XmTab values

The values associated with an XmTab object can be fetched using the routine XmTabGetValues(). This routine is defined as follows:

float XmTabGetValues ( XmTab              tab,
                       unsigned char      *units,
                       XmOffsetModel      *model,
                       unsigned char      *alignment,
                       char               **decimal)
The interpretation of each of the parameters is directly analogous to XmTabCreate(), except that in each case an address is required to hold the returned data. The tab parameter is the object for which the values are required. The following code fragment outlines the basic usage of the routine:

extern XmTab     tab;
XmOffsetModel    offset_model;
unsigned char    units;
unsigned char    alignment;
char             *decimal;
float            value;

value = XmTabGetValues ( tab,
                         &units,
                         &offset_model,
                         &alignment,
                         &decimal);
Note that the returned data at the decimal address directly points into the tab object structure: decimal does not contain a dynamically allocated copy, and so it should neither be modified nor freed by the programmer.


Setting the XmTab value

The value associated with a tab object can be modified using the routine XmTabSetValues(). There is no routine available to modify the units, alignment, offset model, or decimal associated with a tab once it has been created. To do these operations, you need to explicitly remove the tab object from the tab list concerned, and create a new tab with the required attributes from scratch. XmTabSetValues() is defined as follows:

void XmTabSetValues (XmTab tab, float value)

TabLists

A Tab List, represented by the opaque type XmTabList, is an ordered set of tab objects. An XmTabList is not simply an array of XmTab objects, but a distinct opaque type into which tab objects must be explicitly merged.


Creating TabLists

XmTab objects can be added to an XmTabList using the convenience routine XmTabListInsertTabs(), which is defined as follows:

XmTabList XmTabListInsertTabs ( XmTabList    tablist,
                                XmTab        *tabs,
                                Cardinal     tab_count,
                                int          position)
The parameter tablist can be NULL, which means that a new tab list is to be formed out of the XmTab objects specified through the tabs array. If tabs is NULL, the original tablist is returned unmodified. This means that in effect that there is no way to create an XmTabList independently of a set of XmTab objects. The position parameter specifies where the tabs are to be inserted into tablist. If position is 0, the tabs are inserted at the head of tablist. If position is 1, tabs are inserted after the current first tab of tablist, and so forth. To insert using the end of the tablist, position should be specified as a negative quantity.2 A negative position inserts XmTab objects in reverse order at the end of the XmTabList, such that the first new tab in the tabs array becomes the last tab in the newly formed tab set.

Much of the implementation of XmTabListInsertTabs() is complex and probably over-engineered. The simple case, however, is as given in the following code fragment. This creates two XmTab objects, and forms a new XmTabList from them:

XmTab        tabs[2];
XmTabList    tabList;

tabs[0] = XmTabCreate ((float) 1.0, XmINCHES, XmABSOLUTE,
                                XmALIGNMENT_BEGINNING, ".");
tabs[1] = XmTabCreate ((float) 1.5, XmINCHES, XmRELATIVE,
                                XmALIGNMENT_BEGINNING, ".");
tabList = XmTabListInsertTabs (NULL, tabs, XtNumber (tabs), 0);

Freeing TabLists

The XmTabList object uses dynamically allocated memory. This should be reclaimed when no longer in use through the routine XmTabListFree(), which is defined as follows:

void XmTabListFree (XmTabList tablist)

Manipulating Tabs in a TabList

The following routines are available for manipulating the XmTab elements in an XmTabList:

XmTabList    XmTabListCopy (XmTabList tablist, int offset, Cardinal count)3
XmTab        XmTabListGetTab (XmTabList tablist, Cardinal position)
XmTabList    XmTabListRemoveTabs ( XmTabList     tablist,
                                   Cardinal      *positions,
                                   Cardinal      position_count)
XmTabList    XmTabListReplacePositions ( XmTabList    tablist,
                                         Cardinal     *positions,
                                         XmTab        *tabs,
                                         Cardinal     tab_count)
int          XmTabListTabCount (XmTabList tablist)
XmTabListCopy() copies count XmTab objects from the XmTabList specified by the tablist parameter, starting with the tab at the position specified by offset. If offset is zero, tabs are copied from the start of the list. If count is zero, all tabs from offset to the end of the tablist are copied. If the offset is negative, tabs are copied in reverse order from the end of the tablist. Copying an entire tablist is therefore simply a matter of calling the following code:

extern XmTabList old_tablist;
XmTabList full_copy = XmTabListCopy (old_tablist, 0, 0);
An XmTab object can be fetched from a tab list using the routine XmTabListGetTab(). The routine simply fetches the tab at the designated position within the given tablist. The first tab in the list is at position zero. The routine returns a copy of the XmTab object at position, and it is the responsibility of the programmer to free the allocated memory at a suitable point using XmTabFree().

If you wanted to fetch the last XmTab object in a tablist, you would need to know the number of tabs in the list in the first place. The routine XmTabListTabCount() can be used for this: it simply returns the number of tabs in the specified tablist. The following code therefore fetches the last XmTab object:

extern XmTabList tablist;
XmTab last_tab;
int count;

count = XmTabListTabCount (tablist);

if (count > 0)
    /* TabLists offset from zero */
    last_tab = XmTabListGetTab (tablist, count - 1);
...
/* Remember to reclaim the memory: we are returned a copy */
XmTabFree (last_tab);
XmTab objects can be removed from a tablist using the routine XmTabListRemoveTabs(). It creates and returns a new XmTabList formed out of an existing tablist, but with tabs at designated positions excluded. The original tablist has its reference count internally decremented internally by the routine.

XmTabListReplacePositions() can be used to substitute a set of XmTab objects within tablist. The positions parameter specifies an array of offsets representing the locations where tabs are to be replaced. The tabs parameter is the set of new tabs to be merged into the tablist. There is a one-to-one correspondence between the offsets in the positions parameter and the tabs specifier. That is, the nth XmTab in the tabs list is placed at the offset designated by the nth offset in the positions array. For example, the following code replaces the third and fifth tabs in an unspecified tablist:

extern XmTabList   old_tablist;
XmTabList          new_tablist;
XmTab              new_tabs[2];
Cardinal           positions[2];

new_tabs[0] = XmTabCreate (...);
new_tabs[1] = XmTabCreate (...);

positions[0] = 2; /* The third position - offsets are from zero */
positions[1] = 4; /* The fifth position */

new_tablist = XmTabListReplacePositions ( old_tablist,
                                          positions,
                                          new_tabs,
                                          XtNumber (new_tabs));

Using TabLists

TabLists are used by specifying them as an attribute of a rendition in a render table. If we require a multi-column layout, we need to make sure that the render table which is being used to render our compound strings contains a TabList.4 The following code fragment outlines the general scheme of things:

XmTab                tabs[MAX_TABS];
XmTabList            tablist;
XmRendition          renditions[MAX_RENDITIONS];
XmRenderTable        rendertable;
Arg                  args[MAX_ARGS];
int                  i, n;
extern Widget        widget;
...
/* Create the XmTab objects */
tabs[i] = XmTabCreate ((float) 1.5, XmINCHES, XmABSOLUTE,
XmALIGNMENT_BEGINNING, ".");
...
/* Create the XmTabList from the XmTab objects */
tablist = XmTabListInsertTabs (NULL, tabs, XtNumber (tabs), 0);
...
/* Create an XmRendition that uses the XmTabList */
/* Other XmRendition attributes are ignored here */
n = 0;
XtSetArg (args[n], XmNtabList, tablist); n++;
...
renditions[i] = XmRenditionCreate (widget, "rendition tag", args, n);
...
/* Create an XmRenderTable which uses the XmRendition objects */
rendertable = XmRenderTableAddRenditions ( NULL,
                                           renditions,
                                           XtNumber (renditions),
                                           XmMERGE_NEW);
...
/* Specify the XmRenderTable for the widget concerned */
XtVaSetValues (widget, XmNrenderTable, rendertable, NULL);
...
/* The compound strings associated with widget are now */
/* drawn in multi-column format if the strings contain */
/* embedded XmSTRING_COMPONENT_TAB components */
...
/* Free the memory used above. */
/* The widget takes a copy of the XmRenderTable */
/* Or rather, increases the reference count */
/* The XmRendition takes a copy of the XmTabList */
/* (increases the reference count) */
for (i = 0; i < XtNumber (tabs); i++) {
    XmTabFree (tabs[i]);
}
XmTabListFree (tablist);

for (i = 0; i < XtNumber (renditions); i++) {
    XmRenditionFree (renditions[i]);
}

XmRenderTableFree (rendertable);
...

An Example

The code in Example 24-1 creates a multi-column, multi-color, multi-font List widget. It brings together all the threads of this chapter by utilizing fully the tab, tab list, rendition, and render table functionality as described above.

Example  24-1 The rendered_list.c program

/* rendered_list.c: illustrates all the features of
** render tables and renditions by creating a 
** multi-column, multi-font, multi-color List widget.
*/

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

/* ConvertStringToPixel()
** A utility function to convert a color name to a Pixel 
*/
Pixel ConvertStringToPixel (Widget widget, char *name)
{
    XrmValue from_value, to_value; /* For resource conversion */

    from_value.addr = name;
    from_value.size = strlen(name) + 1;
    to_value.addr = NULL;
    XtConvertAndStore (widget,
    XmRString, &from_value,
    XmRPixel, &to_value);

    if (to_value.addr) {
        return (*((Pixel*) to_value.addr));
    }

    return XmUNSPECIFIED_PIXEL;
}

/*
** A convenient structure to hold the data
** for creating various renditions
*/
typedef struct RenditionData_s
{
    char       *tag;
    char       *color;
    char       *font;
} RenditionData_t;

#define MAX_COLUMNS    4

RenditionData_t rendition_data[MAX_COLUMNS] =
{
    { "one", "red", "fixed" },
    { "two", "green", 
            "-adobe-helvetica-bold-r-normal--10-100-75-75-*-*-iso8859-1" },
    { "three", "blue", "bembo-bold" },
    { "four", "orange",
            "-adobe-*-medium-i-normal--24-240-75-75-*-*-iso8859-1" }
};

/*
** Arbitrary data to display in the List
*/
static char *poem[] =
{
    "Mary", "had a", "little", "lamb",
    "Its", "fleece", "was white", "as snow",
    "And", "everywhere that", "Mary", "went",
    "The", "lamb was", "sure", "to follow",
    (char *) 0
};

/*
** CreateListData(): routine to convert the
** poem into an array of compound strings
*/
XmStringTable CreateListData (int *count)
{
    XmStringTable    table = (XmStringTable) 0;
    int              line = 0;
    int              column = 0;
    int              index = 0;
    XmString         entry = (XmString) 0;
    XmString         row = (XmString) 0;
    XmString         tmp = (XmString) 0;
    XmString         tab;

    tab = XmStringComponentCreate (XmSTRING_COMPONENT_TAB, NULL, 0);

    while (poem[index] != (char *) 0) {
        /* create a compound string, using the rendition tag */
        entry = XmStringGenerate ((XtPointer) poem[index], 
                                NULL, 
                                XmCHARSET_TEXT, 
                                rendition_data[column].tag);

        if (row != (XmString) 0) {
            tmp = XmStringConcat (row, tab);
            XmStringFree (row);
            row = XmStringConcatAndFree (tmp, entry);
        }
        else {
            row = entry;
        }

        ++column;

        if (column == MAX_COLUMNS) {
            if (table == (XmStringTable) 0) {
                table = (XmStringTable) XtMalloc ((unsigned)
                sizeof (XmString));
                }
            else {
                table = (XmStringTable) XtRealloc ((char *) table,
                (unsigned) (line + 1) * sizeof (XmString));
            }

            table[line++] = row;
            row = (XmString) 0;
            column = 0;
        }

        index++;
    }

    XmStringFree (tab);

    table[line] = (XmString) 0;

    *count = line;

    return table;
}

main (int argc, char *argv[])
{
    Widget             toplevel, rowcol, list;
    XtAppContext       app;
    Arg                args[10];
    XmTab              tabs[MAX_COLUMNS];
    XmTabList          tablist;
    XmRendition        renditions[MAX_COLUMNS];
    XmRenderTable      rendertable;
    XmStringTable      xmstring_table;
    int                xmstring_count;
    Pixel              pixels[MAX_COLUMNS];
    int                n, i;

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

    /* Create some colors */
    for (i = 0; i < MAX_COLUMNS; i++) {
        pixels[i] = ConvertStringToPixel (toplevel,
        rendition_data[i].color);
    }

    /* Create tab stops for columnar output */
    for (i = 0; i < MAX_COLUMNS; i++) {
        tabs[i] = XmTabCreate ((float) 1.5, 
                                XmINCHES, 
                                ((i == 0) ? XmABSOLUTE : XmRELATIVE),
                                XmALIGNMENT_BEGINNING, 
                                ".");
    }

    /* Create a tablist table which contains the tabs */
    tablist = XmTabListInsertTabs (NULL, tabs, XtNumber (tabs), 0);

    /* Create some multi-font/color renditions, and use the tablist */
    /* This will be inherited if we use it on the first rendition */
    for (i = 0; i < MAX_COLUMNS; i++) {
        n = 0;

        if (i == 0) {
            XtSetArg (args[n], XmNtabList, tablist); n++;
        }

        XtSetArg (args[n], XmNrenditionForeground, pixels[i]);   n++;
        XtSetArg (args[n], XmNfontName, rendition_data[i].font); n++;
        XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);         n++;
        renditions[i] = XmRenditionCreate (toplevel,
                                    rendition_data[i].tag,
                                    args, n);
    }

    /* Create the Render Table */
    rendertable = XmRenderTableAddRenditions ( NULL,
                                               renditions,
                                               XtNumber (renditions),
                                               XmMERGE_NEW);

    /* Create the multi-column data for the list */

    xmstring_table = CreateListData (&xmstring_count);

    /* Create the List, using the render table */
    n = 0;
    XtSetArg (args[n], XmNrenderTable, rendertable);             n++;
    XtSetArg (args[n], XmNitems, xmstring_table);                n++;
    XtSetArg (args[n], XmNitemCount, xmstring_count);            n++;
    XtSetArg (args[n], XmNwidth, 400);                           n++;
    XtSetArg (args[n], XmNvisibleItemCount, xmstring_count + 1); n++;
    list = XmCreateScrolledList (rowcol, "list", args, n);
    XtManageChild (list);

    /* Free the memory now the widget has the data */
    /* First, the compound strings */
    for (i = 0; i < xmstring_count; i++)
        XmStringFree (xmstring_table[i]);
    XtFree ((char *) xmstring_table);

    /* Secondly, the XmTab objects */
    for (i = 0; i < XtNumber (tabs); i++)
        XmTabFree (tabs[i]);

    /* Thirdly, the XmTabList object */
        XmTabListFree (tablist);

    /* Fourthly, the XmRendition objects */
    for (i = 0; i < XtNumber (renditions); i++)
        XmRenditionFree (renditions[i]);

    /* Lastly, the XmRenderTable object */
    XmRenderTableFree (rendertable);

    XtManageChild (rowcol);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
The output from the program is given in Figure 24-1. Of course, the color details of the program are lost on a black-and-white printed page, although it is clear even in grey scale that the List contains differently colored compound string segments. The multi-font and multi-column aspects of the program are fully in evidence.

Figure  24-1 The output of rendered_list

Render Tables and Resource Files

RenderTables, Renditions, Tabs, and TabLists can all be specified in resource files.Resource converters are installed for each of the various attribute types automatically by the Motif toolkit. Since the rendition objects described in this chapter are implemented very much as pseudo-widgets, the resource specifications required to describe the objects are natural and familiar. Only the specification of Tabs and Tab lists present new formats. Since renditions contain tab lists, we should start from the bottom up and describe the specification of a tab list in a resource file first.


Tabs and TabList

In a resource file, a tab list is represented by a comma-separated list of tab specifications. Each tab specification has the formal syntax:

tab := float [ WHITESPACE units ]
float := [ sign ] [[ DIGIT]*. ]DIGIT+
sign := +
Less formally, a tab is represented by a floating point number, optionally preceded by a plus sign, and optionally followed by a units description. The following are examples of correctly formed tab specifications:

12.4 in
+1in
+14.56 mm
0.15 font_units
+116.0 pix
The presence of a plus sign indicates that the tab is relative to the previous tab stop in the current tab list. If no plus sign is given, the tab is interpreted as an absolute value across the widget wherever it is used. In code, we would write the following in order to duplicate the effect of the tab resource specifications given above:

XmTab a = XmTabCreate (12.4, XmINCHES, XmABSOLUTE, ...)
XmTab b = XmTabCreate (1.0, XmINCHES, XmRELATIVE, ...)
XmTab c = XmTabCreate (14.56, XmMILLIMETERS, XmRELATIVE, ...)
The units descriptions are in the same format as a normal widget XmNunitType resource. That is, the following are acceptable to the resource converter:

pixels             pix          pixel
inches             in           inch
centimeters        cm           centimeter
millimeters        mm           millimeter
points             pt           point
font_units         fu           font_unit
Note that the converter does not directly handle the fractional unit types (Xm1000TH_INCHES, Xm1000TH_MILLIMETERS, Xm100TH_POINTS, Xm100TH_FONT_UNITS). To specify these in a resource file, you need to divide the float value by the appropriate quantity. For example, although we can write in code...

XmTab tab = XmTabCreate (150.0, Xm1000TH_INCHES, XmABSOLUTE, ...)
... in a resource file, we need to specify the following:

0.15 in
A tab list as specified in a resource file is a simple comma separated set of tab specifications. The following are examples:

*tabList: 1.5in, +1.5in, +1.5in
XApplication*my_rendition.tabList: 220.0 mm, 450.0 mm, +1.0 in

Rendition Resources

The rendition object is a pseudo-widget, and nowhere is this more true than in specifying external rendition resources. We simply use the rendition tag as the X resource key, and thereafter pretend the object is a normal widget. For example, the following resources specify a rendition whose tag is "my_rendition". The rendition is filly loaded. As is normal, the Xm prefix is stripped off any enumerated types when specifying resource file entries.

*my_rendition.fontName: fixed
*my_rendition.fontType: FONT_IS_FONT
*my_rendition.loadModel: LOAD_DEFERRED
*my_rendition.renditionForeground: red
*my_rendition.renditionBackground: blue
*my_rendition.strikethruType: AS_IS
*my_rendition.underlineType: SINGLE_LINE
*my_rendition.tablist: 1in, +1.5in

RenderTable Resources

Just as a tablist is specified by a comma-separated list of tabs, a render table is specified externally to code by a list of rendition tags. The list of rendition tags can be whitespace or comma separated. The following are valid render table specifications:

*XmList.renderTable: renditionA, my_rendition
*.renderTable: r1 r2 r3, r4

Missing Fonts and Renditions

In Motif 1.2, whenever an attempt was made to render a compound string, the tags associated with the compound string segments were matched against tags within the XmFontList which was being used to display the string. Whenever there occurred a mismatch, if a segment referred to a fontlist tag which was not satisfied by the current XmFontList, the toolkit would simply use the first font in the font list. There was nothing that the programmer could dynamically do at that point to rectify the situation, either by directly tendering an alternative font, or indirectly by querying the user for a preferred alternative.

In Motif 2.1, the situation is entirely different. Whenever an attempt is made to render a compound string and there is a mismatch between the required string tags and the current render table, the toolkit now invokes callbacks. In the callback, the programmer can take whatever action is necessary in order to find an appropriate solution to the rendering problem. Once an appropriate font or rendition is found, the programmer simply returns the new font or rendition to the toolkit through elements in the callback structure.

The new callbacks are implemented in the XmDisplay object. There are two callback types, each of which is invoked depending upon the nature of the problem to hand: the XmNnoRenditionCallback, and the XmNnoFontCallback.

The XmNnoRenditionCallback is invoked when an attempt is made by the toolkit to draw a compound string segment, and no matching rendition can be found in the current render table.

The XmNnoFontCallback is called by the toolkit if an XmRendition object refers to a font name, and that font cannot be located on the system. Recall that fonts in a rendition object can have the load model XmLOAD_DEFERRED: this means that the potential exists for mis-specifying a font name when creating a rendition. The effects of the error are not apparent until much later, when an attempt is made to actually render using the rendition concerned. This is different to the Motif 1.2 handling of fonts because the fonts within an XmFontList are loaded immediately on creation of the object.

The XmNnoFontCallback is therefore an internal error in the specification of the attributes of a rendition. The XmNnoRenditionCallback indicates a flaw in either the render table itself, or in the tags associated with segments in the compound string.


Callback Structure

Both the XmNnoFontCallback and the XmNnoRenditionCallback are passed the following structure by the Motif toolkit when invoked:

typedef struct
{
    int                reason;
    XEvent             *event;
    XmRendition        rendition;
    char               *font_name;
    XmRenderTable      render_table;
    XmStringTag        tag;
} XmDisplayCallbackStruct;
Not all the fields of the structure are applicable to each of the callback types. The render_table and tag elements are only applicable to XmNnoRenditionCallback callbacks, and the rendition and font_name fields are only applicable to XmNnoFontCallback routines. The reason field will be either XmCR_NO_FONT or XmCR_NO_RENDITION.


XmNnoFontCallback

When an attempt is made to render using a rendition which refers to a font name which cannot be located, the XmNnoFontCallback is called. The programmer can deduce which font could not be loaded from the font_name element of the callback structure. She can remedy the problem simply by choosing an alternative font, and then updating the rendition element. The following code fragment outlines the basic scheme of operations:

void no_font_callback ( Widget       widget,
                        XtPointer    client_data,
                        XtPointer    call_data)
{
    XmDisplayCallbackStruct *dptr = (XmDisplayCallbackStruct *) call_data;
    Arg args[4];
    int n;

    printf ("Warning: could not load font %s\n", dptr->font_name);

    /* Just use a simple alternative */
    /* A better algorithm would try and find */
    /* a close match to the missing font type */

    n = 0;
    XtSetArg (args[n], XmNfontName, "fixed");           n++;
    XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);    n++;
    XtSetArg (args[n], XmNloadModel, XmLOAD_IMMEDIATE); n++;

    XmRenditionUpdate (dptr->rendition, args, n);
}

...
extern Display *display;
Widget xmdisplay = XmGetXmDisplay (display);
XtAddCallback (xmdisplay, XmNnoFontCallback, no_font_callback, NULL);
...

XmNnoRenditionCallback

If a compound string segment refers to a rendition tag which is absent from the current render table, the XmNnoRenditionCallback is called. Again, the programmer can deduce the missing information from the callback structure: the tag element identifies the missing rendition. The programmer simply has to update the render_table element field of the structure by merging a new rendition. The only issue is one of memory management: the render_table element is allocated for the purpose of the callback, and so the programmer should make sure that the old value of render_table is freed (reference count reduced) if she modifies the element. The following code fragment shows how this can be done:

void no_rendition_callback ( Widget       widget,
                             XtPointer    client_data,
                             XtPointer    call_data)
{
    XmDisplayCallbackStruct *dptr = (XmDisplayCallbackStruct *) call_data;
    XmRendition new_rendition;
    XmRenderTable new_table;
    Arg args[4];
    int n;

    printf ("Warning: could not find a rendition with tag %s\n", dptr->tag);

    /* Again, this algorithm is slightly defective: */
    /* we ought to try and deduce a sensible rendition */
    /* given whatever clues that the missing tag can tell us */

    n = 0;
    XtSetArg (args[n], XmNfontName, "fixed");
    XtSetArg (args[n], XmNfontType, XmFONT_IS_FONT);    n++;
    XtSetArg (args[n], XmNloadModel, XmLOAD_IMMEDIATE); n++;

    /* Make sure we create the rendition using the missing tag */
    new_rendition = XmRenditionCreate (widget, dptr->tag, args, n);

    /* Merge into the current render table */
    new_table = XmRenderTableAddRenditions (dptr->render_table,
                                            &new_rendition, 1,
                                            XmMERGE_NEW);

    /* Decrement the old table reference count */
    XmRenderTableFree (dptr->render_table);

    /* Reset the element */
    dptr->render_table = new_table;
}

...
extern Display *display;
Widget xmdisplay = XmGetXmDisplay (display);
XtAddCallback (xmdisplay,
XmNnoRenditionCallback, no_rendition_callback, NULL);
...

Nota Bene

An important difference exists between Motif 1.2 and Motif 2.1 behavior in this respect: whereas Motif 1.2 uses the first font in an XmFontList by default whenever there is a mismatch between compound string segment and font list tags, in Motif 2.1 there is no default behavior. That is, if a programmer fails to provide an alternative font or rendition through the appropriate callback then the given compound string is simply not drawn at all.

Summary

Render tables are shareable resources. They extend the separation between the abstract representation of a compound string and the way it is rendered which was implicit in the Motif 1.2 XmFontList. What the Render Table provides is the ability to specify not only font information, but also color and multi-column detail, and as such they offer far greater control over the presentation and layout of compound strings.

A side effect of the XmFontList deprecation is a greater degree of compatibility with the underlying X Internationalization modules, because the fonts contained within the renditions of a render table are now properly only held internally in the form of XFontStruct or X FontSet data.



1 It ought to have been properly named XmNstrikeThroughType, in our humble opinion.

2 This is not consistent with other Motif insertion routines, where zero is generally taken to mean the end of the given list of objects, and negative positions are disallowed.

3 Erroneously listed as XmTabListTabCopy() in Volume 6B, Motif Reference Manual. Humble Apologies!

4 The exception to the rule is the Container widget, which has an XmNdetailTabList resource independent of its XmNrendertable resource.






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.