X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation |
In this chapter:
Chapter: 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, the List widget is the optimal choice.
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. Figure 13-1 shows a typical List widget.
Internally, the List widget operates on an array of compound strings that are defined by the application. (See Chapter 13, The List Widget, for a discussion of how to create and manage compound strings. 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:
In single selection mode, selecting an item toggles its selection state and deselects any other selected item. Single selection Lists should be used when only one of many choices maybe selected at a time, although under this policy there may also be no items selected. Some possible uses for a single selection List include choosing a font family or style for text input and choosing a color for a bitmap editor.
In browse selection mode, selecting a new item deselects any other selected item, but there can never be a state where no items are selected. From the user's perspective, browse selection is similar to single selection, except that there is an initial selected item. There are also differences with respect to callback routines. This issue is addressed in later in this chapter.
In multiple selection mode, any number of items can be selected atone time. When an item is selected, the selection state of the item is toggled; the selection states of the rest of the items are not changed. The List can be in a state where none of the items are selected or all of the items are selected. Multiple selection mode is advantageous in situations where an action may be taken on more than one item at a time, such as in an electronic mail application, where the user might choose to delete, save, or print multiple messages simultaneously.
In extended selection mode, the user can select discontiguous ranges of items. This selection policy is an extension of the multiple selection policy that provides more flexibility.
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:Example 13-1 shows a program that creates a simple List widget.1Widget list = XmCreateList ( parent, "name", resource-value-array, resource-value-count); Widget list = XtCreateWidget ( "name", xmListWidgetClass, parent, resource-value-list, NULL);
The program simply creates a List widget as the child of the top level widget. The List contains the names of the months as its choices. The output of the program is shown in Figure 13-2./* simple_list.c -- introduce the List widget. Lists present ** a number of compound 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 (int argc, char *argv[]) { Widget toplevel, list; XtAppContext app; int i, n = XtNumber (months); XmStringTable str_list; Arg args[4]; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); str_list = (XmStringTable) XtMalloc (n * sizeof (XmString)); for (i = 0; i < n; i++) str_list[i] = XmStringCreateLocalized (months[i]); i = 0; XtSetArg (args[i], XmNvisibleItemCount, n); i++; XtSetArg (args[i], XmNitemCount, n); i++; XtSetArg (args[i], XmNitems, str_list); i++; list = XmCreateList (toplevel, "Hello", args, i); for (i = 0; i < n; i++) XmStringFree (str_list[i]); XtFree ((char *) str_list); XtManageChild (list); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
The selection policy of the List is controlled by the XmNselectionPolicy resource. The possible values for this resource are:
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.XmSINGLE_SELECT XmBROWSE_SELECT XmMULTIPLE_SELECT XmEXTENDED_SELECTThe 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:
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 25, 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.extern Widget list; XmStringTable choices; int n_choices; XtVaGetValues (list, XmNitems, &choices, XmNitemCount, &n_choices, NULL);Example 13-1 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.
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 10, ScrolledWindows and ScrollBars.A ScrolledList is built from two widget classes, so we could create and manage the widgets separately. However, since ScrolledLists are used so frequently, Motif provides a convenience function to create this compound object. XmCreateScrolledList() takes the following form:
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 the routine 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().Widget XmCreateScrolledList ( Widget parent, char *name, ArgList arglist, Cardinal argcount)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:
The size of the viewport into the entire List widget is controlled by the XmNvisibleItemCount resource. The resource calculates its default value based on the XmNheight of the List. We set the resource to 5. The output resulting from these changes is shown in Figure 13-3.... /* 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); ...
Figure 13-3 Output of the simple_list program modified to use a ScrolledList
The XmNscrollBarDisplayPolicy and XmNlistSizePolicy resources control the display of the ScrollBars in a ScrolledList widget. 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.
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 accessible 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.
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 toolkit functions: XmListAddItem(), XmListAddItemsUnselected(), XmListAddItems(), and XmListAddItemsUnselected(). These functions take the following form: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.void XmListAddItem (Widget list_w, XmString item, int position) void XmListAddItemUnselected (Widget list_w, XmString item, int position) void XmListAddItems ( Widget list_w, XmString *items, int item_count, int position) void XmListAddItemsUnselected ( Widget list_w, XmString *items, int item_count, int position)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.
Example 13-2 shows how items can be added to a ScrolledList dynamically using XmListAddItemUnselected().2
In Example 13-2, 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 Figure 13-4./* alpha_list.c -- insert items into a list in alphabetical order. */ #include <Xm/List.h> #include <Xm/RowColumn.h> #include <Xm/TextF.h> main (int argc, char *argv[]) { Widget toplevel, rowcol, list_w, text_w; XtAppContext app; Arg args[5]; int n = 0; void add_item(Widget, XtPointer, XtPointer); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); rowcol = XmCreateRowColumn (toplevel, "rowcol", NULL, 0); XtSetArg (args[n], XmNvisibleItemCount, 5); n++; list_w = XmCreateScrolledList (rowcol, "scrolled_list", args, n); XtManageChild (list_w); n = 0; XtSetArg (args[n], XmNcolumns, 25); n++; text_w = XmCreateTextField (rowcol, "text", args, n); XtAddCallback (text_w, XmNactivateCallback, add_item, (XtPointer) list_w); XtManageChild (text_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 (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 (!(text = (char *) XmStringUnparse (strlist[i], XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL))) 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);/* XmStringUnparse() allocates memory */ } 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, ""); }
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: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.Boolean XmListItemExists (Widget list_w;, XmString item)If you need to know the position of an item in the List, you can use XmListItemPos(). This routine takes the following form:
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:int XmListItemPos (Widget list_w, XmString item)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:Boolean XmListGetMatchPos ( Widget list_w, XmString item, int **pos_list, int *pos_cnt)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 ((char *) pos_list); }
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: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.void XmListReplaceItemsPos ( Widget list_w, XmString *new_items, int item_count, int position;) void XmListReplaceItemsPosUnselected ( Widget list_w, XmString *new_items, int item_count, int position)You can also replace arbitrary elements in the list with new elements, using XmListReplaceItems() or XmListReplaceItemsUnselected(). These routines take the following form:
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.void XmListReplaceItems ( Widget list_w, XmString *old_items, int item_count, XmString *new_items) void XmListReplaceItemsUnselected ( Widget list_w, XmString *old_items, int item_count, XmString *new_items)There is another routine that allows you to replace items in a List based upon position. The XmListReplacePositions() routine takes the following form:
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.void XmListReplacePositions ( Widget list_w, int *pos_list, XmString *new_items, int item_count)
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: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.void XmListDeleteItem (Widget list_w, XmString item) void XmListDeletePos (Widget list_w, int position)You can delete multiple items using either XmListDeleteItems(), XmListDeleteItemsPos(), or XmListDeletePositions(). These routines take the following form:
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 either of the routines XmListDeleteItemsPos() and 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.void XmListDeleteItems (Widget list_w, XmString *items, int item_count) void XmListDeleteItemsPos (Widget list_w, int item_count, int position) void XmListDeletePositions (Widget list_w, int *pos_list, int pos_count)You can delete all of the items in a List widget using XmListDeleteAllItems(). This routine takes the following form:
void XmListDeleteAllItems (Widget list_w)
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 this chapter 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 XmListSelectItem() and XmListSelectPos() can be used to select individual items. These functions take the following form:
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.void XmListSelectItem (Widget list_w, XmString item, Boolean notify) void XmListSelectPos (Widget list_w, int position, Boolean notify)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. From Motif 2.0 onwards, it is also possible to set the XmNselectedPositions and XmNselectedPositionCount resources. Another alternative is to 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 the routines XmListDeselectItem() and XmListDeselectPos(). These functions take the following form:
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 XmListDeselectItem (Widget list_w, XmString item) void XmListDeselectPos (Widget list_w, int position)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. It takes the following form:void XmListDeselectAllItems (Widget list_w)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 XmListPosSelected (Widget list_w, int position)This in Motif 2.0 and later does little more than return the value of the XmNselectedPositions and XmNselectedPositionCount resources, which as an alternative may as well be fetched directly using XtGetValues().Boolean XmListGetSelectedPos (Widget list_w, int **pos_list, int *pos_cnt)The use of the XmListGetSelectedPos() 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.
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. Example 13-3 shows the new application.3
The output of this program is shown in Figure 13-5. 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 function sre_comp() and re_exec() to do the same thing.4/* 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 (int argc, char *argv[]) { Widget toplevel, rowcol, list_w, text_w, label_w; XtAppContext app; Arg args[5]; int n = 0; XmString label; void add_item(Widget, XtPointer, XtPointer); void search_item(Widget, XtPointer, XtPointer); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); rowcol = XmCreatePanedWindow (toplevel, "rowcol", NULL, 0); label = XmStringCreateLocalized ("List:"); n = 0; XtSetArg (args[n], XmNlabelString, label); n++; label_w = XmCreateLabel (rowcol, "list_lable", args, n); XmStringFree (label); XtManageChild (label_w); n = 0; 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:"); n = 0; XtSetArg (args[n], XmNlabelString, label); n++; label_w = XmCreateLabel (rowcol, "add_label", args, n); XmStringFree (label); XtManageChild (label_w); n = 0; XtSetArg (args[n], XmNcolumns, 25); n++; text_w = XmCreateTextField (rowcol, "add_text",args, n); XtAddCallback (text_w, XmNactivateCallback, add_item, (XtPointer) list_w); XtManageChild (text_w); label = XmStringCreateLocalized ("Search:"); n = 0; XtSetArg (args[n], XmNlabelString, label); n++; label_w = XmCreateLabel (rowcol, "search_label", args, n); XmStringFree (label); XtManageChild (label_w); n = 0; XtSetArg (args[n], XmNcolumns, 25); n++; text_w = XmCreateTextField (rowcol, "search_text", args, n); XtAddCallback (text_w, XmNactivateCallback, search_item, (XtPointer) list_w); XtManageChild (text_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 (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 (!(text = (char *) XmStringUnparse (strlist[i], XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL))) 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 (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)\n", newtext); XtFree (newtext); return; } #else /* BSD */ if (exp = re_comp (newtext)) { printf ("Error with re_comp(%s): %s\n", 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 (!(text = (char *) XmStringUnparse (strlist[cnt], XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL))) 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 ((char *) 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 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.
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: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 XmListSetItem (Widget list_w, XmString item) void XmListBottomItem (Widget list_w, XmString item)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. Example 13-4 shows the MakePosVisible() routine, which makes sure that the item at a specified position is visible.void XmListSetPos (Widget list_w, int position) void XmListSetBottomPos (Widget list_w, int position)
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 Example 13-5.void MakePosVisible (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 MakeItemVisible() routine simple gets the position of the given item in the list using XmListItemPos() and calls MakePosVisible().void MakeItemVisible (Widget list_w, XmString item) { int item_no = XmListItemPos (list_w, item); if (item_no > 0) MakePosVisible (list_w, item_no); }There are some other routines that deal with positions in a List widget. The XmListGetKbdItemPos() and XmListSetKbdItemPos() routines retrieve and set the item in the List that has the location cursor. These routines take the following form:
XmListGetKbdItemPos() returns the position of the item that has the location cursor, while XmListSetKbdItemPos() provides a way to specify the position of this item.int XmListGetKbdItemPos (Widget list_w) Boolean XmListSetKbdItemPos (Widget list_w, int position)The XmListPosToBounds() and XmListYToPos() functions in 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:
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:Boolean XmListPosToBounds ( Widget list_w, int position, Position *x, Position *y, Dimension *width, Dimension *height)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.int XmListYToPos (Widget list_w, Position y)
Navigating the List
In Motif 2.0 and later, the user can navigate through items in the List simply by typing characters which match the first character of an item. The resource XmNmatchBehavior controls this aspect of the List. Match behavior is enabled by default. To disable, set XmNmatchBehavior to XmNONE, and to re-enable, use the value XmQUICK_NAVIGATE.Navigation proceeds cyclically: when the user types a character, it is compared against List items starting below the current item. If no item below the current item matches the keyboard input, the search proceeds from the top of the List. If a match is found, the matching item becomes the new current item. Figure 13-6 shows how this all works.
If no match is found, XBell() is called automatically: there is no way to configure the List otherwise. Note that matching is case sensitive: in the example, typing a lower case "t" would not have matched against any items in the List.
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
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 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.
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 Example 13-6.5
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./* 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 (int argc, char *argv[]) { Widget toplevel, list_w; XtAppContext app; int i, n = XtNumber (months); XmStringTable str_list; void sel_callback(Widget, XtPointer, XtPointer); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, 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 ((char *) str_list); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void sel_callback (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 -- "); choice = (char *) XmStringUnparse (cbs->item, XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); printf ("selected item: %s (%d)\n", choice, cbs->item_position); XtFree (choice); }The List callbacks provide a callback structure of type XmListCallbackStruct, which is defined as follows:
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: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; char auto_selection_type;6 } XmListCallbackStruct;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 XmStringUnparse().XmCR_BROWSE_SELECT XmCR_DEFAULT_ACTION XmCR_EXTENDED_SELECT XmCR_MULTIPLE_SELECT XmCR_SINGLE_SELECT
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. Example 13-7 shows the sel_callback() routine that could be used with a multiple selection List.7
Example 13-7 The sel_callback() routine for a multiple selection list
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.void sel_callback ( 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:\n", cbs->selected_item_count); for (i = 0; i < cbs->selected_item_count; i++) { choice = (char *) XmStringUnparse (cbs->selected_items[i], XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT,NULL, 0, XmOUTPUT_ALL); printf ("%s (%d)\n", choice, cbs->selected_item_positions[i]); XtFree (choice); } } else { choice = (char *) XmStringUnparse (cbs->item, XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); printf ("Default action -- selected item %s (%d)\n", choice, cbs->item_position); XtFree (choice); } }
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. Example 13-8 demonstrates the sel_callback() routine that could be used with an extended selection List.8
Example 13-8 The sel_callback() routine for extended selection
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:void sel_callback ( 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 selection -- modification of selection: "); else /* selection type = XmADDITION */ printf ("Extended selection -- additional selection: "); printf ("%d items selected\n", cbs->selected_item_count); for (i = 0; i < cbs->selected_item_count; i++) { choice = (char *) XmStringUnparse (cbs->selected_items[i], XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); printf ("%s (%d)\n", choice, cbs->selected_item_positions[i]); XtFree (choice); } } else { choice = (char *) XmStringUnparse (cbs->item, XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); printf ("Default action -- selected item %s (%d)\n", choice, cbs->item_position); XtFree (choice); } }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.XmINITIAL XmMODIFICATION XmADDITION
Automatic Selection
A List which is configured for browse or extended selection does not normally invoke callbacks until the user completes the selection by releasing the mouse. This means that notification of selection change does not take place until after the event. If the resource XmNautomaticSelection is set to XmAUTO_SELECT, however, notification is immediate as soon as the user moves over a new item in the List. Automatic selection is disabled by setting XmNautomaticSelection to XmNO_AUTO_SELECT.9 The auto_selection_type field in the XmListCallbackStruct indicates the state of the change to the selection. The field takes the following values:XmAUTO_UNSET XmAUTO_BEGIN XmAUTO_MOTION XmAUTO_NO_CHANGE XmAUTO_CHANGE XmAUTO_CANCEL
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). Even with these shortcomings, the List widget is still a visible and intuitive object that can be used in designing a graphical user interface.In Motif 1.2, individual List items could not be colored independently. With the advent of Render Tables in Motif 2.0, this is no longer the case. See Chapter 24, Render Tables, for more information on this subject. Example 24-1 is a sample application which creates a multi-colored ScrolledList.
Exercises
The following exercises expand on some of the concepts presented in this chapter.
- Write a program that reads each word from the file /usr/dict/words into a ScrolledList. Provide a TextField widget whose callback routine searches for the word typed into it from the entries in the List. Once found, make the List widget scroll so that each item is centered in the ScrolledList's viewport. (Hint: convert the C string from the TextField into a compound string and use one of the List search routines to find the element.)
- ScrolledLists frequently confuse the unsuspecting programmer who forgets that the parent of the List widget is a ScrolledWindow. For example, if you create a ScrolledList as a child of a Form widget, and want to specify attachment constraints on the ScrolledList, you should set these resources on the ScrolledWindow, not the List widget. Write a program that places two ScrolledList widgets next to each other in a single Form widget. (For more information on the role of the ScrolledWindow widget in a ScrolledList object, see the similar discussion on ScrolledText objects in Chapter 18, Text Widgets, and more discussion in Chapter 10, ScrolledWindows and ScrollBars.)
- Consider two List widgets whose items are somewhat dependent on one another.For example, the one List contains login names and the other List contains the corresponding user-IDs.Write a program where the XmNdefaultActionCallback routine for each list selects the dependent/corresponding item in the other list.Since the user ID for "root" is always 0,selecting "root" from the login name list should cause the item 0in the user-ID list to be selected.
1 XtVaAppInitialize() is considered deprecated in X11R6.2 XtVaAppInitialize() is considered deprecated in X11R6. XmStringGetLtoR() is deprecated from Motif 2.0 onwards. XmStringUnparse() is only available from Motif 2.0 onwards.
3 XtVaAppInitialize() is considered deprecated in X11R6. XmStringGetLtoR() is deprecated from Motif 2.0. XmStringUnparse() is only available from Motif 2.0 onwards.
4 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.
5 XtVaAppInitialize() is considered deprecated in X11R6. XmStringGetLtoR() is deprecated in Motif 2.0 and later.
6 The auto_selection_type field is only available from Motif 2.0 onwards.
7 XmStringGetLtoR() is deprecated from Motif 2.0.
8 XmStringGetLtoR() is deprecated in Motif 2.0.
9 In Motif 1.2, XmNautomaticSelection is a simple Boolean resource. In Motif 2.0 and later, the resource changes to the enumerated type. For backwards compatibility, XmNO_AUTO_SELECT is equivalent to False, XmAUTO_SELECT to True.
|