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: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.XmRendition XmRenditionCreate ( Widget widget, XmStringTag tag, Arg *argList, Cardinal argCount)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.
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: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);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.XmDOUBLE_DASHED_LINE XmDOUBLE_LINE XmSINGLE_DASHED_LINE XmSINGLE_LINE XmNO_LINEThere 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: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:void XmRenditionRetrieve ( XmRendition rendition, Arg *argList, Cardinal argCount)All resources for an XmRendition object can be fetched at any time, except for the XmNtag resource, which should not be touched.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);Updating Rendition Resources
Updating the rendition resources is also straightforward, and involves the routine XmRenditionUpdate(), which is defined as follows: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.void XmRenditionUpdate ( XmRendition rendition, Arg *argList, Cardinal argCount)Freeing a Rendition
When a rendition object is no longer required, it should be freed using the routine XmRenditionFree(). This function has the prototype: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.void XmRenditionFree (XmRendition rendition)
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: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:XmRenderTable XmRenderTableAddRenditions ( XmRenderTable old_table, XmRendition *merge_renditions, Cardinal new_rendition_count, XmMergeMode merge_mode)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: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:void XmRenderTableFree (XmRenderTable table)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: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 XmRenderTableCopy ( XmRenderTable old_table, XmStringTag *tags, Cardinal num_tags)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: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:XmRendition XmRenderTableGetRendition ( XmRenderTable table, XmStringTag tag) XmRendition *XmRenderTableGetRenditions ( XmRenderTable table, XmStringTag *tags, Cardinal num_tags)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: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); }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:int XmRenderTableGetTags ( XmRenderTable table, XmStringTag **tags)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: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.XmRenderTable XmRenderTableRemoveRenditions ( XmRenderTable old_table, XmStringTag *tags, int tag_count)
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.
Tabs and TabLists are inoperative unless the compound string to be drawn contains the special XmSTRING_COMPONENT_TAG component.
- The Tab objects themselves, describing specific locations across a widget. Each Tab specifies a single logical column starting point.
- The TabList, which is an ordered set of Tabs.The TabList taken as a whole provides the multi-column appearance.
- 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.
- A Render Table, which contains a TabList as a constituent Rendition resource.
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:The value parameter is interpreted in terms of units, which can be one of the following:XmTab XmTabCreate ( float value, unsigned char units, XmOffsetModel offset_model, unsigned char alignment, char *decimal)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.XmPIXELS XmMILLIMETERS Xm100TH_MILLIMETERS XmINCHES Xm1000TH_INCHES XmCENTIMETERS XmPOINTS Xm100TH_POINTS XmFONT_UNITS Xm100TH_FONT_UNITSThe 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: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:float XmTabGetValues ( XmTab tab, unsigned char *units, XmOffsetModel *model, unsigned char *alignment, char **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.extern XmTab tab; XmOffsetModel offset_model; unsigned char units; unsigned char alignment; char *decimal; float value; value = XmTabGetValues ( tab, &units, &offset_model, &alignment, &decimal);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: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.XmTabList XmTabListInsertTabs ( XmTabList tablist, XmTab *tabs, Cardinal tab_count, int position)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: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: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)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().extern XmTabList old_tablist; XmTabList full_copy = XmTabListCopy (old_tablist, 0, 0);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:
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.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);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.
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./* 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); }
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: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:tab := float [ WHITESPACE units ] float := [ sign ] [[ DIGIT]*. ]DIGIT+ sign := +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:12.4 in +1in +14.56 mm 0.15 font_units +116.0 pixThe units descriptions are in the same format as a normal widget XmNunitType resource. That is, the following are acceptable to the resource converter:XmTab a = XmTabCreate (12.4, XmINCHES, XmABSOLUTE, ...) XmTab b = XmTabCreate (1.0, XmINCHES, XmRELATIVE, ...) XmTab c = XmTabCreate (14.56, XmMILLIMETERS, XmRELATIVE, ...)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...pixels pix pixel inches in inch centimeters cm centimeter millimeters mm millimeter points pt point font_units fu font_unit... in a resource file, we need to specify the following:XmTab tab = XmTabCreate (150.0, Xm1000TH_INCHES, XmABSOLUTE, ...)A tab list as specified in a resource file is a simple comma separated set of tab specifications. The following are examples:0.15 in*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: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.typedef struct { int reason; XEvent *event; XmRendition rendition; char *font_name; XmRenderTable render_table; XmStringTag tag; } XmDisplayCallbackStruct;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.
|