1 static const char CVSID
[] = "$Id: managedList.c,v 1.7 2001/08/14 08:37:16 jlous Exp $";
2 /*******************************************************************************
4 * managedList.c -- User interface for reorderable list of records *
6 * Copyright (C) 1999 Mark Edel *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * Nirvana Text Editor *
25 * Written by Mark Edel *
27 *******************************************************************************/
34 #include <Xm/RowColumn.h>
37 #include "managedList.h"
39 /* Common data between the managed list callback functions */
41 Widget listW
, deleteBtn
, copyBtn
, moveUpBtn
, moveDownBtn
;
42 void *(*getDialogDataCB
)(void *, int, int *, void *);
43 void *getDialogDataArg
;
44 void (*setDialogDataCB
)(void *, void *);
45 void *setDialogDataArg
;
46 void *(*copyItemCB
)(void *);
47 void (*freeItemCB
)(void *);
48 int (*deleteConfirmCB
)(int, void *);
49 void *deleteConfirmArg
;
56 static void destroyCB(Widget w
, XtPointer clientData
, XtPointer callData
);
57 static void deleteCB(Widget w
, XtPointer clientData
, XtPointer callData
);
58 static void copyCB(Widget w
, XtPointer clientData
, XtPointer callData
);
59 static void moveUpCB(Widget w
, XtPointer clientData
, XtPointer callData
);
60 static void moveDownCB(Widget w
, XtPointer clientData
, XtPointer callData
);
61 static int incorporateDialogData(managedListData
*ml
, int listPos
,
63 static void updateDialogFromList(managedListData
*ml
, int selection
);
64 static void updateListWidgetItem(managedListData
*ml
, int listPos
);
65 static void listSelectionCB(Widget w
, XtPointer clientData
, XtPointer callData
);
66 static int selectedListPosition(managedListData
*ml
);
67 static void selectItem(Widget listW
, int itemIndex
, int updateDialog
);
70 ** Create a user interface to help manage a list of arbitrary data records
71 ** which users can edit, add, delete, and reorder.
73 ** The caller creates the overall dialog for presenting the data to the user,
74 ** but embeds the list and button widgets created here (and list management
75 ** code they activate) to handle the organization of the overall list.
77 ** This routine creates a form widget containing the buttons and list widget
78 ** with which the user interacts with the list data. ManageListAndButtons
79 ** can be used alternatively to take advantage of the management code with a
80 ** different arrangement of the widgets (this routine puts buttons in a
81 ** column on the left, list on the right) imposed here.
83 ** "args" and "argc" are passed to the form widget creation routine, so that
84 ** attachments can be specified for embedding the form in a dialog.
86 ** See ManageListAndButtons for a description of the remaining arguments.
88 Widget
CreateManagedList(Widget parent
, char *name
, Arg
*args
,
89 int argC
, void **itemList
, int *nItems
, int maxItems
, int nColumns
,
90 void *(*getDialogDataCB
)(void *, int, int *, void *),
91 void *getDialogDataArg
, void (*setDialogDataCB
)(void *, void *),
92 void *setDialogDataArg
, void (*freeItemCB
)(void *))
97 Widget form
, rowCol
, listW
;
98 Widget deleteBtn
, copyBtn
, moveUpBtn
, moveDownBtn
;
99 XmString
*placeholderTable
;
100 char *placeholderStr
;
102 form
= XmCreateForm(parent
, name
, args
, argC
);
104 rowCol
= XtVaCreateManagedWidget("mlRowCol", xmRowColumnWidgetClass
, form
,
105 XmNpacking
, XmPACK_COLUMN
,
106 XmNleftAttachment
, XmATTACH_FORM
,
107 XmNtopAttachment
, XmATTACH_FORM
,
108 XmNbottomAttachment
, XmATTACH_FORM
, NULL
);
109 deleteBtn
= XtVaCreateManagedWidget("delete", xmPushButtonWidgetClass
,
110 rowCol
, XmNlabelString
, s1
=XmStringCreateSimple("Delete"), NULL
);
112 copyBtn
= XtVaCreateManagedWidget("copy", xmPushButtonWidgetClass
, rowCol
,
113 XmNlabelString
, s1
=XmStringCreateSimple("Copy"), NULL
);
115 moveUpBtn
= XtVaCreateManagedWidget("moveUp", xmPushButtonWidgetClass
,
116 rowCol
, XmNlabelString
, s1
=XmStringCreateSimple("Move ^"), NULL
);
118 moveDownBtn
= XtVaCreateManagedWidget("moveDown", xmPushButtonWidgetClass
,
119 rowCol
, XmNlabelString
, s1
=XmStringCreateSimple("Move v"), NULL
);
122 /* AFAIK the only way to make a list widget n-columns wide is to make up
123 a fake initial string of that width, and create it with that */
124 placeholderStr
= XtMalloc(nColumns
+1);
125 memset(placeholderStr
, 'm', nColumns
);
126 placeholderStr
[nColumns
] = '\0';
127 placeholderTable
= StringTable(1, placeholderStr
);
128 XtFree(placeholderStr
);
131 XtSetArg(al
[ac
], XmNscrollBarDisplayPolicy
, XmAS_NEEDED
); ac
++;
132 XtSetArg(al
[ac
], XmNlistSizePolicy
, XmCONSTANT
); ac
++;
133 XtSetArg(al
[ac
], XmNitems
, placeholderTable
); ac
++;
134 XtSetArg(al
[ac
], XmNitemCount
, 1); ac
++;
135 XtSetArg(al
[ac
], XmNtopAttachment
, XmATTACH_FORM
); ac
++;
136 XtSetArg(al
[ac
], XmNleftAttachment
, XmATTACH_WIDGET
); ac
++;
137 XtSetArg(al
[ac
], XmNleftWidget
, rowCol
); ac
++;
138 XtSetArg(al
[ac
], XmNrightAttachment
, XmATTACH_FORM
); ac
++;
139 XtSetArg(al
[ac
], XmNbottomAttachment
, XmATTACH_FORM
); ac
++;
140 listW
= XmCreateScrolledList(form
, "list", al
, ac
);
141 XtManageChild(listW
);
142 FreeStringTable(placeholderTable
);
144 return ManageListAndButtons(listW
, deleteBtn
, copyBtn
, moveUpBtn
,
145 moveDownBtn
, itemList
, nItems
, maxItems
, getDialogDataCB
,
146 getDialogDataArg
, setDialogDataCB
, setDialogDataArg
, freeItemCB
);
150 ** Manage a list widget and a set of buttons which represent a list of
151 ** records. The caller provides facilities for editing the records
152 ** individually, and this code handles the organization of the overall list,
153 ** such that the user can modify, add, and delete records, and re-order the
156 ** The format for the list of records managed by this code should be an
157 ** array of size "maxItems" of pointers to record structures. The records
158 ** themselves can be of any format, but the first field of the structure
159 ** must be a pointer to a character string which will be displayed as the
160 ** item name in the list. The list "itemList", and the number of items
161 ** "nItems" are automatically updated by the list management routines as the
162 ** user makes changes.
164 ** The caller must provide routines for transferring data to and from the
165 ** dialog fields dedicated to displaying and editing records in the list.
166 ** The callback "setDialogDataCB" must take the contents of the item pointer
167 ** passed to it, and display the data it contains, erasing any previously
168 ** displayed data. The format of the setDialogData callback is:
170 ** void setDialogDataCB(void *item, void *cbArg)
172 ** item: a pointer to the record to be displayed
174 ** cbArg: an arbitrary argument passed on to the callback routine
176 ** The callback "setDialogDataCB" must allocate (with XtMalloc) and return a
177 ** new record reflecting the current contents of the dialog fields. It may
178 ** do error checking on the data that the user has entered, and can abort
179 ** whatever operation triggered the request by setting "abort" to True.
180 ** This routine is called in a variety of contexts, such as the user
181 ** clicking on a list item, or requesting that a copy be made of the current
182 ** list item. To aide in communicating errors to the user, the boolean value
183 ** "explicitRequest" distinguishes between the case where the user has
184 ** specifically requested that the fields be read, and the case where he
185 ** may be surprised that errors are being reported, and require further
186 ** explanation. The format of the getDialogData callback is:
188 ** void *getDialogDataCB(void *oldItem, int explicitRequest, int *abort,
191 ** oldItem: a pointer to the existing record being modified in the
192 ** dialog, or NULL, if the user is modifying the "New" item.
194 ** explicitRequest: True if the user directly asked for the records
195 ** to be changed (as with an OK or Apply button). If a less direct
196 ** process resulted in the request, the user might need extra
197 ** explanation and possibly a chance to proceed using the existing
198 ** stored data (to use the data from oldItem, the routine should
201 ** abort: Can be set to True if the dialog fields contain errors.
202 ** Setting abort to True, stops whetever process made the request
203 ** for updating the data in the list from the displayed data, and
204 ** forces the user to remain focused on the currently displayed
205 ** item until he either gives up or gets it right.
207 ** cbArg: arbitrary data, passed on from where the callback was
208 ** established in the list creation routines
210 ** The return value should be an allocated
212 ** The callback "freeItemCB" should free the item passed to it:
214 ** void freeItemCB(void *item, void *cbArg)
216 ** item: a pointer to the record to be freed
218 ** The difference between ManageListAndButtons and CreateManagedList, is that
219 ** in this routine, the caller creates the list and button widgets and passes
220 ** them here so that they be arranged explicitly, rather than relying on the
221 ** default style imposed by CreateManagedList. ManageListAndButtons simply
222 ** attaches the appropriate callbacks to process mouse and keyboard input from
225 Widget
ManageListAndButtons(Widget listW
, Widget deleteBtn
, Widget copyBtn
,
226 Widget moveUpBtn
, Widget moveDownBtn
, void **itemList
, int *nItems
,
227 int maxItems
, void *(*getDialogDataCB
)(void *, int, int *, void *),
228 void *getDialogDataArg
, void (*setDialogDataCB
)(void *, void *),
229 void *setDialogDataArg
, void (*freeItemCB
)(void *))
233 /* Create a managedList data structure to hold information about the
234 widgets, callbacks, and current state of the list */
235 ml
= (managedListData
*)XtMalloc(sizeof(managedListData
));
237 ml
->deleteBtn
= deleteBtn
;
238 ml
->copyBtn
= copyBtn
;
239 ml
->moveUpBtn
= moveUpBtn
;
240 ml
->moveDownBtn
= moveDownBtn
;
241 ml
->getDialogDataCB
= NULL
;
242 ml
->getDialogDataArg
= setDialogDataArg
;
243 ml
->setDialogDataCB
= NULL
;
244 ml
->setDialogDataArg
= setDialogDataArg
;
245 ml
->freeItemCB
= freeItemCB
;
246 ml
->deleteConfirmCB
= NULL
;
247 ml
->deleteConfirmArg
= NULL
;
249 ml
->maxItems
= maxItems
;
250 ml
->itemList
= itemList
;
251 ml
->lastSelection
= 1;
253 /* Make the managed list data structure accessible from the list widget
254 pointer, and make sure it gets freed when the list is destroyed */
255 XtVaSetValues(ml
->listW
, XmNuserData
, ml
, NULL
);
256 XtAddCallback(ml
->listW
, XmNdestroyCallback
, destroyCB
, ml
);
258 /* Add callbacks for button and list actions */
259 XtAddCallback(ml
->deleteBtn
, XmNactivateCallback
, deleteCB
, ml
);
260 XtAddCallback(ml
->copyBtn
, XmNactivateCallback
, copyCB
, ml
);
261 XtAddCallback(ml
->moveUpBtn
, XmNactivateCallback
, moveUpCB
, ml
);
262 XtAddCallback(ml
->moveDownBtn
, XmNactivateCallback
, moveDownCB
, ml
);
263 XtAddCallback(ml
->listW
, XmNbrowseSelectionCallback
, listSelectionCB
, ml
);
265 /* Initialize the list and buttons (don't set up the callbacks until
266 this is done, so they won't get called on creation) */
267 updateDialogFromList(ml
, -1);
268 ml
->getDialogDataCB
= getDialogDataCB
;
269 ml
->setDialogDataCB
= setDialogDataCB
;
275 ** Update the currently selected list item from the dialog fields, using
276 ** the getDialogDataCB callback. "explicitRequest" is a boolean value
277 ** passed to on to the getDialogDataCB callback to help set the tone for
278 ** how error messages are presented (see ManageListAndButtons for more
281 int UpdateManagedList(Widget listW
, int explicitRequest
)
285 /* Recover the pointer to the managed list structure from the widget's
287 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
289 /* Make the update */
290 return incorporateDialogData(ml
, selectedListPosition(ml
), explicitRequest
);
294 ** Update the displayed list and data to agree with a data list which has
295 ** been changed externally (not by the ManagedList list manager).
297 void ChangeManagedListData(Widget listW
)
301 /* Recover the pointer to the managed list structure from the widget's
303 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
305 updateDialogFromList(ml
, -1);
309 ** Change the selected item in the managed list given the index into the
310 ** list being managed.
312 void SelectManagedListItem(Widget listW
, int itemIndex
)
314 selectItem(listW
, itemIndex
, True
);
318 ** Return the index of the item currently selected in the list
320 int ManagedListSelectedIndex(Widget listW
)
324 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
325 return selectedListPosition(ml
)-2;
329 ** Add a delete-confirmation callback to a managed list. This will be called
330 ** when the user presses the Delete button on the managed list. The callback
331 ** can put up a dialog, and optionally abort the operation by returning False.
333 void AddDeleteConfirmCB(Widget listW
, int (*deleteConfirmCB
)(int, void *),
334 void *deleteConfirmArg
)
338 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
339 ml
->deleteConfirmCB
= deleteConfirmCB
;
340 ml
->deleteConfirmArg
= deleteConfirmArg
;
344 ** Called on destruction of the list widget
346 static void destroyCB(Widget w
, XtPointer clientData
, XtPointer callData
)
348 /* Free the managed list data structure */
349 XtFree((char *)clientData
);
353 ** Button callbacks: deleteCB, copyCB, moveUpCB, moveDownCB
355 static void deleteCB(Widget w
, XtPointer clientData
, XtPointer callData
)
357 managedListData
*ml
= (managedListData
*)clientData
;
360 /* get the selected list position and the item to be deleted */
361 listPos
= selectedListPosition(ml
);
364 /* if there's a delete confirmation callback, call it first, and allow
365 it to request that the operation be aborted */
366 if (ml
->deleteConfirmCB
!= NULL
)
367 if (!(*ml
->deleteConfirmCB
)(ind
, ml
->deleteConfirmArg
))
370 /* free the item and remove it from the list */
371 (*ml
->freeItemCB
)(ml
->itemList
[ind
]);
372 for (i
=ind
; i
<*ml
->nItems
-1; i
++)
373 ml
->itemList
[i
] = ml
->itemList
[i
+1];
376 /* update the list widget and move the selection to the previous item
377 in the list and display the fields appropriate for that entry */
378 updateDialogFromList(ml
, ind
-1);
381 static void copyCB(Widget w
, XtPointer clientData
, XtPointer callData
)
383 managedListData
*ml
= (managedListData
*)clientData
;
384 int i
, listPos
, abort
= False
;
387 /* get the selected list position and the item to be copied */
388 listPos
= selectedListPosition(ml
);
390 return; /* can't copy "new" */
392 /* Bring the entry up to date (could result in operation being canceled) */
393 item
= (*ml
->getDialogDataCB
)(ml
->itemList
[listPos
-2], False
, &abort
,
394 ml
->getDialogDataArg
);
398 (*ml
->freeItemCB
)(ml
->itemList
[listPos
-2]);
399 ml
->itemList
[listPos
-2] = item
;
402 /* Make a copy by requesting the data again */
403 item
= (*ml
->getDialogDataCB
)(ml
->itemList
[listPos
-2], True
, &abort
,
404 ml
->getDialogDataArg
);
406 /* add the item to the item list */
407 for (i
= *ml
->nItems
; i
>=listPos
; i
--)
408 ml
->itemList
[i
] = ml
->itemList
[i
-1];
409 ml
->itemList
[listPos
-1] = item
;
412 /* redisplay the list widget and select the new item */
413 updateDialogFromList(ml
, listPos
-1);
416 static void moveUpCB(Widget w
, XtPointer clientData
, XtPointer callData
)
418 managedListData
*ml
= (managedListData
*)clientData
;
422 /* get the item index currently selected in the menu item list */
423 listPos
= selectedListPosition(ml
);
426 /* Bring the item up to date with the dialog fields (It would be better
427 if this could be avoided, because user errors will be flagged here,
428 but it's not worth re-writing everything for such a trivial point) */
429 if (!incorporateDialogData(ml
, ml
->lastSelection
, False
))
432 /* shuffle the item up in the menu item list */
433 temp
= ml
->itemList
[ind
];
434 ml
->itemList
[ind
] = ml
->itemList
[ind
-1];
435 ml
->itemList
[ind
-1] = temp
;
437 /* update the list widget and keep the selection on moved item */
438 updateDialogFromList(ml
, ind
-1);
441 static void moveDownCB(Widget w
, XtPointer clientData
, XtPointer callData
)
443 managedListData
*ml
= (managedListData
*)clientData
;
447 /* get the item index currently selected in the menu item list */
448 listPos
= selectedListPosition(ml
);
451 /* Bring the item up to date with the dialog fields (I wish this could
453 if (!incorporateDialogData(ml
, ml
->lastSelection
, False
))
456 /* shuffle the item down in the menu item list */
457 temp
= ml
->itemList
[ind
];
458 ml
->itemList
[ind
] = ml
->itemList
[ind
+1];
459 ml
->itemList
[ind
+1] = temp
;
461 /* update the list widget and keep the selection on moved item */
462 updateDialogFromList(ml
, ind
+1);
466 ** Called when the user clicks on an item in the list widget
468 static void listSelectionCB(Widget w
, XtPointer clientData
, XtPointer callData
)
470 managedListData
*ml
= (managedListData
*)clientData
;
471 int ind
, listPos
= ((XmListCallbackStruct
*)callData
)->item_position
;
473 /* Save the current dialog fields before overwriting them. If there's an
474 error, force the user to go back to the old selection and fix it
476 if (ml
->getDialogDataCB
!= NULL
&& ml
->lastSelection
!= 0) {
477 if (!incorporateDialogData(ml
, ml
->lastSelection
, False
)) {
478 XmListDeselectAllItems(ml
->listW
);
479 XmListSelectPos(ml
->listW
, ml
->lastSelection
, False
);
482 /* reselect item because incorporateDialogData can alter selection */
483 selectItem(ml
->listW
, listPos
-2, False
);
485 ml
->lastSelection
= listPos
;
487 /* Dim or un-dim buttons at bottom of dialog based on whether the
488 selected item is a menu entry, or "New" */
490 XtSetSensitive(ml
->copyBtn
, False
);
491 XtSetSensitive(ml
->deleteBtn
, False
);
492 XtSetSensitive(ml
->moveUpBtn
, False
);
493 XtSetSensitive(ml
->moveDownBtn
, False
);
495 XtSetSensitive(ml
->copyBtn
, True
);
496 XtSetSensitive(ml
->deleteBtn
, True
);
497 XtSetSensitive(ml
->moveUpBtn
, listPos
!= 2);
498 XtSetSensitive(ml
->moveDownBtn
, listPos
!= *ml
->nItems
+1);
501 /* get the index of the item currently selected in the item list */
504 /* tell the caller to show the new item */
505 if (ml
->setDialogDataCB
!= NULL
)
506 (*ml
->setDialogDataCB
)(listPos
==1 ? NULL
: ml
->itemList
[ind
],
507 ml
->setDialogDataArg
);
511 ** Incorporate the current contents of the dialog fields into the list
512 ** being managed, and if necessary change the display in the list widget.
513 ** The data is obtained by calling the getDialogDataCB callback, which
514 ** is allowed to reject whatever request triggered the update. If the
515 ** request is rejected, the return value from this function will be False.
517 static int incorporateDialogData(managedListData
*ml
, int listPos
, int explicit)
522 /* Get the current contents of the dialog fields. Callback will set
523 abort to True if canceled */
524 item
= (*ml
->getDialogDataCB
)(listPos
== 1 ? NULL
: ml
->itemList
[
525 listPos
-2], explicit, &abort
, ml
->getDialogDataArg
);
528 if (item
== NULL
) /* don't modify if fields are empty */
531 /* If the item is "new" add a new entry to the list, otherwise,
532 modify the entry with the text fields from the dialog */
534 ml
->itemList
[(*ml
->nItems
)++] = item
;
535 updateDialogFromList(ml
, *ml
->nItems
- 1);
537 (*ml
->freeItemCB
)(ml
->itemList
[listPos
-2]);
538 ml
->itemList
[listPos
-2] = item
;
539 updateListWidgetItem(ml
, listPos
);
545 ** Update the list widget to reflect the current contents of the managed item
546 ** list, set the item that should now be highlighted, and call getDisplayed
547 ** on the newly selected item to fill in the dialog fields.
549 static void updateDialogFromList(managedListData
*ml
, int selection
)
552 XmString
*stringTable
;
554 /* On many systems under Motif 1.1 the list widget can't handle items
555 being changed while anything is selected! */
556 XmListDeselectAllItems(ml
->listW
);
558 /* Fill in the list widget with the names from the item list */
559 stringTable
= (XmString
*)XtMalloc(sizeof(XmString
) * (*ml
->nItems
+1));
560 stringTable
[0] = XmStringCreateSimple("New");
561 for (i
=0; i
< *ml
->nItems
; i
++)
562 stringTable
[i
+1] = XmStringCreateSimple(*(char **)ml
->itemList
[i
]);
563 XtVaSetValues(ml
->listW
, XmNitems
, stringTable
,
564 XmNitemCount
, *ml
->nItems
+1, NULL
);
565 for (i
=0; i
< *ml
->nItems
+1; i
++)
566 XmStringFree(stringTable
[i
]);
567 XtFree((char *)stringTable
);
569 /* Select the requested item (indirectly filling in the dialog fields),
570 but don't trigger an update of the last selected item from the current
572 ml
->lastSelection
= 0;
573 selectItem(ml
->listW
, selection
, True
);
577 ** Update one item of the managed list widget to reflect the current contents
578 ** of the managed item list.
580 static void updateListWidgetItem(managedListData
*ml
, int listPos
)
583 XmString newString
[1];
585 /* save the current selected position (Motif sometimes does stupid things
586 to the selection when a change is made, like selecting the new item
587 if it matches the name of currently selected one) */
588 savedPos
= selectedListPosition(ml
);
589 XmListDeselectAllItems(ml
->listW
);
591 /* update the list */
592 newString
[0] = XmStringCreateSimple(*(char **)ml
->itemList
[listPos
-2]);
593 XmListReplaceItemsPos(ml
->listW
, newString
, 1, listPos
);
594 XmStringFree(newString
[0]);
596 /* restore the selected position */
597 XmListSelectPos(ml
->listW
, savedPos
, False
);
601 ** Get the position of the selection in the menu item list widget
603 static int selectedListPosition(managedListData
*ml
)
606 int *posList
= NULL
, posCount
= 0;
608 if (!XmListGetSelectedPos(ml
->listW
, &posList
, &posCount
)) {
609 fprintf(stderr
, "Internal error (nothing selected)");
613 XtFree((char *)posList
);
614 if (listPos
< 1 || listPos
> *ml
->nItems
+1) {
615 fprintf(stderr
, "Internal error (XmList bad value)");
622 ** Select an item in the list given the list (array) index value.
623 ** If updateDialog is True, trigger a complete dialog update, which
624 ** could potentially reject the change.
626 static void selectItem(Widget listW
, int itemIndex
, int updateDialog
)
628 int topPos
, nVisible
, selection
= itemIndex
+2;
630 /* Select the item */
631 XmListDeselectAllItems(listW
);
632 XmListSelectPos(listW
, selection
, updateDialog
);
634 /* If the selected item is not visible, scroll the list */
635 XtVaGetValues(listW
, XmNtopItemPosition
, &topPos
, XmNvisibleItemCount
,
637 if (selection
< topPos
)
638 XmListSetPos(listW
, selection
);
639 else if (selection
>= topPos
+ nVisible
)
640 XmListSetPos(listW
, selection
- nVisible
+ 1);