This chapter describes another control that the user
can manipulate. The List widget displays a number of text choices that
the user can select interactively.
Almost every application needs to display lists of
choices to the user. This task can be accomplished in many ways,
depending on the nature of the choices. For example, a group of
ToggleButtons is ideal for displaying configuration settings that can
be individually set and unset and then applied all at once. A list of
commands can be displayed in a PopupMenu, or for a more permanent
command palette, a RowColumn or Form widget can manage a group of
PushButton widgets. But for displaying a list of text choices, such as
a list of files to be opened or a list of fonts to be applied to text,
nothing beats a List widget.
A List widget displays a single column of text choices that can be selected or deselected using either the mouse or the keyboard. Each choice is represented by a single-line text element specified as a compound string. the figure shows a typical List widget.
Internally, the List widget operates on an array of
compound strings that are defined by the application. (See Chapter 19,
Compound Strings, for a discussion of how to create and manage
compound strings.) Compound strings that use multiple fonts are
allowed, but the List widget does not render these items very well.
Each string is an element of the array, with the first position
starting at one, as opposed to position zero, which is used in C-style
arrays. The user can select a particular choice by clicking and
releasing the left mouse button on the item. All of the items in the
list are available to the user for selection at all times; you cannot
make individual items unselectable. What happens when an item is
selected is up to the application callback routines invoked by the List
widget.
A List widget is typically a child of a
ScrolledWindow, so that the List is displayed with ScrollBars attached
to it. The selection mechanism for the List does not change, so the
user can still select items as before, but the user can now use the
ScrollBars to adjust the items in the list that are visible.
The List widget supports four different selection
policies:
Using List widgets is fairly straightforward. An
application that uses the List widget must include the header file <
Xm/List.h>. This header file declares the types of the public List
functions and the widget class name xmListWidgetClass. A List
widget can be created as shown in the following code fragment:
Widget list; list = XtVaCreateManagedWidget ("name", xmListWidgetClass, parent, resource-value-list, NULL);the source code shows a program that creates a simple List widget. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1.
/* simple_list.c -- introduce the List widget. Lists present * a number of comound strings as choices. Therefore, strings * must be converted before set in lists. Also, the number of * visible items must be set or the List defaults to 1 item. */ #include <Xm/List.h> char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; main(argc, argv) int argc; char *argv[]; { Widget toplevel; XtAppContext app; int i, n = XtNumber (months); XmStringTable str_list; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); str_list = (XmStringTable) XtMalloc (n * sizeof (XmString)); for (i = 0; i < n; i++) str_list[i] = XmStringCreateLocalized (months[i]); XtVaCreateManagedWidget ("Hello", xmListWidgetClass, toplevel, XmNvisibleItemCount, n, XmNitemCount, n, XmNitems, str_list, NULL); for (i = 0; i < n; i++) XmStringFree (str_list[i]); XtFree (str_list); XtRealizeWidget (toplevel); XtAppMainLoop (app); }The program simply creates a List widget as the child of the toplevel widget. The List contains the names of the months as its choices. The output of the program is shown in the figure.
The selection policy of the List is controlled by
the XmNselectionPolicy resource. The possible values for this
resource are:
XmSINGLE_SELECT XmBROWSE_SELECT XmMULTIPLE_SELECT XmEXTENDED_SELECTXmBROWSE_SELECT is the default selection policy for the List widget. Since this policy is the one that we want to use, we do not need to set the XmNselectionPolicy resource. You should be aware that the user could change this policy with a resource specification. If you want to enforce this selection policy, you can program defensively and hard-code the value for XmNselectionPolicy , despite its default.
The program demonstrates the use of three basic
elements of the List widget: the list of items, the number of items in
the list, and the number of visible items. Because the items in a List
must be compound strings, each of the choices must be converted from a
C string to a compound string. The application allocates an array of
XmStrings, creates a compound string for each month name, and
stores the string in the str_list. The List widget is created
with str_list as the value for the XmNitems resource
and XmNitemCount is set to n.
Just like other widgets that use compound strings,
the List widget copies the entire table of compound strings into its
own internal storage. As a result, the list of strings needs to be
freed after you have used it to set the XmNitems resource.
When you set the items using this resource, you also need to set the
XmNitemCount resource to specify the number of items in the list.
If this resource is not set, the List does not know how many items to
copy. The value of XmNitemCount should never be larger than
the number of items in XmNitems. If the value for
XmNitemCount is less than the number of items, the additional items
are not put in the list.
To retrieve the list of items, you can call
XtVaGetValues() on these resources, as shown in the following code
fragment:
extern Widget list; XmStringTable choices; int n_choices; XtVaGetValues (list, XmNitems, &choices, XmNitemCount, &n_choices, NULL);Since the items that the area returned are compound strings, you must convert them to C-style strings if you need to use any of the standard C library functions to view or manipulate the strings. You can also use any of the compound string functions described in Chapter 19, Compound Strings, for this purpose. Since we used XtVaGetValues() to obtain the values for the resources, the returned data should, as always, be considered read-only. You should not change any of the items in the list or attempt to free them (or the pointer to them) when you are done examining their values.
the source code also makes use of the
XmNvisibleItemCount resource, which sets the height of the list to
match the number of items that should be visible. If you want all the
items to be visible, you simply set the value to the total number of
items in the list. Setting the visible item count to a higher value is
acceptable, assuming that the list is expected to grow to at least that
size. If you want to set the number of visible items to be less than
the number of items actually in the list, you should use a ScrolledList
as described in the next section.
Most applications use List widgets in conjunction
with ScrolledWindows. By creating a List widget as the child of a
ScrolledWindow, we create what Motif calls a ScrolledList. The
ScrolledList is not a widget, but a compound object. While this chapter
describes most of the common resources and functions that deal with
ScrolledLists, more detailed information about ScrolledWindows and
ScrollBars can be found in Chapter 9, ScrolledWindows and ScrollBars
.
A ScrolledList is built from two widget classes, so
we could create and manage the widgets separately using two calls to
XtVaCreateManagedWidget(). However, since ScrolledLists are used so
frequently, Motif provides a convenience function to create this
compound object. XmCreateScrolledList() takes the following
form:
Widget XmCreateScrolledList(parent, name, arglist, argcount) Widget parent; char *name; ArgList arglist; Cardinal argcount;The arglist parameter is an array of size argcount that contains resources to be passed to both the ScrolledWindow widget and the List widget. Generally, the two widgets use different resources that are specific to the widgets themselves, so there isn't any confusion about which resources apply to which widget. However, common resources, such as Core resources, are interpreted by both widgets, so caution is advised. If you want to set some resources on one widget, while ensuring that the values are not set on the other widget, you should avoid passing the values to the convenience routine. Instead, you can set resources separately by using XtVaSetValues() on each widget individually. XmCreateScrolledList() returns the List widget; if you need a handle to the ScrolledWindow, you can use XtParent() on the List widget. When you use the convenience routine, you need to manage the object explicitly with XtManageChild().
ScrolledLists are useful because they can display a
portion of the entire list provided by the widget. For example, we can
modify the previous example, simple_list.c, to use a
ScrolledList by using the following code fragment:
... /* Create the ScrolledList */ list_w = XmCreateScrolledList (toplevel, "Months", NULL, 0); /* set the items, the item count, and the visible items */ XtVaSetValues (list_w, XmNitems, str_list, XmNitemCount, n, XmNvisibleItemCount, 5, NULL); /* Convenience routines don't create managed children */ XtManageChild (list_w); ...The size of the viewport into the entire List widget is controlled by the XmNvisibleItemCount resource. In Motif 1.1, the value of this resource defaults to 1, while in Motif 1.2, the resource calculates its value based on the XmNheight of the List. We set the resource to 5. The output resulting from our changes is shown in the figure.
The XmNscrollBarDisplayPolicy and XmNlistSizePolicy resources control the display of the ScrollBars in a ScrolledList. The value for XmNscrollBarDisplayPolicy controls the display of the vertical ScrollBar; the resource can be set to either XmAS_NEEDED (the default) or XmSTATIC. If the policy is XmAS_NEEDED, when the entire list is visible, the vertical ScrollBar is not displayed. When the resource is set to XmSTATIC, the vertical ScrollBar is always displayed. The XmNlistSizePolicy resource reflects
how the ScrolledList manages its horizontal
ScrollBar. The default setting is XmVARIABLE, which means that
the ScrolledList attempts to grow horizontally to contain its widest
item and a horizontal ScrollBar is not displayed. This policy may
present a problem if the parent of the ScrolledList constrains its
horizontal size. If the resource is set to XmRESIZE_IF_ POSSIBLE
, the ScrolledList displays a horizontal ScrollBar only if it cannot
resize itself accordingly. If the value XmCONSTANT is used,
the horizontal ScrollBar is displayed at all times, whether it is
needed or not.
The size of a ScrolledList is ultimately controlled
by its parent. In most cases, a manager widget such as a RowColumn or
Form allows its children to be any size they request. If a ScrolledList
is a child of a Form widget, its size is whatever you specify with
either the XmNheight resource or the XmNvisibleItemCount
. However, certain constraints, such as the XmNresizePolicy in
a Form widget, may affect the height of its children unexpectedly. For
example, if you set XmNresizePolicy to XmRESIZE_NONE,
the ScrolledList widget's height request is ignored, which makes it
look like XmNvisibleItemCount is not working.
The List widget accepts keyboard input to select
items in the list, browse the list, and scroll the list. Like all other
Motif widgets, the List has translation functions that facilitate this
process. The translations are hard-coded into the widget and we do not
recommend attempting to override this list with new translations. For
ScrolledLists, the List widget automatically sets the ScrollBar's
XmNtraversalOn resource to False so that the ScrollBar
associated with the ScrolledList does not get keyboard input. Instead,
the List widget handles the input that affects scrolling. We
recommended that you do not interfere with this process, so users are
not confused by different applications on the desktop behaving in
different ways.
If a List widget is sensitive, all of the items in
the List are selectable. If it is insensitive, none of them are
selectable. You cannot set certain items to be insensitive to selection
at any given time. Furthermore, you cannot set the entire List to be
insensitive and allow the user to manipulate the ScrollBars. It is not
entirely possible to make a read-only List widget; the user always has
the ability to select items in the List, providing that it is
sensitive. Of course, you can always choose not to hook up callback
procedures to the widget, but this can lead to more confusion than
anything else because if the user selects an object and the toolkit
provides the visual feedback acknowledging the action, the user will
expect the application to respond as well.
From the programmer's perspective, much of the power
of the List widget comes from being able to manipulate its items. The
toolkit provides a number of convenience functions for dealing with the
items in a List. While the items are accesible through the XmNitems
resource, the convenience routines are designed to deal with many
common operations, such as adding items to the List, removing items,
and locating items.
The entire list of choices may not always be
available at the time the List is created. In fact, it is not uncommon
to have no items available for a new list. In these situations, items
can be added to the list dynamically using the following functions
XmListAddItem(), XmListAddItemUnselected(),
XmListAddItems(), and XmListAddItemsUnselected().
XmListAddItemsUnselected() is a new routine in Motif 1.2. These
functions take the following form:
void XmListAddItem(list_w, item, position) Widget list_w; XmString item; int position;
void XmListAddItemUnselected(list_w, item, position) Widget list_w; XmString item; int position;
void XmListAddItems(list_w, items, item_count, position) Widget list_w; XmString *items; int item_count; int position;
void XmListAddItemsUnselected(list_w, items, item_count, position) Widget list_w; XmString *items; int item_count; int position;
These routines allow you to add one or more items to
a List widget at a specified position. Remember that list positions
start at 1, not 0. The position 0 indicates
the last position in the List; specifying this position appends the
item or items to the end of the list. If the new item(s) are added to
the list in between existing items, the rest of the items are moved
down the list.
The difference between XmListAddItem() and
XmListAddItemUnselected() is that XmListAddItem() compares
each new item to each of the existing items. If a new item matches an
existing item and if the existing item is selected, the new item is
also selected. XmListAddItemUnselected() simply adds the new
item without performing this check. In most situations, it is clear
which routine you should use. If you know that the new item does not
already exist, you should add it unselected. If the List is a single
selection list, you should add new items as unselected. The only time
that you should really add new items to the list using
XmListAddItem() is when there could be duplicate entries, the list
supports multiple selections, and you explicitly want to select all new
items whose duplicates are already selected. The same is true of the
routines that add multiple items.
the source code shows how items can be added to a
ScrolledList dynamically using XmListAddItemUnselected().
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4. XmStringCreateLocalized() is
only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG
replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.
/* alpha_list.c -- insert items into a list in alphabetical order. */ #include <Xm/List.h> #include <Xm/RowColumn.h> #include <Xm/TextF.h> main(argc, argv) int argc; char *argv[]; { Widget toplevel, rowcol, list_w, text_w; XtAppContext app; Arg args[5]; int n = 0; void add_item(); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, NULL); XtSetArg (args[n], XmNvisibleItemCount, 5); n++; list_w = XmCreateScrolledList (rowcol, "scrolled_list", args, n); XtManageChild (list_w); text_w = XtVaCreateManagedWidget ("text", xmTextFieldWidgetClass, rowcol, XmNcolumns, 25, NULL); XtAddCallback (text_w, XmNactivateCallback, add_item, list_w); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* Add item to the list in alphabetical order. Perform binary * search to find the correct location for the new item position. * This is the callback routine for the TextField widget. */ void add_item(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { Widget list_w = (Widget) client_data; char *text, *newtext = XmTextFieldGetString (text_w); XmString str, *strlist; int u_bound, l_bound = 0; /* newtext is the text typed in the TextField widget */ if (!newtext || !*newtext) { /* non-null strings must be entered */ XtFree (newtext); /* XtFree() checks for NULL */ return; } /* get the current entries (and number of entries) from the List */ XtVaGetValues (list_w, XmNitemCount, &u_bound, XmNitems, &strlist, NULL); u_bound--; /* perform binary search */ while (u_bound >= l_bound) { int i = l_bound + (u_bound - l_bound) / 2; /* convert the compound string into a regular C string */ if (!XmStringGetLtoR (strlist[i], XmFONTLIST_DEFAULT_TAG, &text)) break; if (strcmp (text, newtext) > 0) u_bound = i - 1; /* newtext comes before item */ else l_bound = i + 1; /* newtext comes after item */ XtFree (text); /* XmStringGetLtoR() allocates memory ... yuk */ } str = XmStringCreateLocalized (newtext); XtFree (newtext); /* positions indexes start at 1, so increment accordingly */ XmListAddItemUnselected (list_w, str, l_bound+1); XmStringFree (str); XmTextFieldSetString (text_w, ""); }
In the source code the ScrolledList is created with no items. However, we do specify XmNvisibleItemCount, in anticipation of items being added to the list. A TextField widget is used to prompt for strings that are added to the list using the add_item() callback. This function performs a binary search on the list to determine the position where the new item is to be added. A binary search can save time, as it is expensive to scan an entire List widget and convert each compound string into a C string. When the position for the new item is found, it is added using XmListAddItemUnselected(). The output of this program is shown in the figure.
It is often useful to be able to determine whether
or not a List contains a particular item. The simplest function for
determining whether a particular item exists is XmListItemExists()
, which takes the following form:
Boolean XmListItemExists(list_w, item) Widget list_w; XmString item;This function performs a linear search on the list for the specified item. If you are maintaining your list in a particular order, you may want to search the list yourself using another type of search to improve performance. The List's internal search function does not convert the compound strings to C strings. The search routine does a direct byte-by-byte comparison of the strings using XmStringByteCompare(), which is much more efficient than converting the compound strings to C strings for comparison. However, the linear search is still slower than a binary search by orders of magnitude. And unfortunately, XmStringByteCompare() does not return which string is of greater or lesser value. The routine just returns whether the strings are different, so we cannot use it to alphabetize the items in a List.
If you need to know the position of an item in the
List, you can use XmListItemPos(). This routine takes the
following form:
int XmListItemPos(list_w, item) Widget list_w; XmString item;This function returns the position of the first occurrence of item in the List, with 1 being the first position. If the function returns 0, the element is not in the List. If a List contains duplicate entries, you can find all of the positions of a particular item using XmListGetMatchPos(), which takes the following form:
Boolean XmListGetMatchPos(list_w, item, pos_list, pos_cnt) Widget list_w; XmString item; int **pos_list; int *pos_cnt;This function returns True if the specified item is found in the List in one or more locations. The pos_list parameter is allocated to contain the array of positions of the item and the number of items found is returned in pos_cnt. When you are done using pos_list, you should free it using XtFree(). The function returns False if there are no items in the List, if memory cannot be allocated for pos_list, or if the specified item isn't in the List. In these cases, pos_list does not point to allocated space and should not be referenced or freed and the value of pos_cnt is not specified. The following code fragment shows the use of XmListGetMatchPos() to get the positions of an item in a List:
extern Widget list_w; int *pos_list; int pos_cnt, i; char *choice = "A Sample Text String"; XmString str = XmStringCreateLocalized (choice); if (!XmListGetMatchPos (list_w, str, &pos_list, &pos_cnt)) XtWarning ("Can't get items in list"); else { printf ("%s exists in positions %d:", choice, pos_cnt); for (i = 0; i < pos_cnt; i++) printf (" %d", pos_list[i]); puts (""); XtFree (pos_list); }
There are also a number of functions for replacing
items in a List. To replace a contiguous sequence of items, use either
XmListReplaceItemsPos() or XmListReplaceItemsPosUnselected()
. These functions take the following form:
void XmListReplaceItemsPos(list_w, new_items, item_count, position) Widget list_w; XmString *new_items; int item_count; int position; void XmListReplaceItemsPosUnselected(list_w, new_items, item_count, position) Widget list_w; XmString *new_items; int item_count; int position;These functions replace the specified number of items with the new items starting at position. The difference between the two functions is the same as the difference between the List routines that add items selected and unselected. XmListReplaceItemsPosUnselected() is a new routine in Motif 1.2.
You can also replace arbitrary elements in the list
with new elements, using XmListReplaceItems() or
XmListReplaceItemsUnselected. These routines take the following
form:
void XmListReplaceItems(list_w, old_items, item_count, new_items) Widget list_w; XmString *old_items; int item_count; XmString *new_items;
void XmListReplaceItemsUnselected(list_w, old_items, item_count, new_items) Widget list_w; XmString *old_items; int item_count; XmString *new_items;These functions work by searching the entire list for each element in old_items. Every occurrence of each element that is found is replaced with the corresponding element from new_items. The search continues for each element in old_items until item_count has been reached. The difference between the two functions is the same as the difference between the List routines that add items selected and unselected. XmListReplaceItemsUnselected() is a new routine in Motif 1.2.
There is another new routine in Motif 1.2 that
allows you to replace items in a List based upon position. The
XmListReplacePositions() routine takes the following form:
void XmListReplacePositions(list_w, pos_list, new_items, item_count) Widget list_w; int *pos_list; XmString *new_items; int item_count;This routine replaces the item at each position specified in pos_list with the corresponding item in new_items until item_count has been reached.
You can delete items from a List widget in many
ways. First, to delete a single item, you can use either
XmListDeleteItem() or XmListDeletePos(). These functions
take the following form:
void XmListDeleteItem(list_w, item) Widget list_w; XmString item;
void XmListDeletePos(list_w, position) Widget list_w; int position;XmListDeleteItem() finds the given item and deletes it from the list, while XmListDeletePos() removes an item directly from the given position. If you know the position of an item, you can avoid creating a compound string and use XmListDeletePos(). After an item is deleted, the items following it are moved up one position.
You can delete multiple items using either
XmListDeleteItems(), XmListDeleteItemsPos(), or
XmListDeletePositions(). These routines take the following form:
void XmListDeleteItems(list_w, items, item_count) Widget list_w; XmString *items; int item_count;
XmListDeleteItemsPos(list_w, item_count, position) Widget list_w; int item_count; int position;
XmListDeletePositions(list_w, pos_list, pos_count) Widget list_w; int *pos_list; int pos_count;XmListDeleteItems() deletes each of the items in the items array from the List; there are item_count strings in the array. You must create and initialize this array before calling the function and you must free it afterwards. If you already know the positions of the items you want to delete, you can avoid creating an array of compound strings and use XmListDeleteItemsPos() or XmListDeletePositions(). XmListDeleteItemsPos() deletes item_count items from the List starting at position. XmListDeletePositions() deletes the item at each position specified in pos_list until item_count has been reached. This routine is new in Motif 1.2.
You can delete all of the items in a List widget
using XmListDeleteAllItems(). This routine takes the following
form:
void XmListDeleteAllItems(list_w) Widget list_w;
Since the main purpose of the List widget is to
allow a user to make a selection from a set of choices, one of the most
important tasks for the programmer is to determine which items have
been selected by the user. In this section, we present an overview of
the resources and functions available to set or get the actual items
that are selected in the List widget. Later in Section #slistcb, we
discuss how to determine the items that are selected by the user when
they are selected. The resources and functions used to set and get the
selected items in the List widget are directly analogous to those that
set the actual items in the list. Just as XmNitems represents
the entire list, the XmNselectedItems resource represents the
list of selected items. The XmNselectedItemCount resource
specifies the number of items that are selected.
There are convenience routines that allow you to
modify the items that are selected in a List. The functions
XmSelectItem() and XmSelectPos() can be used to select
individual items. These functions take the following form:
void XmListSelectItem(list_w, item, notify) Widget list_w; XmString item; Boolean notify;
void XmListSelectPos(list_w, position, notify) Widget list_w; int position; Boolean notify;These functions cause the specified item to be selected. If you know the position in the list of the item to be selected, you should use XmListSelectPos() rather than XmListSelectItem(). The latter routine uses a linear search to find the specified item. The search can take a long time in a large list, which can affect performance if you are performing frequent list operations.
When the specified item is selected, any other items
that have been previously selected are deselected, except when
XmNselectionPolicy is set to XmMULTIPLE_SELECT. In this
case, the specified item is added to the list of selected items. Even
though the extended selection policy allows multiple items to be
selected, the previous selection is deselected when one of these
routines is called. If you want to add an item to the list of selected
items in an extended selection list, you can set the selection policy
to XmMULTIPLE_SELECT, use one of the routines, and then set
the selection policy back to XmEXTENDED_SELECT.
The notify parameter indicates
whether or not the callback routine for the List widget should be
called. If your callback routine does special processing of list items,
then you can avoid having redundant code by passing True. As a
result, the callback routine is called just as if the user had made the
selection himself. If you are calling either of these functions from
the callback routine, you probably want to pass False to avoid
a possible infinite loop.
There are no functions available for selecting
multiple items at the same time. To select multiple items, use
XtVaSetValues() and set the XmNselectedItems and
XmNselectedItemCount resources to the entire list of selected
items. Another alternative is to follow the suggestion made earlier and
temporarily set XmNselectionPolicy to XmMULTIPLE_SELECT
. You can call the above routines repeatedly to select the desired items
individually and then set the selection policy back to
XmEXTENDED_SELECT.
Items can be deselected in the same manner that they
are selected using XmListDeselectItem() and
XmListDeselectPos(). These functions take the following form:
void XmListDeselectItem(list_w, item) Widget list_w; XmString item;
void XmListDeselectPos(list_w, position) Widget list_w; int position;These routines modify the list of selected items, but they do not have a notify parameter, so they do not invoke the callback routine for the List. You can deselect all items in the list by calling XmListDeselectAllItems(), which takes the following form:
void XmListDeselectAllItems(list_w) Widget list_w;
There are also convenience routines that allow you
to check on the selected items in a List. You can use
XmListPosSelected() to determine whether an item is selected. This
routine in new in Motif 1.2; it takes the following form:
Boolean XmListPosSelected(list_w, position) Widget list_w; int position;The routine returns True if the item at the specified position is selected and False otherwise. You can get the positions of all of the selected items in a List using XmListGetSelectedPos() , which takes the following form:
Boolean XmListGetSelectedPos(list_w, pos_list, pos_cnt) Widget list_w; int **pos_list; int *pos_cnt;The use of this function is identical to that of XmListGetMatchPos(). The pos_list parameter is allocated to contain the array of positions of selected items and the number of items selected is returned in pos_cnt. When you are done using pos_list, you should free it using XtFree(). The function returns False if there are no selected items in the List or if memory cannot be allocated for pos_list. In these cases, pos_list does not point to allocated space and should not be referenced or freed and the value of pos_cnt is not specified.
In this section, we pull together all of the
functions we have described in the preceding sections. This example
builds on alpha_list.c, the program that adds items that are
input by the user to a ScrolledList in alphabetical order. Using
another Text widget, the user can also search for items in the list.
The searching method uses regular expression pattern-matching functions
intrinsic to UNIX systems. the source code shows the new application.
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4. XmStringCreateLocalized() is
only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG
replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.
/* search_list.c -- search for items in a List and select them */ #include <stdio.h> #include <Xm/List.h> #include <Xm/LabelG.h> #include <Xm/Label.h> #include <Xm/RowColumn.h> #include <Xm/PanedW.h> #include <Xm/TextF.h> main(argc, argv) int argc; char *argv[]; { Widget toplevel, rowcol, list_w, text_w; XtAppContext app; Arg args[5]; int n = 0; XmString label; void add_item(), search_item(); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); rowcol = XtVaCreateWidget ("rowcol", xmPanedWindowWidgetClass, toplevel, NULL); label = XmStringCreateLocalized ("List:"); XtVaCreateManagedWidget ("list_lable", xmLabelWidgetClass, rowcol, XmNlabelString, label, NULL); XmStringFree (label); XtSetArg (args[n], XmNvisibleItemCount, 10); n++; XtSetArg (args[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++; list_w = XmCreateScrolledList (rowcol, "scrolled_list", args, n); XtManageChild (list_w); label = XmStringCreateLocalized ("Add:"); XtVaCreateManagedWidget ("add_label", xmLabelWidgetClass, rowcol, XmNlabelString, label, NULL); XmStringFree (label); text_w = XtVaCreateManagedWidget ("add_text", xmTextFieldWidgetClass, rowcol, XmNcolumns, 25, NULL); XtAddCallback (text_w, XmNactivateCallback, add_item, list_w); label = XmStringCreateLocalized ("Search:"); XtVaCreateManagedWidget ("search_label", xmLabelWidgetClass, rowcol, XmNlabelString, label, NULL); XmStringFree (label); text_w = XtVaCreateManagedWidget ("search_text", xmTextFieldWidgetClass, rowcol, XmNcolumns, 25, NULL); XtAddCallback (text_w, XmNactivateCallback, search_item, list_w); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* Add item to the list in alphabetical order. Perform binary * search to find the correct location for the new item position. * This is the callback routine for the Add: TextField widget. */ void add_item(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { Widget list_w = (Widget) client_data; char *text, *newtext = XmTextFieldGetString (text_w); XmString str, *strlist; int u_bound, l_bound = 0; if (!newtext || !*newtext) { /* non-null strings must be entered */ XtFree (newtext); return; } XtVaGetValues (list_w, XmNitemCount, &u_bound, XmNitems, &strlist, NULL); u_bound--; /* perform binary search */ while (u_bound >= l_bound) { int i = l_bound + (u_bound - l_bound)/2; if (!XmStringGetLtoR (strlist[i], XmFONTLIST_DEFAULT_TAG, &text)) break; if (strcmp (text, newtext) > 0) u_bound = i-1; /* newtext comes before item */ else l_bound = i+1; /* newtext comes after item */ XtFree (text); } str = XmStringCreateLocalized (newtext); XtFree (newtext); /* positions indexes start at 1, so increment accordingly */ XmListAddItemUnselected (list_w, str, l_bound+1); XmStringFree (str); XmTextFieldSetString (text_w, ""); } /* find the item in the list that matches the specified pattern */ void search_item(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { Widget list_w = (Widget) client_data; char *exp, *text, *newtext = XmTextFieldGetString (text_w); XmString *strlist, *selectlist = NULL; int matched, cnt, j = 0; #ifndef SYSV extern char *re_comp(); #endif /* SYSV */ if (!newtext || !*newtext) { /* non-null strings must be entered */ XtFree (newtext); return; } /* compile expression into pattern matching library */ #ifdef SYSV if (!(exp = regcmp (newtext, NULL))) { printf ("Error with regcmp(%s)0, newtext); XtFree (newtext); return; } #else /* BSD */ if (exp = re_comp (newtext)) { printf ("Error with re_comp(%s): %s0, newtext, exp); XtFree (newtext); return; } #endif /* SYSV */ /* get all the items in the list ... we're going to search each one */ XtVaGetValues (list_w, XmNitemCount, &cnt, XmNitems, &strlist, NULL); while (cnt--) { /* convert item to C string */ if (!XmStringGetLtoR (strlist[cnt], XmFONTLIST_DEFAULT_TAG, &text)) break; /* do pattern match against search string */ #ifdef SYSV /* returns NULL if match failed */ matched = regex (exp, text, NULL) != NULL; #else /* BSD */ /* -1 on error, 0 if no-match, 1 if match */ matched = re_exec (text) > 0; #endif /* SYSV */ if (matched) { selectlist = (XmString *) XtRealloc (selectlist, (j+1) * (sizeof (XmString *))); selectlist[j++] = XmStringCopy (strlist[cnt]); } XtFree (text); } #ifdef SYSV free (exp); /* this must be freed for regcmp() */ #endif /* SYSV */ XtFree (newtext); /* set the actual selected items to be those that matched */ XtVaSetValues (list_w, XmNselectedItems, selectlist, XmNselectedItemCount, j, NULL); while (j--) XmStringFree (selectlist[j]); XmTextFieldSetString (text_w, ""); }The output of this program is shown in the figure. The TextField widget that is used to search for items in the List widget works identically to the one that is used to add new items. Its callback routine, search_item(), searches the list for the specified pattern. The version of UNIX you are running (System V or BSD) dictates which kind of regular expression matching is done. System V machines use the function regcmp() to compile the pattern and regex() to search for the pattern within another string, while BSD UNIX systems use the functions re_comp() and re_exec() to do the same thing. Systems that support both BSD and System V may support one, the other, or both methods of regular expression handling. You should consult your system's documentation for more information on these functions.
The items in the list are retrieved using XtVaGetValues() and the strlist parameter. This variable points to the internal list used by the List widget, so it is important that we do not change any of these elements or free these pointers when we are through with them. Changing the value of XmNselectedItems causes the internal list to change. Since the internal list is referenced by strlist, it is important to copy any values that we want to use elsewhere. If the pattern matches a list item, the item is copied using XmStringCopy() and is later added to the List's XmNselectedItems.
The items within a List can be positioned such that
an arbitrary element is placed at the top or bottom of the List. If the
List is being used as part of a ScrolledList, the item is placed at the
top or bottom of the viewport of the ScrolledWindow. To position a
particular item at the top or bottom of the window, use either
XmListSetItem() or XmListSetBottomItem(). These routines
take the following form:
void XmListSetItem(list_w, item) Widget list_w; XmString item;
void XmListBottomItem(list_w, item) Widget list_w; XmString item;
Both of these functions require an XmString
parameter to reference a particular item in the list. However, if you
know the position of the item, you can use XmListSetPos() or
XmListSetBottomPos() instead. These functions take the following
form:
void XmListSetPos(list_w, position) Widget list_w; int position;
void XmListSetBottomPos(list_w, position) Widget list_w; int position;The position parameter can be set to 0 to specify that the last item be positioned at the bottom of the viewport. Through a mixture of resource values and simple calculations, you can position any particular item anywhere in the list. For example, if you have an item that you want to be sure is visible, but you are not concerned about where in the viewport it is displayed, you can write a function to make the item visible. the source code shows the MakePosVisible() routine, which makes sure that the item at a specified position is visible.
void MakePosVisible(list_w, item_no) Widget list_w; int item_no; { int top, visible; XtVaGetValues (list_w, XmNtopItemPosition, &top, XmNvisibleItemCount, &visible, NULL); if (item_no < top) XmListSetPos (list_w, item_no); else if (item_no >= top + visible) XmListSetBottomPos (list_w, item_no); }The function gets the number of visible items and the position of the item at the top of the viewport. The XmNtopItemPosition resource stores this information. If the item comes before top , item_no is set to the top of the List using XmListSetPos(). If it comes after top + visible, the item is set at the bottom of the List using XmListSetBottomPos(). If you don't know the position of the item in the List, you can write a function that makes a specified item visible, as shown in the source code
MakeItemVisible(list_w, item) Widget list_w; XmString item; { int item_no = XmListItemPos (list_w, item); if (item_no > 0) MakePosVisible (list_w, item_no); }The MakeItemVisible() routine simple gets the position of the given item in the list using XmListItemPos() and calls MakePosVisible().
In Motif 1.2, there are some new routines that deal
with positions in a List. The XmListGetKbdItemPos() and
XmListSetKbdItemPos() routines retrieve and set the item in the
List that has the location cursor. These routines take the following
form:
int XmListGetKbdItemPos(list_w) Widget list_w;
Boolean XmListSetKbdItemPos(list_w, position) Widget list_w; int position;XmListGetKbdItemPos() returns the position of the item that has the location cursor, while XmListSetKbdItemPos() provides a way to specify the position of this item.
The XmListPosToBounds() and
XmListYToPos() functions in Motif 1.2 provide a way to translate
list items to x,y coordinates and vice versa. XmListPosToBounds()
returns the bounding box of the item at a specified position in a List.
This routine takes the following form:
Boolean XmListPosToBounds(list_w, position, x, y, width, height) Widget list_w; int position; Position *x; Position *y; Dimension *width; Dimension *height;This routine returns True if the item at the specified position is visible and False otherwise. If the item is visible, the return parameters specify the bounding box of the item. This information can be useful if you need to perform additional event processing or draw special graphics for the item. The XmListYToPos() routine returns the position of the List item at a specified y-coordinate. This function takes the following form:
int XmListYToPos(list_w, y) Widget list_w; Position y;The position information returned by this routine can be useful if you are processing events that report a pointer position and you need to convert the location of the event into an item position.
While the callback routines associated with the List
widget are not affected by whether the List is scrollable, they do
depend on the selection policy currently in use. There is a separate
callback resource for each selection policy, plus a callback for the
default action. The default action is invoked when the left mouse
button is double-clicked on an item or the RETURN key is pressed. The
callback resources are:
XmNbrowseSelectionCallback XmNdefaultActionCallback XmNextendedSelectionCallback XmNmultipleSelectionCallback XmNsingleSelectionCallback
In all of the selection modes there is the concept
of the default action. This term refers to the action that is
taken when the user double clicks the left mouse button on an item or
presses the RETURN key when an item has the location cursor. The
default action always indicates that the active item should be
selected, regardless of the selection policy. The
XmNdefaultActionCallback is invoked for the default action.
The default selection is activated when the user
double clicks on a List item. The time interval between two consecutive
button clicks determines whether the clicks are interpreted as
individual clicks or as a double click. You can set or get the time
interval using the XmNdoubleClickInterval resource. The value
is stored as milliseconds, so a value of 500 is half a second.
If the resource is not set, the value of the multiClickTime
resource is used instead. This resource is a fundamental X resource
that is understood by all X applications; it is not an Xt or Motif
toolkit resource. You should let the user specify the double-click
interval in a resource file; the value should be set using the more
global multiClickTime resource.
The browse and single selection modes only allow the
selection of a single item. The browsing mode is regarded as a simpler
interface for the user. Interactively, browse selection allows the user
to drag the selection over many items; the selection is not made till
the mouse button is released. In the single selection mode, the
selection is made as soon as the mouse button is pressed. For browse
selection, the callback list associated with the
XmNbrowseSelectionCallback is used, while the
XmNsingleSelectionCallback is used for the single selection mode.
Keyboard traversal in the List is also different
between the two modes. If the user uses the keyboard to move from one
item to the next in single selection mode, the
XmNsingleSelectCallback is not invoked until the SPACEBAR is
pressed. In browse selection, the XmNbrowseSelectionCallback
is invoked for each item the user traverses. Since these two modes for
the List widget are visually similar, your treatment of the callbacks
is very important for maintaining consistency between Lists that use
different selection modes.
A simple example of using callbacks with a List
widget is shown in the source code XtSetLanguageProc() is only
available in X11R5; there is no corresponding function in X11R4.
XmStringCreateLocalized() is only available in Motif 1.2;
XmStringCreateSimple() is the corresponding function in Motif 1.1.
XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET
in Motif 1.2.
/* browse.c -- specify a browse selection callback for a simple List. */ #include <Xm/List.h> char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; main(argc, argv) int argc; char *argv[]; { Widget toplevel, list_w; XtAppContext app; int i, n = XtNumber (months); XmStringTable str_list; void sel_callback(); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); str_list = (XmStringTable) XtMalloc (n * sizeof (XmString *)); for (i = 0; i < n; i++) str_list[i] = XmStringCreateLocalized (months[i]); list_w = XmCreateScrolledList (toplevel, "months", NULL, 0); XtVaSetValues (list_w, XmNvisibleItemCount, n, XmNitemCount, n, XmNitems, str_list, NULL); XtManageChild (list_w); XtAddCallback (list_w, XmNdefaultActionCallback, sel_callback, NULL); XtAddCallback (list_w, XmNbrowseSelectionCallback, sel_callback, NULL); for (i = 0; i < n; i++) XmStringFree (str_list[i]); XtFree (str_list); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void sel_callback(list_w, client_data, call_data) Widget list_w; XtPointer client_data; XtPointer call_data; { XmListCallbackStruct *cbs = (XmListCallbackStruct *) call_data; char *choice; if (cbs->reason == XmCR_BROWSE_SELECT) printf ("Browse selection -- "); else printf ("Default action -- "); XmStringGetLtoR (cbs->item, XmFONTLIST_DEFAULT_TAG, &choice); printf ("selected item: %s (%d)0, choice, cbs->item_position); XtFree (choice); }For this example, we modified our previous example that uses a ScrolledList to display the months of the year. We have added the same callback routine, sel_callback(), to the XmNbrowseSelectionCallback and XmNdefaultActionCallback resources. Since the default action may happen for any List widget, it is advisable to set this callback, even if there are other callbacks. The callback routine prints the type of action performed by the user and the selection that was made. The callback structure is used to get information about the nature of the List widget and the selection made.
The List callbacks provide a callback structure of
type XmListCallbackStruct, which is defined as follows:
typedef struct { int reason; XEvent *event; XmString item; int item_length; int item_position; XmString *selected_items; int selected_item_count; int *selected_item_positions; char selection_type; } XmListCallbackStruct;The reason field specifies the reason that the callback was invoked, which corresponds to the type of action performed by the user. The possible values for this field are:
XmCR_BROWSE_SELECT XmCR_DEFAULT_ACTION XmCR_EXTENDED_SELECT XmCR_MULTIPLE_SELECT XmCR_SINGLE_SELECTThe reason field is important with List callbacks because not all of the fields in the callback structure are valid for every reason. For the browse and single selection policies, the reason, event, item, item_length, and item_position fields are valid. For the default action, all of the fields are valid. List items are stored as compound strings in the callback structure, so to print an item using printf(), we must convert the string with the compound string function XmStringGetLtoR().
When XmNselectionPolicy is set to
XmMULTIPLE_SELECT, multiple items can be selected in the List
widget. When the user selects an item, its selection state is toggled.
Each time the user selects an item, the callback routine associated
with the XmNmultipleSelectionCallback is invoked. the source
code shows the sel_callback() routine that could be used with
a multiple selection List. XmFONTLIST_DEFAULT_TAG replaces
XmSTRING_DEFAULT_CHARSET in Motif 1.2.
void sel_callback(list_w, client_data, call_data) Widget list_w; XtPointer client_data; XtPointer call_data; { XmListCallbackStruct *cbs = (XmListCallbackStruct *) call_data; char *choice; int i; if (cbs->reason == XmCR_MULTIPLE_SELECT) { printf ("Multiple selection -- %d items selected:0, cbs->selected_item_count); for (i = 0; i < cbs->selected_item_count; i++) { XmStringGetLtoR (cbs->selected_items[i], XmFONTLIST_DEFAULT_TAG, &choice); printf ("%s (%d)0, choice, cbs->selected_item_positions[i]); XtFree (choice); } } else { XmStringGetLtoR (cbs->item, XmFONTLIST_DEFAULT_TAG, &choice); printf ("Default action -- selected item %s (%d)0, choice, cbs->item_position); XtFree (choice); } }The routine tests the callback structure's reason field to determine whether the callback was invoked as a result of a multiple selection action or the default action. When the reason is XmCR_MULTIPLE_SELECT, we print the list of selected items by looping through selected_items and selected_item_positions . With this reason, all of the fields in the callback structure except selection_type are valid. If the reason is XmCR_DEFAULT_ACTION, there is only one item selected, since the default selection action causes all of the other items to be deselected.
With the extended selection model, the user has the
greatest flexibility to select and deselect individual items or ranges
of items. The XmNextendedSelectionCallback is invoked whenever
the user makes a selection or modifies the selection. the source code
demonstrates the sel_callback() routine that could be used
with an extended selection List. XmFONTLIST_DEFAULT_TAG
replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.
void sel_callback(list_w, client_data, call_data) Widget list_w; XtPointer client_data; XtPointer call_data; { XmListCallbackStruct *cbs = (XmListCallbackStruct *) call_data; char *choice; int i; if (cbs->reason == XmCR_EXTENDED_SELECT) { if (cbs->selection_type == XmINITIAL) printf ("Extended selection -- initial selection: "); else if (cbs->selection_type == XmMODIFICATION) printf ("Extended selction -- modification of selection: "); else /* selection type = XmADDITION */ printf ("Extended selection -- additional selection: "); printf ("%d items selected0, cbs->selected_item_count); for (i = 0; i < cbs->selected_item_count; i++) { XmStringGetLtoR (cbs->selected_items[i], XmFONTLIST_DEFAULT_TAG, &choice); printf ("%s (%d)0, choice, cbs->selected_item_positions[i]); XtFree (choice); } } else { XmStringGetLtoR (cbs->item, XmFONTLIST_DEFAULT_TAG, &choice); printf ("Default action -- selected item %s (%d)0, choice, cbs->item_position); XtFree (choice); } }Most of the callback routine is the same as it was for multiple selection mode. With an extended selection callback, the selection_type field is also valid. This field can have the following values:
XmINITIAL XmMODIFICATION XmADDITIONThe XmINITIAL value indicates that the selection is an initial selection for the List. All previously-selected items are deselected and the items selected with this action comprise the entire list of selected items. The value is XmMODIFICATION when the user modifies the selected list by using the SHIFT key in combination with a selection action. In this case, the selected item list contains some items that were already selected before this action took place. XmADDITION indicates that the items that are selected are in addition to what was previously selected. The user can select additional items by using the CTRL key in combination with a selection action. Regardless of the value for selection_type, the selected_items and selected_item_positions fields always reflect the set of currently selected items.
The List widget is a powerful user interface tool
that has a simple design. The programming interface to the widget is
mostly mechanical. The List allows you to present a vast list of
choices to the user, although the choices themselves must be textual in
nature. Lists are not suitable for all situations however, as they
cannot display choices other than text (pixmaps cannot be used as
selection items) and there is no ability to set color on individual
items. Even with these shortcomings, the List widget is still a visible
and intuitive object that can be used in designing a graphical user
interface.
The following exercises expand on some of the
concepts presented in this chapter.