Long choice lists in OPL

Choice lists in OPL dialogs are limited to a maximum of 255 
items. For some applications this may be considered a problem 
and there is a mechanism by which this limit may be 
circumvented. Consider the following from the SIBO C SDK 
Introduction to HWIF:

	Longer choice lists

In some cases, the list of choices presented in a particular 
choice list may be too long for a choice list to be conveniently 
defined simply by one call to

uAddChoiceList.

The three functions uBeginDCL, uGrowDCL, and uAddDCL exist to help 
out with these so-called dynamic choice lists (the name reflects 
the fact that the contents of the choice list are built up over a 
few lines of code, rather than just being defined statically; in 
some cases, the contents in the list may change between 
different invocations of the dialog, to reflect changing run-time 
circumstances).

For example, the following routine builds up a choice list 
whose contents are the twelve month names:

LOCAL_C INT AddMonthChoiceList(UWORD *pmonno)
    {
    H_DI_CHOICE ch;
    TEXT mon[32];
    INT i;

    if (uBeginDCL(&ch))
        return(-1);            /* report failure to caller */
    for (i=0; i<12; i++)
        {
        p_nmmon(&mon[0],i);
        if (uGrowDCL(&ch,&mon[0]))
            return(-1);        /* report failure to caller */
        }
    return(uAddDCL("Month",pmonno,&ch));
    }

Alternatively, the function hSetVarrayInChlist can be used to 
build up a choice list, especially if the list might contain more 
than 255 items.



For example, by modifying the above code, the same effect can 
be achieved as shown below:

#define C_VASTR 6
#define O_VA_APPEND 7
#define OLIB_CAT 1

LOCAL_C INT AddMonthChoiceList(INT item_no,INT *pmonno)
    {
    UWORD used = 0;
    VOID  *pvarray;
    TEXT  mon[32];
    INT   i;
    
    
    if (uAddChoiceList("month",&used,NULL))
        return(-1);            /* failure */
    pvarray = p_new(OLIB_CAT,C_VASTR);
    for (i = 0; i < 12; i++)
        {
        p_nmmon(&mon[0],i);
        p_send3(pvarray,O_VA_APPEND,&mon[0]);
        }
    
hSetVarrayInChlist(item_no,(*pmonno),pvarray):
    }

Note that error handling in this code is incomplete.

It is entirely possible to emulate this fucntionality in an OPL 
program and the two OPL programs which accompany this 
documentation provide guidance on exactly how this may be 
done. The OPL programs are called STRLIST.OPL and 
FLATLIST.OPL and their names reflect a subtle variation in 
the way they operate. The OPL code is extensively commented 
and should be read with the following in mind. The basic 
process is straightforward:

*	Create an array which contains the a variable number of 
	records representing the data of our choice list. We use one 
	of two subclasses of the OLIB class VAROOT: VASTR or 
	VAFLAT. In the case of a VASTR array, our records are of 
	variable length. In the case of a VAFLAT array, the records 
	are of a fixed length. Both approaches have their 
	advantages and disadvantages. Assuming they contain the 
	same 'live' data, VASTR arrays use less memory than 
	VAFLAT arrays. However, VAFLAT arrays can be 
	navigated more swiftly than VASTR arrays - this is 
	particularly noticeable when pressing <Tab> to pop-out 
	the choice list while in a dialog. Consider the following 
	(taken from the SIBO SDK OLIB Reference):



	Usage summary
	
	VAROOT and VAFIX are abstract classes. VAROOT defines a 
	relatively large number of deferred methods in order to 
	promote polymorphism between the directly usable classes.
	
	The VASTR class is used to create arrays of variable length 
	text records, stored as zero terminated strings. The storage 
	overhead per string is only one byte, so it is particularly 
	suitable for storing short strings, of up to, say, several tens 
	of bytes, or for strings with a wide variation in size (such as 
	file names, which can be any length up to 128 bytes, but are 
	usually much shorter). Since the whole array is stored in a 
	single allocated cell, the VASTR class is most suitable for 
	arrays that contain:
		*	a small number of records
		*	a moderately large, but fixed maximum, number
			of records (for which the maximum capacity can
			be allocated in advance)
	Because of its suitability for storing file names, the VASTR 
	class is used, for example, to store directory listings. In 
	general, however, the VASTR class not particularly suitable for 
	arrays which can dynamically grow to a very large size. 

	The resulting repeated calls to p_realloc are likely to cause 
	heap fragmentation and seriously reduce the effective use 
	of memory.

	The VAFLAT class is used to create arrays of fixed length 
	records, where the whole array is stored in a single 
	allocated cell. The preferred usage is as for the VASTR class.

*	Create a dialog which contains the desired components but 
	don't run it yet. This is done using the dINIT "title", 
	dCHOICE..., commands.

*	Call the O_WN_SET method of the DLGBOX class to alter 
	the data elements used by the dCHOICE. This is clearly 
	marked in the code. Consider the following excerpts (taken 
	from the SIBO SDK Object Oriented Programming Guide):
	
	WN_SET	Set item by index

	VOID wn_set(INT index, VOID *par);

	Set one or more data elements in the property of the control 
	associated with the dialog box item with index number 
	index, by sending a WN_SET message to the control.

	The parameter par is assumed to be a pointer to a struct that 
	specifies the data to be set. The type of struct that is 
	expected depends on the class of the control that is being 
	set; the various structs are described in the following 
	Dialog Controls chapter.

	Setting
	
	A choice list is set by passing a pointer to an SE_CHLIST struct 
	to the wn_set method

	typedef struct
	   {
	    UWORD set_flags;    /* which fields are significant */
	    PR_VAROOT *data;    /* pointer to array containing data */
	    UWORD nsel;         /* index of current item */
	    } SE_CHLIST;

	The property to be set is indicated by ORing one or more of 
	the following flags into the flags field of the above struct.
	
	SE_CHLIST_NSEL	the index of the current item is to be set.

	SE_CHLIST_DATA	the data is to be replaced. The replacement
			data is a string array - see variable arrays
			in the OLIB Reference manual.

	SE_CHLIST_RETAIN	data should not be destroyed on
				destruction of the choice list
				control - once set this flag cannot
				be cleared.

	The content of a choice list can be set dynamically, say, 
	from the dialog's dl_dyn_init method. However, changing the 
	content of a choice list once the dialog has become visible 
	is not recommended, since the width of the dialog box is set 
	on initialisation. If the choice list content must be replaced, 
	then care should be taken to ensure that the text does not 
	become too wide for the dialog box to display.

*	Run the dialog. This is done using the standard DIALOG 
	function.

While viewing the OPL code, bear the following in mind:

*	DatDialogPtr is a magic static always at location 0x36. 
	System user interface library code may assume that this 
	location contains a pointer to the current dialog structure. 
	Otherwise it is free for use by application code. Hence, this 
	value will almost certainly be NULL until dINIT is 
	complete.

*	Note the use of ENTERSEND0() to ensure error values are 
	returned appropriately (given that many of the functions 
	call p_leave() rather than return an error). Using the 
	SEND function may result in your application panicing 
	unexcpectedly if an error arises.

*	Note the extensive use of '#' as a prefix to variable names. 
	This is done to tell OPL that the following expression is the 
	address to be used - not a variable whose address is to be 
	taken.

*	Note the use of UADD to skip the leading count byte of an 
	OPL string. This ensures that we do not cause any 'Integer 
	overflow' errors.
