Contents Previous Next

13 The List Widget


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.

figs.eps/V6a.12.01.eps.png
A List widget with two selected items


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:

13.1 Creating a List Widget

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.

figs.eps/V6a.12.02.eps.png
Output of simple_list.c

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_SELECT
XmBROWSE_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 XmN­items. 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.

13.2 Using ScrolledLists

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

figs.eps/V6a.12.03.eps.png
Output of simple_list.c modified to use a ScrolledList

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 XmN­height 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.

13.3 Manipulating Items

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.

13.3.1 Adding 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 XmN­visibleItemCount, 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.

figs.eps/V6a.12.04.eps.png
Output of alpha_list.c


13.3.2 Finding Items

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);
   }

13.3.3 Replacing Items

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.

13.3.4 Deleting Items

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;

13.3.5 Selecting Items

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 XmN­selectedItemCount 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.

13.3.6 An Example

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.

figs.eps/V6a.12.05.eps.png
Output of search_list.c


13.4 Positioning the List

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.

13.5 List Callback Routines

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

13.5.1 The Default Action

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 XmN­defaultActionCallback 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 XmN­doubleClickInterval 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.

13.5.2 Browse and Single Selection Callbacks

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_SELECT
The 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().

13.5.3 Multiple Selection Callback

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.

13.5.4 Extended Selection Callback

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
   XmADDITION
The 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.

13.6 Summary

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.

13.7 Exercises

The following exercises expand on some of the concepts presented in this chapter.


Contents Previous Next