1 static const char CVSID
[] = "$Id: managedList.c,v 1.10 2002/06/26 23:39:21 slobasso 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 *******************************************************************************/
30 #include "../config.h"
33 #include "managedList.h"
39 #include <X11/Intrinsic.h>
43 #include <Xm/RowColumn.h>
49 /* Common data between the managed list callback functions */
51 Widget listW
, deleteBtn
, copyBtn
, moveUpBtn
, moveDownBtn
;
52 void *(*getDialogDataCB
)(void *, int, int *, void *);
53 void *getDialogDataArg
;
54 void (*setDialogDataCB
)(void *, void *);
55 void *setDialogDataArg
;
56 void *(*copyItemCB
)(void *);
57 void (*freeItemCB
)(void *);
58 int (*deleteConfirmCB
)(int, void *);
59 void *deleteConfirmArg
;
66 static void destroyCB(Widget w
, XtPointer clientData
, XtPointer callData
);
67 static void deleteCB(Widget w
, XtPointer clientData
, XtPointer callData
);
68 static void copyCB(Widget w
, XtPointer clientData
, XtPointer callData
);
69 static void moveUpCB(Widget w
, XtPointer clientData
, XtPointer callData
);
70 static void moveDownCB(Widget w
, XtPointer clientData
, XtPointer callData
);
71 static int incorporateDialogData(managedListData
*ml
, int listPos
,
73 static void updateDialogFromList(managedListData
*ml
, int selection
);
74 static void updateListWidgetItem(managedListData
*ml
, int listPos
);
75 static void listSelectionCB(Widget w
, XtPointer clientData
, XtPointer callData
);
76 static int selectedListPosition(managedListData
*ml
);
77 static void selectItem(Widget listW
, int itemIndex
, int updateDialog
);
80 ** Create a user interface to help manage a list of arbitrary data records
81 ** which users can edit, add, delete, and reorder.
83 ** The caller creates the overall dialog for presenting the data to the user,
84 ** but embeds the list and button widgets created here (and list management
85 ** code they activate) to handle the organization of the overall list.
87 ** This routine creates a form widget containing the buttons and list widget
88 ** with which the user interacts with the list data. ManageListAndButtons
89 ** can be used alternatively to take advantage of the management code with a
90 ** different arrangement of the widgets (this routine puts buttons in a
91 ** column on the left, list on the right) imposed here.
93 ** "args" and "argc" are passed to the form widget creation routine, so that
94 ** attachments can be specified for embedding the form in a dialog.
96 ** See ManageListAndButtons for a description of the remaining arguments.
98 Widget
CreateManagedList(Widget parent
, char *name
, Arg
*args
,
99 int argC
, void **itemList
, int *nItems
, int maxItems
, int nColumns
,
100 void *(*getDialogDataCB
)(void *, int, int *, void *),
101 void *getDialogDataArg
, void (*setDialogDataCB
)(void *, void *),
102 void *setDialogDataArg
, void (*freeItemCB
)(void *))
107 Widget form
, rowCol
, listW
;
108 Widget deleteBtn
, copyBtn
, moveUpBtn
, moveDownBtn
;
109 XmString
*placeholderTable
;
110 char *placeholderStr
;
112 form
= XmCreateForm(parent
, name
, args
, argC
);
114 rowCol
= XtVaCreateManagedWidget("mlRowCol", xmRowColumnWidgetClass
, form
,
115 XmNpacking
, XmPACK_COLUMN
,
116 XmNleftAttachment
, XmATTACH_FORM
,
117 XmNtopAttachment
, XmATTACH_FORM
,
118 XmNbottomAttachment
, XmATTACH_FORM
, NULL
);
119 deleteBtn
= XtVaCreateManagedWidget("delete", xmPushButtonWidgetClass
,
120 rowCol
, XmNlabelString
, s1
=XmStringCreateSimple("Delete"), NULL
);
122 copyBtn
= XtVaCreateManagedWidget("copy", xmPushButtonWidgetClass
, rowCol
,
123 XmNlabelString
, s1
=XmStringCreateSimple("Copy"), NULL
);
125 moveUpBtn
= XtVaCreateManagedWidget("moveUp", xmPushButtonWidgetClass
,
126 rowCol
, XmNlabelString
, s1
=XmStringCreateSimple("Move ^"), NULL
);
128 moveDownBtn
= XtVaCreateManagedWidget("moveDown", xmPushButtonWidgetClass
,
129 rowCol
, XmNlabelString
, s1
=XmStringCreateSimple("Move v"), NULL
);
132 /* AFAIK the only way to make a list widget n-columns wide is to make up
133 a fake initial string of that width, and create it with that */
134 placeholderStr
= XtMalloc(nColumns
+1);
135 memset(placeholderStr
, 'm', nColumns
);
136 placeholderStr
[nColumns
] = '\0';
137 placeholderTable
= StringTable(1, placeholderStr
);
138 XtFree(placeholderStr
);
141 XtSetArg(al
[ac
], XmNscrollBarDisplayPolicy
, XmAS_NEEDED
); ac
++;
142 XtSetArg(al
[ac
], XmNlistSizePolicy
, XmCONSTANT
); ac
++;
143 XtSetArg(al
[ac
], XmNitems
, placeholderTable
); ac
++;
144 XtSetArg(al
[ac
], XmNitemCount
, 1); ac
++;
145 XtSetArg(al
[ac
], XmNtopAttachment
, XmATTACH_FORM
); ac
++;
146 XtSetArg(al
[ac
], XmNleftAttachment
, XmATTACH_WIDGET
); ac
++;
147 XtSetArg(al
[ac
], XmNleftWidget
, rowCol
); ac
++;
148 XtSetArg(al
[ac
], XmNrightAttachment
, XmATTACH_FORM
); ac
++;
149 XtSetArg(al
[ac
], XmNbottomAttachment
, XmATTACH_FORM
); ac
++;
150 listW
= XmCreateScrolledList(form
, "list", al
, ac
);
151 XtManageChild(listW
);
152 FreeStringTable(placeholderTable
);
154 return ManageListAndButtons(listW
, deleteBtn
, copyBtn
, moveUpBtn
,
155 moveDownBtn
, itemList
, nItems
, maxItems
, getDialogDataCB
,
156 getDialogDataArg
, setDialogDataCB
, setDialogDataArg
, freeItemCB
);
160 ** Manage a list widget and a set of buttons which represent a list of
161 ** records. The caller provides facilities for editing the records
162 ** individually, and this code handles the organization of the overall list,
163 ** such that the user can modify, add, and delete records, and re-order the
166 ** The format for the list of records managed by this code should be an
167 ** array of size "maxItems" of pointers to record structures. The records
168 ** themselves can be of any format, but the first field of the structure
169 ** must be a pointer to a character string which will be displayed as the
170 ** item name in the list. The list "itemList", and the number of items
171 ** "nItems" are automatically updated by the list management routines as the
172 ** user makes changes.
174 ** The caller must provide routines for transferring data to and from the
175 ** dialog fields dedicated to displaying and editing records in the list.
176 ** The callback "setDialogDataCB" must take the contents of the item pointer
177 ** passed to it, and display the data it contains, erasing any previously
178 ** displayed data. The format of the setDialogData callback is:
180 ** void setDialogDataCB(void *item, void *cbArg)
182 ** item: a pointer to the record to be displayed
184 ** cbArg: an arbitrary argument passed on to the callback routine
186 ** The callback "setDialogDataCB" must allocate (with XtMalloc) and return a
187 ** new record reflecting the current contents of the dialog fields. It may
188 ** do error checking on the data that the user has entered, and can abort
189 ** whatever operation triggered the request by setting "abort" to True.
190 ** This routine is called in a variety of contexts, such as the user
191 ** clicking on a list item, or requesting that a copy be made of the current
192 ** list item. To aide in communicating errors to the user, the boolean value
193 ** "explicitRequest" distinguishes between the case where the user has
194 ** specifically requested that the fields be read, and the case where he
195 ** may be surprised that errors are being reported, and require further
196 ** explanation. The format of the getDialogData callback is:
198 ** void *getDialogDataCB(void *oldItem, int explicitRequest, int *abort,
201 ** oldItem: a pointer to the existing record being modified in the
202 ** dialog, or NULL, if the user is modifying the "New" item.
204 ** explicitRequest: True if the user directly asked for the records
205 ** to be changed (as with an OK or Apply button). If a less direct
206 ** process resulted in the request, the user might need extra
207 ** explanation and possibly a chance to proceed using the existing
208 ** stored data (to use the data from oldItem, the routine should
211 ** abort: Can be set to True if the dialog fields contain errors.
212 ** Setting abort to True, stops whetever process made the request
213 ** for updating the data in the list from the displayed data, and
214 ** forces the user to remain focused on the currently displayed
215 ** item until he either gives up or gets it right.
217 ** cbArg: arbitrary data, passed on from where the callback was
218 ** established in the list creation routines
220 ** The return value should be an allocated
222 ** The callback "freeItemCB" should free the item passed to it:
224 ** void freeItemCB(void *item, void *cbArg)
226 ** item: a pointer to the record to be freed
228 ** The difference between ManageListAndButtons and CreateManagedList, is that
229 ** in this routine, the caller creates the list and button widgets and passes
230 ** them here so that they be arranged explicitly, rather than relying on the
231 ** default style imposed by CreateManagedList. ManageListAndButtons simply
232 ** attaches the appropriate callbacks to process mouse and keyboard input from
235 Widget
ManageListAndButtons(Widget listW
, Widget deleteBtn
, Widget copyBtn
,
236 Widget moveUpBtn
, Widget moveDownBtn
, void **itemList
, int *nItems
,
237 int maxItems
, void *(*getDialogDataCB
)(void *, int, int *, void *),
238 void *getDialogDataArg
, void (*setDialogDataCB
)(void *, void *),
239 void *setDialogDataArg
, void (*freeItemCB
)(void *))
243 /* Create a managedList data structure to hold information about the
244 widgets, callbacks, and current state of the list */
245 ml
= (managedListData
*)XtMalloc(sizeof(managedListData
));
247 ml
->deleteBtn
= deleteBtn
;
248 ml
->copyBtn
= copyBtn
;
249 ml
->moveUpBtn
= moveUpBtn
;
250 ml
->moveDownBtn
= moveDownBtn
;
251 ml
->getDialogDataCB
= NULL
;
252 ml
->getDialogDataArg
= setDialogDataArg
;
253 ml
->setDialogDataCB
= NULL
;
254 ml
->setDialogDataArg
= setDialogDataArg
;
255 ml
->freeItemCB
= freeItemCB
;
256 ml
->deleteConfirmCB
= NULL
;
257 ml
->deleteConfirmArg
= NULL
;
259 ml
->maxItems
= maxItems
;
260 ml
->itemList
= itemList
;
261 ml
->lastSelection
= 1;
263 /* Make the managed list data structure accessible from the list widget
264 pointer, and make sure it gets freed when the list is destroyed */
265 XtVaSetValues(ml
->listW
, XmNuserData
, ml
, NULL
);
266 XtAddCallback(ml
->listW
, XmNdestroyCallback
, destroyCB
, ml
);
268 /* Add callbacks for button and list actions */
269 XtAddCallback(ml
->deleteBtn
, XmNactivateCallback
, deleteCB
, ml
);
270 XtAddCallback(ml
->copyBtn
, XmNactivateCallback
, copyCB
, ml
);
271 XtAddCallback(ml
->moveUpBtn
, XmNactivateCallback
, moveUpCB
, ml
);
272 XtAddCallback(ml
->moveDownBtn
, XmNactivateCallback
, moveDownCB
, ml
);
273 XtAddCallback(ml
->listW
, XmNbrowseSelectionCallback
, listSelectionCB
, ml
);
275 /* Initialize the list and buttons (don't set up the callbacks until
276 this is done, so they won't get called on creation) */
277 updateDialogFromList(ml
, -1);
278 ml
->getDialogDataCB
= getDialogDataCB
;
279 ml
->setDialogDataCB
= setDialogDataCB
;
285 ** Update the currently selected list item from the dialog fields, using
286 ** the getDialogDataCB callback. "explicitRequest" is a boolean value
287 ** passed to on to the getDialogDataCB callback to help set the tone for
288 ** how error messages are presented (see ManageListAndButtons for more
291 int UpdateManagedList(Widget listW
, int explicitRequest
)
295 /* Recover the pointer to the managed list structure from the widget's
297 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
299 /* Make the update */
300 return incorporateDialogData(ml
, selectedListPosition(ml
), explicitRequest
);
304 ** Update the displayed list and data to agree with a data list which has
305 ** been changed externally (not by the ManagedList list manager).
307 void ChangeManagedListData(Widget listW
)
311 /* Recover the pointer to the managed list structure from the widget's
313 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
315 updateDialogFromList(ml
, -1);
319 ** Change the selected item in the managed list given the index into the
320 ** list being managed.
322 void SelectManagedListItem(Widget listW
, int itemIndex
)
324 selectItem(listW
, itemIndex
, True
);
328 ** Return the index of the item currently selected in the list
330 int ManagedListSelectedIndex(Widget listW
)
334 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
335 return selectedListPosition(ml
)-2;
339 ** Add a delete-confirmation callback to a managed list. This will be called
340 ** when the user presses the Delete button on the managed list. The callback
341 ** can put up a dialog, and optionally abort the operation by returning False.
343 void AddDeleteConfirmCB(Widget listW
, int (*deleteConfirmCB
)(int, void *),
344 void *deleteConfirmArg
)
348 XtVaGetValues(listW
, XmNuserData
, &ml
, NULL
);
349 ml
->deleteConfirmCB
= deleteConfirmCB
;
350 ml
->deleteConfirmArg
= deleteConfirmArg
;
354 ** Called on destruction of the list widget
356 static void destroyCB(Widget w
, XtPointer clientData
, XtPointer callData
)
358 /* Free the managed list data structure */
359 XtFree((char *)clientData
);
363 ** Button callbacks: deleteCB, copyCB, moveUpCB, moveDownCB
365 static void deleteCB(Widget w
, XtPointer clientData
, XtPointer callData
)
367 managedListData
*ml
= (managedListData
*)clientData
;
370 /* get the selected list position and the item to be deleted */
371 listPos
= selectedListPosition(ml
);
374 /* if there's a delete confirmation callback, call it first, and allow
375 it to request that the operation be aborted */
376 if (ml
->deleteConfirmCB
!= NULL
)
377 if (!(*ml
->deleteConfirmCB
)(ind
, ml
->deleteConfirmArg
))
380 /* free the item and remove it from the list */
381 (*ml
->freeItemCB
)(ml
->itemList
[ind
]);
382 for (i
=ind
; i
<*ml
->nItems
-1; i
++)
383 ml
->itemList
[i
] = ml
->itemList
[i
+1];
386 /* update the list widget and move the selection to the previous item
387 in the list and display the fields appropriate for that entry */
388 updateDialogFromList(ml
, ind
-1);
391 static void copyCB(Widget w
, XtPointer clientData
, XtPointer callData
)
393 managedListData
*ml
= (managedListData
*)clientData
;
394 int i
, listPos
, abort
= False
;
397 /* get the selected list position and the item to be copied */
398 listPos
= selectedListPosition(ml
);
400 return; /* can't copy "new" */
402 /* Bring the entry up to date (could result in operation being canceled) */
403 item
= (*ml
->getDialogDataCB
)(ml
->itemList
[listPos
-2], False
, &abort
,
404 ml
->getDialogDataArg
);
408 (*ml
->freeItemCB
)(ml
->itemList
[listPos
-2]);
409 ml
->itemList
[listPos
-2] = item
;
412 /* Make a copy by requesting the data again */
413 item
= (*ml
->getDialogDataCB
)(ml
->itemList
[listPos
-2], True
, &abort
,
414 ml
->getDialogDataArg
);
416 /* add the item to the item list */
417 for (i
= *ml
->nItems
; i
>=listPos
; i
--)
418 ml
->itemList
[i
] = ml
->itemList
[i
-1];
419 ml
->itemList
[listPos
-1] = item
;
422 /* redisplay the list widget and select the new item */
423 updateDialogFromList(ml
, listPos
-1);
426 static void moveUpCB(Widget w
, XtPointer clientData
, XtPointer callData
)
428 managedListData
*ml
= (managedListData
*)clientData
;
432 /* get the item index currently selected in the menu item list */
433 listPos
= selectedListPosition(ml
);
436 /* Bring the item up to date with the dialog fields (It would be better
437 if this could be avoided, because user errors will be flagged here,
438 but it's not worth re-writing everything for such a trivial point) */
439 if (!incorporateDialogData(ml
, ml
->lastSelection
, False
))
442 /* shuffle the item up in the menu item list */
443 temp
= ml
->itemList
[ind
];
444 ml
->itemList
[ind
] = ml
->itemList
[ind
-1];
445 ml
->itemList
[ind
-1] = temp
;
447 /* update the list widget and keep the selection on moved item */
448 updateDialogFromList(ml
, ind
-1);
451 static void moveDownCB(Widget w
, XtPointer clientData
, XtPointer callData
)
453 managedListData
*ml
= (managedListData
*)clientData
;
457 /* get the item index currently selected in the menu item list */
458 listPos
= selectedListPosition(ml
);
461 /* Bring the item up to date with the dialog fields (I wish this could
463 if (!incorporateDialogData(ml
, ml
->lastSelection
, False
))
466 /* shuffle the item down in the menu item list */
467 temp
= ml
->itemList
[ind
];
468 ml
->itemList
[ind
] = ml
->itemList
[ind
+1];
469 ml
->itemList
[ind
+1] = temp
;
471 /* update the list widget and keep the selection on moved item */
472 updateDialogFromList(ml
, ind
+1);
476 ** Called when the user clicks on an item in the list widget
478 static void listSelectionCB(Widget w
, XtPointer clientData
, XtPointer callData
)
480 managedListData
*ml
= (managedListData
*)clientData
;
481 int ind
, listPos
= ((XmListCallbackStruct
*)callData
)->item_position
;
483 /* Save the current dialog fields before overwriting them. If there's an
484 error, force the user to go back to the old selection and fix it
486 if (ml
->getDialogDataCB
!= NULL
&& ml
->lastSelection
!= 0) {
487 if (!incorporateDialogData(ml
, ml
->lastSelection
, False
)) {
488 XmListDeselectAllItems(ml
->listW
);
489 XmListSelectPos(ml
->listW
, ml
->lastSelection
, False
);
492 /* reselect item because incorporateDialogData can alter selection */
493 selectItem(ml
->listW
, listPos
-2, False
);
495 ml
->lastSelection
= listPos
;
497 /* Dim or un-dim buttons at bottom of dialog based on whether the
498 selected item is a menu entry, or "New" */
500 XtSetSensitive(ml
->copyBtn
, False
);
501 XtSetSensitive(ml
->deleteBtn
, False
);
502 XtSetSensitive(ml
->moveUpBtn
, False
);
503 XtSetSensitive(ml
->moveDownBtn
, False
);
505 XtSetSensitive(ml
->copyBtn
, True
);
506 XtSetSensitive(ml
->deleteBtn
, True
);
507 XtSetSensitive(ml
->moveUpBtn
, listPos
!= 2);
508 XtSetSensitive(ml
->moveDownBtn
, listPos
!= *ml
->nItems
+1);
511 /* get the index of the item currently selected in the item list */
514 /* tell the caller to show the new item */
515 if (ml
->setDialogDataCB
!= NULL
)
516 (*ml
->setDialogDataCB
)(listPos
==1 ? NULL
: ml
->itemList
[ind
],
517 ml
->setDialogDataArg
);
521 ** Incorporate the current contents of the dialog fields into the list
522 ** being managed, and if necessary change the display in the list widget.
523 ** The data is obtained by calling the getDialogDataCB callback, which
524 ** is allowed to reject whatever request triggered the update. If the
525 ** request is rejected, the return value from this function will be False.
527 static int incorporateDialogData(managedListData
*ml
, int listPos
, int explicit)
532 /* Get the current contents of the dialog fields. Callback will set
533 abort to True if canceled */
534 item
= (*ml
->getDialogDataCB
)(listPos
== 1 ? NULL
: ml
->itemList
[
535 listPos
-2], explicit, &abort
, ml
->getDialogDataArg
);
538 if (item
== NULL
) /* don't modify if fields are empty */
541 /* If the item is "new" add a new entry to the list, otherwise,
542 modify the entry with the text fields from the dialog */
544 ml
->itemList
[(*ml
->nItems
)++] = item
;
545 updateDialogFromList(ml
, *ml
->nItems
- 1);
547 (*ml
->freeItemCB
)(ml
->itemList
[listPos
-2]);
548 ml
->itemList
[listPos
-2] = item
;
549 updateListWidgetItem(ml
, listPos
);
555 ** Update the list widget to reflect the current contents of the managed item
556 ** list, set the item that should now be highlighted, and call getDisplayed
557 ** on the newly selected item to fill in the dialog fields.
559 static void updateDialogFromList(managedListData
*ml
, int selection
)
562 XmString
*stringTable
;
564 /* On many systems under Motif 1.1 the list widget can't handle items
565 being changed while anything is selected! */
566 XmListDeselectAllItems(ml
->listW
);
568 /* Fill in the list widget with the names from the item list */
569 stringTable
= (XmString
*)XtMalloc(sizeof(XmString
) * (*ml
->nItems
+1));
570 stringTable
[0] = XmStringCreateSimple("New");
571 for (i
=0; i
< *ml
->nItems
; i
++)
572 stringTable
[i
+1] = XmStringCreateSimple(*(char **)ml
->itemList
[i
]);
573 XtVaSetValues(ml
->listW
, XmNitems
, stringTable
,
574 XmNitemCount
, *ml
->nItems
+1, NULL
);
575 for (i
=0; i
< *ml
->nItems
+1; i
++)
576 XmStringFree(stringTable
[i
]);
577 XtFree((char *)stringTable
);
579 /* Select the requested item (indirectly filling in the dialog fields),
580 but don't trigger an update of the last selected item from the current
582 ml
->lastSelection
= 0;
583 selectItem(ml
->listW
, selection
, True
);
587 ** Update one item of the managed list widget to reflect the current contents
588 ** of the managed item list.
590 static void updateListWidgetItem(managedListData
*ml
, int listPos
)
593 XmString newString
[1];
595 /* save the current selected position (Motif sometimes does stupid things
596 to the selection when a change is made, like selecting the new item
597 if it matches the name of currently selected one) */
598 savedPos
= selectedListPosition(ml
);
599 XmListDeselectAllItems(ml
->listW
);
601 /* update the list */
602 newString
[0] = XmStringCreateSimple(*(char **)ml
->itemList
[listPos
-2]);
603 XmListReplaceItemsPos(ml
->listW
, newString
, 1, listPos
);
604 XmStringFree(newString
[0]);
606 /* restore the selected position */
607 XmListSelectPos(ml
->listW
, savedPos
, False
);
611 ** Get the position of the selection in the menu item list widget
613 static int selectedListPosition(managedListData
*ml
)
616 int *posList
= NULL
, posCount
= 0;
618 if (!XmListGetSelectedPos(ml
->listW
, &posList
, &posCount
)) {
619 fprintf(stderr
, "Internal error (nothing selected)");
623 XtFree((char *)posList
);
624 if (listPos
< 1 || listPos
> *ml
->nItems
+1) {
625 fprintf(stderr
, "Internal error (XmList bad value)");
632 ** Select an item in the list given the list (array) index value.
633 ** If updateDialog is True, trigger a complete dialog update, which
634 ** could potentially reject the change.
636 static void selectItem(Widget listW
, int itemIndex
, int updateDialog
)
638 int topPos
, nVisible
, selection
= itemIndex
+2;
640 /* Select the item */
641 XmListDeselectAllItems(listW
);
642 XmListSelectPos(listW
, selection
, updateDialog
);
644 /* If the selected item is not visible, scroll the list */
645 XtVaGetValues(listW
, XmNtopItemPosition
, &topPos
, XmNvisibleItemCount
,
647 if (selection
< topPos
)
648 XmListSetPos(listW
, selection
);
649 else if (selection
>= topPos
+ nVisible
)
650 XmListSetPos(listW
, selection
- nVisible
+ 1);