        What is the Frankie Library?
        

        The Frankie library is a collection of Clipper 5.01/5.2 mouseable
        and configurable user interfaces.  Every UI is built upon an
        engine that has default looks and functionality, but can be
        reconfigured. The reconfiguration takes place by sending messages
        to the engine via APIs.

        The Engines
        
        The following UI engines are available in version 2.00:

        1. Database Browser
        2. Get/Read Subsystem
        3. Boxed Gets
        4. Vertical Menu
        5. Horizontal Menu
        6. Dialog Box
        7. Push Buttons
        8. Radio Buttons
        9. Scroll Bar
        10. File Viewer

        The APIs
        
        Each engine has its own set of API functions that serve to
        configure the engine (a Config API), interrogate its state (an
        Info API), or instruct it to do something (an Action API).

        Specialized UI Fucntions
        
        In addition to the generic engines, Frankie contains a number
        of specialized UI functions:

        1.  Drives picklist
        2.  Directory picklist
        3.  Files picklist
        4.  Fields picklist
        5.  "Drive Not Ready" dialog box
        6.  "Printer Not Ready" dialog box
        7.  "Which Drive?" dialog box
        9.  "Which Floppy?" dialog box
        10. Switch toggler dialog box
        11. Database browser scroll bars  
        12. Horizontal and vertical push buttons
        13.  A Read of one Get (two variants)
        
        Miscellaneous Functions
        
        Aside from the engine functions and their APIs, Frankie contains
        a wide variety of miscellaneous and useful functions.  These,
        however, are primarily for the purpose of supporting the Frankie
        engines.  They are not intended to serve as a full repertoire of 
        Clipper functions.  Frankie is primarily a UI engines library.
        


        What is a Frankie Engine?
        

        A Frankie engine is a self-contained entity that does a specific
        function.  Like an object, it contains the procedures and data
        that it needs to accomplish its purpose.  Every engine has its
        own default behaviour, looks, and functionality, which can be
        reconfigured via a set of APIs.

        In version 1.x of Frankie, the engines are almost transparent
        to the programmer.  A user-interface creates an engine,
        manipulates and otherwise processes it, then kills it, without
        much intervention by the programmer.  The farthest extent that
        the programmer knows about the engine is in the APIs where he,
        at times, has to make sure that the API he uses passes an engine
        identifier.

        In Frankie v2, the engines are more formally defined.  The
        programmer ow has a greater responsibility, if he so desires,
        in their manipulation.  This makes for greater flexibility and
        openness of the library.

        You may conceptualize an engine as something akin to a file
        handle.  When you open a file with F_OPEN(), you get a file
        handle.  To process the file you opened, you pass the file
        handle to the processing functions like F_READ(), F_WRITE(),
        F_CLOSE(), etc.  In the same manner, you process the engine by
        passing its identifier to an API.  Some users made the
        observation that the APIs are similar to the methods in OOP.


        There are certain behaviours common to all engines:
        

         F1 - Displays a Help screen if one has been defined.

         ALT-0 - EVALuates the Developer's Block.  The default block is
          merely a {|e|nil}.  It can however be globally set by
          ADdevblock().  For example,

          ADdevblock( {|e| tone(100,1)} ) // will beep the speaker for
                                          // whatever purpose.

          ADdevblock( {|e| ADaview( ADtrace() )} ) // will display a
                                                   // procedure trace

          ADdevblock( {|e| ADmessage( { ADn2s( memory(0) ),;
                                        ADn2s( memory(1) ),;
                                        ADn2s( memory(2) ) } )
                                        // will display the  memory values

          ADdevblock( {|e| swpruncmd( "", 0, "", "" )} )
                                // will shell to Dos, swapping as much 
                                // memory as possible, if app is
                                // linked with  Blinker.


         Click on top/left corner of screen - makes a selection (only for
          selection engines like ADvermenu(), ADhormenu(), etc.)

         Click on bottom/right corner of screen - displays a Help screen
          if one has been defined for the current context.      



        The Engine APIs
        

        The power and the flexibility of an engine are derived from a
        set of APIs specially developed for that particular engine.  These
        APIs are classified into three: the Config APIs, the Action APIs, 
        and the Info APIs.  

        A Config API is a function that configures the engine to behave
        differently from the default behaviour.

        An Action API is a function that instructs the engine to do
        some specified action.  Usually, this API is tied to a Config
        API to configure the engine to do something when a particular
        event (e.g., the F10 key is pressed, or the right button is
        clicked on a particular location) occurs.

        An Info API is a function that extracts information about the
        engine's status.  In the vertical menu engine, ADvermenu(), for
        example, the Info API ADvm_current() returns the currently
        highlighted menu option.  As with an Action API, an Info API may
        be tied to a Config API to configure the engine to return
        engine information when a particular event occurs.




        Configuring an Engine - General
        

        Every engine function accepts a number of parameters, one of
        which is uniformly referred to in the Frankie documentation as
        the Config Parameter, and written as bConfig or [bConfig].  The
        brackets serve to emphasize that it is an optional parameter.
        It is a codeblock, and it is by means of it that you configure
        an engine's behaviour.

        As an illustration, let us say you want to implement a vertical
        menu with three options, "First", "Second" and "Third".  You will
        call the vertical menu engine function like this:

          aMenu := { "First", "Second", "Third" }
          ADvermenu( ,, aMenu )

        NOTE: For a complete description of ADvermenu() please refer to
              the documentation.

        The above call will implement a menu with default behaviour. One
        such behaviour is that the first menu option is the initially
        highlighted option.  You may reconfigure this behaviour by:

          bConfig := {|| ADvm_initsel(2)}
          aMenu := { "First", "Second", "Third" }
          ADvermenu( ,, aMenu,,, bConfig )

        When the engine is so configured, the 2nd option will be initially
        highlighted, instead of the first.

        Another reconfigurable attribute of a vertical menu is the type
        of the selected option.  By the default, ADvermenu() returns the
        index position of the selected option, which is a numeric.  For
        example, if "Third" is selected from the above menu, the engine
        will return the numeric 3.  You may configure the engine to return
        the option itself, which is a character string. If you do so, it
        will return "Third" instead of 3.  You reconfigure by:

          bConfig := {|| ADvm_returntype( "C" )}
          aMenu := { "First", "Second", "Third" }
          ADvermenu( ,, aMenu,,, bConfig )

        You may reconfigure several attributes within the same bConfig.
        For example:
  
          bConfig := {|| ADvm_returntype( "C" ), ADvm_initsel( 3 )}
          aMenu := { "First", "Second", "Third" }
          ADvermenu( ,, aMenu,,, bConfig )

        In general then, you reconfigure an engine by defining the Config
        Parameter, bConfig, and passing it to the engine.  bConfig may
        contain one or a series of Config APIs.  If you call more than
        one Config API, separate them with a comma.


                ͸
                    The Database Browser Collection    
                ;

 The Engine Function
 
 ADdbview( [nTop], [nLeft], [nBottom], [nRight], [bConfig], [lKill],;
           [cHelpId] ) --> <nEngine>
 
 [nTop], [nLeft], [nBottom], [nRight] are the 4 corners of the  browse box.
 They are optional and default to 0, 0, maxrow()  and maxcol().

 [bConfig] - is the optional Configuration Specifier.  It is a codeblock that
 the engine EVALuates to configure itself.  It is typically a call to one or
 a series of configuration  functions.  See the examples below.

 [lKill] is a logical.  If it is TRUE, the engine that is created is automa-
 tically killed at exit, and the return value is NIL.  If it is FALSE,
 ADdbview() merely creates an engine and returns  its value.  It will not
 display the browse.  To subsequently  run the engine you need to explicitly
 make some API calls:

 nEngine := ADdbview(,,,,, .F. )    // create but not activate
 ADdb_activate( nEngine )           // activate the engine
 ADdb_kill( nEngine )               // kill the engine
 //See Example 5 below.

 [cHelpID] is a Help Identifier string used by the Frankie Help Facility

 <nEngine> is the numeric return value, which serves as a handle to the
 browse engine created by ADdbview().  It returns a 0 if no database is in
 use in the current workarea.

 Example 1:
 ----------

 ADdbview()   // browses the current database


 Example 2:
 ----------

 ADdbview( 10, 10, 20, 69 )   // browses the current database and limits the
                              // browse box to 10,10,20,69

 Example 3:
 ----------

 // browses the current database, and does a Get/Read when the Enter key is
 // pressed or when the left mouse button is clicked on the current field.

 ADdbview(,,,, {|| ADdb_fldread()} )


 Example 4:
 ----------

 // Initiates a dragging peration when F10 is pressed.  To drag the browse
 // box, use the arrow keys.

 ADdbview( 10,10,20,69, {|| ADdb_keys( { K_F10 }, {ADdb_drag()} )} )

 See DB_TOUR.PRG and the various DB_DEM??.PRGs for more examples.


 Example 5:
 ----------

 func MYBROWSE()
 local nEngine := ADdbview( 4,4,20,76,, .f. )  // create the engine

 ADwait()                       // wait for a keypress or mouse click
 ADdb_activate( nEngine )       // activate the browse
 ADdb_kill( nEngine )           // kill the engine
 return nil


 The Config APIs
 
 ADdb_box( [cFrame], [cHeadSep], [cColSep], [lShadow], [lExplode],;
           [nEngine] ) --> NIL
 
 Sets the attributes of the db box.

 [cFrame] is the box frame string.  It defaults to "͸Գ ".

 [cHeadSep] is the header separator string.  It consists of three characters. 
 The first character connects to the left side of the box; the third to the
 right side; the middle is the main separator character.  It defaults to
 "͵".

 [cColSep] is the column separator.  It defaults to space(3).

 [lShadow] is a logical value.  When TRUE, a shadow is dropped from the db
 box.  Defaults to FALSE.

 [lExplode] is a logical value.  When TRUE, the db box explodes. It defaults
 to FALSE.


 ADdb_color( [cStdColor], [cEnhColor], [nEngine] ) --> NIL
 
 Sets the colors of the browser, [cStdColor] and [cEnhColor] being the
 standard and enhanced colors, respectively.  Defaults to "W+/B" and "GR+/R"
 for color monitors, and "W/N" and  "N/W"  for monochrome.


 ADdb_columns( [aFldPos], [nEngine] ) --> NIL
 
 Specifies which fields are to be included in the engine. [aFldPos] is an
 array of the field positions of the fields to be included.  If [aFldPos] is
 not passed, it defaults to an array of all fields.  This is the same as not
 configuring with ADdb_columns() at all.  If an element is zero, a headingless
 column object will be created for it, with retrieved values always equal to
 the literal string "<calc>".  Such a column may be redefined with
 ADdb_defcolumn() with a heading and to retrieve a non-field value, or any
 calculated value.

 Example:
 --------
 select 0
 use child index child
 select 0
 use master index master
 set relation to id_num into child

 /*
 Define 3 columns.  The first is a calculated column that displays the record
 number.  The next 2 are for fields 1 and 5 of the master database.  The 4th
 column is another calculated column that retrieves data from the related 
 child.
 */
 bConfig := {|| ADdb_columns( {0,1,5,0} )},;
                ADdb_defcolumn( 1,;
                                "Rec #",;
                                {||recno()};
                              ),;
                ADdb_defcolumn( 4,;
                                "Child Data",;
                                {||child->detail};
                              );
            }

 ADdbview( ,,,, bConfig )



 ADdb_colheadings( [aHeadings], [nEngine] ) --> <aOldHeadings>
 
 Changes the default column headings to those specified in the array
 [aHeadings].  The default are of course the field names.  If [aHeadings] is
 not passed, no reconfiguration occurs.  If the number of elements of
 [aHeadings] is less than the number of columns in the browse, the extra
 columns will not be reconfigured.  If there are more elements in [aHeadings]
 than there are columns, the extra elements will be disregarded.

 <aOldHeadings> is an array of the old headings.


 ADdb_defcolumn( <nthColumn>, [cHeading], [bRetrieve], [nEngine] ) --> NIL
 
 Redefines the column object of the <nthColumn> column.  This is typically
 used for calculated columns, but may be used for any column.

 [cHeading] is a string to be used as the new heading.

 [bRetrieve] is a retrieval codeblock that returns any data type.

 Example:
 --------
 select 0
 use child index child
 select 0
 use master index master
 set relation to id_num into child

 /*
 Define 3 columns.  The first is a calculated column that displays the record
 number.  The next 2 are for fields 1 and 5 of the master database.  The 4th
 column is another calculated column that retrieves data from the related 
 child.
 */
 bConfig := {|| ADdb_columns( {0,1,5,0} )},;
                ADdb_defcolumn( 1,;
                                "Rec #",;
                                {||recno()};
                              ),;
                ADdb_defcolumn( 4,;
                                "Child Data",;
                                {||child->detail};
                              );
            }

 ADdbview( ,,,, bConfig )


 ADdb_enter( [bEnter], [nEngine] ) --> NIL
 
 Assigns a codeblock [bEnter] that ADdbview() EVALuates when the Enter key is
 pressed, or when the currently highlighted field value is clicked with the
 mouse left button.  There is no default action associated with the Enter
 key.  This API allows for its redefinition.

 Example:
 --------
 /*
 The speaker will beep whenever the Enter key is pressed, or the mouse is
 clicked on the highlighted field value.
 */
 ADdbview( ,,,, {|| ADdb_enter( {||tone(100,1)} )} )


 ADdb_escape( [bEscape], [nEngine] ) --> NIL
 
 Assigns a codeblock [bEscape] that ADdbview() EVALuates when the Escape key
 is pressed, or when the right button is pressed.  The default action when
 the Escape key is pressed is to exit the browse.  This API allows for a
 redefinition of such action. The codeblock must EVALuate to a logical value. 
 If it returns TRUE, the browse exits.

 Example:
 --------
 /*
 The user is asked to confirm his intention to exit when the Escape key is
 pressed, or the right button is pressed.
 */
 bConfirm := {|| ADboxmenu( "Exit?", { "Yes", "No" } ) == 1}
 ADdbview( ,,,, {|| ADdb_escape( bConfirm )} )


 ADdb_expattr( [aCoords], [cColor], [cFrame], [cPrompt], [nEngine] )  --> NIL
 
 Configures the looks of the expanded memoedit.  The default attributes of
 the memoedit are:

 Coordinates - {10,10,20,69}
 Main Color - "GR+/R" if colored, "N/W" if mono
 Prompt Color - "N/W"
 Frame - "͸Գ "
 Prompt - "Esc:CloseWindow   :ScrollUP   :ScrollDown   F1:Help"

 Example:
 -------
 ADdbview( ,,,, {|| ADdb_expattr( {0,0,24,79},;  // full screen
                                  {;
                                    "GR+/B",;   // main color
                                    "B/R";      // prompt color
                                  },;
                                  "Ŀ ";   // box frame
                                );
                };
         )


 ADdb_extra( [bDispBegin], [bDispEnd], [bUndisplay], [lAdditive] ) --> NIL
 
 Defines extra actions to be done before and after displaying the browse.

 [bDispBegin] is a codeblock that is EVALuated just before the browse is
 displayed.

 [bDispEnd] is a codeblock that is EVALuated right after the initial browse
 screen is displayed.

 [bUndisplay] is a codeblock that is EVALuated right after a Browse is
 undisplayed.

 [lAdditive] is an optional logical value.  If it is FALSE (the default), the
 behaviours defined in the call replaces whatever is already defined.  If it
 is TRUE, these definitions are added to the currently defined ones.

 Example:
 -------
 /*
 Display the alias at the center of the bottom line of the browse window.
 */
 bDispBegin := NIL
 bDispEnd := {|| ADcsay( ADdb_bottom(),;
                         ADdb_left(),;
                         ADdb_right(),;
                         alias() );
             }
 ADdbview( ,,,, {|| ADdb_extra( bDispBegin, bDispEnd )} )


 ADdb_fldread( [bConfig] ) --> NIL
 
 Defines the Enter key to initiate a Get/Read-ing of the currently
 highlighted field value.  The same action is taken if the mouse left button
 is clicked on the currently highlighted field value.  If the current column
 is a calculated column, ADdbview() does not go to edit mode.  ADdb_fldread()
 is identical to ADdb_enter( {||ADdb_adget()} ).  

 [bConfig] is a Configuration Block used by ADdb_fldread() to configure
 ADdb_adget().  See ADdb_adget() for a full discussion.

 Example (See also DB_DEM23.PRG):
 -------
 ADdbview(,,,, {|| ADdbfldread()} )


 ADdb_horsbar( [bConfig] ) --> NIL
 
 Adds a horozontal scroll bar to ADdbview().

 [bConfig] is an optional Configuration Block for the scroll bar.  This is
 where you define the scroll bar characters and colors.  See also the
 Horizontal Scroll Bar Engine.

 NOTE: This API MUST be called via the [bDispEnd] parameter of the
 ADdb_extra() API.

 Example:
 -------
 func main( cDbf )
 local bConfig := {||ADdb_extra( , {||ADdb_horsbar()} )}

 use ( cDbf )
 ADdbview( 10,10,20,69, bConfig )
 return NIL


 ADdb_hotedges( <lHot> ) --> NIL
 
 Specifies whether the browse window box edges are hot spots for scrolling
 or not.  By default they are.

 <lHot> is a logical value.  If it is TRUE, clicking on the edges will cause
 the browse pointer to move.  You may want to inactivate this default
 when you create special scroll push bottons, or when you add scroll bars to
 the browse.


 ADdb_keys( [aKeys], [bHandler], [lAdditive] ) --> NIL
 
 Defines hot keys that are local to the Browse.

 [aKeys] is an array of the inkey codes of the keys to be defined as hot
 keys.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot keys
 is pressed.  It ia automatically passed two parameters:

  1 - the index position of the pressed key
  2 - the inkey code of the pressed key

 [lAdditive] is an optional logical value.  If it is FALSE (the default), the
 hot keys defined in the call replaces whatever is already defined.  If it is
 TRUE, these definitions are added to the currently defined ones.

 Example:
 --------
 aKeys := { asc( "E" ), asc( "e" ), K_F10 }
 ADdbview( ,,,, {|| ADdb_keys( aKeys,;
                               {|n,k| Xhandler(n,k)};
                             );
                };
         )

 func Xhandler( nIndexPos, nKeyCode )
 if nIndexPos == 1 .or. nIndexPos == 2
    ADdb_adget()        // does a Get/Read when either "E" or
                        // "e" is pressed
 elseif nIndexPos = 3
    ADdb_exit()         // exits the browse when F10 is pressed
 endif
 return nil


 ADdb_lbuttons( [aSpots], [bHandler], [lAdditive] ) --> NIL
 
 Defines hot spots for the mouse left button.

 [aSpots] is an array of the screen sections that are to be defined as hot
 spots.  Each hot spot is an array of {top,left,bottom,right} coordinates.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot
 spots is clicked.  It ia automatically passed three parameters:

  1 - the index position of the clicked spot
  2 - the mouse cursor row position when the hot spot was clicked
  3 - the corresponding mouse column position

 [lAdditive] is an optional logical value.  If it is FALSE (the default), the
 hot spots defined in the call replaces whatever is already defined.  If it
 is TRUE, these definitions are added to the currently defined ones.

 Example:
 -------
 func main()
 local nT := 10, nL := 10, nB := 20, nR := 70
 use demo
 ADdbview( nT, nL, nB, nR,;
           {|| ADdb_extra( {|| NIL},;
                           {|| ADsay( nB, nL+1, "Edit" ),;
                               ADsay( nB, nL+11, "Exit" );
                           };
                         ),;
               ADdb_lbuttons( {;
                                { nB, nL+1, nB, nL+4 },;
                                { nB, nL+11, nB, nL+14 };
                              },;
                              {|n,r,c| Xhandler(n,r,c)};
                            );
           };
         )
 return nil

 func Xhandler( nIndexPos, nRow, nCol )
 if nIndexPos == 1
    ADdb_adget()
 elseif nIndexPos = 2
    ADdb_exit()
 endif
 return nil


 ADdb_move( [bVertical], [bHorizontal], [lAdditive] ) --> NIL
 
 Defines two codeblocks that are EVALuated when the browser highlight bar
 moves.  These blocks will typically update some kind of status information.

 [bVertical] is an optional code block that is EVALuated whenever the browse
 cursor moves vertically.  It is passed the browse engine ID.

 [bHorizontal] is an optional code block that is EVALuated whenever the
 browse cursor moves horizontally.  It is passed the browse engine ID.

 [lAdditive] is an optional logical value.  If it is FALSE (the default), the
 behaviours defined in the call replaces whatever is already defined.  If it
 is TRUE, these definitions are added to the currently defined ones.

 Example:
 -------
 /*
 Display the record number at the top left corner of the browse box.
 */
 ADdbview( ,,,,;
           {|| ADdb_move( {||ADsay( ADdb_top(),;
                                    ADdb_left() + 1,;
                                    padr(trim(str(recno())), 10);
                                  );
                          };
                        );
           };
         )

 ADdb_scope( [cKey] ) --> NIL
 
 Restricts the scope to records whose index values "contain" [cKey].  If
 [cKey] is NIL, no scope is set; if a scope is currently set, it will be
 removed; the whole database will become "visible".

 NOTE 1:  The database must be indexed when using this function.  The index
          key may be single- or multi-field, but must always be a character.

 NOTE 2:  An index value is said to "contain" [cKey] if dbseek( cKey, .t. )
          is TRUE.

 Example:
 --------
 func main()
 local aScn := ADmessage( { "Press Enter to initiate Subset Browsing" },;
                          18,, .F., .F. )
 use ..\software index ..\software
 ADdbview( 4,10,16,69, {|e| ADdb_enter( {|| Xfilter19()} )} )
 ADrestscn( aScn )
 return nil

 static func Xfilter19()
 static cKey

 if cKey == NIL
    cKey := &( indexkey(0) )
 endif

 if ADg_oneb( ,,, "Enter Key Value of Data Subset", @cKey )
     ADdb_scope( cKey )
 else
     if ADboxmenu( "Change Scope To", { "Whole Databae", "No Change" } ) == 1
         ADdb_scope()
     endif
 endif
 return nil

 ADdb_tmdrag() --> NIL
 
 Adds re-sizing feature to ADdbview() with a mouse. 
 
 NOTE: The smallest size that the browse box can be minimized to 3 data rows
 by 3 screen (not browse) columns.
 
 NOTE: This API MUST be called via the [bDispEnd] parameter of the
 ADdb_extra() API.

 Example
 -------
 func main( cDbf )
 local bConfig := {||ADdb_extra( , {||ADdb_tmdrag()} )}
 local aScn
 
 use ( cDbf )
 aScn = ADmessage( { "Click any corner of the browse box, drag, then release" }, 22,, .f., .f. )
 ADdbview( 4,4,20,76, bConfig )
 ADrestscn( aScn )
 return nil

 ADdb_versbar( [bGetPos], [bChangePos], [bRecCount], [bConfig] ) --> NIL
 
 Adds a vertical scroll bar to ADdbview()

 [bGetPos] is a codeblock used by ADdbview() to calculate the record position
 of the current record.  If not passed, it defaults to {||recno()}.

 [bChangePos] is a codeblock used to move the record pointer in the
 database.  Defaults to {|nPos| dbgoto(nPos)}.  Note that it receives one
 parameter, nPos, which is the numeric position to go to.

 [bRecCount] is a codeblock used to calculate the total number of records in
 the browse.  It defaults to {||lastrec()}.

 [bConfig] is an optional Configuration Block for the scroll bar.  This is
 where you define the scroll bar characters and colors.  See also the
 Vertical Scroll Bar Engine.

 NOTE: This API MUST be called via the [bDispEnd] parameter of the
 ADdb_extra() API.

 Example:
 -------
 func main( cDbf )
 local bConfig := {||ADdb_extra( , {||ADdb_versbar()} )}

 use ( cDbf )
 ADdbview( 10,10,20,69, bConfig )
 return NIL


 ADdbc_color( <nCol>, <cStdColor>, <cEnhColor>, [lRefresh] ) --> <aOld>
 
 Configures the color of a column.  May also be used as an Action API to
 change color on the fly.

 [nCol] is the position of the column whose color is to be changed.  It
 defaults to the current column position.

 <cStdColor> and <cEnhColor> are the new standard and enhanced colors,
 respectively.

 [lRefresh] is an optional logical value.  If it is TRUE, the change takes
 effect immediately.  Otherwise, the change takes effect at the next full
 stabilization.  The default is FALSE.  Typically, you would set this to TRUE
 when using the function as an Action API, and leave it as FALSE as a Config
 API.

 <aOld> is the return array containing two elements: the current standard
 color and the current enhanced color.  Both elements are padded up to 7
 spaces.

 NOTE: When using this function as a Config API, it MUST be called via the
 [bDispBegin] parameter of the ADdb_extra() API, and [nCol] MUST be speci-
 fied.


 Example:
 --------
 #include "inkey.ch"

 func main( cDbf )
 local bConfig

 bConfig := {||ADdb_extra( {||ADdbc_color( 2, "R+/G", "W+/W" )} ),;
               ADdb_keys( {K_F10}, {||ADdbc_color( 2, "W+/W", "R+/G" )} );
            }

 use (cDbf)
 ADdbview(,,,, bConfig)
 return NIL


 ADdbc_freeze( <nCols> ) --> NIL
 
 Freezes one or more columns.  May also be used as an Action API to freeze or
 unfreeze columns on the fly.

 <nCols> is the number of left-most columns to freeze.

 NOTE: When using this function as a Config API, it MUST be called via the
 [bDispBegin] parameter of the ADdb_extra() API.

 Example:
 -------
 #include "inkey.ch"

 func main( cDbf )
 local bConfig

 bConfig := {||ADdb_extra( {||ADdbc_freeze( 2 )} ),;
               ADdb_keys( {K_F10}, {||ADdbc_freeze( 0 )} );
            }

 use (cDbf)
 ADdbview(,,,, bConfig)
 return NIL


 ADdbc_width( [nCol], <nWidth>, [lRefresh] ) --> <nOldWidth>
 
 Changes the column width.  It may be used as a Config API or Action API.

 [nCol] is the position of the column whose width is to be changed.  It
 defaults to the current column position.

 <nWidth> is the new width dimension.

 [lRefresh] is an optional logical value.  If it is TRUE, the change takes
 effect immediately.  Otherwise, the change takes effect at the next full
 stabilization.  The default is FALSE.  Typically, you would set this to TRUE
 when using the function as an Action API, and leave it as FALSE as a Config
 API.

 NOTE: When using this function as a Config API, it MUST be called via the
 [bDispBegin] parameter of the ADdb_extra() API, and [nCol] MUST be speci-
 fied.

 Example:
 -------
 #include "inkey.ch"

 func main( cDbf )
 local bConfig

 bConfig := {||ADdb_extra( {|| ADdbc_width( 1, 40 )} ),;
               ADdb_keys( {asc( "-" ), asc( "+" )}, {|n,k|Xwidth(n)} );
            }

 use (cDbf)
 ADdbview(,,,, bConfig)
 return NIL


 func Xwidth( nthKey )
 if nthKey == 1
    ADdbc_width( , ADdbc_width() - 1, .t. )
 else
    ADdbc_width( , ADdbc_width() + 1, .t. )
 endif
 return NIL

 ADdbget_color( <cColor> ) --> NIL
 
 Configures the ADdb_adget() and ADdb_fldread() color.

 <cColor> is the Get color.  It defaults to "R/BG" in color monitors, and
 "W+/N" in monochromes.

 Example
 -------
 func main()
 use ..\tour\demo
 ADdbview(,,,, {|| ADdb_fldread( {||ADdbget_color( "R/W" )} )} )
 return NIL


 ADdbget_dict( <aDict> ) --> NIL
 
 Defines ADdb_adget() and ADdb_fldread() dictionary values.

 <aDict> is an array of dictionary entries.  Its size is equal to fcount(),
 that is, one element for each field.  Each element is a subarray of 3
 sub-elements:

  1 - the PICTURE clause, or NIL
  2 - the WHEN clause, or NIL
  3 - the VALID clause, or NIL

 NOTE:  An element in <aDict> may be specified as NIL.  Frankie will
 interpret this as { NIL, NIL, NIL }.  Similarly, the size of <aDict> may
 be less than fcount().  In such a case, the "missing" elements will become
 NILs.

 Example
 -------
 func main()
 local aDict := {;
                    { "!!xxxxxxxxxxxxxxxxxx", NIL, {||Xv_funcname()} },;
                    { "Y", {||!empty(fieldget(1))}, {||Xv_mouseable()} },;
                    { NIL, {||!empty(fieldget(1))}, {||Xv_param()} },;
                    { NIL, {||!empty(fieldget(1))}, NIL },;
                    { NIL, {||!empty(fieldget(1))}, NIL };
                }
 use demo
 ADdbview(,,,, {|| ADdb_fldread( {||ADdbget_dict( aDict )} )} )
 return NIL


 func Xv_funcname()
 local lValid := .t.
 local cValue := ADr_varget()

 if empty( cValue )
    ADzaprec()
 else
    if left( cValue, 2 ) != "AD"
        lValid := .f.
    endif
 endif
 return lValid


 func Xv_mouseable()
 return ADr_varget()


 func Xv_param()
 return ( ADr_varget() >= 0 )

                      values.

 ADdbget_extra( <bBefore>, <bAfter> ) --> NIL
 
 Defines ADdb_adget() before/after behaviours.  There are no default values.

 <bBefore> is a codeblock that is EVALuated before editing starts.

 <bEnd> is a codeblock that is EVALuated after editing ends.


 ADdbget_lock( <bRecLock>, <bRecUnlock> ) --> NIL
 
 Defines record locking/unlocking behaviours.

 <bLock> is a codeblock that is EVALuated just before ADdb_adget() fetches
 the field value to be edited.  This block must return a TRUE if it succeeds
 to lock the record, otherwise it returns a FALSE.  If it returns a FALSE,
 editing does not proceed.  Defaults to {||rlock()}.

 <bUnlock> is a codeblock that is EVALuated after the edited record is saved.
 Defaults to {||dbcommit(), dbunlock()}.

 ADdbget_stay() --> NIL
 
 Instructs ADdb_adget() not to move the highlight bar after editing.  By
 default the light bar moves after editing.


 The Action APIs
 
 ADdb_activate( <nEngine>, [lUndisplay] )--> <aOverwrittenScreen>
 
 Activates a dormat dbview engine.  An engine is dormant if it was created by
 ADdbview() with [lKill] set to FALSE.  Unlike an engine that was created
 with [lKill] := TRUE, a dormant engine is displayed only after an
 ADdb_activate() call.

 [lUndisplay] is a logical value.  When TRUE, the overwritten screen is
 restored when the browse is exited.  Defaults to TRUE.

 <aOverwrittenScreen> is a screen variable that defines the overwritten
 screen.  This value may be passed to ADrestscn() to restore it.

 Example:
 --------
 nEngine := ADdbview(,,,,, .F. )    // create but not activate
 ADdb_activate( nEngine )           // activate the engine
 ADdb_kill( nEngine )               // kill the engine


 ADdb_adget( [bConfig] ) --> NIL
 
 Does a Get/ADread() on the currently highlighted field value.  

 [bConfig] is a codeblock that ADdb_adget() EVALuates to configure itself.
 It is a series of calls to ADdb_adget() APIs.

 NOTE: ADdb_adget() APIs start with "ADdbget_".  Example: ADdbget_color(),
 ADdbget_lock(), ADdbget_unlock().

 Example:
 --------
 ADdbview( ,,,, {|| ADdb_enter( {|| ADdb_adget()} )} )


 ADdb_display( <nEngine> ) --> <aOverwrittenScreen>
 
 Displays a screen of a dormant dbview engine.  To remove it from the screen,
 use ADrestscn().

 Example:
 -------
 nEngine := ADdbview(,,,,, .F. )    // create but not activate
 aScn := ADdb_display( nEngine )    // display it
 ADwait()                           // wait for an event
 ADrestscn( aScn )                  // restore the overwritten screen


 ADdb_drag( [aColor] ) --> NIL
 
 Drags the browse window with the arrow keys.  As soon as ADdb_drag() is
 called a box outline is overlaid on the browse window.  To move the outline,
 press the arrow keys.  To transfer the browse window to the location of
 the outline, press Enter.  To abort the transfer, press Esc.  By default,
 the box outline is drawn in "N/W", but may be changed by passing [aColor].

 Example:
 -------
 /*
 Initiate dragging with the Enter key.
 */
 ADdbview(,,,, {|| ADdb_enter( {|| ADdb_drag()} )}


 ADdb_exit( [nEngine] ) --> NIL
 
 Exits the browse identified by [nEngine].  If [nEngine] is mot passed, it
 defaults to the currenly active engine.  Note that ADdb_exit() merely exits
 a browse; it does not kill it.

 Example:
 -------
 /*
 Assign F10 key to exit the browse
 */
 ADdbview(,,,, {|| ADdb_keys( {K_F10}, {||ADdb_exit()} )} )


 ADdb_fldput( <xValue>, [nEngine] ) --> NIL
 
 Stores a new value in the current field of the current record.

 Example:
 -------
 /*
 Stores the system date into the current field.  A runtime error  may occur
 if there is a data type incompatibility.
 */
 ADdbview(,,,, {|| ADdb_keys( {K_F10},;
                              {||ADdb_fldput( date() )};
                            );
               };
         )



 ADdb_kill( <nEngine> ) --> NIL
 
 Kills the engine identified by <nEngine>.  Note that it is good housekeeping
 practice to kill engines no longer in use.  When an engine is killed, the
 memory it occupies is released.

 You will only need to keep track of your engines if you call ADdbview()
 with the [lKill] parameter set to FALSE.  When [lKill] is not passed, or
 when it is explicitly set to TRUE, the engine is automatically killed at
 exit.

 Example:
 -------
 nEngine := ADdbview( ,,,,, .f. )
 ADdb_activate( nEngine )
 ADdb_kill( nEngine )


 ADdb_mdrag( [nMinimumRows], [nMinimumColumns], [aColor], [nEngine] ) --> NIL
 
 Initiates dragging and resizing of the browse with the mouse.  The resizing
 is initiated by clicking any browse box corner, then dragging while keeping
 the button pressed, and finally releasing the button on the location where
 you want the corner to be placed.

 NOTE: The smallest size that the browse box can be minimized to 3 data rows
 by 3 screen (not browse) columns.
 

 ADdb_navigate( <nDirection> ) --> NIL
 
 Moves the browse cursor to the direction indicated by <nDirection>.  The
 valid values of <nDirection> are #DEFINEd in Frankie.ch as:

 #define DBNAVIGATE_UP           1
 #define DBNAVIGATE_LEFT         2
 #define DBNAVIGATE_DOWN         3
 #define DBNAVIGATE_RIGHT        4

 Example
 -------
 #include "frankie.ch"
 #include "inkey.ch"

 func main(cDbf)
 use (cDbf)
 ADdbview(10,10,20,69, {||ADdb_keys({K_F10,K_F9,K_F8,K_F7},;
                                    {|n,k| Xnavigate(k)};
                                   );
                       };
         )
 return NIL

 //---------------
 func Xnavigate(k)
 
 if k == K_F10
    ADdb_navigate( DBNAVIGATE_UP )
 elseif k == K_F9
    ADdb_navigate( DBNAVIGATE_LEFT )
 elseif k == K_F8
    ADdb_navigate( DBNAVIGATE_DOWN )
 elseif k == K_F7
    ADdb_navigate( DBNAVIGATE_RIGHT )
 endif
 return NIL
    

 ADdb_seek( <cPrompt> ) --> <lFound>
 
 An ACTION API that gets from the user a key value to seek, then seeks it. If
 it is found, it repositions the browse to the matching record.  Otherwise,
 stays.

 <cPrompt> is the prompt string used to prompt the user to enter the key
 value to seek.

 [bNoMatch] is a codeblock that is EVALuated when no matching record is
 found.  Defaults to {||NIL}

 <lFound> - the return value


 Example:
 -------
 func main()
 use software index software
 ADdbview( ,,,, {|e| ADdb_enter( {|| ADdb_seek( "Key to seek",;
                                                {||tone(100,1)};
                                              );
                                 };
                               );
                };
         )
 return nil


 NOTE:  If the database is not indexed, the ADdb_seek() is disregarded.


 ADdb_size( [nT], [nL], [nB], [nR], [nEngine] ) --> NIL
 
 Changes the browse size on the fly. Does not automatically refresh the
 browse window.

 Example
 -------
 func main()
 local nArea := select()
 local nEngine, aScn
 
 select 0
 use ..\b\tour\customer
 nEngine := ADdbview( 6,10,18,69,, .f. )

 do while .t.
     ADdb_activate( nEngine )

     if !Xresize( nEngine )
        exit
     endif
 enddo

 ADdb_kill( nEngine )
 customer->( dbclosearea() )
 select (nArea)
 return NIL
 
 static func Xresize( nEngine )
 local aCoords := { ADdb_top(), ADdb_left(), ADdb_bottom(), ADdb_right() }
 local bConfig := {||ADgm_header( "New Coordinates" ),;
                     ADgm_labels( { "Top", "Left", "Bottom", "Right" } );
                  }
 local lResize := ADg_many( aCoords, bConfig )

 if lResize
    ADdb_size( aCoords[1], aCoords[2], aCoords[3], aCoords[4], nEngine )
 endif

 return lResize


 ADdb_softseek( [nRow], [nCol], [cPrompt], [cColor], [xExtra] ) --> NIL
 
 Opens up a Get field where the the user may enter a key value.  Each time a
 key is pressed, the browse does a softseek.  It repositions the browse if a
 match is found, or beeps if otherwise.

 [nRow], [nCol] are the coordinates where the Get is opened up.  Default to
 the bottom/left corner of the browse box.

 [cPrompt] is the 'say' part of the Get.  Defaults to "SOFTSEEK:"

 [cColor] is the 'color' part of the Get.  Defaults to the browse enhanced
 color.

 [xExtra] is either a logical value or a codeblock.  If it is a TRUE, then a
 box is drawn around the Get.  If it is a codeblock, then that codeblock is
 EVALuated before the Get is read.

 Example:
 -------
 func main()
 local aScn := ADmessage( { "Press Enter to initiate SoftSeek" },;
                          18,, .F., .F. )

 use ..\software index ..\sw_name
 ADdbview( 4,10,14,69, {|e| ADdb_enter( {|| ADdb_softseek( 19, 16, "Enter 'SOFTWARE' to seek:", "N+/W", .t. )} )} )
 use
 ADrestscn( aScn )
 return nil


 ADdb_stabilize( <lFullDisplay>, [lRefreshAll], [nEngine] ) --> NIL
 
 Stabilizes the browse.  

 <lFullDisplay> is a logical value.  If it is TRUE, the whole browse
 including the headers, but not the window frame, is refreshed.

 [lRefreshAll] is a logical value.  It is necessary only when <lFullDisplay>
 is FALSE.  If [lRefreshAll] is TRUE, all the data rows are refreshed;
 otherwise only the current data row is refreshed.


 ADdb_undisplay( <Engine> ) --> NIL
 
 Removes the browse display from the screen and restores the overwritten
 screen.

 Example
 -------
 func main()
 local nEngine, aScn

 use ..\b\tour\customer
 nEngine := ADdbview( 6,10,18,69,, .f. )
 ADmessage( {"Press any key to display the browse"}, 20,,, .f. )
 ADdb_display( nEngine )
 ADmessage( {"Press any key to undisplay the browse"}, 20,,, .f. )
 ADdb_undisplay( nEngine )
 return NIL

 ADdbc_hide( [nCol] ) --> <oColumn>
 
 Hides a column from the browse.  When this API is called, the browse is
 immediately refreshed.

 [nCol] is the position of the column to be hiden.  If it is not passed, it
 defaults to the current column position.

 <oColumn>, the return value, is the column object that was hiden.


 Example:
 --------
 #include "inkey.ch"

 func main( cDbf )
 local bConfig

 bConfig := {||ADdb_keys( {K_F9, K_F10}, {|n,k|Xhide(k)} ) }

 use (cDbf)
 ADdbview(,,,, bConfig)
 return NIL


 func Xhide( nKey )
 static aHidenColumns := {}, aHeadings := {}
 local oCol, nHiden, nSel

 if nKey == K_F9
    oCol := ADdbc_hide()

    if oCol != NIL
        aadd( aHidenColumns, ADdbc_hide() )
        aadd( aHeadings, atail( aHidenColumns ):heading )
    endif
 else
    nHiden := len( aHeadings ) 

    if nHiden > 0
        nSel := ADvermenu( ,, aHeadings )

        if nSel > 0
            ADdbc_unhide( , aHidenColumns[nSel] )
            adel( aHidenColumns, nSel )
            adel( aHeadings, nSel )
            asize( aHidenColumns, nHiden - 1 )
            asize( aHeadings, nHiden - 1 )
        endif
    endif
 endif

 return NIL

 ADdbc_move( [nCol], <nColPosition> ) --> NIL
 
 Moves a column to a new position, <nColPosition>.  When this API is called,
 the browse is immediately refreshed.

 [nCol] is the position of the column is to be moved.  If it is not passed,
 it defaults to the current column position.

 <nColPosition> is the new column position.

 Example:
 -------
 #include "inkey.ch"

 func main( cDbf )
 local bConfig

 bConfig := {||ADdb_keys( {K_F9, K_F10}, {|n,k|Xmove(k)} ) }

 use (cDbf)
 ADdbview(,,,, bConfig)
 return NIL

 func Xmove( nKey )
 if nKey == K_F9
    ADdbc_move(, 1)                          // to first column
 else
    ADdbc_move(, ADdb_object():colcount )    // to last column
 endif
 return NIL

 ADdbc_reset( [aHiden] ) --> NIL
 
 Moves a column to the end of the browse.  When this API is called, the
 browse is immediately refreshed.  Typically, this function is used to unhide
 a column previously hiden by ADdbc_hide().

 [aHiden] is an optional array of columns that have been previously hiden
 with ADdbc_hide()

 Example:
 -------
 #include "inkey.ch"

 func main( cDbf )
 local bConfig

 bConfig := {||ADdb_keys( {K_F2, K_F7, K_F8, K_F9, K_F10}, {|n,k|Xhide(k)} ) }

 use (cDbf)
 ADdbview(,,,, bConfig)
 return NIL


 func Xhide( nKey )
 static aHidenColumns := {}, aHeadings := {}
 local oCol, nHiden, nSel

 if nKey == K_F2
    ADdbc_reset( aHidenColumns )
    aHidenColumns := {}
    aHeadings := {}
 elseif nKey == K_F7
    oCol := ADdbc_hide()

    if oCol != NIL
        aadd( aHidenColumns, ADdbc_hide() )
        aadd( aHeadings, atail( aHidenColumns ):heading )
    endif
 elseif nKey == K_F8
    nHiden := len( aHeadings ) 

    if nHiden > 0
        nSel := ADvermenu( ,, aHeadings )

        if nSel > 0
            ADdbc_unhide( , aHidenColumns[nSel] )
            adel( aHidenColumns, nSel )
            adel( aHeadings, nSel )
            asize( aHidenColumns, nHiden - 1 )
            asize( aHeadings, nHiden - 1 )
        endif
    endif
 elseif nKey == K_F9
    ADdbc_move(, -1)
 else
    ADdbc_move()
 endif

 return NIL

 ADdbc_unhide( [nCol], <oColumn> ) --> NIL
 
 Adds a column to the browse.  When this API is called, the browse is
 immediately refreshed.  Typically, this function is used to unhide a column
 previously hiden by ADdbc_hide().

 [nCol] is the position where the column is to be un-hiden.  If it is not
 passed, it defaults to the current column position.

 <oColumn> is the column object to be added.

 Example:
 -------
 See ADdbc_hide()


 The Info APIs
 
 ADdb_bottom( [nEngine] ) --> <nBottom>
 
 Returns the row position of the bottom of the browse window.

 Example:
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADn2s( ADdb_bottom() )} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_colobject( [nColumn], [nEngine] ) --> <oColumn>
 
 Returns the a column object from a browse engine.

 [nColumn] is the numeric position of the desired column.  Defaults to the
 current column.

 [nEngine] is the numeric identifier of the browse engine being interrogated.
 Defaults to the current engine.

 <oColumn> is a reference to the returned tbcolumn object.

 Example
 -------
 ADdbview(10,10,20,69,;
          {||ADdb_keys({-9},;
                       {||ADmessage({"Cur col: " + ADdb_colobject():heading,;
                                     "1st col: " + ADdb_colobject(1):heading;
                                    });
                       };
                      );
          };
         )
 return NIL

 // When K_F10 is pressed, the headings of the current and the 1st columns
 // are displayed.



 ADdb_coords( [nEngine] ) --> <aCoords>
 
 Returns the coordinates of the browse engine identified by [nEngine].  If
 [nEngine] is not passed, it defaults to the current engine.  <aCoords> is
 an array in the form {top, left, bottom, right}.

 Example
 -------
 ADdbview(10,10,20,69, {||ADdb_keys({K_F10}, {||ADaview( ADdb_coords() )})})

 // When K_F10 is pressed, the coordinates 10,10,20,69 are displayed.



 ADdb_engine() --> <nEngine>
 
 Returns the active ADdbview engine ID.  If an ADdbview is currently active,
 ADdb_engine() returns the ID of this engine.  If one is currently being
 created, it returns its ID.  In all other cases, it returns the ID of the
 engine last activated or created.

 Example
 -------
 func main()
 local nArea := select()
 local nEngine, aScn
 local bConfig := {||ADmessage( {ADn2s(ADdb_engine())} ),;      // 1
                     ADdb_keys( {K_F10}, {||Xtest()} );
                  }
 
 select 0
 use customer
 ADdbview( 6,10,18,69, bConfig )
 customer->( dbclosearea() )
 select (nArea)
 return NIL
 
 static func Xtest()
 ADmessage( {ADn2s(ADdb_engine())} )       // 1
 return NIL


 ADdb_fldget( [nEngine] ) --> <xFieldValue>
 
 Returns the value of the current field.  Returns NIL if it is a calculated
 column.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADccast( ADdb_fldget() )} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_fldtype( [nEngine] ) --> <cFieldType>
 
 Returns the type of the current field.  Returns "U" if it is a calculated
 column.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADdb_fldtype()} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_fldname( [nEngine] ) --> <cFieldName>
 
 Returns the name of the current field.  Returns "" if it is a calculated
 column.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADdb_fldname()} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_fldpos( [nEngine] ) --> <nFieldPosition>
 
 Returns the database position of the current field.  Returns 0 if it is a
 calculated field.

 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADn2s( ADdb_fldpos() )} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_frame( [nEngine] ) --> <cWindowFrame>
 
 Returns the window frame string.


 ADdb_headlines( [nEngine] ) --> <nHeadLines>
 
 Returns the number of rows that the headings occupy.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADn2s(ADdb_headlines())} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_left( [nEngine] ) --> <nLeft>
 
 Returns the column position of the left of the browse window.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADn2s(ADdb_left())} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_object( [nEngine] ) --> <oTBrowse>
 
 Returns a reference to the tbrowse object.  CAUTION:  It is not recommended
 to send action messages to the tbrowse object directly.  It is however very
 safe to send info or status messages.


 ADdb_right( [nEngine] ) --> <nRight>
 
 Returns the column position of the right of the browse window.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADn2s(ADdb_right())} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_shadow( [nEngine] ) --> <lShadow>
 
 Returns a logical value, TRUE if the browse window casts a shadow.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADccast(ADdb_shadow())} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_top( [nEngine] ) --> <nTop>
 
 Returns the row position of the top of the browse window.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADn2s(ADdb_top())} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL

 ADdb_version() --> <cVersionNumber>
 
 Returns the version number of the ADdbview() collection as a string.

 Example
 -------
 func main()
 local bConfig := {||ADdb_keys( {K_F10},;
                                {||ADmessage( {ADdb_version()} )};
                              );
                  }
 use ..\b\tour\customer
 ADdbview( 6,10,18,69, bConfig ) 
 return NIL




                     ͸
                         The Get/Read Collection    
                     ;

  The Engine Function
 
 ADread( <getlist>, [bConfig], [nInitGet], [lKill], [xHelpID] )
     --> <aReadRetval>
 
 ADread() is a replacement of the original Clipper Read command.

 <getlist> is the array of Get objects to read.  If this is not passed,
 ADread() immediately with an exit code of RX_NOGETLIST (#defined in
 Frankie.ch).

 [bConfig] - is the optional Configuration Specifier.  It is a codeblock that
 the engine EVALuates to configure itself.  It is typically a call to one or
 a series of configuration functions.  See the examples below.

 [nInitGet] is the numeric index position of the Get to initially focus. 
 Defaults to 1.  If the WHEN clause of [nInitGet] returns a FALSE, the
 next-in-line Gets' WHENs are EVALuated one at a time until a TRUE is
 returned, at which time the rest of the EVALuations are discontinued and
 the focus set on the Get that returned a TRUE.  If no Get returned a TRUE,
 then ADread() immediately exits with an exitcode of RX_ALLGETS_NOT_AVAIL-
 ABLE, which is #defined in Frankie.ch.  If ADr_setscan() is set to FALSE,
 and [nInitGEt] returns a FALSE in its WHEN EVALuation, ADread() exits
 immediately with an exit code of RX_INITGET_NOT_AVAILABLE.

 [lKill] is a logical value.  If it is FALSE, the Read engine created by
 ADread() is preserved alive, and may be reactivated using the API,
 ADr_again().  Defaults to TRUE.

 [xHelpID] an optional Help Identifier string or array of identifiers.  If
 only one string is passed, it will be treated as the ID for all Gets.  If an
 array is passed, each Get will have its own ID.

 <aReadRetval> is the return value.  It is an array of the following 
 structure:

   1 - the numeric identifier of the Read engine created by ADread().  This
   value is nil if ADread() has not been configured to preserve the engine at
   exit.  By default is is automatically killed.  The  engine identifier is
   the gateway to reconfiguring the behaviour of ADread().  See Config.rd for
   a full discussion of how to change it behaviour via the various APIs.

   2 - is the numeric exit code.  It is equal to the inkey() code of the
   exit key unless:

     a. ADread was exited with the mouse right button, in which case its
        value is RX_ABORT
     b. ADread was exited because SET CONFIRM was FALSE, and the current Get
        is the last Get, and it exited when the Get edit buffer was fully
        filled.  The exit code is then RX_NOCONFIRM.
     c. ADread was programmatically aborted.  (RX_ABORT).
     d. ADread was programmatically saved/exited.  (RX_SAVE).

     NOTE: RX_NOCONFIRM, RX_ABORT and RX_SAVE are #DEFINEd in Frankie.Ch.


 NOTE 1: These is the equivalent command
 ---------------------------------------
 @ .., .. [SAY ..] ADGET .. [PICTURE ..] [VALID ..] [WHEN ..]
 -------------------------------------------------------------
 There is only one difference between this and CA's command:  It is not a
 GET but it's an ADGET.

 NOTE 2:  There are certain subtleties that you must know about the
 implementation of the VALID clause.
 ------------------------------------------------------------------
 1.  When the VALID clause is invoked, the cursor stays on its current
 location.  This behaviour is different from CA's GetSys where the cursor is
 typed out of the Get buffer.

 2.  The current values of the Get variables are not directly available from
 within a VALID function.  Use ADr_varget() instead.  For example:

 func main()
 local getlist[0]
 local cName := "Frankie"

 @10,10 say "Name" adget cName valid myfunc( cName )
 ADread( getlist )
 return nil


 func myfunc( cName )
 ? cName             // will always yield "frankie" even if the Get
                     //   was edited
 ? ADr_varget()      // will yield the current value
 return .t.

 For the same reason, updating a Get variable directly from within a VALID
 clause is not encouraged.  Use ADr_varput().

 This discussion is also applicable to WHEN clauses.

 Example 1:
 ----------
 // The simplest implementation of ADread()
 local getlist[0]
 @10,10 say "Short string" adget cShort
 @11,10 say "Long string " adget cLong picture "@S20"
 aScn := Xinfo1()
 ADread( getlist )

 Example 2:
 ----------
 // Note how ADr_varget() is used within the VALID function to
 // access the Get variable.
 
 @10,10 say "Date" adget dDate valid Xval2_date()

 static func Xval2_date()
 if ADr_varget() < date()
 ADmessage( { "A past date is not acceptable" } )
 return .f.
 endif
 return .t.


 Example 3:
 ----------
 // You may specify the Get to be initially focused
 ADread( getlist,, 3 )        // initial focus on 3rd Get
 
 Example 4:
 ----------
 // You may preserve the ADread engine at exit, and recall it some
 // time later.
 #define DO_NOT_KILL     .F.
 #define DO_NOT_BLANK    .F.
 ADread( getlist,,, DO_NOT_KILL )     // keep the Read engine alive
 nEngine := ADr_engine()              // Remember the engine identifier
 .
 .
 ADr_again( nEngine,, DO_NOT_BLANK )  // Resurrect the Read, preserving
                                 // the current values of the Gets
 
 Example 5:
 ----------
 // How to define hot keys.
 func main()
 local getlist[0]
 local cOne := "111"
 local cTwo := "222"
 local cThree := "333"
 // defines the hot keys and the hot key handler
 local bConfig := {|e| ADr_keys( {K_F10, K_F9, K_F8, K_F7},;
                                 {|ee,nth,nkey| Xkeys5(nkey)};
                                 );
                 }
 
 @10,10 say "One  " adget cOne
 @11,10 say "Two  " adget cTwo
 @12,10 say "Three" adget cThree
 ADread( getlist, bConfig )
 return nil
 
 
 static func Xkeys5( nKey )

 if nKey == K_F10
    ADr_blank()              // blank current Get
 elseif nKey == K_F9
    ADr_jump( 2 )            // jump to Get #2
 elseif nKey == K_F8
    ADr_abort()              // abort current Get and exit
 elseif nKey == K_F7
    ADr_save()               // save current Get and exit
 endif
 return nil
 
 
 static func Xinfo5()
 return ADmessage( {;
    "The simplest implementation of ADread().  Try jumping from one Get to",;
    "another with the mouse.  Try expanding 'Long string' into a memoedit",;
    "by pressing the Tab key while it is on focus.";
                   },;
                   18,, .f., .f.;
                 )
      
 Example 6:
 ----------
 // How to get-wrap ADread()
 func main()
 local getlist[0], aScn
 local cOne := "111"
 local cTwo := "222"
 local cThree := "333"
 
 @10,10 say "One  " adget cOne valid Xwrap6()
 @11,10 say "Two  " adget cTwo
 @12,10 say "Three" adget cThree valid Xwrap6()
 ADread( getlist )
 return nil
 
 
 static func Xwrap6()
 local nthGet := ADr_nthGet()
 local nExitCode := ADr_exitcode()
 
 if nthGet == 1
   if nExitCode == K_UP
      ADr_jump( 3 )
   endif
 elseif nthGet = 3
   if nExitCode == K_DOWN
     ADr_jump( 1 )
   endif
 endif
 return .t.

 Example 7:
 ----------
 // How to define hot spots.
 func main()
 local nT := 0, nL := 0, nB := 6, nR := 11
 local getlist[0], aScn2
 local cOne := "111"
 local cTwo := "222"
 local cThree := "333"
 // defines the hot spots and the hot spot handler
 local bConfig := {|e| ADr_lbuttons( {;
                                        {nT+1,nL+1,nT+1,nR-1},;
                                        {nT+3,nL+1,nT+3,nR-1},;
                                        {nT+5,nL+1,nT+5,nR-1};
                                     },;
                                     {|ee,nth,mrow,mcol| Xbuttons7(nth)};
                                   );
                  }


 ADcls( "" )
 aScn2 := Xspots7(nT,nL,nB,nR)       // paints the mouse _icons_
 @10,10 say "One  " adget cOne
 @11,10 say "Two  " adget cTwo
 @12,10 say "Three" adget cThree
 ADread( getlist, bConfig )
 ADrestscn( aScn2 )
 return nil
 
 
 static func Xspots7(nT,nL,nB,nR)
 local aScn := ADbox( nT, nL, nB, nR )
 ADsay( nT+1,nL+1,  "Blank Gets" )
 ADsay( nT+2,nL, "" + repl( "", nR - nL - 1 )  + "" )
 ADsay( nT+3,nL+1, "Abort" )
 ADsay( nT+4,nL, "" + repl( "", nR - nL - 1 ) + "" )
 ADsay( nT+5,nL+1,  "Save" )
 return aScn

 
 static func Xbuttons7( nthSpot )
 if nthSpot == 1
   ADr_blank( {} )         // blank all Gets
 elseif nthSpot == 2
   ADr_abort()             // abort current Get and exit
 elseif nthSpot == 3
   ADr_save()              // save current Get and exit
 endif
 return nil

 Example 8:
 ----------
 // How to use ADr_required() and ADr_popup()
 func main()
 local getlist[0]
 local dDate := ctod("")
 local cName := space(11)
 local aValidNames := { "ADvermenu()", "ADhormenu()", "ADboxmenu()",;
                        "ADdbview()", "ADread()" }

 @10,10 say "DATE" adget dDate;
                   valid ADr_required()
 @11,10 say "NAME" adget cName;
                   valid ADr_popup( 10, 40, aValidNames, {3,3,3,3,3} ) > 0
 ADread( getlist )
 return nil

 Example 9:
 ----------
 // An example of validating the whole Read
 func main()
 local getlist[0]
 local cOne := "   "
 local cTwo := "   "
 local cThree := "   "
 local bConfig := {|e| ADr_exit( {||Xvalread9()} )}
 
 @10,10 say "One  " adget cOne picture "@!"
 @11,10 say "Two  " adget cTwo
 @12,10 say "Three" adget cThree
 ADread( getlist, bConfig )
 return nil
 
 static func Xvalread9()
 local lValid := .t.
 local bValRead := {|| ADr_filled( {2,3},;
                                   {|| ADmessage( { "Two and Three may not be left blank" } )};
                                 );
                   }

 lValid := eval( bValRead )
 
 if lValid
    if ADr_varget(1) != "ABC"
       ADmessage( { "The only valid entry for One is 'ABC'" } )
       lValid := .f.
    endif
 endif

 return lValid

 Example 10:
 -----------
 // Demonstration of key validation
 func main()
 local getlist[0]
 local cPartNum := space(6)
 local nQuantity := 0
 local bConfig := {|e| ADr_keyval( {|c,p,n,b|Xkeyval10(c,p,n,b)} )}
 
 @10,10 say "Part number" adget cPartNum picture "@R !-9999"
 @11,10 say "Quantity   " adget nQuantity
 ADread( getlist, bConfig )
 return nil


//------------------------------------------------
static func Xkeyval10( cKey, nPos, nGet, cBuffer )
local lValid := .t.

if nGet == 1 .and. nPos == 1
if ascan( {"A", "B", "C" }, upper( cKey ) ) == 0
        ADmessage( { "Enter either 'A', 'B', or 'C' in the first position" } )
        lValid := .f.
endif
endif
return lValid

 
  The Config APIs
 
 ADr_escape( <bEscape> ) --> NIL
 
 Assigns an Abort codeblock to the Read engine.  This codeblock is EVALuated
 when the user attempts to abort (not Save/Exit ) the Read.  It must return a
 logical value.  If this return value is TRUE, the Read terminates as
 requested.  Otherwise, it stays.

 Example:
 -------
 /*
 User is asked to confirm an abort.
 */
 bConfig := {|| ADr_escape( {||ADboxmenu( "Abort?",;
                                          { "Yes", "No" };
                                        ) == 1;
                            };
                          );
            }
 ADread( getlist, bConfig )


 ADr_exit( <bExit> ) --> nil
 
 Assigns an Exit codeblock to the Read engine.  This codeblock is EVALuated
 when the user attempts to save/exit (not abort) the Read.  It must return a
 logical value.  If this return value is TRUE, the Read terminates as
 requested.  Otherwise, it stays.

 Example:
 --------
 /*
 User is asked to confirm a save/exit
 */
 bConfig := {|| ADr_exit( {||ADboxmenu( "Done?",;
                                        { "Yes", "No" };
                                      ) == 1;
                          };
                        );
            }
 ADread( getlist, bConfig )


 ADr_expattr( [aCoords], [cColor], [cFrame], [cPrompt] ) --> NIL
 
 Redefines the attributes of the memoedit.

 [aCoords] - the memoedit window coordinates. Defaults to {8,20,16,59}.

 [cColor] - the window color.  Defaults to the current standard color.

 [cFrame] - the window frame string.  Defaults to "͸Գ ".

 [cPrompt] - the prompt displayed at the bottom of the window.  Defaults to
 "Esc:Abort    Tab:Save    F1:Help".

 Example:
 -------
 ADread( getlist, {|| ADr_expattr( {12,10,26,69},;
                                   "GR+/R",;
                                   "Ŀ ",;
                                   "Abandon     Save    Help";
                                 );
                  };
       )


 ADr_keys( [aKeys], [bKeysHandler] ) --> NIL
 
 Defines hot keys local to the Read.

 [aKeys] is an array of the inkey codes of the keys to be defined as hot
 keys.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot keys
 is pressed.  It ia automatically passed two parameters:

   1 - the index position of the pressed key
   2 - the inkey code of the pressed key

 Example:
 --------
 bConfig := {|| ADr_keys( {K_F9, K_F10}, {|n,k|Xhandler(n,k)} )}
 ADread( getlist, bConfig )

 func Xhandler( nPos, nKeyCode )
 if nPos == 1         // same as: if nKeyCode == K_F9
   ADmessage( { "Pressed F9" } )
 elseif nPos = 2     // same as: if nKeyCode == K_F10
   ADmessage( { "Pressed F10" } )
 endif
 return nil

 Note: If you use ADr_keys() to implement nested Get/Reads, always save the
 cursor state before going down one level, and restore it when returnning.


 ADr_keyvalid( <bValid> ) --> nil
 
 Defines a codeblock that is EVALuated whenever a non-hotkey is pressed.  The
 codeblock typically validates the keystroke.  It is passed 4 parameters:

   1 - the character (chr() value) of the pressed key
   2 - the position of the cursor relative to the Get buffer
   3 - the index position of the current Get
   4 - the contents of the current Get buffer just prior to the key press.
       Note that this value contains non-picture template characters, if any.

 The codeblock must return a logical value.  If it returns a TRUE, the
 pressed key is accepted and placed in the Get buffer. Otherwise, it is
 rejected.

 Typically <bValid> is an IF..ELSEIF..ELSE..ENDIF construct.

 Example:
 --------
 // Only "A", "B", and "C" are acceptable in the first position of the Get.
 func main()
 local getlist[0], cEmpNum := "    "

 @10,10 say "Employee Number" adget cEmpNum picture "@A-999"
 ADread( getlist,;
         {|| ADr_keyvalid( {|c,p,n,b|Xkeyvalid(c,p,n,b)} )};
       )
 return nil

 func Xkeyvalid( cKey, nPos, nGet, cBuffer )
 local lValid := .t.

 if nPos == 1
   if ascan( { "A", "B", "C" }, upper( cKey ) ) = 0
      lValid := .f.
   endif
 endif
 return lValid


 ADr_lbuttons( [aSpots], [bLBHandler] ) --> NIL
 
 Defines hot spots for the mouse left button.

 [aSpots] is an array of the screen sections that are to be defined as hot
 spots.  Each hot spot is an array of {top,left,bottom,right} coordinates.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot
 spots is clicked.  It ia automatically passed three parameters:

   1 - the index position of the clicked spot
   2 - the mouse cursor row position when the hot spot was clicked
   3 - the corresponding mouse column position

 Example:
 -------
 func main()
 local getlist[0], bConfig
 local cVar1 := "Frankie", cVar2 := "Clipper"
 bConfig := {|| ADr_lbuttons( { { 14,10,16,17 } },;
                              {|n,r,c| Xlbhandler(r)};
                            );
            }
 @10,10 adget cVar1
 @11,10 adget cVar2

 @14,10 say "HotSpot1"       // hot spots
 @15,10 say "HotSpot2"
 @16,10 say "HotSpot3"
 ADread( getlist, bConfig )
 return nil

 func Xlbhandler( nRow )
 // Interrogate where the mouse cursor was when the left button was clicked.
 if nRow == 14
   ADmessage( { "HotSpot1" } )
 elseif nRow == 15
   ADmessage( { "HotSpot2" } )
 elseif nRow == 16
   ADmessage( { "HotSpot3" } )
 endif
 return nil

 Note: If you use ADr_lbuttons() to implement nested Get/Reads, always save
 the cursor state before going down one level, and restore it when returning.


 ADr_readexit( [lUpDown], [lLeftRight] ) --> NIL
 
 Defines whether the Up/Down keys and the Left/Down keys will be considered
 as exit keys.  Bt default, they are not.  

 [lUpDown] is a logical value.  If it is TRUE, the up/down keys are treated
 as exit keys.  ADread() will exit if the current Get is the first (or last)
 Get.

 [lLeftRight] is a logical value.  If it is TRUE, then the left/right keys
 are treated as exit keys.  If they are exit keys, then the Read behaviour is
 modified as follows:

   If the cursor is on the first position, pressing the Left key moves the
    focus to the preceeding Get, or exits ADread() if the current Get is the
    first Get.

   If the cursor is on the last position, pressing the Right key moves the
    focus to the next Get, or exits ADread() if the current Get is the last
    Get.

 ADr_setscan( <lScan> ) --> NIL
 
 Defines how ADread() locates the next Get to focus in the following
 situations:

   1.  At startup, if the WHEN of the initially-to-be-focused Get returns a
       FALSE.  See ADread()'s discussion of the its [nInitGet] parameter.

   2.  When a jump is called (via ADr_jump() or a mouse click to an unfocused
       Get), and the intended Get's WHEN returns a FALSE.  See ADr_jump().

  <lScan> is a logical value to which the ADr_setscan() switch is set to.  By
  default, this switch is set to TRUE.

  NOTE: The setting if ADr_setscan() is disregarded if ADr_valjump() is set
        to FALSE.

  Example
  -------
  ADread( getlist, {||ADr_setscan( .f. )} )


 ADr_timeout( <nSeconds>, <bTimeOut> ) --> NIL
 
 Configures ADread() to timeout after <nSeconds> of idle time, and EVALuates
 <bTimeOut>.

 Example:
 -------
 // beep the speaker every one second of inactivity
 ADread( getlist, {|| ADr_timeout( 1, {||tone(100,1)} )} )

 // abort after 10 seconds of inactivity
 ADread( getlist, {|| ADr_timeout( 10, {||AD_abort()} )} )

 ADr_valjumps( <lValidate> ) --> NIL
 
 Configures ADread() whether or not to execute the Valid clause when jumping
 from one Get to another.  By "jumping" it is meant when an unfocused Get
 is clicked to set the focus on it, or when ADr_jump() is called.  A Up or
 Down key press is not considered "jumping."  By default, ADread() executes
 the Valid clause before an intended jump, and will only do the jump if the
 clause returns a TRUE.

 If ADr_valjump() is set to FALSE, the jump is immediately done without even
 bothering to EVALuate the Valid clause.  Furtheremore, any intervening
 Valids and Whens are not EVALuated either.

 NOTE:  If ADr_valjump() is set to FALSE, ADr_setscan()'s setting is dis-
        regarded.

 Example
 -------
 @10,10 adget xxx valid .f.
 @11,10 adget yyy
 @12,10 adget zzz
 ADread( getlist, {||ADr_valjump( .f. )} )

 In the above example, assume that 'xxx' is the current Get and an
 unfocused Get is clicked, the focus will jump to that Get.

 
  The Action APIs
 
 ADr_abort( [nExitCode] ) --> NIL
 
 Aborts ADread(), optionally sets the exit code to [nExitCode].  If
 [nExitCode] is not passed, it is set to RX_ABORT which is #defined in
 Frankie.ch.

 Example:
 -------
 // abort after 10 seconds of inactivity
 ADread( getlist, {|| ADr_timeout( 10, {||AD_abort()} )} )


 ADr_activate( <nEngine>, [nInitGet], [lBlank], [lRetainUpdated] )
    --> <nExitCode>
 
 Re-activates an exited (but not killed) ADread().  If the [lKill] parameter
 in ADread() is set to FALSE, ADread() preserves the Read engine when it
 exits.  By invoking ADr_activate(), you may activate this Read engine again.

 [nInitGet] is the index position of the Get to initially focus at
 reactivation time.  Defaults to 1.

 [lBlank] is a logical value.  If TRUE, the reactivated Gets will be blanked
 at reactivation time.  Defaults to TRUE.

 [lRetainUpdated] is a logical value.  If TRUE, the Gets will retain their
 updated status from the previous Read.  Defaults to FALSE.  Note that this
 is only relevant if [lBlank] is FALSE.

 Example:
 --------
 ADread( getlist,,, .F. )    // preserve the engine at exit
 nEngine := ADr_engine()     // remember the engine identifier
 //
 // more code here
 //
 ADr_activate( nEngine )     // re-activates the engine

 Note that if [lKill] is specified as FALSE in ADread(), the engine must be
 explicitly killed using ADr_kill() when no longer needed.


 ADr_blank( [xGets] ) --> NIL
 
 Blanks one or more Gets.

 [xGets] identifies which Gets to blank, according to these rules:

   [xGets] == NIL, the current Get is blanked
   [xGets] == {}, all the Gets are blanked
   [xGets] == {n1,n2,..}, the Gets identified by n1,n2,.. are blanked
   [xGets] == n1, Get #n1 is blanked

 Example:
 -------
 // Blank all the Gets when F10 ir pressed
 bConfig := {|| ADr_keys( {K_F10}, {|| ADr_blank( {} )} )}
 ADread( getlist, bConfig )


 ADr_jump( <nthGet> ) --> NIL
 
 Jumps to a Get number <nthGet>.

 NOTE:  When ADr_jump() is called,  the following series of actions happen:
 --------------------------------------------------------------------------
 1.  First, the VALID clause of the current Get is EVALuated.  If it returns
     a FALSE, then the focus stays in the current Get.
 2.  Then, the WHEN clause of <nthGet> is EVALuated.  If it returns a FALSE,
     the focus stays where it was originally.
 3.  If action #2 returns a TRUE, the WHEN clause of the next-in-line Get is
     EVALuated.  If it returns a FALSE, the focus stays there.
 4.  If action #3 returns a TRUE, the VALID of that Get is EVALuated.  If it
     returns a FALSE, the focus stays there.
 5.  If action #4 returns a TRUE, the next-in-line EVALuations (actions 3
     and 4) are done, and so on until the <nthGet>.

 Steps 3,4 and 5 may be skipped by setting ADr_setscan() to FALSE.  In such a
 case, if action #2 returns a TRUE, the focus immediately goes to <nthGet>.

 NOTE:  If ADr_valjump() is set to FALSE, the above will not be carried out.
        The jump will immediately take effect.

 Example:
 -------
 func main()
 local getlist[0], bConfig
 local nVar1 := 1
 local nVar2 := 2
 local nVar3 := 3
 local nVar4 := 4
 bConfig := {|| ADr_keys( {K_F10}, {|| ADr_jump( 1 )} )}

 @10,10 adget nVar1
 @11,10 adget nVar2
 @12,10 adget nVar3
 @13,10 adget nVar4 valid Xvalid()
 ADread( getlist, bConfig )
 return nil

 func Xvalid()
 if ADr_varget() == 0
   ADr_jump(1)
 endif
 return .t.


 ADr_kill( <nEngine> ) --> NIL
 

 Kills the ADread engine identified by <nEngine>.  Typically used to kill an
 engine created by an ADread() call that was passed a FALSE [lKill].

 If you specify a non-existent engine, or the currently "reading" engine,
 ADr_kill() you will get a runtime error.

 Example: See ADr_engine().


 ADr_save( [nExitCode] ) --> NIL
 

 Exits the current ADread after saving the current Get. Optionally sets the
 exit code to [nExitCode].  If no parameter is passed, the exit code is set to
 RX_SAVE which is #defined in Frankie.ch.

 Note that the save request is ignored if the current Get does not pass the
 VALID clause, if any.

 Example:
 -------
 // Assigns the F10 key to save and exit the Read.
 bConfig := {|| ADr_keys( {K_F10}, {|| ADr_save()} )}
 ADread( getlist, bConfig )


 ADr_varput( <xValue>, [nthGet], [nEngine] ) --> NIL
 
 Puts a specified value to a Get.

 <xValue> is the value to put.  Its type must correspond to the type of the
 target Get, otherwise a runtime error will occur.

 [nthGet] is the index position of the target Get.  It defaults to the current
 Get if it is not specified.

 [nEngine] is the identifier of the target engine.  It defaults to the current
 engine if it is not specified.

 Example:
 -------
 /*
 Assigns the F10 key to put the current date to the current Get (assuming that
 the current Get is a date variable).
 */
 bConfig := {|| ADr_keys( {K_F10}, {|| ADr_varput( date() )} )}
 ADread( getlist, bConfig )

 
  The Info APIs
 
 ADr_engine() --> <nEngine>
 
 Returns the currently active ADread engine.  This function may be called
 during the Read via the WHEN clause, VALID clause, a hot key, or hot spot
 handler.  It may also be called right after an ADread() that was called with
 a FALSE [lKill] exits.

 Example
 -------
 func main()
 local aaa := "aaaaaa"
 local bbb := "bbbbbb"
 local ccc := "cccccc"
 local ddd := "dddddd"
 local eee := "eeeeee" 
 local getlist[0], nEngine, aScn

 ADcls(,,,,,, .t.)
 aScn := ADbox( 9,8,15,17 )
 @10,10 adget aaa valid {||ADmessage( {str( ADr_engine() )} ), .t.}
 @11,10 adget bbb 
 @12,10 adget ccc 
 @13,10 adget ddd
 @14,10 adget eee when {||ADmessage( {str( ADr_engine() )} ), .t.}
 ADread( getlist, {||ADr_keys({-9}, {||ADmessage( {str( ADr_engine() )} )})},,.f. )
 nEngine := ADr_engine()
 ADrestscn( aScn )
 ADmessage( {str( ADr_engine() )} )     // 1
 aScn := ADbox( 9,8,15,17 )
 ADr_activate( nEngine,, .f. )
 ADr_kill( nEngine )
 ADmessage( {str( ADr_engine() )} )     // 0
 ADrestscn( aScn )
 return NIL

 ADr_exitcode( [nEngine] ) --> <nExitCode>
 
 Returns the exit code of the last Get.  If the ADread() was exited after the
 Get was exited, then this is also the exit code of the ADread().  The exit
 code is normally the inkey code of the key used to exit the Get.  If it was
 exited via other means, e.g. ADr_save(), ADr_abort() or ADr_jump() the exit
 code may be one of the following, which are #defined in Frankie.ch:

 RX_NOCONFIRM - when SET CONFIRM is FALSE, and the Get was automatically
                exited because the Get buffer was full
 RX_SAVE      - when an ADr_save() with no parameter was called
 RX_ABORT     - when an ADr_abort() with no parameter was called
 RX_JUMP      - when an ADr_jump() was called
 RX_NOGETLIST - when the ADread() was called with no getlist passed to it.
                When this happens, ADread() exits right away.
 RX_ALLGETS_NOT_AVAILABLE - when the initial Get is not available,
                            ADr_setscan() is TRUE, and all the succeeding
                            Gets are also not available.
 RX_INITGET_NOT_AVAILABLE - when the initial Get is not available and
                            ADr_setscan() is set to FALSE.

 [nEngine] is an optional engine ID.  It defaults to the last created or
 activated engine.

 ADr_getobj( [nEngine] ) --> <oGet>
 
 Returns a reference to the currently focused Get object of the Read engine,
 [nEngine] (defaults to the currently active engine).


 ADr_issaved() --> <lSaved>
 
 Returns a logical value indicating whether the last exited ADread() was
 saved or not.


 ADr_nextget() --> <nNthGet>
 
 Returns the index position of the Get intended to be focused next.  ADread()
 updates this value every time it detects an intention to change focus.  The
 return value of this API depends only on intention of the user, and not
 whether the intended change in focus was actually accomplished or not.

 In the following scenarios, assume that there are two Gets in a Read. If
 Get#1 is currently focused, and

 1. the Enter or Down key is pressed, ADr_nextget() will return 2.
 2. the PgUp or Up key is pressed, ADr_nextget() will return 0.
 3. the PgDn key is pressed, ADr_nextget() will return a 3.

 If the currently focused Get is #2, and

 1. the Enter, Down or PgDn ley is pressed, ADr_nextget() will return 3.
 2. the Up key is pressed, ADr_nextget() will return 1.
 3. the PgUp key is pressed, ADr_nextget() will return 0.

 If a Get is clicked, ADr_nextget() will return the index position of that
 Get.

 At startup, ADr_nextget()'s value is the Get specified to be initially
 focused.

 See also ADr_prevget() and ADr_nthget().


 ADr_nthget( [nEngine] ) --> <nNthGet>
 
 Returns the index position of the currently focused Get in [nEngine].
 [nEngine] defaults to the current engine.

 When does the Get's focus change?  It changes when the user attempts to
 change it.  The focus is immediately moved to the intended Get as soon as
 the post-block returns a TRUE.  If it is not possible to lodge in the
 intended Get, as when the pre-block returns a FALSE, the focus moves away
 from it.  Otherwise, it stays there until the user attempts to move it
 again.  Thus, during post-validation, the focused Get is the one that is
 being post-validated.  During pre-validation, the focused get is the one
 being pre-validated.


 When ADread() is waiting for a user response, NTHGET is the current Get.
 While a WHEN clause is being EVALuated, NTHGET is temporarily set to the Get
 that is being pre-validated.  After pre-validation, its value becomes
 whatever Get gets focused.  At any other times, NTHGET is the Get in focus.
 Its value does not change at exit.  Its value is 0 just before it focuses
 on the Get to be initially highlighted.

 See also ADr_prevget() and ADr_nextget().

 ADr_prevget() --> <nNthGet>
 
 Returns the index position of the previously focused Get.

 Description of operation:  ADread() keeps track of the index positions of
 three types of Gets: (1) the current Get, (2) the Get that was in focus
 before the current Get was focused, and (3) the Get that the user intends to
 focus.  For the sake of this discussion, we will refer to them was NTHGET,
 PREVGET and NEXTGET, and they are returned respectively by the 3 APIs
 ADr_nthget(), ADr_prevget() and ADr_nextget().

 When ADread() is waiting for a user response, NTHGET is the current Get.
 While a WHEN clause is being EVALuated, NTHGET is temporarily set to the Get
 that is being pre-validated.  After pre-validation, its value becomes
 whatever Get gets focused.  At any other times, NTHGET is the Get in focus.
 Its value does not change at exit.  Its value is 0 just before it focuses
 on the Get to be initially highlighted.

 At startup, PREVGET is 0.  Its value changes when the focus changes.  Unlike
 NTHGET, PREVGET's value during pre-validation does not change.  Its value
 does not change at exit.

 At startup, NEXTGET's value is the Get specified to be initially focused.
 Its value always changes when the user does something (e.g. presses the
 cursor keys) to change focus.  NEXTGET returns the intended Get,
 irrespective of whether that particular Get actually gets the focus or not.


 ADr_spot( [nthGet], [nEngine] ) --> <aSpot>
 
 Returns the screen coordinates of the Get buffer.

 [nthGet] is the target Get.  It defaults to the current Get.
 
 [nEngine] is the target engine.  It defaults to the current engine.

 ADr_type( [nthGet], [nEngine] ) --> <cType>
 
 Returns the type of a Get.

 [nthGet] is the target Get.  It defaults to the current Get.

 [nEngine] is the target engine.  It defaults to the current engine.


 ADr_updated( [nEngine] ) --> <aUpdatedStatus>
 

 Returns the array of update status of the Gets.  When ADread() is called,
 all the elements of this array are initialized with FALSE.  When a Get is
 edited, the corresponding element is set to TRUE.

 [nEngine] is an optional engine ID.  if there is an active engine when
 ADr_updated() is called, it defaults to the current engine.  Otherwise, it
 defaults to the last created or activated engine.  To return the update
 status of the last exited engine, pass a -1 to the function.

 NOTE: If ADread() exits, and its engine is not killed, the update status of
       the Gets is preserved.  When the engine is subsequently reactivated,
       this status info may be restored or re-initialized to FALSE.  See
       ADr_activate().

 ADr_varget( [nthGet], [nEngine] ) --> <xValue>
 
 Returns the current value of a Get.  This is the preferred way of inter-
 rogating the current values of the Gets while the engine is active, e.g.,
 inside Valid and When clauses, or within routines called via hot keys and
 hot spots.

 Example
 -------
 local nAge := 0, getlist[0]
 @10,10 ADget nAge valid Xvalidate()
 ADread( getlist )

 func Xvalidate()
 local lValid := .t.

 if ADr_varget() < 18
   ADmessage( { "Under age!" } )
   lValid := .f.
 endif
 return lValid


 ADr_varname( [nthGet] ) --> <cName>
 
 Returns the name of the Get variable identified by [nthGet].  If [nthGet]
 is not passed, the current Get's name is returned.  If the Get is an array
 element, the return value will contain an element index number.


 ADr_version() --> <cVersionNumber>
 
 Returns the version number of ADread() as a string.


 
  The Global  APIs
 
 ADrg_insert( <aAttributes> ) --> NIL
 
 Configures the Insert/Overwrite toggle attributes.  By default, ADread()
 starts with Overwrite mode, and the cursor size that cooresponds to this mode
 is the solid cursor.  With this API, you can change these default values.
 <aAttributes> is an array of 3 elements:

   1 - logical.  If it is TRUE, ADread() starts with Insert mode.  Otherwise,
       it starts with Overwite mode.

   2 - numeric.  Cursor size indicator for Overwrite mode.  Defaults to
       SC_SPECIAL1 (full block).

   3 - numeric.  Cursor size indicator for Insert mode.  Defaults to
       SC_NORMAL (underscore).

 Example
 -------
 ADrg_insert( { .t., 1, 2 } )
 @..adget..
 ADread( getlist )

 
  The Valid APIs
 
 ADr_filled( <aGetPositions>, [bNotFilled], [nEngine] ) --> <lFilled>
 
 Tests if the specified Gets have been filled with data. Typically used in
 validating the whole Read.

 <aGetPositions> is an array containing the index positions of the Gets to be
 checked.

 [bNotFilled] is an optional codeblock that is EVALuated if at least one of
 the <aGets> is empty.

 <lFilled> is the logical return value.  It is TRUE if all the Gets in
 <aGetPositions> contain data.

 Example
 -------
 func main()
 local xx := "123"
 local yy := "abc"
 local getlist[0]
 local bNotFilled := {||ADmessage( { "Not all Gets are filled" } )}
 cls
 @10,10 ADget xx
 @11,10 ADget yy
 ADread( getlist, {||ADr_exit( {||ADr_filled( {1,2}, bNotFilled )} )} )
 return NIL



 ADr_popup( [nRow], [nCol], <aOptions>, [aTrigger], [lForce], [bConfig] )
     --> <lSelected>
 
 Pops up a picklist of valid selections.  The selected option is automatically
 entered into the Get.  Typically used within a VALID clause.

 [nRow], [nCol] are the top and left corrdinates of the window. Default to
 just underneath the Get.

 <aOptions> is an array of valid selections.

 [aTrigger] is an array of the column positions of the trigger keys.  Defaults
 to an array of 1s.  Note that if you do not want any trigger, specify numbers
 higher than the length of the options.

 [lForce] is a logical value.  If it is TRUE, then it forces the opening of
 the picklist even when the current Get value is valid.  Also, the initially
 highlighted option will be the current Get value.  Defaults to FALSE.

 [bConfig] is a Configuration Block similar to the [bConfig] of ADvermenu().

 [lSelected] is a logical value.  It is TRUE if a selection was made.  If
 [lForce] is TRUE, then the selected option has to be different from the
 current Get value for [lSelected] to be set to TRUE.

 Example:
 -------
 func main()
 local getlist[0]
 local cVar := "   "
 local aVar := { "AAA", "BB", "C" }
 
 @11,10 adget cVar valid ADr_popup( 4, 4, aVar )
 ADread( getlist )
 return nil


 ADr_required( [bNotFilled] ) --> <lFilled>
 
 Checks if the current Get contains data.

 [bNotFilled] is a codeblock that is EVALuated if the current Get is empty. It
 defaults to:

   {||ADmessage( { "May not be left blank" } )}

 [lFilled] is a logical.  It is TRUE if the current Get is not empty.

 Example:
-------
 // Beep the speaker if the current Get is empty.
 @11,10 adget cVar2 valid ADr_required( {||tone(100,1)} )


 ADr_tbpopup( [nRow], [nCol], [aIndexOrders], [aForce], [bConfig] )
     --> <lSelected>
 
 Pops up a picklist of valid selections with a database tbrowse.  The selected
 value is automatically entered into the Get.  Typically used within a VALID
 clause.

 [nRow], [nCol] are the top and left corrdinates of the window. Default to
 just underneath the Get.

 [aIndexOrders] is an array of index orders.  It has two elements:

   1 - the display order.  This refers to the index used to display the
       tbrowse.
   2 - the value order.  This refers to the index whose key value will be
       entered into the Get.

 If [aIndexOrders] is not passed, it defaults to {1,1}.

 [aForce] is an array of two logical values that determine whether the popup
 is forced to open when the Get is valid and/or empty.  The elements are:

   1 - if this is TRUE, the tbrowse is forced to popup even when the Get is
       valid.  Defaults to FALSE.
   2 - if this is TRUE, the tbrowse is forced to popup when the Get is empty.
       Defaults to FALSE.

 [bConfig] is a Configuration Block similar to the [bConfig] of ADdbview().

 [lSelected] is a logical value.  It is TRUE if a selection was made.

 Example:
 -------
 func main()
 local cIdnum := "     "
 local bDBConfig := {|| ADdb_color( "R/W", "GR+/B" )}
 
 use ..\employee
 set index to ..\emp_num, ..\emp_name
 ADcls( "",,,,,, .t. )
 ADg_oneb(4,4,, "ID number", @cIDnum,,;
          {||employee->( ADr_tbpopup(,, {1,2}, {.f.,.f.}, bDBConfig ) )};
         )
 return NIL




 
  Specialized Functions
 
 ADg_many()
 
 SEE the Boxed Gets Engine


 ADg_one( [nTop], [nLeft], [aColor], <cLabel>, <xVar>, [cPic], [bValid],;
    [bConfig], [cHelpId] ) --> <lSaved>
 
 Implements a boxed one-Get ADread() that looks like this:

         Enter library name: ͸
         Frankie Clipper Library by The Programmer's Desk 
        ;

 [nTop], [nLeft] are the top/left coordinates of the box.  If they are not
 passed, the box is centered on the screen.

 [aColor] is an array of color specifiers.  It has two elements:  the color of
 the box, and the color of the Get.  If it is not passed, the elements default
 to the current standard and enhanced settings.

 <cLabel> is a label displayed on the top of the box.  Note that the length of
 this label must be less than the length of the Get.  If it is not, use
 ADg_oneb(), instead.

 <xVar> is the Get variable.  It must be passed by reference.

 [cPic] is the Picture string.

 [bValid] is the VALID codeblock

 [bConfig] is a Configuration block that has a similar meaning with ADread's
 parameter of the same name.  In version 1, ADg_one() was not configurable.

 [cHelpID] is a Help identifier string.

 [lSaved] is the logical return value.  It is TRUE if the Get was saved.

 Note:
 -----
 If xVar is longer than the screen width less 4 columns, the Get will scroll
 horizontally.  If the TAB key is then pressed, it will expand to a memoedit.
 This feature was absent in version 1.

 Example:
 -------
 cString := "Frankie Clipper Library by The Programmer's Desk"
 aColor := { "R/B", "W+/GR" }
 cLabel := "Enter library name:"
 ADg_one( ,, aColor, cLabel, @cString )


 ADg_oneb( [nTop], [nLeft], [aColor], <cLabel>, <xVar>, [cPic], [bValid],;
     [bConfig], [cHelpId] ) --> <lSaved>
 
 This is a variant of ADg_one().  The only difference between these two
 functions is the way the boxed Get is displayed.  ADg_one() is the preferred
 variant when the label is longer than the Get like the following.

        ͸
         Enter Library Name: Frankie 
        ;

 The parameters and return value have the same meaning as in ADg_one().

 Example:
 -------
 cString := "Frankie"
 aColor := { "R/B", "W+/GR" }
 cLabel := "Enter library name:"
 ADg_oneb( ,, aColor, cLabel, @cString )

 
  CLAUSES
 
 @.. [SAY..] ADGET.. [CALC]
 
 A command clause that specifies a calculator-style Get reader.  This is
 applicable only to numeric and character Gets.  It will not work properly on
 logicals and dates.

 Note:
 -----
 If xVar is longer than the screen width less the length of the label and
 less 4 columns, the Get will scroll horizontally.  If the TAB key is then
 pressed, it will expand to a memoedit.  This feature was absent in version
 1.

 Example:
 -------

 local xx := 0
 local yy := "         "
 local zz := date()
 local getlist[0]

 @10,10 say "Number" adget xx calc
 @11,10 say "String" adget yy calc
 @12,10 say "Date  " adget zz calc   // will not work here
 ADmessage( { "Number and String are calculator-style.",;
              "The entry is keyed in from the right.";
            }, 16,, .F., .F.;
          )
 ADread( getlist )

 @.. [SAY..] ADGET.. [JUMPKEY <nkey>]
 
 A command clause that that assigns a "jump key" to a Get. When a jump key is
 pressed, focus is immediately moved to the Get to which the key is attached.

 Example:
 --------
 local cName := space(20)
 local cAddress := space(40)
 local dBirth := ctod("")
 local getlist[0]

 @10,10 say ADdisplist( "N",             "R/BG",;
                        "ame         ",  "W+/BG"  );
       adget cName jumpkey K_ALT_N
 @11,10 say ADdisplist( "A",             "R/BG",;
                        "ddress      ",  "W+/BG"  );
       adget cAddress jumpkey K_ALT_A
 @12,10 say ADdisplist( "Date of ",      "W+/BG",;
                        "B",             "R/BG",;
                        "irth",          "W+/BG"  );
       adget dBirth jumpkey K_ALT_B
 ADread( getlist )

                ͸
                    The Boxed Gets Collection  (NEW)   
                ;

 The Engine Function
 
 ADg_many( <aGets>, [bConfig], [xHelpID] ) --> <lStatus>
 
 Implements an ADread() of an array of Gets.  Automatically draws a box
 around the Gets, and centers the box on the screen.

 <aGets> is an array of Get variables to be read.

 [bConfig] is a configuration block.  If passed, ADg_many() EVALuates it to
 configure itself.  It is typically a series of ADgm_.. API calls.

 [xHelpID] is a Help ID string, or an array of Help ID strings.  If it is a
 string, it applies for the whole Read.  If it is an array, there must be one
 string for each Get in <aGets>.

 <lStatus> is the logical return value.  It is TRUE if any of the Gets was
 changed AND the Read was saved.  It is a FALSE in any other situation.

 Example 1
 ---------
 func main()
 local lStatus
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk";
                }
 cls
 lStatus := ADg_many( aGets )
 ? if( lStatus, "Saved", "Aborted and/or Not Changed" )
 ? aGets[1]
 ? aGets[2]
 return nil

 Example 2
 ---------
 // Adds labels to the Gets, changes the default colors, and relocates the
 // box.
 func main()
 local lStatus
 local aLabels := {;
                     "Library",;
                     "Author",;
                     "Impression";
                  }
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_labels( aLabels ),;                   // labels
                     ADgm_corner( 2,2 ),;                       // relocates
                     ADgm_color( { "W+/B", "GR+/R", "W/B" } );  // colors
                  }
 cls
 lStatus := ADg_many( aGets, bConfig )
 ? if( lStatus, "Saved", "Aborted and/or Not Changed" )
 ? aGets[1]
 ? aGets[2]
 return nil


 The Config APIs
 
 ADgm_color( <aColor> ) --> NIL
 
 Changes ADg_many()'s colors.

 <aColor> is an array of 4 color strings.

    1 - the Get standard color and the box color
    2 - the Get enhnaced color
    3 - the Get unselected color
    4 - Box header and footer color

 The default Get colors are the current Clipper color settings.  The default
 header/footer color is whatever the current Get standard color is.  You
 may pass a NIL in any of the elements if you do not want to change that
 corresponding color setting.

 Example
 -------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_color( { "W+/B", "GR+/R", "W/B" } )}
 cls
 ADg_many( aGets, bConfig )
 return nil
  
 ADgm_corner( [nTop], [nLeft] ) --> NIL
 
 Relocates the box.  By default, the box is centered on the screen.

 [nTop] is the new top corrdinate.  If this is not passed, the box is
 centered vertically.

 [nLeft] is the new left corrdinate.  If this is not passed, the box is
 centered horizontally.

 Example
 -------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_corner( 0, 0 )}
 cls
 ADg_many( aGets, bConfig )
 return nil

 ADgm_extra( [bBefore], [bAfter] ) --> NIL
 
 Defines before and/or after behaviours.

 [bBefore] is a codeblock that ADg_many() EVALuates before displaying the
 boxed Gets.

 [bAfter] is a codeblock that ADg_many() EVALuates after displaying the
 boxed Gets.

 Example
 -------
 #define DO_NOT_EXPLODE      .f.
 #define DEFAULT_FRAME       NIL
 #define WITH_SHADOW         NIL
 //---------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk  ",;
                     "Excellent!";
                }
 local aLabels := {;
                     "Library",;
                     "Author",;
                     "Impression";
                  }
 
 local bConfig := {|| ADgm_labels( aLabels ),;
                      ADgm_colors( { "W+/B",, "R+/W" } ),;
                      ADgm_extra( {||dispbegin(),;
                                     aScn := ADchgcolor(0,0,24,79, "N/BG" );
                                  },;
                                  {||aCoords := ADgm_coords(),;
                                     aScn2 := ADbox( aCoords[1],;
                                                     aCoords[4] + 1,;
                                                     aCoords[3],;
                                                     aCoords[4] + 20,;
                                                     "GR+/B",;
                                                     DEFAULT_FRAME,;
                                                     WITH_SHADOW,;
                                                     DO_NOT_EXPLODE;
                                                   ),;
                                     ADcsay( aCoords[1] + 2,;
                                             aCoords[4] + 1,;
                                             aCoords[4] + 20,;
                                             "WOW!",;
                                             "GR+/B";
                                           ),;
                                     dispend();
                                  };
                                );
                  }                
 local aScn, aScn2, aCoords
 
 ADmessage({""})
 ADg_many( aGets, bConfig )
 ADrestscn( aScn2 )
 ADrestscn( aScn )
 return nil
 
 ADgm_header( [cHeader], [cFooter] ) --> NIL
 
 Adds a header and/or footer to the box.

 [cHeader] is an optional string that is displayed at the top of the box.

 [cFooter] is an optional string that is displayed at the bottom of the box.

 Example
 -------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_header( " Header ", " Footer " )}
 cls
 ADg_many( aGets, bConfig )
 return nil

 ADgm_labels( <aLabels> ) --> NIL
 
 Adds labels to the box.

 <aLabels> is an array of string labels, one for each Get.  If there are more
 labels than there are Gets, the excess will be disregarded.  If there are
 more Gets than there are labels, the unlabeled Gets will be just that -
 unlabeled.  If an <aLabels> element is NIL, the corresponding Get will be
 unlabeled.

 Example
 -------
 func main()
 local aLabels := {;
                     "Library",;
                     "Author",;
                     "Impression";
                  }
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_labels( aLabels )}
 cls
 ADg_many( aGets, bConfig )
 return nil


 ADgm_memo( <aMemos> ) --> NIL
 
 Tells ADg_many() which Gets are memo fields.

 <aMemos> is an array of logical values, one for each Get.  A TRUE indicates
 that the corresponding Get is a memo field.

 Example
 -------
 func main()
 local aRec := ADgetrec()
 local aMemo := ADamemo()
 local bConfig := {||ADgm_memo( aMemo )}
 cls
 ADg_many( aRec, bConfig )
 return nil


 ADgm_pads( <aPads> ) --> NIL
 
 Sizes the space padding between the Gets and the box sides, and between the
 labels and the Gets.  By default, there is no padding between the Gets and
 the top/bottom sides, there is a space between the Gets and the left/right
 sides, and there is a space between the labels and the Gets.

 <aPads> is an array of 5 numeric values:

   1 - top padding
   2 - left padding
   3 - bottom padding
   4 - right padding
   5 - space between the labels and Gets

 If any of the elements is NIL, the corresponding default value is not
 changed.

 Example
 -------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_pads( { , 5,, 5, 3 } )}
 cls
 ADg_many( aGets, bConfig )
 return nil

 ADgm_pictures( <aPictures> ) --> NIL
 
 Adds picture clauses to the Gets.

 <aPictures> is an array of picture clauses, one for each Get.  If there are
 more clauses than there are Gets, the excess will be disregarded.  If there
 are more Gets than there are clauses, the "unclaused" Gets will be just that
 - "unclaused".  If an <aPictures> element is NIL, the corresponding Get will
 be "unclaused".

 Example
 -------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_pictures( { "@!", "@!" } )}
 cls
 ADg_many( aGets, bConfig )
 return nil


 ADgm_readconfig( <bConfig> ) --> NIL
 
 Configures the ADread() engine used by ADg_many()

 <bConfig> is a Configuration Block used by the Read engine.  See ADread()
 for a full description of this block.

 Example
 -------
 func main()
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!",;
                     "Yes";
                }
 local aLabels := {;
                     "Library",;
                     "Author",;
                     "Impression",;
                     "Recommended?";
                  }
 local aPads := {;
                     ,;     // top
                     15,;   // left
                     ,;     // bottom
                     ,;     // horizontal
                     ;      // middle
                }
 
 local bConfig := {|| ADgm_labels( aLabels ),;
                      ADgm_pads( aPads ),;
                      ADgm_colors( { "GR+/B",, "R+/W" } ),;
                      ADgm_extra( , {||Xextra2()} );
                  }                
 local lStatus
 
 lStatus := ADg_many( aGets, bConfig )
 ADmessage( { if( lStatus, "Changed and Saved", "Cancelled or Not Changed" ) } )
 return nil
 
 
 //------------
 func Xextra2()
 local aCoords := ADgm_coords()
 local nPB := ADpb_vertical( aCoords[1] + 1, aCoords[2] + 2,;
                             { "   OK   ", " Cancel " },;
                             {     4,        2 },;
                             1;
                           )
 local bReadConfig := {|| ADr_lbuttons( ADpb_spots( nPB ),;
                                        {|n|Xprocess(n,nPB)};
                                      );
                      }
 
 ADgm_readconfig( bReadCOnfig )
 ADpb_show( nPB, "B" )
 return NIL
 
 
 //---------------------
 func Xprocess(nth, nPB)
 if nth == 1     // OK
     ADpb_push( nPB, nth )
     ADr_save()
 elseif nth == 2
     ADpb_push( nPB, nth )
     ADr_abort()
 endif
 return NIL
    

 ADgm_valids( <aValids> ) --> NIL
 
 Adds valid clauses to the Gets.

 <aValids> is an array of valid clauses, one for each Get.  If there are
 more clauses than there are Gets, the excess will be disregarded.  If there
 are more Gets than there are clauses, the "unclaused" Gets will be just that
 - "unclaused".  If an <aValids> element is NIL, the corresponding Get will
 be "unclaused".

 Example
 -------
 func main()
 local aValids := {;
                     {|| left( ADr_varget(), 1 ) == "F"},;
                     {|| left( ADr_varget(), 3 ) == "The"},;
                     {|| right( ADr_varget(), 1 ) == "!"};
                  }
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local bConfig := {||ADgm_valids( aValids )}
 cls
 ADg_many( aGets, bConfig )
 return nil



 ADgm_whens( <aWhens> ) --> NIL
 
 Adds when clauses to the Gets.

 <aWhens> is an array of when clauses, one for each Get.  If there are more
 clauses than there are Gets, the excess will be disregarded.  If there are
 more Gets than there are clauses, the "unclaused" Gets will be just that -
 "unclaused".  If an <aValids> element is NIL, the corresponding Get will be
 "unclaused".

 Example
 -------
 func main()
 local aLabels := {;
                     "Library",;
                     "Author",;
                     "Impression!";
                  }
 local aGets := {;
                     "Frankie Clipper Library",;
                     "The Programmer's Desk",;
                     "Excellent!";
                }
 local aValids := {;
                     {|| Xvalid() },;
                     {|| Xvalid() },;
                     {|| Xvalid() };
                  }
 local aWhens := {;
                     {|| Xwhen() },;
                     {|| Xwhen() },;
                     {|| Xwhen() };
                 }
 local bConfig := {||ADgm_labels( aLabels ),;
                     ADgm_valids( aValids ),;
                     ADgm_whens( aWhens ),;
                     ADgm_corner( 6 ),;
                     ADgm_color( { "W+/B", "GR+/R", "W/B" } );
                  }
 cls
 ADg_many( aGets, bConfig )
 ADrestscn( aScn )
 return nil
 
 
 //-----------
 func Xvalid()
 local nthGet :=  ADr_nthget()
 local lValid := .t.
 
 if nthGet == 1
     if left( ADr_varget(), 1 ) != "F"
         ADmessage( { "The first letter must be an 'F'" }, 0 )
         lValid := .f.
     endif
 elseif nthGet == 2
     // no validation
 elseif nthGet == 3
     if right( trim( ADr_varget() ), 1 ) != "!"
         ADmessage( { "The last character must be an '!'" }, 0 )
         lValid := .f.
     endif
 endif
 return lValid
 
 
 //-----------
 func Xwhen()
 local nthGet :=  ADr_nthget()
 local lValid := .t.
 
 if nthGet == 1
     // no pre-validation
 elseif nthGet == 2
     // no pre-validation
 elseif nthGet == 3
     if empty( ADr_varget(2) )
         lValid := .f.
     endif
 endif
 return lValid


 The Info APIs
 
 ADgm_coords() --> <aCoords>
 
 Returns the coordinates of the box.

 Example
 -------
 See ADgm_extra()


                ͸
                    The Vertical Menu Collection     
                ;

 The Engine Function
 
 ADvermenu( [nTop], [nLeft], <aMenu>, [aTrigger], [xProcess], [bConfig],;
    [xHelpID] ) --> <xSel>
 
 [nTop] and [nLeft] - are the top/left corner coordinates of the menu box.
 Defaults to the center of the screen.  In version 1, the location defaulted
 to the top/left corner of the screen.

 <aMenu> - is an array of string menu options

 [aTrigger] - is an optional array of the positions of the options trigger
 characters.  If it is not passed, it defaults to an array of 1's, i.e, the
 first characters are the triggers.   The trigger characters are highlighted
 in the menu.  If you do not want to highlight any trigger, specify a number
 higher than the length of the option.
 
 [xProcess] - is an optional specifier of what is to be done with the selected
 option.  It can be a single codeblock or an array of codeblocks.  If it is a
 single codeblock, ADvermenu() EVALuates it when a selection is made.  If it
 is an array of codeblocks, ADvermenu() EVALuates the block corresponding to
 the selected option.  ADvermenu() passes one parameter to the codeblock: the
 selected option.  It could be the index position of the selection (numeric),
 or the menu option itself (character), or even an array, depending on the
 ADvermenu() configuration.  By default it is the index position.
 
 NOTE:  ADvermenu() does not exit after EVALuating the codeblock.  If
 [xProcess] is not passed, the menu exits when a selection is made.
 
 [bConfig] - is the optional Configuration Specifier.  It is a codeblock that
 ADvermenu() EVALuates to configure itself.  It is typically a call to one or
 a series of the APIs.  See the examples below.
 
 [xHelpID] - is either a Help Identifier string, or an array of such strings,
 one for each menu option.
 
 <xSel> - is the return value.  It is the index position of the selected
 option at exit.  It is zero, if ADvermenu() was aborted.  Typically, <xSel>
 is important to the program only if [xBlock] is not passed.
 
 NOTE:  If ADvermenu() is configured as a taglist, <xSel> becomes an array.

 v1 to v2
 --------
 1.  ADvermenu() now supports inactive options.  An inactive option is
     displayed in a different color, and cannot be highlighted.

 2.  ADvermenu() now automatically calculates the right coordinate of the
     menu box.  If it falls beyond maxcol()-1, the left coordinate is
     adjusted accordingly to prevent such a spill over.

 3.  The default location is now at the center of the screen.

 4.  [xHelpID] used to be [cHelpID] which accepts only one character string.

 5.  By default, menus and picklists do not wrap anymore.  They may be
     configured to wrap with ADvm_wrap().


 Example 1:
 ----------
 ? ADvermenu(  10,10,;                 // top/left corner of box
               {;                      // menu options
                  "ADvermenu()",;
                  "ADhormenu()",;
                  "ADboxmenu()",;
                  "ADdbview()",;
                  "ADread()";
               },;
               { 3,3,3,3,3 };          // trigger key positions
            )

 Example 2:
 ----------
 // Add a header to the menu
 ? ADvermenu(  10,10,;                 // top/left corner of box
               {;                      // menu options
                     "ADvermenu()",;
                     "ADhormenu()",;
                     "ADboxmenu()",;
                     "ADdbview()",;
                     "ADread()";
               },;
               { 3,3,3,3,3 },;         // trigger key positions
               NIL,;
               {|e| ADvm_header( e, "Engine Functions" )};
            )

 Example 3:
 ----------
 // change the default colors and box attributes
 ? ADvermenu( 10,10,;            // top/left corner of box
              {;                 // menu options
                  "ADvermenu()",;
                  "ADhormenu()",;
                  "ADboxmenu()",;
                  "ADdbview()",;
                  "ADread()";
              },;
              { 3,3,3,3,3 },;    // trigger key positions
              NIL,;
              {|e| ADvm_header( e, "Engine Functions",;// header
                                   "Ĵ";              // head sep
                              ),;
                   ADvm_boxattr( e, {;
                                       "Ŀ ",;    // box frame
                                       NIL,;
                                       NIL,;
                                       10;              // padding
                                    };
                               ),;
                   if( iscolor(),;
                       ADvm_colors( e, {;
                                          "GR+/W",; // std color
                                          "B+/W",;  // enh color
                                          "R+/W",;  // header color
                                          "G+";     // trigger color
                                       };
                                  ),;
                       NIL;
                     );
             };
           )

 


 CONFIG APIs
 
 ADvm_boxattr( <aAttr> ) --> NIL
 
 Reconfigures the box attributes.  [aAttr] contains 4 elements:

 1 - Frame.  Default is "͸Գ "
 2 - Shadow.  Default is .T.
 3 - Explode.  Default is .T.
 4 - Pad.  Default is 1, that is, 1 space padding between the left of the box
     and the starting column of the options list.  There is an equal amount
     of paading at the right.

 Example:
 -------
 aMenu := { "111", "222", "333", "444", "555", "666", "777" }
 bConfig := {|| ADvm_boxattr(;
                         {;
                                 "Ŀ ",; // frame
                                 .f.,;    // no shadow
                                 .f.,;    // do not explode
                                 10;      // pad with 10 spaces
                         };
                         );
         }
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_color( <aColors> ) --> NIL
 
 Sets the ADvermenu() colors. [aColors] is an array with 5 elements:

 1 - the standard color
 2 - the enhanced color
 3 - the box and header color.  If this is not specified, it takes the value
     of the standard color.
 4 - the foreground of the trigger color (the background is always the
     background of the standard color)
 5 - the foreground of the inactive color

 If any of the 5 elements is nil, the corresponding color is not changed.
 The default colors are:

     Color       Mono
     -----       ----
 1 - "W+/W"      "W/N"           standard color
 2 - "GR+/R"     "I"             enhanced color
 3 - "B/W"       "W+/N"          box and header color
 4 - "B"         "W+"            trigger foreground color
 5 - "N"         "W+"            inactive foreground color

 v1 to v2
 --------
 1.  Added a 5th color - the inactive foreground color


 Example:
 -------
 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_color(;
                             {;
                                                                                
                                 "B+/GR*",;  // standard color
                                 "N+/W*",;   // enhanced color
                                 "N+/GR*",;  // box color
                                 "R",;       // trigger color
                                 "W";        // inactive color
                             };
                                                                
                          );
            }
 setblink( .f. )
 ADvermenu( 4, 4, aMenu,,, bConfig )
 setblink( .t. )


 ADvm_escape( <bEscape> ) --> NIL
 
 Specifies how ADvermenu() will behave when the Esc key is pressed, or when
 the right button is clicked.  The default action is to abort the menu.

 <bEscape> - a codeblock that ADvermenu() EVALuates.

 Example:
 -------
 // beep the speaker when Esc is pressed
 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_escape( {|| tone(100,1)} )}
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_extra( [bDispBegin], [bDispEnd], [bUndisplay] ) --> NIL
 
 Defines a set of behaviour associated with the displaying and the exiting of
 the menu.

 [bDispBegin] - an optional codeblock that ADvermenu() EVALuates just before
 displaying the menu.

 [bDispEnd] - an optional codeblock that ADvermenu() EVALuates just after 
 displaying the menu.

 [bUndisplay] - an optional codeblock that ADvermenu() EVALuates after 
 un-displaying the menu.

 Example:
 -------
 func main()
 local aMenu, bConfig, aScn, bDispEnd, bDispBegin, bUndisplay

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bDispBegin := {|| dispbegin()}
 bDispEnd := {|| aScn := ADmessage( { "Choose One" },,, .F. ),;
                 dispend();
         }
 bUndisplay := {|| ADrestscn( aScn )}
 bConfig := {|| ADvm_extra( bDispBegin, bDispEnd, bUndisplay )}
 ADvermenu( 4, 4, aMenu,,, bConfig )
 return nil


 ADvm_header( <cHeader>, <cDivider> ) --> NIL
 
 Specifies a header for the menu.

 <cHeader> - the header string to be displayed at the top of the menu box.  A
 multi-line header may be defined by embedding semi-colons.

 [cDivider] is a three-character defining the separator between the header 
 and the menu options.  The significance of these characters are:

 1st char - joins the separator with the left side of the menu box
 3rd char - joins the separator with the right side of the menu box
 2nd char - the rest of the separator is built from this character

 Example:
 -------
 bConfig := {|| ADvm_header( "Typing Test" )}
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_immediate( <lImmediate> ) --> NIL
 
 Specifies how ADvermenu() behaves when a trigger key is pressed, or when a
 menu item is clicked.  By default, if the menu has not been configured as a
 taglist, and, there is only one pageful of options (that is, it does not
 scroll), the highlight jumps to the option that corresponds to the pressed
 trigger, the option is selected and processed.  Sometimes, it is desirable
 to delay the processing of the selection until after the Enter key or a
 defined hot key is pressed, or a defined hot spot is clicked.  This can be
 done by passing a FALSE in ADvm_immediate()

 Example:
 -------
 bConfig := {|| ADvm_immediate( .f. )}
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_initsel( [xInitSel] ) --> NIL
 

 Specifies the index position/s of the option to be initially
 highlighted/marked.  [xInitSel] must be a numeric value if ADvermenu() is a
 picklist;  it must be an array of numeric values if it is a taglist.

 v1 to v2
 --------
 1.  [xInitSel] may now be either a single numeric value, or an array of
     numeric values.

 Example1
 --------
 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_initsel(3)} // initial highlight at #3
 ADvermenu( 4, 4, aMenu,,, bConfig )

 Example2
 --------
 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_taglist(),;
                ADvm_initsel( {1,3} )} // initial marks on #1 and #3
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_keys( <aKeys>, <bHandler>, [lAdditive] ) --> NIL
 
 Defines hot keys local to the menu.

 [aKeys] is an array of the inkey codes of the keys to be  defined as hot 
 keys.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot keys 
 is pressed.  It ia automatically passed two parameters:

   1 - the index position of the pressed key
   2 - the inkey code of the pressed key

 [lAdditive] is a logical value.  If it is FALSE, the hot keys defined now 
 will supercede all previous definitions, if any.  If it is TRUE, the new 
 definitions are added or chained to existing ones.  The default is FALSE.

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_keys( { K_F9, K_F10 },;
                           {|n,k| Xhandler(n,k)};
                         );
            }
 ADvermenu( 4, 4, aMenu,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nKeyCode )
 if nKeyCode == K_F9
    ADvm_abort()
 elseif nKeyCode == K_F10
    ADvm_select()
 endif
 return nil


 ADvm_lbuttons( <aSpots>, <bHandler>, [lAdditive] ) --> NIL
 
 Defines hot spots for the mouse left button.

 [aSpots] is an array of the screen sections that are to be defined as hot 
 spots.  Each hot spot is an array of  {top,left,bottom,right} coordinates.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot 
 spots is clicked.  It ia automatically passed three parameters:

 1 - the index position of the clicked spot
 2 - the mouse cursor row position when the hot spot was clicked
 3 - the corresponding mouse column position

 [lAdditive] is a logical value.  If it is FALSE, the hot spots defined now 
 will supercede all previous definitions, if any.  If it is TRUE, the new 
 definitions are added or chained to existing ones.  The default is FALSE.

 Example:
 -------
 func main()
 local aMenu, bConfig, aSpots

 aMenu := { "The", "Quick", "Brown", "Fox" }
 aSpots := {;
             { 0,0,0,0 },;
             { maxrow(), maxcol(), maxrow(), maxcol() };
           }
 bConfig := {|| ADvm_lbuttons( aSpots,;
                               {|n,r,c| Xhandler(n,r,c)};
                             );
            }
 ADvermenu( 4, 4, aMenu,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nRow, nCol )
 if nIndexPos == 1
    ADvm_abort()
 elseif nIndexPos == 2
    ADvm_select()
 endif
 return nil


 ADvm_maxright( [nRight] ) --> NIL
 
 Defines the rightmost column position to be used by ADvermenu(). By default, 
 ADvermenu() automatically calculates the right column position, based on the 
 value of the left column position, and the length of the longest option.  If 
 the calculated right is more than maxcol()-1, it is pegged at maxcol()-1.
 This calculated value may be overriden with ADvm_maxright(), and the left
 position is adjusted accordingly.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_maxright( 10 )}

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL


 
 ADvm_maxbottom( [nBottom] ) -> NIL
 
 Defines the lowest bottom row position to be used by ADvermenu().  By 
 default, ADvermenu() automatically calculates the bottom row position, based 
 on the number of menu options. If the calculated bottom is more than 
 maxrow()-1, it is pegged at maxrow()-1, and ADvermenu() scrolls.  This
 calculated value may be overriden with ADvm_maxbottom().

 Example:
 -------
 /*
 Only 5 menu options will be displayed at a time because the bottom is pegged
 at row #10.
 */
 aMenu := { "111", "222", "333", "444", "555", "666", "777" }
 bConfig := {|| ADvm_maxbottom( 10 )}
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_move( [bMoveBegin], [bMoveEnd] ) --> NIL
 
 Assigns two Move codeblocks to ADvermenu(). [bMoveBegin] is EVALuated just 
 before the highlight is moved to another option. [bMoveEnd] is EVALuated 
 after the highlight has been moved.  It is also EVALuated at startup after 
 the initial screen is displayed.

 Example:
 -------
 /*
 Displays an option-sensitive message
 */
 func main()
 local aMenu, bConfig, bMoveEnd, aMsg, aScn

 aMenu := { "The", "Quick", "Brown", "Fox" }
 aMsg := {;                          
            "First word",;          
            "Second word",;
            "Third word",;
            "Fourth word";
         }

 // code block that displays the apporpriate message
 bMoveEnd := {|| ADsay( 24,0,;
                        padc( aMsg[ADvm_current()], 80 ),;
                        "GR+/B";
                      );
             }

 bConfig := {|| ADvm_move( , bMoveEnd )}

 // saves the screen to be overwritten by message area
 aScn := ADsavescn( 23,0,24,79 )

 // prepares the message area
 scroll( 23,0,24,79,0 )
 ADsay( 23, 0, repl( "", 80 ), "GR+/B" )
 ADsay( 24, 0, repl( " ", 80 ), "GR+/B" )

 ADvermenu( 4, 4, aMenu,,, bConfig )

 // restores the screen overwritten by the message area
 ADrestscn( aScn )
 return nil


 ADvm_returntype( <cRetyrnType> ) --> NIL
 
 Specifies the type of ADvermenu's return value.  <cReturnType> is either "C" 
 or "N".  If it is "N" the return value is the numeric index position of the 
 selected option.  If it is "C", it is the option string itself.  Defaults to 
 "N".

 Example:
 --------
 // Returns the option string, instead of its numeric index position
 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_returntype( "c" )}
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_taglist( [cTagChars], [lTagImmediately] ) --> NIL
 
 Configures ADvermenu() to be a taglist, instead of a picklist. When 
 ADvermenu() is a taglist, its behaviour changes:

 1.  Pressing the Spacebar tags/untags the highlighted option.

 2.  To select all tagged options, press the Enter key.

 3.  The default tagging character is the checkmark "".  This can be changed
 by specifying the new character in [cTagChars].

 4.  [cTagChars] may consist of more than one characters.  The first unused
 character is used. Since a used character is not used a second time, the 
 maximum number of options that may be tagged is equal to the number of 
 characters in [cTagChars].

 5.  The return value is an array of the tagged options.  If no option was
 tagged, the return value is {}.  The elements will either be index positions
 (default), or menu options depending on the configuration of ADvermenu(). 
 Use ADvm_returntype() to change this configuration.

 6.  If [cTagChars] consists of more than one characters, the return value is
 sorted according to their tags.

 7.  Pressing a trigger key tags/untags the corresponding option, unless
 [lTagImmediately] is set to FALSE.

 8.  Clicking an option tags/untags it.

 Example 1:
 ---------
 // configures the menu as a taglist
 bConfig := {|| ADvm_taglist() }
 ADvermenu( 4, 4, aMenu,,, bConfig )


 Example 2:
 ---------
 // uses the asterisk as the tagging character, instead of the default
 // checkmark
 bConfig := {|| ADvm_taglist( "*" ) }
 ADvermenu( 4, 4, aMenu,,, bConfig )


 Example 3:
 ---------
 // a maximum of three options may be tagged with one each of "1", "2" and 
 // "3".  At exit, the selected options will be sorted according to their
 // tagmarks.
 bConfig := {|| ADvm_taglist( "123" ) }
 ADvermenu( 4, 4, aMenu,,, bConfig )


 Example 4:
 ---------
 // the return value is an array of menu options, not index positions
 bConfig := {|| ADvm_taglist(), ADvm_returntype( "C" )}
 ADvermenu( 4, 4, aMenu,,, bConfig )


 ADvm_timeout( <nTimeOut>, <bTimeout> ) --> NIL
 
 Designates a time-out routine.

 <nTimeOut> is the number of idle seconds to pass before the timeout routine
 is invoked.

 <bTimeOut> is a codeblock that ADvermenu() EVALuates after <nTimeOut>
 seconds of inactivity.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_timeout( 5, {||ADblankscn()} )}

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL



 ADvm_toggle( <xOption>, <lStatus> ) --> <aOldStatus>
 
 Defines the "active" status of options.  An inactive is always displayed in
 the "inactive color" (which is grey by default) and cannot be highlighted
 nor selected. 

 <xOption> may either be a numeric value or an array of numeric values.  If 
 it is the former, then the status of the option pointed to by it is switched 
 to <lStatus>.  If <xOption> is an array, the options pointed to by its 
 elements are switched.

 <lStatus> is a logical value that stands for the active status to switch to.
 If <lStatus> is TRUE, then the pointed to option is activated.

 <aOldStatus>, the return value, is an array of the previous status of menu
 options.

 Example
 -------
 func main()
 local aMenu := { "The",;
                  "-----",;
                  "Quick",;
                  "Brown",;
                  "Fox",;
                  "Jumps",;
                  "-----",;
                  "Over";
                }
 local bConfig := {||ADvm_toggle( {1,2,7}, .F. )} // Inactivates first option
 ADvermenu( ,, aMenu,,{|n|Xprocess(n)}, bConfig ) // and the separating lines
 return NIL


 static func Xprocess( nSel )
 if nSel == 8       // "Over"
    if ADvm_toggle()[1]                // "The" is currently active
        ADvm_toggle( {1}, .f. )        // Therefore inactivate it
        ADvm_toggle( {3,4,5,6}, .t. )  // and activate the middle options
    else
        ADvm_toggle( {1}, .t. )        // Otherwise, activate it
        ADvm_toggle( {3,4,5,6}, .f. )  // and inactivate the middle options
    endif

    ADvm_refresh()                     // Refresh the menu
 else
    ADnotyet()                         // Only "Over" is processed in
                                       // this demo
 endif
 return NIL


 NOTE:
 -----
 This API is useful only in non-scrollable menus, that is, if the number of
 options can be contained in one menu screen.


 ADdb_versbar( [bConfig] ) --> NIL
 
 Adds a vertical scroll bar to ADvermenu()

 [bConfig] is an optional Configuration Block for the scroll bar.  This is
 where you define the scroll bar characters and colors.  See also the
 Vertical Scroll Bar Engine.

 NOTE: This API MUST be called via the [bDispEnd] parameter of the
 ADvm_extra() API.

 NOTE: If ADvermenu() does not scroll because there are only a few menu
 options, this API will be disregarded.

 Example:
 -------
 func main()
 local aMenu[120], xx

 for xx := 1 to 120
     aMenu[xx] := ADn2s( xx)
 next

 ADvermenu(,,aMenu ,,, {|| ADvm_extra( ,{||ADvm_versbar()}),;
                           ADvm_taglist();
                       };
          )
 return NIL



 ADvm_wrap() --> NIL
 
 By default, vertical menus and picklists, do not wrap.  That is, if the
 first option is currently highlighted, and the Up arrow is pressed, the
 highlight stays on option #1.  It does not wrap to the last option.  The
 same behaviour is true when the highlight is at the last option and the
 Down arrow is pressed.  This API configures ADvermenu() to wrap.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_wrap()}

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL



 ACTION APIs
 
 ADvm_abort( [nExitCode] ) --> NIL
 
 Intructs the menu to abort, setting the exit code to [nExitCode].  If 
 [nExitCode] is not passed, it defaults to VMX_ABORT, which is #defined in 
 Frankie.ch

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_keys( { K_F9, K_F10 },;
                           {|n,k| Xhandler(n,k)};
                         );
            }
 ADvermenu( 4, 4, aMenu,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nKeyCode )
 if nKeyCode == K_F9
    ADvm_abort()
 elseif nKeyCode == K_F10
    ADvm_select()
 endif
 return nil


 ADvm_clrtag() --> NIL
 
 Clears all tags in a taglist.

 Example: See ADvm_tagall()


 ADvm_navigate( <nDirection> ) --> NIL
 
 Moves the menu highlight bar to the direction indicated by <nDirection>.
 The valid values of <nDirection> are #DEFINEd in Frankie.ch as:

 #define VMNAVIGATE_UP           1
 #define VMNAVIGATE_DOWN         2

 Example
 -------
 #include"inkey.ch"

 func main()
 local aMenu[20], xx

 for xx := 1 to 20
     aMenu[xx] := ADn2s( xx)
 next

 ADvermenu(,,aMenu ,,, {||ADvm_keys( {K_F10, K_F9},;
                                     {|n,k|ADvm_navigate( if( n==1,;
                                                              VMNAVIGATE_UP,;
                                                              VMNAVIGATE_DOWN;
                                                            );
                                                        );
                                     };
                                   );
                       };
          )
 return NIL



 ADvm_refresh() --> NIL
 
 Repaints the screen.  You typically would want to do this if you change
 the menu attributes programatically, as when you deactivate/activate
 options.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four", "Six", "Eight", "Nine" }
 local bConfig := {||ADvm_keys( {-9,-8,-7}, {|n,k|Xrefresh(k)} )}
 ADvermenu( ,, aMenu,,, bCOnfig )
 return NIL
 
 func Xrefresh( nKey )
 if nKey == -9
     ADvm_toggle( 3, .f. )
 elseif nKey == -8
     ADvm_toggle( 3, .t. )
 elseif nKey == -7
     ADvm_color( { "R/W", "W/B",, "GR+" } )
 endif
 
 ADvm_refresh()
 return NIL


 ADvm_revtag() --> NIL
 
 Reverses the tags in a taglist

 Example: See ADvm_tagall()


 ADvm_select( [nExitCode] ) --> NIL
 
 Intructs the menu to select, setting the exit code to [nExitCode].  If 
 [nExitCode] is not passed, it defaults to VMX_SELECT, which is #defined 
 in Frankie.ch

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_keys( { K_F9, K_F10 },;
                           {|n,k| Xhandler(n,k)};
                         );
            }
 ADvermenu( 4, 4, aMenu,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nKeyCode )
 if nKeyCode == K_F9
    ADvm_abort()
 elseif nKeyCode == K_F10
    ADvm_select()
 endif
 return nil

 ADvm_tagall() --> NIL
 
 Tags all options in a taglist.

 Example:
 --------
 #include "inkey.ch"

 func main()
 local aMenu := { "1", "2", "3" }
 local bConfig := {||ADvm_taglist(),;
                     ADvm_keys( {K_F10, K_F9, K_F8}, {|n,k| Xtag(k)} )}

 ADvermenu( 10,10, aMenu,,, bConfig )
 return nil


 //----------
 func Xtag(k)
 if k == K_F10
     ADvm_tagall()
 elseif k == K_F9
     ADvm_clrtag()
 elseif k == K_F8
     ADvm_revtag()
 endif
 return NIL



 INFO APIs
 
 ADvm_bottom() --> <nBottomRowPosition>
 
 Returns the row position of the menu box.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADn2s( ADvm_bottom() ) } )};
                               );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL


 ADvm_current() --> <xCurrentOption>
 
 Returns the index position (if the return type is configured as "N") of the
 currently highlighted option, or the highlighted option itself (if the
 return type is configured as "C").

 Example 1
 ---------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADn2s( ADvm_current() ) } )};
                               );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL

 Example 2
 ---------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADvm_current() } )};
                               ),;
                      ADvm_returntype( "C" );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL


 ADvm_exitcode() --> <nExitCode>
 
 Returns the exit code of the last exited menu.  If the menu exited after a
 keypress (e.g. Escape, Enter), the exit code is the inkey code of the
 pressed key. Otherwise, it was one of the values #defined in Frankie.ch:

 //ADvermenu() special exit codes
 #define VMX_ABORT           -102   // if the menu is exited by the mouse
                                    // right button or is programmatically
                                    // aborted with ADvm_abort()
 #define VMX_SELECT          -103   // if the menu is exited after an option
                                    // is selected with the left mouse button
                                    // or is programmatically selected with
                                    // ADvm_select()
 #define VMX_MENU_ISEMPTY    -104   // if the menu does not contain any
                                    // option and therefore is immediately
                                    // exited
 #define VMX_MENU_ISNOTARRAY -105   // if the menu array is not actually an
                                    // array and therefore immediately exited

 NOTE:  Both ADvm_abort() and ADvm_select() accept an optional numeric
        parameter to which the exit code is set to.


 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 ADvermenu( ,,aMenu )
 ? ADvm_exitcode()
 return NIL

 ADvm_istagged( [nOption] ) --> <lTagged>
 
 Checks if a taglist option # [nOption] is currently tagged.  If [nOption] is 
 not passed, it defaults to the currently highlighted option.

 Example:
 --------
 func main()
 local aMenu := { "1", "2", "3" }
 local bConfig := {||ADvm_taglist(),;
                     ADvm_keys( {K_F10}, {|| Xprocess()} )}

 ADvermenu( 10,10, aMenu,,, bConfig )
 return nil


 //-------------
 func Xprocess()
 if ADvm_istagged(1)
     ADmessage( { "#1 is currently tagged" } )
 else
     ADmessage( { "#1 is not currently tagged" } )
 endif
 return NIL

 
 ADvm_left() --> <nLeftColumnPosition>
 
 Returns the column position of the left of the menu box.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADn2s( ADvm_left() ) } )};
                               );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL

 ADvm_right() --> <nRightColumnPosition>
 
 Returns the column position of the right of the menu box.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADn2s( ADvm_right() ) } )};
                               );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL

 ADvm_row() --> <nRowPosition>
 
 Returns the row position of the currently highlighted option.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADn2s( ADvm_row() ) } )};
                               );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL

 ADvm_top() --> <nTopRowPosition>
 
 Returns the row position of the top of the menu box.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four" }
 local bConfig := {|| ADvm_keys( {-9},;
                                 {||ADmessage( { ADn2s( ADvm_top() ) } )};
                               );
                  }

 ADvermenu( ,,aMenu,,, bConfig )
 return NIL

 ADvm_version() --> <cVersionNumber>
 
 Returns the version of ADvermenu() as a string.



 GLOBAL APIs
 
 ADvmg_boxattr( <aNewAttr> ) --> <aOldAttr>
 
 Globally sets the box attributes.  Once the attributes are  globally set, 
 all succeeding ADvermenu() calls will use these values until they are 
 globally reset, or unless they are locally set with ADvm_boxattr().

 <aNewAttr> is an array of new settings.  See ADvm_boxattr() for a discussion 
 of its elements.

 <aOldAttr> is the array of old values.

 Example:
 --------
 ADvmg_boxattr( { "Ŀ ", .f., .f., 6 } )


 ADvmg_color( <aNewColors> ) --> <aOldColors>
 
 Globally sets the menu colors.  Once the colors are globally set, all 
 succeeding ADvermenu() calls will use these values until they are gloabally
 reset, or unless they are locally set with ADvm_color().

 <aNewColors> is an array of new settings.  See ADvm_color() for a discussion 
 of its elements.

 <aOldColors> is the array of old values.

 Example:
 --------
 ADvmg_color( { "R/W", "B/W", "W+" } )



 SPECIALIZED MENUS
 
 ADpl_dirs( [nTop], [nLeft], [cPath], [bProcess], [bConfig], [cHelpID] )
     --> <xSelection>
 
 Implements an ADvermenu-derived directories picklist.

 [nTop] and [nLeft] are the top/left coordinates of the picklist box.  Just 
 like ADvermenu(), they default to the center of the screen.

 [cPath] is the path identifier from which the directories will be read.  It
 defaults to the current directory of the current drive.  If a null string is
 passed, it takes the root directory of the current drive.

 [bProcess] is a codeblock that gets EVALuated when a dir is selected.  It is
 automatically passed the currently highlighted directory (character string).

 [bConfig] has the same meaning as the [bConfig] of ADvermenu().

 <xSelection> is the return value.  It is either the selected directory or an
 array of tagged directories, depending on whether, the picklist is
 configured as a taglist or not.

 NOTE:  Unlike ADvermenu(), ADpl_dirs() may not be configured to return the
 numeric index position of the selection.  It always returns the selection
 itself (a character string).

 Example
 -------
 ? ADpl_dirs(,, "c:\" )



 ADpl_drives( [nTop], [nLeft], [nMode], [bProcess], [bConfig], [cHelpId] )
      --> <xSelection>
 
 Implements an ADvermenu-derived drives picklist.

 [nTop] and [nLeft] are the top/left coordinates of the picklist box.  Just
 like ADvermenu(), they default to the center of the screen.

 [nMode] is a numeric code that specifies the display format. The valid
 values are #defined in Frankie.ch

 #define DRIVE_LETTER_ONLY       2    // drive letters
 #define DRIVE_LETTER_COLON      3    // drive letters + ":"
 #define DRIVE_LETTER_PATH       4    // drive letters + path

 The default value is DRIVE_LETTER_COLON

 [bProcess] is a codeblock that gets EVALuated when a drive is selected.  It
 is automatically passed the currently highlighted drive (character string).

 [bConfig] has the same meaning as the [bConfig] in ADvermenu().

 <xSelection> is the return value.  It is either the selected drive or an
 array of tagged drives, depending on whether, the picklist is configured as
 a taglist or not.

 NOTE:  Unlike ADvermenu(), ADpl_drives() may not be configured to return the
 numeric index position of the selection.  It always returns the selection
 itself (a character string).

 Example
 -------
 #include "tour\frankie.ch"
 ? ADpl_drives( ,, DRIVE_LETTER_PATH )



 ADpl_fields( [nTop], [nLeft], [bProcess], [bConfig], [cInclude], [cFormat],;
     [cHelpID] ) --> <xSel>
 
 A specialized and ADvermenu-derived picklist of field names of the current
 database.  The parameters, [nTop], [nLeft], [bProcess], [bConfig], and
 [cHelpID], and the return value, <xSel> have the same significance as those
 of the same name as in ADvermenu().  Refer to ADvermenu() for a more
 detailed description of them.
 
 [nTop], nLeft] - the top/left corner of the box.
 
 [bProcess] - a codeblock that is EVALuated when a selection is made.  The
 current selection is automatically passed to it.
 
 [bConfig] - is a codeblock used to configure ADpl_fields().
 
 [cInclude] - is a string containing the characters "C", "N", "D", "L" and
 "M" used to specify the types of fields to include in the picklist.  A value
 of "CN" will include only the character and numeric fields.  A value of ""
 will include nothing.  A value of NIL will include all.
 
 [cFormat] - is a string containing the characters "T", "L" and "D" used to
 specify what information to include in the picklist.  A value of "TLD" will
 include type, length and decimal information, in addition to the field
 names.  A value of NIL will include only the field names.
 
 [cHelpID] - is a Help identifier string.
 
 [xSel] - the return value, is either a field name, a field position, or even
 an array, depending on how ADpl_fields() is configured.
 
 Example:
 --------
 func main()
 local bConfig := {|| ADvm_returntype( "C" ),;
                      ADvm_taglist();
                  }
 local bProcess := {|x| aadd( x, NIL ),;
                        aadd( x, NIL ),;
                        ains( x, 1 ),;
                        ains( x, 1 ),;
                        x[1] := "Selected Fields",;
                        x[2] := "---------------",;
                        ADmessage( x );
                   }
 select 0
 use mydbf
 ADpl_fields( 4, 4, bProcess, bConfig )
 use
 return nil


 ADpl_files( [nTop], [nLeft], [cPath], [cPattern], [bProcess], [bConfig],;
     [bPLConfig], [cHelpID] ) --> <xSelection>
 
 Implements an ADvermenu-derived files picklist.

 [nTop] and [nLeft] are the top/left coordinates of the picklist box.  Just
 like ADvermenu(), they default to the center of the screen.

 [cPath] is the path identifier from which the files info will be read.  It
 defaults to the current directory of the current drive.  If a null string is
 passed, it takes the root directory of the current drive.

 [cPattern] is the Dos file pattern.  Accepts the wildcard values "*" and
 "?".  Defaults to "*.*"

 [bProcess] is a codeblock that gets EVALuated when a dir is selected.  It is
 automatically passed the currently highlighted file (character string).

 [bConfig] has the same meaning as the Configuration Specifier of ADvermenu().

 [bPLConfig] is the Picklist Configuration Specifier.  Its use is similar to
 [bConfig].  No parameter is passed to it.

 <xSelection> is the return value.  It is either the selected file or an
 array of tagged files, depending on whether, the picklist is configured as a
 taglist or not.

 NOTE:  Unlike ADvermenu(), ADpl_files() may not be configured to return the
 numeric index position of the selection.  It always return the selection
 itself (a character string).

 Example 1: Picklist of all .PRG files
 -------------------------------------
 ? ADpl_files(,,, "*.PRG")

 Example 2: Picklist contains file size, date and time stamp information
 -----------------------------------------------------------------------
 ? ADpl_files( ,,, "*.PRG",,, {|| ADpl_filformat( "SDT" )} )




 ADpl_filattr( <cAttr> ) --> NIL
 
 An API specific to the files picklist, ADpl_files().  It tells ADpl_files()
 what type of files to include in the picklist, in addition to the the "A" or
 normal files.  <cAttr> is a string concatenated from the following file
 attribute values:

 "S" - system file
 "R" - read only file
 "H" - hidden file
 "A" - normal file.  This is always included.
 "D" - directory
 "V" - volume label

 Example 1: All normal files plus the read, hidden and system files
 ------------------------------------------------------------------
 ? ADpl_files( ,, "c:\",,,, {|| ADpl_filattr( "RHS" )} )

 Example 2: All normal files plus the directories
 ------------------------------------------------
 ? ADpl_files( ,, "c:\",,,, {|| ADpl_filattr( "D" )} )

 Example 3: All normal files plus the volume label
 ------------------------------------------------
 ? ADpl_files( ,, "c:\",,,, {|| ADpl_filattr( "V" )} )


 ADpl_filformat( <cFormat> ) --> NIL
 
 An API specific to the files picklist.  It tells ADpl_files() what format to
 use when displaying the picklist. <cFormat> is a string concatenated from
 the following file information values:

 "S" - include file size information
 "D" - include file date information
 "T" - include file time information
 "A" - include file attributes information
 "B" - do not include the file extension. ("B" for bare)

 Example 1:  Basic ADpl_files()
 ------------------------------
 ADpl_files( ,,, "*.PRG" )
 /*
 ͸
  MYPROG.PRG   
  OURPROG.PRG  
  YOURPROG.PRG 
 ;
 */

 Example 2: Without file extention
 ---------------------------------
 ADpl_files( ,,, "*.PRG",, {||ADplfil_format( "B" )} )  // bare
 /*
 ͸
  MYPROG   
  OURPROG  
  YOURPROG 
 ;
 */

 Example 3: With file date and time stamp
 ----------------------------------------
 ADpl_files( ,,, "*.PRG",, {||ADplfil_format( "DT" )} )  // bare
 /*
 ͸
  MYPROG.PRG     03/26/93 13:53:16 
  OURPROG.PRG    05/26/93 23:57:12 
  YOURPROG.PRG   11/13/92 09:45:02 
 ;
 */

 ADpl_headings( [nTop], [nLeft], [bProcess], [bConfig], [cHelpID] )
     --> <xSel>
 
 A specialized and ADvermenu-derived picklist of column headings of the
 current database browser.  The parameters, [nTop], [nLeft], [bProcess],
 [bConfig] and [cHelpID], and the return value, <xSel> have the same
 significance as those of the same name as in ADvermenu().  Refer to
 ADvermenu() for a more detailed description of them.
 
 [nTop], nLeft] - the top/left corner of the box.
 
 [bProcess] - a codeblock that is EVALuated when a selection is made.  The
 current selection is automatically passed to it.
 
 [bConfig] - is a codeblock used to configure ADpl_headings().
 
 [cHelpID] - is a Help identifier string.
 
 [xSel] - the return value, is either a heading, a column position, or even
 an array, depending on how ADpl_headings() is configured.
 
 
 Example:
 -------
 func main()
 local bPL_Config := {|| ADvm_header( "Column Headings" )}
 local bDB_Config :=  {|| ADdb_colheadings( {;
                                                 "Function;Name",;
                                                 "Is It;Mouseable?",;
                                                 "Number of;Parameters",;
                                                 "Description",;
                                                 "Date Last;Updated";
                                            };
                                          ),;
                          ADdb_enter( {|| ADpl_headings( 7,12,, bPL_Config )} );
                      }
 local aScn
 
 use mydbf
 aScn := ADmessage( { "Press Enter to popup a picklist of column headings" },;
                    18,, .f., .f. )
 ADdbview( 4,10,16,69, bDB_Config )
 ADrestscn( aScn )
 use
 return nil


                ͸
                    The Horizontal Menu Collection     
                ;

 The Engine Function
 
 ADhormenu( <aMenu>, [aTrigger], [xProcess], [bConfig], [cHelpID] )
    --> <nSel>
 

 Implements a horizontal menu, where the options are displayed horizontally
 on one line. The Esc key is not active, by default, but can be activated
 with the API, ADhm_setescape().

 <aMenu> - is an array of string menu options

 [aTrigger] - is an optional array of the positions of the options trigger
 characters.  If it is not passed, it defaults to an array of 1's, i.e, the
 first characters are the triggers.   The trigger characters are highlighted
 in the menu.

 [xProcess] - is an optional specifier of what is to be done with the
 selected option.  It can be a single codeblock or an array of codeblocks.
 If it is a single codeblock, ADhormenu() EVALuates it when a selection is
 made, passing to it the index position of the selected option.  If it is an
 array of codeblocks, ADhormenu() EVALuates the block corresponding to the
 selected option.

 NOTE:  ADhormenu() does not exit after EVALuating the codeblock.  If
 [xProcess] is not passed, the menu exits when a selection is made.

 [bConfig] - is the optional Configuration Specifier.  It is a codeblock that
 ADhormenu() EVALuates to configure itself.  It is typically a call to one or
 a series of the APIs.

 [cHelpID] - an optional Help identifier used by the Frankie Help Facility

 <nSel> - is the return value.  It is the index position of the selected
 option at exit.  It is zero, if ADhormenu() was aborted. Typically, <nSel>
 is important to the program only if [xBlock] is not passed.


 Example 1
 ---------
 // A basic implementation of ADhormenu().
 func main()
 local aMenu := { "The", "Quick", "Brown", "Fox" }
 ADhormenu( aMenu )
 return nil

 Example 2
 ---------
 // Trigger keys, other than the first characters, are specified in this
 // example.
 func main()
 local aMenu := { "The", "Quick", "Brown", "Fox" }
 local aTrigger := { 3, 5, 5, 3 }
 ADhormenu( aMenu, aTrigger )
 return nil

 Example 3
 ---------
 // ADhormenu passed with a codeblock in [xProcess]
 func main()
 local aMenu := { "The", "Quick", "Brown", "Fox", "Exit" }
 ADhormenu( aMenu,, {|n| Xprocess(n)} )
 return nil


 func Xprocess( nSel )
 if nSel == 1
 ADmessage( { "The" } )
 elseif nSel == 2
 ADmessage( { "Quick" } )
 elseif nSel == 3
 ADmessage( { "Brown" } )
 elseif nSel == 4
 ADmessage( { "Fox" } )
 elseif nSel == 5
 ADmessage( { "Exit" } )
 ADhm_abort()
 endif
 return nil


 Example 4
 ---------
 // ADhormenu() passed with an array of codeblocks in [xProcess]
 func main()
 local aMenu := { "The", "Quick", "Brown", "Fox", "Exit" }
 local aProcess := {;
                 {|| ADmessage( { "The" } )},;
                 {|| ADmessage( { "Quick" } )},;
                 {|| ADmessage( { "Brown" } )},;
                 {|| ADmessage( { "Fox" } )},;
                 {|| ADmessage( { "Exit" } ),;
                         ADhm_abort() };
                 }
 
 ADhormenu( aMenu,, aProcess )
 return nil


 Example 5
 ---------
 // Reconfigures the colors
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADhm_colors( { "N/W*", "GR+/R", "BG+" } )}
 setblink( .f. )
 ADhormenu( aMenu,,, bConfig )
 setblink( .t. )
 return nil


 THE CONFIG APIs
 
 ADhm_color( [aColor] ) --> <aOldColors>
 

 Reconfigures the default colors, and returns the old settings [aColor] and
 <aOldColors> contain 3 elements:

 1 - the standard color. Default is "B/W", "W/N" for color and monochrome,
     respectively.
 2 - the enhanced color. Default is "GR+/W", "I" for color and monochrome,
     respectively
 3 - the foreground part of the trigger color.  Default is "R", "W+" for
     color and monochrome, respectively.

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADhm_colors( { "N/W*", "GR+/R", "BG+" } )}
 setblink( .f. )
 ADhormenu( aMenu,,, bConfig )
 setblink( .t. )
 return nil


 ADhm_ends( <nLeftColumn>, <nRightColumn> ) --> NIL
 
 Sets the horizontal limits of the menu.

 <nLeftColumn> is the column position where the menu starts.  The first
 option is displayed at <nLeftColumn> + 1.

 <nRightColumn> is the column position where the menu ends. When ADhormenu()
 starts, it clears the line from <nLeftColumn> to <nRightColumn>.  When it
 exits, it restores it.  
 
 NOTE: ADhormenu() does not check if there are too many options to fit within
 the space allocated by the two end column positions.  If there are too many
 the options will overflow.

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADhm_ends( 10, 40 )}
 ADcls( "" )
 ADhormenu( aMenu,,, bConfig )
 return nil


 ADhm_escape( <bEscape> ) --> NIL
 

 Defines a codeblock that gets EVALuated when the Esc key is pressed, or when
 the right mouse button is clicked.  If the Esc key is currently not active
 (the default behaviour, which may be reset with ADhm_setescape()) this
 codeblock never gets EVALuated. 

 Example:
 -------
 func main()
 local aMenu, bConfig, aScn, bEscape

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bEscape := {|| aScn := ADmessage( { "Exiting..." },,, .f., .f. ),;
                inkey( 3 ),;     // do housekeeping here
                ADrestscn( aScn );
            }
bConfig := {|| ADhm_escape( bEscape )}
ADhormenu( aMenu,,, bConfig )
return nil


 ADhm_extra( [bDispBegin], [bDispEnd], [bUndisplay] ) --> NIL
 
 Defines a set of behaviours associated with the displaying and the exiting
 of the menu.

 [bDispBegin] - an optional codeblock that ADhormenu() EVALuates just before
 displaying the menu.

 [bDispEnd] - an optional codeblock that ADhormenu() EVALuates just after
 displaying the menu.

 [bUndisplay] - an optional codeblock that ADhormenu() EVALuates at exit 
 after un-displaying the menu.

 Example
 -------
 // Horizontal menu in a window
 func main()
 local aScn
 local aMenu := { "Databases", "Utilities", "Quit" }
 local bDispBegin := {|| ADhm_colors( { "N/W*",;
                                        "GR+/R",;
                                        "BG+";
                                       };
                                     ),;
                         aScn := ADbox( 4,4,20,49,;
                                        ADhm_colors()[1]; 
                                      ),;                 
                         ADcsay( 6,4,49,;
                                 "" + repl( "", 44 ) + "",;
                                 ADhm_colors()[1];
                                );                 
                     }                 
 local bConfig := {|| ADhm_extra( bDispBegin,;
                                  NIL,;
                                  {|| ADrestscn( aScn )};
                                ),;
                      ADhm_row( 5 ),;
                      ADhm_ends( 5, 48 );
                  }

 setblink( .f. )
 ADhormenu( aMenu,,, bConfig )
 setblink( .t. )
 return nil

 ADhm_initsel( <nInitSel> ) --> NIL
 
 Specifies the index position of the option to be initially highlighted.

 Example:
 -------
 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADvm_initsel(3)} // initial highlight at #3
 ADhormenu( aMenu,,, bConfig )


 ADhm_keys( <aKeys>, <bHandler>, [lAdditive] ) -->NIL
 
 Defines hot keys local to the menu.

 <aKeys> is an array of the inkey codes of the keys to be defined as hot
 keys.

 <bHandler> is a codeblock that is EVALuated when one of the defined hot keys
 is pressed.  It ia automatically passed two parameters:

        1 - the index position of the pressed key
        2 - the inkey code of the pressed key

 [lAdditive] is an optional logical parameter.  If it is TRUE, <aKeys> and
 <bHandler> are chained to already-defined hot keys.  If it is FALSE, <aKeys>
 and <bHandler> replace already_defined ones.  Defaults to FALSE.

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADhm_keys( { K_F8, K_F9, K_F10 },;
                           {|n,k| Xhandler(n,k)};
                         );
         }
 ADhormenu( aMenu,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nKeyCode )
 if nKeyCode == K_F8
    ADhm_jump( 3 )
 elseif nKeyCode == K_F9
    ADhm_abort()
 elseif nKeyCode == K_F10
    ADhm_select()
 endif
 return nil

 ADhm_lbuttons( <aSpots>, <bHandler>, [lAdditive] ) --> NIL
 
 Defines hot spots for the mouse left button.

 [aSpots] is an array of the screen sections that are to be defined as hot
 spots.  Each hot spot is an array of {top,left,bottom,right} coordinates.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot
 spots is clicked.  It ia automatically passed three parameters:

        1 - the index position of the clicked spot
        2 - the mouse cursor row position when the hot spot was clicked
        3 - the corresponding mouse column position

 [lAdditive] is an optional logical parameter.  If it is TRUE, <aKeys> and
 <bHandler> are chained to already-defined hot keys.  If it is FALSE, <aKeys>
 and <bHandler> replace already_defined ones.  Defaults to FALSE.

 Example:
 -------
 func main()
 local aMenu, bConfig, aSpots

 aMenu := { "The", "Quick", "Brown", "Fox" }
 aSpots := {;
             { 0,0,0,0 },;
             { maxrow(), maxcol(), maxrow(), maxcol() };
           }
 bConfig := {|| ADhm_lbuttons( aSpots,;
                               {|n,r,c| Xhandler(n,r,c)};
                             );
            }
 ADhormenu( aMenu,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nRow, nCol )
 if nIndexPos == 1
    ADhm_abort()
 elseif nIndexPos == 2
    ADhm_select()
 endif
 return nil

 ADhm_move( <bMoveBegin>, <bMoveEnd> ) --> NIL
 
 Defines two codeblocks that are EVALuated when the highlight bar moves.
 <bMoveBegin> is EVALuated just before unhilighting the current option;
 <bMoveEnd> just after highlighting the new option.

 Example
 -------
 func main()
 local aMenu, bConfig, bMoveEnd, aScn, aMsg

 aMenu := { "The", "Quick", "Brown", "Fox" }
 aMsg := { "THE", "QUICK", "BROWN", "FOX" }
 bMoveEnd := {|| ADsay( 23,1, padc( aMsg[ADhm_current()], 78 ) )}
 bConfig := {|| ADhm_move( , bMoveEnd )}
 aScn := ADbox(22,0,24,79,,, .f., .f.)
 ADhormenu( aMenu,,, bConfig )
 ADrestscn( aScn )
 return nil

 ADhm_pad( <nSpaces> ) --> NIL
 
 Specifies the number of spaces <nSpaces> to insert between options.
 Defaults to 3.

 Example 1:
 ---------
 ADhormenu( { "One", "Two", "Four" } ) 
 /*
        One   Two   Four        // default of 3 spaces between options
 /*

 Example 2:
 ---------
 ADhormenu( { "One", "Two", "Four" },,, {||ADhm_pad( 6 )} ) 
 /*
        One      Two      Four        // 6 spaces between options
 /*

 ADhm_pulldown( <aPositions> ) --> NIL
 
 Specifies which options in the horizontal menu gets automatically "pulled
 down" when the highlight is moved to it.  <aPositions> is an array of the
 index positions of options to be auto-pulled-down.

 Example
 -------
 func main()
 local aMenu := { "One", "Two", "Four", "Quit" }
 local bConfig := {||ADhm_pulldown( { 2,3 } )}  // the routines corresponding
                                                // to options 2 and 3 will be
                                                // automatically called as
                                                // soon as the highlight
                                                // falls on them.  You do not
                                                // have to press Enter.
 local bProcess := {|n|Xprocess(n)}
 ADhormenu( aMenu,, bProcess, bConfig )
 return NIL

 func Xprocess( nSel )
 if nSel == 1
     ADmessage( { "One" } )
 elseif nSel == 2
     ADmessage( { "Two" } )
 elseif nSel == 3
     ADmessage( { "Four" } )
 elseif nSel == 4
     ADhm_abort()
 endif
 return NIL

 ADhm_row( <nRow> ) --> NIL
 
 Set the row position, <nRow> of the menu.  Defaults to row #0.

 Example:
 -------
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADhm_row( 10 )}
 ADcls( "" )
 ADhormenu( aMenu,,, bConfig )
 return nil

 ADhm_setescape( <lEscape> ) --> NIL
 
 Toggles the Esc key as an abort key.  If <lEscape> is TRUE, the Esc key will
 be an abort key.  If Esc is an abort key, so is the right mouse button.  If
 the [xProcess] parameter is passed to ADhormenu(), the Esc key is always
 inactive.  By default, the Esc is active.

 Example:
 -------
 // The escape key is inactivated as an abort key
 func main()
 local aMenu, bConfig

 aMenu := { "The", "Quick", "Brown", "Fox" }
 bConfig := {|| ADhm_setescape( .f. )}
 ADhormenu( aMenu,,, bConfig )
 return nil


 ADhm_timeout( <nTimeOut>, <bTimeout> ) --> NIL
 
 Designates a time-out routine.

 <nTimeOut> is the number of idle seconds to pass before the timeout routine
 is invoked.

 <bTimeOut> is a codeblock that ADhormenu() EVALuates after <nTimeOut>
 seconds of inactivity.

 Example: Blank the screen after 5 seconds of idle time.
 ------------------------------------------------------
 ADhormenu({"One", "Two", "Four"},,, {||ADhm_timeout(5, {||ADblankscn()})}) 




 THE ACTION APIs
 
 ADhm_abort( [nExitCode] ) --> NIL
 
 Aborts the menu, and at the same time, setting the exit code to [nExitCode].
 If [nExitCode] is not passed, it defaults to HMX_ABORT, which is #defined in
 Frankie.ch

 Example: See ADhm_keys()


 ADhm_jump( <nOptionPosition> ) --> NIL
 
 Moves the highlight to the option whose index position is <nOptionPosition>.

 Example:  See ADhm_keys()


 ADhm_select( [nExitCode] ) --> NIL
 
 Selects the currently highlighted option, and at the same time, sets the
 exit code to [nExitCode].  If [nExitCode] is not passed, it defaults to 
 HMX_SELECT, which is #defined in Frankie.ch.

 Example: See ADhm_keys()



 ^THE INFO APIs
 
 ADhm_col() --> <nCurrentColumnPosition>
 
 Returns the starting column position of the currently highlighted selection.

 Example
 -------
 func main()
 local bConfig := {||ADhm_keys( {-9},;
                                {|| ADmessage( {ADn2s(ADhm_col())} )};
                              );
                  }
 ADhormenu( { "One", "Two", "Four" },,, bConfig )
 return NIL



 ADhm_current() --> <nCurrentPosition>
 
 Returns the index position of the currently highlighted option.

 Example
 -------
 func main()
 local bConfig := {||ADhm_keys( {-9},;
                                {|| ADmessage( {ADn2s(ADhm_current())} )};
                              );
                  }
 ADhormenu( { "One", "Two", "Four" },,, bConfig )
 return NIL

 ADhm_exitcode() --> < nExitCode>
 
 Returns the exit code of the last exited horizontal menu.  If the menu was
 exited after a keypress, the exit code is simply the inkey code of the key.
 At other times it will be one of the following values #defined in
 Frankie.ch.

 //ADhormenu() special exit codes
 #define HMX_ABORT   -102  // when menu is aborted with the mouse right button
                           // or programmatically with ADhm_abort()
 #define HMX_SELECT  -103  // when a menu option is selected with the mouse
                           // left button or programmatically selected with
                           // ADhm_select()

 NOTE:  Both ADhm_abort() and ADhm_select() accept an optional numeric
        parameter to which the exit code is set to.

 Example
 -------
 ADhormenu( { "one", "two", "four" } )
 ?ADhm_exitcode()



 ADhm_version() --> <cVersionNumber>
 
 Returns the version of ADhormenu() as a string.

 Example
 -------
 ? ADhm_version()


 GLOBAL APIs
 
 ADhmg_color( <aNewColors> ) --> <aOldColors>
 
 Globally sets the menu colors.  Once the colors are globally set, all 
 succeeding ADhormenu() calls will use these values until they are gloabally
 reset, or unless they are locally set with ADhm_color().

 <aNewColors> is an array of new settings.  See ADhm_color() for a discussion 
 of its elements.

 <aOldColors> is the array of old values.

 Example:
 --------
 ADhmg_color( { "R/W", "B/W", "W+" } )



SPECIALIZED FUNCTIONS
 
 ADpulldown( <aVMMenu>, [aVMTrigger], [xVMProcess] ) --> <nVMSelection>
 
 Implements a pulldown ADvermenu() from an ADhormenu().  This API is now
 obsolete.  Use ADpdmenu(), instead.

 <aVMMenu>, [aVMTrigger] and [xVMProcess] are the vertical menu's options
 array, triggers array, and options processor. Their meanings are similar to
 the corresponding <aMenu>, [aTrigger] and [xProcess] parameters of
 ADvermenu().

 [aColors] is an array of colors used by the pulled down ADvermenu().  Its
 elements are:

         1 - the standard color
         2 - the enhanced color
         3 - the header color
         4 - the foreground of the trigger color (the background
             is always the background of the standard color)

 If any of the 4 elements is nil, the corresponding color is not
 changed.  The default colors are:

         Color       Mono
         -----       ----
         1 - "W+/W"      "W/N"
         2 - "GR+/R"     "I" 
         3 - "B/W"       "W+/N"
         4 - "B"         "W+"

 [aBoxAttr] is an array of vertical menu box attributes.  Its elements are:

 1 - Frame.  Default is "ѳԳ "
 2 - Shadow.  Default is .T.
 3 - Explode.  Default is .T.
 4 - Pad.  Default is 1, that is 1 space padding between
         the left of the box and the column start of the options
         list, and similarly  at the right.


 <nVMSelection> is the return value similar to that of ADvermenu().



 ADpdmenu( <aMenu>, [lTrigger], [xProcess], [bConfig], [cHelpID] ) --> <nSel>
 
 Implements a pulldown ADvermenu() from an ADhormenu().  Replaces 
 ADpulldown().  In a pulled down menu, the options of the parent horizontal
 menu are still active via the mouse left button, and the ALT key
 combinations of their triggers.

 <aMenu>, [aTrigger], [xProcess], [bConfig], and [cHelpID] are the vertical
 menu's options array, triggers array, and options processor, configuration
 block, and Help identifier string.  Their meanings are similar to the
 corresponding ADvermenu() parameters.

 <nSel> is the return value similar to that of ADvermenu().

 Example
 -------
 func main()
 local aScn
 local aMenu := { "One", "Two", "Quit" }
 local bConfig := {||ADhm_pulldown( { 1,2 } ),;
                     ADhm_extra( {||aScn := ADsavescn(1,0,1,79),;
                                    ADsay( 1,0,;
                                           repl( "", 80 ),;
                                           ADhm_color()[1];
                                         );
                                 },;
                                 ,;
                                 {||ADrestscn( aScn )};
                               );
                  }
 local bProcess := {|n|Xprocess(n)}
 ADhormenu( aMenu,, bProcess, bConfig )
 return NIL
 
 func Xprocess( nSel )
 if nSel == 1
     ADpdmenu( { "The", "Quick", "Brown" } )
 elseif nSel == 2
     ADpdmenu( { "Jumps", "Over", "The", "Lazy", "Dog" } )
 elseif nSel == 3
     ADhm_abort()
 endif
 return NIL



                ͸
                      The Dialog Box Collection      
                ;

 The Engine Function
 
ADboxmenu( <xPrompt>, <aMenu>, [aTrigger], [bBlock], [nTop], [nLeft],;
   [bConfig], [xHelpID] ) --> <nSelection>
 
 Displays a dialog box that looks like this:

        ͸
            Drive A: Not Ready     
        Ĵ
            Retry        Abort    
        ;

 <xPrompt> is the prompt string. ( "Drive A: Not Ready" ) A prompt of two or
 more lines is possible by passing an array of strings.

 <aMenu> is an array of string options.  ({ "Retry", "Abort" })  

 [aTrigger] is an optional array of the index positions of the trigger
 characters.  Defaults to an array of ONEs.

 [bBlock] is an optional codeblock that gets EVALuated when a selection is
 made.  The engine identifier is automatically passed to it.  Note that if
 [bBlock] is passed, ADboxmenu() does not exit when a selection is made.  To
 exit, you must specifically send an exit message via the API, ADbm_exit().

 [nTop] and [nLeft] are the top/left coordinates of the box.  If they are not
 passed, the box is centered on the screen.

 [bConfig] is the Configuration Specifier.  It is typically a codeblock that
 calls one or a series of APIs that send messages to the engine to
 reconfigure itself.  It is automatically passed the engine identifier.  See
 the examples below.

 [xHelpID] an optional Help Identifier string or array of identifiers.  If
 only one string is passed, it will be treated as the ID for all options.  If
 an array is passed, each option will have its own ID.

 <nSelection> is the numeric return value.  It is the index position of the
 selected option.  It is zero if the menu was aborted.  Note that if [bBlock]
 is passed, the return value may not be relevant.


 v1.0 to v2.0
 ------------
 1.  In 1.0, the prompt was limited to a one-liner.
 2.  The default box attributes and colors may now be globally set.  See
     ADbmg_boxattr() and ADbmg_color().
 3.  The initially highlighted option may now be set.  See ADbm_initsel().
 4.  The padding is now automatically expanded if <xPrompt> is very long.
 5.  The help identifier is no longer restricted to one ID for the whole
     engine.  An array of IDs, one for each option, may now be passed.

 Example 1:
 ----------

 ? ADboxmenu( "Which color?", { "White", "Blue", "Red" } )


 Example 2:
 ---------

 // change the default colors

 ? ADboxmenu( "Which color?",;
              { "White", "Blue", "Red" },;
              NIL,;
              NIL,;
              NIL,;
              NIL,;
              {|| ADbm_colors( { "N/BG", "R+/B", "W+" } )};
            )

 NOTE: See BM_TOUR.PRG and the various BM_DEMO?.PRGs for more examples.


 The Config APIs
 
 ADbm_boxattr( <aAttributes> ) --> NIL
 
 Reconfigures the attributes of the box.

 [aAttributes] is an array of 5 values.  If it is passed, the corresponding
 box attributes are set to its values.  the elements are:

   1 - the box frame.  Defaults to "͸Գ "
   2 - the box divider.  Defaults to "Ĵ³"
   3 - a logical value, whether to drop a shadow or not.  Defaults to .T.
   4 - a logical value, whether to explode or not.  Defaults to .T.
   5 - a numeric value defining the number of spaces to pad each option with.
       Defaults to 4.

 Example:
 -------
 bConfig := {|| ADbm_boxattr(;
                              {;
                                "Ŀ ",;  // box frame
                                "Ĵ³",;     // divider
                                .f.,;          // no shadow
                                .f.,;          // do not explode
                                10;            // pad with 10 spaces
                              };
                            );
            }
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )


 ADbm_color( <aColor> ) --> NIL
 
 Reconfigures the colors.

 <aColor> is an array of 3 color specifiers.  If it is passed, the menu
 colors are set to its values.  Its elements are:

   1 - the standard color.  Defaults to "B/W" or "W/N"
   2 - the enhanced color. Defaults to "GR+/BG" or "I"
   3 - the foreground part of the trigger color.  The background part is
       always set to the background part of the standard color.  Defaults to
       "R"

 Example:
 -------
 bConfig := {|| ADbm_colors( { "N+/W*", "GR+/R", "B+" } )}
 setblink( .f. )
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )
 setblink( .t. )


 ADbm_initsel( <nOption> ) --> NIL
 
 Specifies which option is to be initially highlighted.

 <nOption> is the index position of the option to be initially highlighted.

 Example:
 --------
 ADboxmenu( "What Is Your Answer?",;
            { "Yes", "No", "Maybe" },;
            NIL, NIL, NIL, NIL,;
            {|| ADbm_initsel( 3 )}; // initial hilite on "Maybe"
          )


 ADbm_isescape( <lActive> ) --> NIL
 
 Toggles the active state of the Esc key.  When it is active, ADboxmenu()
 aborts when it is pressed, or when the right mouse button is clicked.  By
 default, the Esc key is not active.

 Example
 -------
 bConfig := {|| ADbm_isescape( .t. )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )


 ADbm_keys( <aKeys>, <bHandler> ) --> NIL
 
 Defines hot keys local to the dialog box.

 <aKeys> is an array of the inkey codes of the keys to be defined as hot
 keys.

 [bHandler] is a codeblock that is EVALuated when one of the defined hot keys
 is pressed.  It is automatically passed two parameters:

   1 - the index position of the pressed key
   2 - the inkey code of the pressed key

 Example:
 -------
 func main()
 local bConfig := {|| ADbm_keys( { K_F9, K_F10 },;
                                 {|n,k| Xhandler(n,k)};
                               );
                  }
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nKeyCode )
 if nIndexPos == 1       // same functionality as:
                         // if nKeyCode == K_F9
    ADmessage( { "F9" } )
 elseif nIndexPos == 2   // same functionality as:
                         // elseif nKeyCode == K_F10
    ADmessage( { "F10" } )
 endif
 return nil


 ADbm_lbuttons( <aSpots>, <bHandler> ) --> NIL
 
 Defines hot spots for the mouse left button.

[aSpots] is an array of the screen sections that are to be defined as hot
spots.  Each hot spot is an array of {top,left,bottom,right} coordinates.

[bHandler] is a codeblock that is EVALuated when one of the defined hot spots
is clicked.  It is automatically passed three parameters:

   1 - the index position of the clicked spot
   2 - the mouse cursor row position when the hot spot was clicked
   3 - the corresponding mouse column position

 Example:
 -------
 func main()
 local aSpots := {;
                   { 0,0,0,0 },;
                   { maxrow(), maxcol(), maxrow(), maxcol() };
                 }
 local bConfig := {|| ADbm_lbuttons( aSpots,;
                                     {|n,r,c| Xhandler(n,r,c)};
                                   );
                  }
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )
 return nil

 func Xhandler( nIndexPos, nRow, nCol )
 if nIndexPos == 1
    ADmessage( { "Top/Left Corner" } )
 elseif nIndexPos == 2
    ADmessage( { "Bottom/Right Corner" } )
 endif
 return nil



 ADbm_timeout( <nTimeout>, <bTimeout> ) --> NIL
 
 Designates a time-out routine.

 <nTimeOut> is the number of idle seconds to pass before the timeout routine
 is invoked.

 <bTimeOut> is a codeblock that ADboxmenu() EVALuates after <nTimeOut>
 seconds of inactivity.

 Example
 -------
 bConfig := {|| ADbm_timeout( 3 , {||ADblankscn()} )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )




 The Action APIs
 
 ADbm_abort( [nExitCode] ) --> NIL
 
 Aborts the dialog box, and optionally sets the exit code to [nExitCode].  If
 [nExitCode] is not specified, it defaults to the BMX_ABORT which is #defined
 in Frankie.ch.

 NOTE:  This function used to be ADbm_exit() prior to Frankie v2.

 Example:
 -------
 /*
 Assigns F10 as an exit key.
 */

 bConfig := {|| ADbm_keys( { K_F10 }, {|| ADbm_abort()} )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )


 ADbm_select( [nExitCode] ) --> NIL
 
 Selects the currently highlighted option, and optionally sets the exit code
 to [nExitCode].  If [nExitCode] is not specified, it defaults to the
 BMX_SELECT which is #defined in Frankie.ch.

 -------
 /*
 Assigns F10 to select currently highlighted option
 */

 bConfig := {|| ADbm_keys( { K_F10 }, {|| ADbm_select()} )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )



 The Info APIs
 
 ADbm_col() --> <nColumn>
 
 Returns the starting column position of the currently highlighted option.

 Example
 -------
 bConfig := {|| ADbm_keys( {-9}, {||ADmessage( { ADn2s( ADbm_col() ) } )} )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )

 ADbm_current() --> <nCurrentSelection>
 
 Returns the index position of the currently highlighted option.

 Example
 -------
 bConfig := {|| ADbm_keys( {-9}, {||ADmessage( { ADn2s( ADbm_current() ) } )} )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )

 ADbm_exitcode() --> <nExitCode>
 
 Returns the exit code of the last exited ADboxmenu().  If the menu was
 exited by means of a keypress, the exit code is simply the inkey code
 of key.  Otherwise, it is one of the following values #defined in
 Frankie.ch.

 //ADboxmenu() special exit codes
 #define BMX_ABORT   -102  // if the menu is exited with the mouse right
                           // button, or programmatically with an
                           // ADbm_abort() call.
 #define BMX_SELECT  -103  // if an option was selected with the mouse left
                           // button, or programmatically with an
                           // ADbm_select() call.

 Example
 -------
 ADboxmenu( "Exit?", { "Yes", "No" } )
 ?ADbm_exitcode()


 ADbm_row() --> <nRow>
 
 Returns the row position of the menu options.

 Example
 -------
 bConfig := {|| ADbm_keys( {-9}, {||ADmessage( { ADn2s( ADbm_row() ) } )} )}
 ADboxmenu( "Exit?", { "Yes", "No" },,,,, bConfig )

 ADbm_version() --> <nVersion>
 
 Returns the version number of ADboxmenu() as a string.

 Example
 -------
 ? ADbm_version().


 Global APIs
 
 ADbmg_boxattr( [aAttr] ) --> <aOld>        
 

 Returns the current ADboxmenu() box attributes, <aOld>, and optionally
 globally sets them to the new values in [aAttr]. Both [aAttr] and <aOld>
 contain 5 elements:

        1 - the box frame
        2 - the box divider
        3 - to drop a shadow or not
        4 - to explode or not
        5 - number of space padding around options

 Example:
 --------
 ADbmg_boxattr( { "Ŀ ", "Ĵ³", .f., .f., 2 } )


 ADbmg_color( [aColors] ) --> <aOld>
 

 Returns the current ADboxmenu() color setting, <aOld>, and optionally
 globally sets them to new values in [aColors].  Both [aColors] and <aOld>
 contain 3 elements:

        1 - standard color
        2 - trigger color
        3 - the foreground of the trigger color

 Example:
 --------
 ADbmg_color( { "R/W", "B/W", "W+" } )



 Specialized Functions
 
 ADbm_drives( [bConfig], [xHelpID] ) --> <cDrive>
 
 Implements a dialog box of all available drives.  Returns the letter
 identifying the selected drive ( "A", "B", etc. ) or a null string if the
 menu was aborted.  

 NOTE: The parameters [bConfig] and [xHelpID] are optional and have the same
       meaning as the corresponding parameters in ADboxmenu()

 Example
 -------
 func main()
 local cDrive := ADbm_drives()

 if cDrive $ "AB"
    ADmessage( {"Insert disk in Drive " + cDrive } )
 endif
 return NIL



 ADbm_floppy( [cPrompt], [bProcess], [bConfig], [xHelpID] ) --> <nSelection>
 
 A specialized "Which Floppy?" dialog box.  Automatically determines what
 floppy drives are available.  Automatically adds a "Cancel" option.

 [cPrompt] is an optional prompt string.  It defaults to "Which Floppy?"

 [bProcess] is an optional codeblock that is EVALuated when a selection is
 made.  It is passed the letter and numeric identifiers of the selected
 drive.  Before it is EVALuated, ADbm_floppy() first checks if the selected
 drive is ready.

 NOTE: The parameters [bConfig] and [xHelpID] are optional and have the same
       meaning as the corresponding parameters in ADboxmenu()

 <nSelection> is the numeric return value.  It is always 0 if [bProcess] is
 passed.  Otherwise, it is the index position of the selection.

 Example:
 --------
 func main()
 local nDiskSpace
 ADbm_floppy( , {|cDrive, nDrive| nDiskSpace := diskspace( nDrive )} )
 ? nDiskSpace
 return nil


 ADbm_isfloppy( [cDrive], [bConfig], [xHelpID] ) -> <lReady>
 
 Checks if floppy drive [cDrive] is ready; implements a "Drive Not Ready"
 dialog box if it is not; subsequently returns whether it is ready or not. 
 [cDrive] defaults to the A:  drive.  Note that [cDrive] always refers to a
 floppy, even if "C" or "D" is specified.

 NOTE: The parameters [bConfig] and [xHelpID] are optional and have the same
       meaning as the corresponding parameters in ADboxmenu()

 Example
 -------
 if ADbm_isfloppy( "B" )
    copy file Q.PRG to B:Q.PRG
 endif


 ADbm_isprinter( [bConfig], [xHelpID] ) --> <lReady>
 
 Checks if LPT1 is ready. Implements a "Printer Not Ready" dialog box if it
 is not. Subsequently returns whether it is ready or not.

 NOTE: The parameters [bConfig] and [xHelpID] are optional and have the same
       meaning as the corresponding parameters in ADboxmenu()

 Example
 -------
 if ADbm_isprinter()
    copy file Q.PRG to PRN
 endif

 ADbm_switch( <vSwitch>, <cPrompt>,  [bConfig], [xHelpID] ) --> <lOldSetting>
 
 Toggles a switch.

 <vSwitch> is the name of the variable that refers to a logical switch.
 Always pass it by reference.

 <cPrompt> is the prompt displayed by ADboxmenu().  The current setting of
 <vSwitch> is appended to it.

 NOTE: The parameters [bConfig] and [xHelpID] are optional and have the same
       meaning as the corresponding parameters in ADboxmenu()

 <lOldSetting> is the setting of <vSwitch> before the call.

 Example:
 -------
 func main()
 local lSetting := .t.
 ADbm_switch( @lSetting, "The Switch is Currently " )
 ADbm_switch( @lSetting, "The Switch is Currently " )
 ADbm_switch( @lSetting, "The Switch is Currently " )
 return nil


 The Push Button Collection

 The Engine Function
 
 ADpb_create( [bConfig], [cHelpID] ) --> <nEngine>
 
 Creates a "bare" push button engine.  A bare engine is one that does not
 have any actual buttons added to it yet.  To add a button, use ADpb_add().
 To show the buttons, use ADpb_show().  To activate the engine, use
 ADpb_activate().

 [bConfig] is an optional configuration block.  You use it to call the
 engine's Config APIs that reconfigures the engine.

 [cHelpID] is a Help ID string.

 <nEngine> is the numeric return value.  You use it to send messages to the
 engine.

 Example
 -------
 func main()
 local e := ADpb_create()
 
 ADpb_add( e,2,2, "The" )          // button #1
 ADpb_add( e,4,2, "Quick" )        // button #2
 ADpb_add( e,6,2, "Brown" )        // button #3
 cls
 ADpb_show( e )
 ADmessage( { "Selected #" + ADn2s( ADpb_activate( e ) ) } )
 ADpb_kill( e )
 return NIL




 The Config APIs
 
 ADpb_colors( <aColors> ) --> NIL
 
 Configures the colors of the pushbutton engine.

 <aColors> is an array of two elements:

    1 - the standard color.  Defaults to "W+/W in color monitors
        and "W/N" in monochrome.

    2 - the foreground color of the trigger.  The background is the same as
        the standard color.  Defaults to "R" in color monitors and "W+" in
        monochrome.

 Example
 -------
 func main()
 local e := ADpb_create( {|| ADpb_colors( { "R/B", "W+" } )} )
 
 ADpb_add( e,2,2, "The" )          // button #1
 ADpb_add( e,4,2, "Quick" )        // button #2
 ADpb_add( e,6,2, "Brown" )        // button #3
 ADpb_add( e,8,2, "Fox" )          // button #4
 cls
 ADpb_show( e )
 return NIL


 ADpb_setalt( <lActive> ) --> NIL
 
 (De)activates the alt-keys as trigger keys.  <lActive> is a logical value.
 If it is TRUE, the alt-equivalents of the trigger keys will be activated,
 and the regular trigger keys are de-activated.
 
 Example
 -------
 func main()
 local e := ADpb_create( {|| ADpb_setalt( .t. )} )
 
 ADpb_add( e,2,2, "The" )          // button #1
 ADpb_add( e,4,2, "Quick" )        // button #2
 ADpb_add( e,6,2, "Brown" )        // button #3
 cls
 ADpb_show( e )
 ADmessage( { "Selected #" + ADn2s( ADpb_activate( e ) ) } )
 ADpb_kill( e )
 return NIL



 The Action APIs
 
 ADpb_activate( <nEngine> ) --> NIL
 
 Activates a pushbutton engine, <nEngine>.  This API does not display the
 buttons, but merely activates them, that is, pauses the program until
 a button is selected, or until engine is aborted.  Use ADpb_show() to
 display the buttons.

 Example
 -------
 See ADpb_create()
 

 ADpb_add( <nEngine>, <nRow>, <nCol>, <cLabel>, [nTrigger] ) --> NIL
 
 Adds a button into the engine

 <nEngine> is the numeric dentifier of the push button engine.

 <nRow>, <nCol> is the starting position of the added button.

 <cLabel> is the label of the added button

 [nTrigger] is the index position of the trigger key.  It defaults to 1.

 Example
 -------
 See ADpb_create()

 
 ADpb_kill( <nEngine> ) --> NIL
 
 Kills a push button engine, <nEngine>

 Example
 -------
 See ADpb_create()

 
 ADpb_push( <nEngine>, <nButton> ) --> NIL
 
 "Pushes" the button <nButton> in engine <nEngine>.  If the buttons are
 shadowed (See ADpb_show()), the button actually "moves" when pushed.  If
 the buttons are not shadowed, they merely change in color briefly.
 ADpb_activate() calls this function, so you typically will not have to call
 it directly unless you are attaching the PB engine to another user inter-
 face, like ADdbview(), where you have to program your hot key and hot spot
 handler.

 Example: A vertical push button engine is attached to a dbviewer engine.
 To simplify this example, no hot keys are defined for the dbviewer.  Note
 that ADpb_activate() is not used here.  Instead, the dbviewer's hot spot
 handler calls ADpb_push() to push the selected button.
 -------------------------------------------------------------------------
 func main()
 local aScn          // screen occupied by push buttons
 local nPBEngine     // push button engine ID
 local nArea := select()
 
 select 0
 use tour\demo
 ADdbview( 10,10,20,79,;
           {||ADdb_extra( {||dispbegin(),;
                             aScn := ADbox( 10,0,20,9,;
                                            "W+/B",;
                                            space(9),;
                                            .f.,;
                                            .f.;
                                          ),;
                             nPBEngine := ADpb_vertical( 11, 2,;
                                                         { "Edit  ",;
                                                           "Add   ",;
                                                           "Delete",;
                                                           "Recall",;
                                                           "Pack  ";
                                                         };
                                                       );
                          },;
                          {||ADpb_show( nPBEngine, "B" ),;
                             ADdb_lbuttons( ADpb_spots( nPBEngine ),;
                                            {|n|Xdb_shandler(n,nPBEngine)};
                                          ),;
                             ADcsay( 20, 10, 79,;
                                     " Push Buttons With ADdbview() Demo ",;
                                     "GR+/B";
                                   ),;
                             dispend();
                          },;
                          {||ADpb_kill( nPBEngine ),;
                             ADrestscn( aScn );
                          };
                        );
           };
         )
 use
 select (nArea)
 return NIL
 
 //--------------------------------------
 static func Xdb_shandler( n, nPBEngine )
 ADpb_push( nPBEngine, n )
 
 if n == 1
     ADnotyet( "Edit" )
 elseif n == 2
     ADnotyet( "Add" )
 elseif n == 3
     ADnotyet( "Delete" )
 elseif n == 4
     ADnotyet( "Recall" )
 elseif n == 5
     ADnotyet( "Pack" )
 endif
 return NIL


 ADpb_show( <nEngine>, [cBackColor] ) --> NIL
 
 Displays the buttons in the engine.

 <nEngine> is the numeric engine identifier.

 [cBackColor] is an optional background color specifier.  If it is passed,
 a shadow will be drawn around the button.  The shadow's foreground color
 is black while its background color is [cBackColor].  When a button is
 shadowed as such, it will "move" when "pushed."

 Example
 -------
 See ADpb_create()
 


 The Info APIs
 
 ADpb_altkeys( <nEngine> ) --> <aKeys>
 
 Returns an array of the alt equivalents of the trigger keys 

 <nEngine> is the numeric engine identifier.

 <aKeys> is the return value.

 Example
 -------
 func main()
 local e := ADpb_create()
 
 ADpb_add( e,2,2, "The" )          // button #1
 ADpb_add( e,4,2, "Quick" )        // button #2
 ADpb_add( e,6,2, "Brown" )        // button #3
 cls
 ADpb_show( e )
 setpos( 10, 0 )
 aeval( ADpb_altkeys( e ), {|x| qout( x )} )   // 276  -> K_ALT_T
                                               // 272  -> K_ALT_Q
                                               // 304  -> K_ALT_B
 return NIL



 ADpb_triggers( <nEngine> ) --> <aKeys>
 
 Returns an array of trigger keys. 

 <nEngine> is the numeric engine identifier.

 <aKeys> is the return value.  Each button in the engine is mapped to two
 trigger keys in <aKeys>, an upper case, and a lower case.  Thus the number
 of elements in <aKeys> is twice as many buttons there are in the engine.

 Example
 -------
 func main()
 local e := ADpb_create()
 
 ADpb_add( e,2,2, "The" )          // button #1
 ADpb_add( e,4,2, "Quick" )        // button #2
 ADpb_add( e,6,2, "Brown" )        // button #3
 cls
 ADpb_show( e )
 setpos( 10, 0 )
 aeval( ADpb_triggers( e ), {|x| qout( x )} )   // 84  -> "t"
                                                // 116 -> "T"
                                                // 81  -> "q"
                                                // 113 -> "Q"
                                                // 66  -> "b"
                                                // 98  -> "B"
 return NIL


 ADpb_spots( <nEngine> ) --> <aSpots>
 
 Returns an array of the corresponding hot spots of the buttons.

 <nEngine> is the numeric engine identifier.

 <aSpots> is the return value.

 Example
 -------
 func main()
 local e := ADpb_create()
 
 ADpb_add( e,2,2, "The" )          // button #1
 ADpb_add( e,4,2, "Quick" )        // button #2
 ADpb_add( e,6,2, "Brown" )        // button #3
 ADpb_add( e,8,2, "Fox" )          // button #4
 cls
 ADpb_show( e )
 setpos( 10, 0 )
 aeval( ADpb_spots( e ),;
        {|x| aeval( x, {|y| qqout( y, " " )} ), qout("");
        };
      )         // 2 2 2 4
                // 4 2 4 6
                // 6 2 6 6
                // 8 2 8 4
 return NIL



 Specialized Push Buttons
 
 ADpb_horizontal( <nRow>, <nCol>, <aLabels>, [aTrigger], [nSpace], [bConfig],;
     [cHelpID] ) --> <nEngine>
 
 Creates a horizontal push button engine.

 <nRow>, <nCol> is the starting location of the buttons in the engine

 <aLabels> is an array of button labels

 [aTrigger] is an array of the index positions of the trigger keys.  Defaults
 to an array of all 1s.

 [nSpace] is the number of spaces to place between buttons.  Defaults to 3.

 [bConfig] is an optional Configuration Specifier.  See also ADpb_create().

 [cHelpID] is an optional Help ID string.

 <nEngine> is the numeric identifier of the created engine.

 Example
 -------
 func main()
 local e

 cls
 e := ADpb_horizontal( 10, 10, { "The", "Quick", "Brown" } )
 ADpb_show( e )
 ? ADpb_activate( e )
 return NIL


 ADpb_vertical( <nRow>, <nCol>, <aLabels>, [aTrigger], [nSpace], [bConfig],;
     [cHelpID] ) --> <nEngine>
 
 Creates a vertical push button engine.

 <nRow>, <nCol> is the starting location of the buttons in the engine

 <aLabels> is an array of button labels

 [aTrigger] is an array of the index positions of the trigger keys.  Defaults
 to an array of all 1s.

 [nSpace] is the number of lines to place between buttons.  Defaults to 1.

 [bConfig] is an optional Configuration Specifier.  See also ADpb_create().

 [cHelpID] is an optional Help ID string.

 <nEngine> is the numeric identifier of the created engine.

 Example
 -------
 func main()
 local e

 cls
 e := ADpb_vertical( 10, 10, { "The", "Quick", "Brown" } )
 ADpb_show( e )
 ? ADpb_activate( e )
 return NIL

                ͸
                    The Radio Buttons Collection    
                ;

 The Engine Function
 
 ADrb_create( [bConfig], [cHelpID] ) --> <nEngine>
 
 Creates a "bare" radio buttons engine.  

 [bConfig] is an optional configuration block.  You use it to call the
 engine's Config APIs that reconfigures the engine.

 [cHelpID] is an optional Help ID.

 <nEngine> is the numeric return value.  You use it to send messages to the
 engine.

 Example
 -------
 func main()
 local e := ADrb_create()
 ADrb_add( e,12,14, "The" )              // button #1
 ADrb_add( e,13,14, "Quick Brown Fox" )  // button #2
 ADrb_add( e,14,14, "Jumps" )            // button #3
 ADrb_show( e )
 ADmessage( { "Selected #" + ADn2s( ADrb_activate( e ) ) } )
 ADrb_kill( e )
 return NIL






 The Config APIs
 
 ADrb_chkbox() --> NIL
 
 Configures the radio buttons to behave as a checkbox.  The buttons may then
 be toggled "on" or "off", and as many buttons as desired may be toggled
 "on".

 Example
 -------
 func main()
 local e := ADrb_create( {|| ADrb_chkbox()} )

 ADrb_add( e,2,2, "The" )          // button #1
 ADrb_add( e,4,2, "Quick" )        // button #2
 ADrb_add( e,6,2, "Brown" )        // button #3
 ADrb_add( e,8,2, "Fox" )          // button #4
 cls
 ADrb_show( e )
 ADrb_activate( e )
 ADrb_kill( e )
 return NIL


 ADrb_okbutton( [cLabel], [nTrigger], [aColors] ) --> NIL
 
 Configures the "OK" pushbutton.

 [cLabel] is the label of the "OK" button.  Defaults to "Okay"

 [nTrigger] is the index position of the button's trigger.  Defaults to 1.

 [aColors] is an array of two elements:

    1 - the standard color.  Defaults to "W+/B" and "N/W" in color and BW
        monitors, respectively

    2 - the foreground color of the trigger.  The background is the same as
        the standard color.  Defaults to "R+" and "W+" in color and BW
        monitors, respectively.

 Example
 -------
 #define OKAY_LABEL      "Accept"
 #define OKAY_TRIGGER    1
 #define OKAY_STDCOLOR   "GR+/BR"
 #define OKAY_TRGCOLOR   "G+"

 func main()
 local e := ADrb_create( {|| ADrb_okbutton( OKAY_LABEL,;
                                            OKAY_TRIGGER,;
                                            { OKAY_STDCOLOR, OKAY_TRGCOLOR };
                                          );
                         };
                       )

 ADrb_add( e,2,2, "The" )          // button #1
 ADrb_add( e,4,2, "Quick" )        // button #2
 ADrb_add( e,6,2, "Brown" )        // button #3
 ADrb_add( e,8,2, "Fox" )          // button #4
 cls
 ADrb_show( e )
 ADrb_activate( e, 3 )
 ADrb_kill( e )
 return NIL



 The Action APIs
 
 ADrb_activate( <nEngine>, [xInit] ) --> <xSelection>
 
 Activates a radio buttons engine, <nEngine>.  This API does not display the
 buttons, but merely activates them, that is, pauses the program until
 a button is selected, or until engine is aborted.  Use ADrb_show() to
 display the buttons.

 [xInit] is an optional value designating the options(s) to be initially
 toggled "on".  If the buttons are operating as a checkbox, [xInit] is an
 array of logical values, one element for each button.  A TRUE indicates an
 "on" position.  If the buttons are operating as radiobuttons (the default),
 [xInit] is a numeric value indicating the index position of the button that
 will be initially turned "on".

 <xSelection> is the return value.  If the buttons are operating as
 radiobuttons, it is the index position of the selected button (0 if
 aborted).  If it is operating as a checkbox, it is an array of logical
 values (one for each button, TRUE="on", empty array if aborted).

 Example
 -------
 See ADrb_create()
 

 ADrb_add( <nEngine>, <nRow>, <nCol>, <cLabel>, [nTrigger], [lToggle] )
     --> NIL
 
 Adds a button into the engine.

 <nEngine> is the numeric dentifier of the push button engine.

 <nRow>, <nCol> is the starting position of the added button.

 <cLabel> is the label of the added button

 [nTrigger] is the index position of the trigger key.  It defaults to 1.

 [lToggle] is an optional logical value that may be passed when the buttons
 are configured as a checkbox.  A TRUE designates, that it is turned "on"
 initially.  Note that ADrb_activate() may also be used to designate the
 initial settings of the buttons.  ADrb_activate() takes precedence over
 ADrb_add().

 Example
 -------
 See ADrb_create()

 
 ADrb_kill( <nEngine> ) --> NIL
 
 Kills a radio buttons engine, <nEngine>.  If an engine is not killed after
 exiting, it remains alive in a dormant condition.  A dormant engine may be
 reactivated with:

    ADrb_show( nEngine ); ADrb_activate( nEngine )

 A resurrected engine restores the settings of the toggles at the time the
 engine was last exited.

 Example
 -------
 See ADrb_create()

 
 ADrb_show( <nEngine> ) --> NIL
 
 Displays the buttons in the engine.

 <nEngine> is the numeric engine identifier.

 Example
 -------
 See ADrb_create()
 


 The Info APIs
 
 ADrb_triggers( <nEngine> ) --> <aKeys>
 
 Returns an array of trigger keys. 

 <nEngine> is the numeric engine identifier.

 <aKeys> is the return value.  Each button in the engine is mapped to two
 trigger keys in <aKeys>, an upper case, and a lower case.  Thus the number
 of elements in <aKeys> is twice as many buttons there are in the engine.

 Example
 -------
 func main()
 local e := ADrb_create()
 cls
 ADrb_add( e,1,14, "The" )              // button #1
 ADrb_add( e,2,14, "Quick Brown Fox" )  // button #2
 ADrb_add( e,3,14, "Jumps" )            // button #3
 ADrb_show( e )
 setpos( 10, 0 )
 aeval( ADrb_triggers( e ), {|x| qout( x )} )   // 84  -> "t"
                                                // 116 -> "T"
                                                // 81  -> "q"
                                                // 113 -> "Q"
                                                // 66  -> "b"
                                                // 98  -> "B"
 return NIL


 ADrb_spots( <nEngine> ) --> <aSpots>
 
 Returns an array of the corresponding hot spots of the buttons.

 <nEngine> is the numeric engine identifier.

 <aSpots> is the return value.

 Example
 -------
 func main()
 local e := ADrb_create()
 cls
 ADrb_add( e,1,14, "The" )              // button #1
 ADrb_add( e,2,14, "Quick Brown Fox" )  // button #2
 ADrb_add( e,3,14, "Jumps" )            // button #3
 ADrb_show( e )
 setpos( 10, 0 )
 aeval( ADrb_spots( e ),;
        {|x| aeval( x, {|y| qqout( y, " " )} ), qout("");
        };
      )         // 1 14 1 20
      )         // 2 14 2 32
      )         // 3 14 3 22
 return NIL



 The Horizontal Scroll Bar Collection

 The Engine Function
 
 ADsbx_create( <nRow>, <nCol>, <nLength>, <nTotal>, [bConfig] ) --> <nEngine>
 
 Creates a horizontal scroll bar engine.

 <nRow>, <nCol> are the coordinates of the starting position of the bar.

 <nLength> is the length (number of columns) of the bar.

 <nTotal> is the number of actual items to be represented in the scroll bar.

 [bConfig] is an optional configuration block.  You use it to call the
 engine's Config APIs that reconfigures the engine.

 <nEngine> is the numeric return value.  You use it to send messages to the
 engine.

 Example
 -------
 func main()
 local nTotal := 500
 local nHengine := ADsbx_create( 24, 10, 60, nTotal )
 local xx

 cls
 ADsbx_display( nHengine )

 for xx := 1 to nTotal
    setpos( 12,40 )
    dispout( xx )
    ADsbx_put( nHEngine, ADsbx_tocell( nHEngine, xx ) )
 next

 ADsbx_kill( nHengine )
 return NIL




 The Config APIs
 
 ADsbx_colors( <aColors> ) --> NIL
 
 Configures the colors of the scroll bar.

 <aColors> is an array of two elements:

    1 - the color of the scroll bar.  Defaults to "W+/B" in color monitors
        and "W/N" in monochrome.

    2 - the color of the scroll character.  Defaults to "R/B" in color
        monitors and "W+/N" in monochrome.

 Example
 -------
 func main()
 local nTotal := 500
 local nHengine := ADsbx_create( 24, 10, 60, nTotal,;
                                 {||ADsbx_colors( { "W+/R", "B+"} )};
                               )
 local xx

 cls
 ADsbx_display( nHengine )

 for xx := 1 to nTotal
    setpos( 12,40 )
    dispout( xx )
    ADsbx_put( nHEngine, ADsbx_tocell( nHEngine, xx ) )
 next

 ADsbx_kill( nHengine )
 return NIL



 ADsbx_chars( <aChars> ) --> NIL
 
 Configures the characters to be used by the scroll bar.

 <aChars> is an array of two elements:

    1 - the scroll bar charcater.  Defaults to "" (chr(219)).

    2 - the scroll character.  Defaults to "" (chr(176)).

 Example
 -------
 func main()
 local nTotal := 500
 local nHengine := ADsbx_create( 24, 10, 60, nTotal,;
                                 {||ADsbx_chars( { "", ""} )};
                               )
 local xx

 cls
 ADsbx_display( nHengine )

 for xx := 1 to nTotal
    setpos( 12,40 )
    dispout( xx )
    ADsbx_put( nHEngine, ADsbx_tocell( nHEngine, xx ) )
 next

 ADsbx_kill( nHengine )
 return NIL





 The Action APIs
 
 ADsbx_display( <nEngine>, [nPosition] ) --> NIL
 
 Displays a horizontal scroll bar

 <nEngine> is the engine identifier of the bar to display.

 [nPosition] is the initial position of the scroll charcater.  Defaults to 1,
 meaning the first position.

 Example:  See ADsbx_create()


 ADsbx_put( <nEngine>, <nPosition>, [lForce] ) --> NIL
 
 Moves the scroll char on the bar.

 <nEngine> is the numeric scroll bar engine identifier.

 <nPosition> is the relative row position the scroll character is to be
 moved to.

 [lForce] is an optional logical value.  By default, ADsbx_put() is ignored
 if <nPosition> is the same as the current position,  unless [lForce] is
 TRUE.

 Example:  See ADsbx_create()

 ADsbx_kill( <nEngine> ) --> NIL
 
 Kills a horizontal scroll bar engine, <nEngine>

 Example:  See ADsbx_create()



 The Info APIs
 
 ADsbx_get( <nEngine> ) --> <nPosition>
 
 Returns the current row postion of the scroll char relative to the top of
 the scroll bar.

 <nEngine> is the numeric scroll bar engine identifier.

 <nPosition> is the return value.

 Example
 -------
 func main()
 local nTotal := 500        // 500 items to be represented in a scroll bar
 local nHengine := ADsbx_create( 2, 1, 10, nTotal ) // The scroll bar is
                                                    // 10 rows long.  Its
                                                    // top is at row #2.
 cls
 ADsbx_display( nHengine, 8 )    // display the bar and hilite the 8th cell
 ? ADsbx_get( nHEngine )         // what is the currently hilited cell? = 8
 ? ADsbx_relpos( nHEngine, 5 )   // what is the relative position of the
                                 // absolute row position 5? = 4
 ? ADsbx_tocell( nHEngine, 250 ) // what is the equivalent cell position of
                                 // item #250? = 5 (halfway)
 ? ADsbx_fromcell( nHEngine, 2 ) // what is the item corresponding to cell
                                 // position 2? = 51
 ADsbx_kill( nHengine )
 return NIL

 ADsbx_relpos( <nEngine>, <nRow> ) --> <nPosition>
 
 Returns the equivalent bar position of a row position.

 <nEngine> is the numeric scroll bar engine identifier.

 <nRow> is an absolute screen position whose equivalent bar position is to be
 returned.

 <nPosition> is the return value.

 Example:  See ADsbx_get()

 ADsbx_tocell( <nEngine>, <nItem> ) --> <nPosition>
 
 Returns the equivalent bar position of an item position.

 <nEngine> is the numeric scroll bar engine identifier.

 <nItem> is the position of an item whose equivalent bar position is to be
 determined.  If the scroll bar is that of a database browser, <nItem> might
 be a record number or a key number.  If the browser is an array browser,
 <nItem> is an element number.

 <nPosition> is the return value.

 Example:  See ADsbx_get()

 ADsbx_fromcell( <nEngine>, <nPosition> ) --> <nItem>
 
 Returns the equivalent item position of a bar position.

 <nEngine> is the numeric scroll bar engine identifier.

 <nPosition> is the position of the scroll char whose equivalent item
 position is to be determined.

 <nItem> is the return value.  If the scroll bar is that of a database
 browser, then <nItem> would be the equivalent record or key number.

 Example:  See ADsbx_get()

 ADsbx_coords( <nEngine> ) --> <aCoords>
 
 Returns the coordinates of the scroll bar.

 <nEngine> is the numeric scroll bar engine identifier.

 <aCoords> is the return value.

 Example
 -------
 nHengine := ADsbx_create( 2, 1, 10, 500 )
 ADaview( ADsbx_coords( nHEngine ))    // {2,1,2,10}




 The Vertical Scroll Bar Collection

 The Engine Function
 
 ADsby_create( <nRow>, <nCol>, <nLength>, <nTotal>, [bConfig] ) --> <nEngine>
 
 Creates a vertical scroll bar engine.

 <nRow>, <nCol> are the coordinates of the starting position of the bar.

 <nLength> is the height (number of rows) of the bar.

 <nTotal> is the number of actual items to be represented in the scroll bar.

 [bConfig] is an optional configuration block.  You use it to call the
 engine's Config APIs that reconfigures the engine.

 <nEngine> is the numeric return value.  You use it to send messages to the
 engine.

 Example
 -------
 func main()
 local nTotal := 500
 local nVengine := ADsby_create( 0, 79, 24, nTotal )
 local xx

 cls
 ADsby_display( nVengine )

 for xx := 1 to nTotal
    setpos( 12,40 )
    dispout( xx )
    ADsby_put( nVEngine, ADsby_tocell( nVEngine, xx ) )
 next

 ADsby_kill( nVengine )
 return NIL



 The Config APIs
 
 ADsby_colors( <aColors> ) > NIL
 
 Configures the colors of the scroll bar.

 <aColors> is an array of two elements:

    1 - the color of the scroll bar.  Defaults to "W+/B" in color monitors
        and "W/N" in monochrome.

    2 - the color of the scroll character.  Defaults to "R/B" in color
        monitors and "W+/N" in monochrome.

 Example
 -------
 func main()
 local nTotal := 500
 local nVengine := ADsby_create( 0, 79, 24, nTotal,;
                                 {||ADsby_colors( { "W+/G", "R+"} )};
                               )
 local xx

 cls
 ADsby_display( nVengine )

 for xx := 1 to nTotal
    setpos( 12,40 )
    dispout( xx )
    ADsby_put( nVEngine, ADsby_tocell( nVEngine, xx ) )
 next

 ADsby_kill( nVengine )
 return NIL



 ADsby_chars( <aChars> ) --> NIL
 
 Configures the characters to be used by the scroll bar.

 <aChars> is an array of two elements:

    1 - the scroll bar character.  Defaults to "" (chr(219)).

    2 - the scroll character.  Defaults to "" (chr(176)).

 Example
 -------
 func main()
 local nTotal := 500
 local nVengine := ADsby_create( 0, 79, 24, nTotal,;
                                 {||ADsby_chars( { "", ""} )};
                               )
 local xx

 cls
 ADsby_display( nVengine )

 for xx := 1 to nTotal
    setpos( 12,40 )
    dispout( xx )
    ADsby_put( nVEngine, ADsby_tocell( nVEngine, xx ) )
 next

 ADsby_kill( nVengine )
 return NIL





 The Action APIs
 
 ADsby_display( <nEngine>, [nPosition] ) --> NIL
 
 Displays a vertical scroll bar

 <nEngine> is the engine identifier of the bar to display.

 [nPosition] is the initial position of the scroll character.  Defaults to 1,
 meaning the first position.

 Example:  See ADsby_create()


 ADsby_put( <nEngine>, <nPosition>, [lForce] ) --> NIL
 
 Moves the scroll char on the bar.

 <nEngine> is the numeric scroll bar engine identifier.

 <nPosition> is the relative row position the scroll character is to be
 moved to.

 [lForce] is an optional logical value.  By default, ADsby_put() is ignored
 if <nPosition> is the same as the current position, unless [lForce] is TRUE.

 Example:  ADsby_create()

 ADsby_kill( <nEngine> ) --> NIL
 
 Kills a vertical scroll bar engine, <nEngine>

 Example:  ADsby_create()



 The Info APIs
 
 ADsby_get( <nEngine> ) --> <nPosition>
 
 Returns the current row postion of the scroll char relative to the top of
 the scroll bar.  In other words, it answers the question: what is the
 currently highlighted cell?

 <nEngine> is the numeric scroll bar engine identifier.

 <nPosition> is the return value which is the position of the highlighted
 cell

 Example
 -------
 func main()
 local nTotal := 500        // 500 items to be represented in a scroll bar
 local nVengine := ADsby_create( 2, 1, 10, nTotal ) // The scroll bar is
                                                    // 10 rows long.  Its 
                                                    // top is at row #2.

 cls
 ADsby_display( nVengine, 8 )    // display the bar and hilite the 8th cell
 ? ADsby_get( nVEngine )         // what is the currently hilited cell? = 8
 ? ADsby_relpos( nVEngine, 5 )   // what is the relative position of the
                                 // absolute row position 5? = 4
 ? ADsby_tocell( nVEngine, 250 ) // what is the equivalent cell position of
                                 // item #250? = 5 (halfway)
 ? ADsby_fromcell( nVEngine, 2 ) // what is the item corresponding to cell
                                 // position 2? = 51
 ADsby_kill( nVengine )
 return NIL


 ADsby_relpos( <nEngine>, <nRow> ) --> <nPosition>
 
 Returns the equivalent bar position of a row position.  Suppose you have a
 vertical scroll bar.  Now you click the mouse somewhere within it.  The
 mouse function ADm_row() will return the absolute row position of the mouse
 cursor.  But which cell is that?  ADsby_relpos() will return the cell
 position of the mouse cursor position (returned by ADm_row()).

 <nEngine> is the numeric scroll bar engine identifier.

 <nRow> is an absolute screen position whose equivalent bar position is to be
 returned.

 <nPosition> is the return value.

 Example:  See ADsby_get().

 ADsby_tocell( <nEngine>, <nItem> ) --> <nPosition>
 
 Returns the equivalent bar position of an item position.  Suppose you have
 a scroll bar representing 500 items.  ADsby_tocell() will answer the 
 question:  What is the cell corresponding to 250th item?

 <nEngine> is the numeric scroll bar engine identifier.

 <nItem> is the position of an item whose equivalent bar position is to be
 determined.  If the scroll bar is that of a database browser, <nItem> might
 be a record number or a key number.  If the browser is an array browser,
 <nItem> is an element number.

 <nPosition> is the return value.

 Example:  See ADsby_get().

 ADsby_fromcell( <nEngine>, <nPosition> ) --> <nItem>
 
 Returns the equivalent item position of a bar position.  Suppose you have
 a scroll bar representing 500 items, and cell #2 is currently highlighted.
 ADsby_fromcell() will answer the question:  What is the item corresponding
 to 2nd cell?

 <nEngine> is the numeric scroll bar engine identifier.

 <nPosition> is the position of the scroll char whose equivalent item 
 position is to be determined.

 <nItem> is the return value.  If the scroll bar is that of a database
 browser, then <nItem> would be the equivalent record or key number.

 Example: See ADsby_get()

 ADsby_coords( <nEngine> ) --> <aCoords>
 
 Returns the coordinates of the scroll bar.

 <nEngine> is the numeric scroll bar engine identifier.

 <aCoords> is the return value.

 Example
 -------
 nVengine := ADsby_create( 2, 1, 10, 500 )
 ADaview( ADsby_coords( nVEngine ))    // {2,1,11,1}


                ͸
                    The File Viewer Collection     
                ;

 The Engine Function
 
 ADfview( <xFileMemo>, [bOption], [bConfig], [cHelpId] ) --> NIL
 
 Displays a file in a mouseable window.

 <xFileMemo> is the name of the file or memo field to display.  By deafult,
 ADfview() understands <xFileMemo> as a file name.  If, instaed,  a memo
 field name is passed, it has to be explicitly configured as a memo viewer
 with the API ADfv_memo().

 [bOption] is an optional codeblock that is EVALuated when F10 is pressed or
 when the 'F10:xxxxx' hor spot is clicked.  [bOption] defaults to
 {||ADfprint( <cFile> )}, and the F10 prompt defaults to 'F10:Print'.

 [bConfig] is an optional codeblock.  If passed, ADfview() EVALuates it to
 reconfigure itself.  Typically, [bConfig] is a series of calls to the
 Config APIs.

 [cHelpId] is the Help identifier string.


 Example:
 --------
 ADfview( "frankie.ch" )

 See also the various SAMPLE\FV_DEMO?.PRGs and FV_TOUR.PRG

 
  The APIs
 
 ADfv_coords( <aCoords> ) --> NIL
 
 Reconfigures the coordinates of the ADfview() window. <aCoords> is an array
 of 4 elements:

   1 - top, defaults to 0
   2 - left, defaults to 0
   3 - bottom, defaults to maxrow()
   4 - right, defaults to maxcol()

 Example
 -------
 bConfig := {||ADfv_coords( { 10,10,20,69 } )}
 ADfview( "q.prg",, bConfig )

 ADfv_colors( <aColors> ) --> NIL
 
 Reconfigures the default colors.  <aColors> is an array of 3 elements:

   1 - the standard color.  Defaults to "W+/G" and "W/N" in color and
       monochrome, respectively.
   2 - the header color.  Defaults to "N/GR*" and "N/W" in color and
       monochrome, respectively.  Note that setblink is toggled to FALSE to
       enable the bright yellow background.  It is toggled to TRUE at exit.
   3 - the option prompt color.  Defaults to "R+/GR*" and "N/W" in color and
       monochrome, respectively.

 Example
 -------
 bConfig := {||ADfv_colors( { "R/B", "W+/B", "W+/GR" } )}
 ADfview( "q.prg",, bConfig )

 ADfv_footer( <cFooter> ) --> NIL
 
 Reconfigures the default first 4 footer prompts.

 NOTE: The prompt string must contain all 4 prompts, that is,
 the prompts for F1, ESC, , and .

 Example
 -------
 local bConfig := {|| ADfv_footer( "F1:Saup  " +;
                                   "ESC:Lumual  " +;
                                   ":MuntaLalam  " +;
                                   ":MuntaBabo";
                                 ),;
                      ADfv_colors( {,, "GR+/GR*" } ); // Hides the F10 prompt
                   }
 ADfview( "q.prg", {||NIL}, bConfig )
 return nil


 ADfv_header( <cHeader> ) --> NIL
 
 Reconfigures the default header displayed at the top of the ADfview()
 window.  Defaults to "Viewing <filename>".

 Example
 -------
 local bConfig := {|| ADfv_header( "MyHeader") }
 ADfview( "q.prg", {||NIL}, bConfig )


 ADfv_memo [cHeader] ) --> NIL
 
 Tells ADfview() that it is a memo field, and not a file, that is to be
 viewed.  [cHeader] is the header string that is displayed at the window
 top.  It defaults to "Viewing Memo".

 Example
 -------
 bConfig := {|| ADfv_memo( "Viewing Description Field") }
 ADfview( demo->descriptn,, bConfig )



 ADfv_prompt( <cPrompt> ) --> NIL
 
 Reconfigures the "F10:Print" default options prompt.  Note that only the
 "Print" part is actually configured.  The "F10:" part stays the same.  This
 is because F10 is always the hot key for this special option.

 bConfig := {|| ADfv_prompt( "PrintTheFile") }
 ADfview( "Q.PRG",, bConfig )

 ADfv_linelength( nLength> ) --> NIL
 
 Reconfigures the linelength.  Defaults to the window width.  If the lines
 are longer than the line length, they will wrap to the next line.

 bConfig := {|| ADfv_linelength( 124 }
 ADfview( "Q.PRG",, bConfig )

 ADfv_vermenu( <aMenu>, [aTrigger], [xProcess] ) --> <xSel>
 
 Activates a special pulldown menu when F10 is pressed.  It is designed to
 be used in a codeblock form and passed as [bOption] in ADfview().  The menu
 is pulled down from the top of the ADfview() window.  It will be displayed
 in ADfview's header color.  The parameters <aMenu>, [aTrigger] and [xProcess]
 have the same meaning as the corresponding parameters in ADvermenu().

 NOTE: The F10 prompt may need to be reconfigured.

 Example
 -------
 bConfig := {||ADfv_prompt( "PullDown" )}
 ADfview( "Q.PRG", {||ADfv_vermenu( {"one", "two" } )}, bConfig )



                ͸
                    The Help Facility     
                ;

 ADset_help( <cHelpFile>, [aCoords], [aColors] ) --> NIL
 
 Sets up the Help Facility.

 <cHelpFile> is the name of the Help file without any extension.  .HLP is
 assumed.  The Help index file with the same name but with an extension of
 .HLX will be read.  It is also assumed that the Help file resides in the
 same directory as the application's executable file.

 [aCoords] is an array of screen coordinates that define the Help Window
 boundaries.  Defaults to the whole screen.

 [aColors] is an array of colors of 4 elements:

   1 - The standard color
   2 - The header color, and the standard color of the NextHelp vertical menu
   3 - The enhanced color of the NextHelp vertical menu
   4 - The trigger color of the NextHelp vertical menu

   The default values are:

      Color    Mono
      -----    -----
   1 - "W+/B"   "W/N"
   2 - "R/GR*"  "N/W"
   3 - "GR+/G"  "N/W"
   4 - "B"      "W+"

 Example
 -------
 ADset_help( "f_tour", { 10,10,20,69 } )



 ADhelp_index( <cHelpFile> ) --> NIL
 
 Indexes a Help file named <cHelpFile>.  The passed file name is not given an
 extension.  .HLP is assumed.  An index file with the same name but with an
 extension of .HLX will be created.

 Example
 -------
 ADhelp_index( "f_tour" )


 
 ADhelp( <cHelpID>, [cHeader] ) --> NIL
 
 Displays a Help screen.

 <cHelpID> is the Help identifier string.  There must be a Help Block in the
 Help file that is identified by this string.

 [cHeader] is a string that will be displayed as the title or heading of the
 the Help window.  It defaults to " HELP ".


 NOTES:

 1.  If ADset_help() has not been called yet, ADhelp() calls are ignored.

 2.  If the Help file is missing, ADhelp() calls will display "No Help File".

 3.  If <cHelpID> is NIL, ADhelp() will display "Undefined Help ID".

 4.  If <cHelpID> is not in the Help file, or there is no Help Block for
     <cHelpID>, ADhelp() will display "Undefined Help Text for <cHelpID>".


 Example
 -------
 ADhelp( "SAMPLE HELP ID", "Sample Help Screen Title" )


 The Help File Format

 The Help file is divided into two areas, the Comments Area and the Screens
 Area.

 The Comments Area
 -----------------
 This is the area between-and-including the top and the first occurrrence of
 a tilde (~) character.  The Help Facility disregards anything within this
 area.  It may serve as documentation purposes for the developer.


 The Screens Area
 ----------------
 This is everything from-and-including the first occurrence of the tilde (~)
 character and the bottom of the file.

 It is a series of Help Block.  A help Block contains all the information
 needed by the Help Facility to display the desired Help screen correctly.
 It follows a format of its own.


 The Help Block Format
 ---------------------
 A Help Block is a block of text that starts with:

 1. A tilde (~) charcater, alone on a line.  This signals the start of a Help
 block.

 2. The Help identifier string of this Help Block.  There are no special
 retrictions on this string.  This is the same string that you pass to
 ADhelp() to display the Help screen.  It is also the string indexed by
 ADhelp_index().

 3. A NextHelp Descriptions line.  This is a comma-delimited list of
 descriptions of additonal Help screens callable from the current Help
 screen.  Leave this as a blank line if no other Help screen is to be called
 from the current Help screen.

 4. A NextHelp Options line.  This is a comma-separated list of Help
 identifiers corresponding to the NextHelp Descriptions list.  Leave this
 line as a blank line if the NextHelp Descriptions line is blank.

 5. The Help screen text.  What appears in this section will be displayed as
 is, including any special formatting, line drawing, etc.  This text starts
 from the 5th line of the Help Block and ends to the line just before the
 next tilde (~) character.

 6. A tilde (~) character, alone on a line.  This signals the end of a Help
 block.

 An Example of a Help Block
 --------------------------

 ~
 SAMPLE IDENTIFIER
 Additional Help 1, Additional Help 2, Additional Help 3
 IDENTIFIER_1     , IDENTIFIER_2     , IDENTIFIER_3
 This is a sample Help Screen.
 ~

 A Typical Help File Structure
 -----------------------------
 Comments Area starts from this line up to the line just before the first
 tilde (~).

 This is still part of the comments area.

 Copyright, Angelito Dizon, 1992,1993.  All rights reserved.
 

 This is the last line of the comments area.
 ~
 First Help Block starts here up to the line just before the next tilde (~).





 This is the last line of the first block.
 ~
 2nd Help Block starts here up to the line just before the next tilde (~).





 This is the last line of the 2nd block.
 ~
 3rd Help Block starts here up to the line just before the next tilde (~).





 This is the last line of the 3rd block.
 ~



                ͸
                    Miscellaneous  Functions   
                ;

 Array Functions
 
 ADacombine( <a1>, <a2> ) --> <a1>
 
 Combines two arrays <a1> and <a2> together.  The elements of <a2> are added
 to the end of <a1>.  Thus, <a1> actually grows.

 Example
 -------
 a1 := { 1, 2, 3 }
 a2 := { "a", "b" }
 ADacombine( a1, a2 )   // { 1, 2, 3, "a", "b" }


 ADamaxlen( <a> ) --> <nLength>
 
 Returns the length of the longest element in array <a>.  All elements in <a>
 must be strings.  A runtime error will occur if there is a non-string element.

 Example:
 --------
 ? ADamaxlen( { "1", "23", "456", "78", "9" } )      // 3
 ? ADamaxlen( { "1", 23 } )                          // error

 ADamodify( <a>, <b> ) --> NIL
 
 Modifies the elements in array <a>, according to the logic of the codeblock,
 <b>.  The function walks through the array, EVALuating the block for each
 element, and passing the element for every EVALuation.  The element's value
 changes to the result of the block EVALuation.

 Example:
 --------
 a := { 1,,2,,,3 }
 ADamodify( a, {|x| if( x == NIL, 0, x ) } )
 /* The array becomes { 1,0,2,0,0,3 } */

 ADarotate( <a>, <nth>, <xMode> ) --> NIL
 
 Moves an array element to another location within the array.

 <a> - name of the array to be manipulated.

 <nth> - the index position of the element to be moved.

 <xMode> - mode of movement.  The manner in which the element is moved
 depends on the value of this parameter.  If it a TRUE, the element is moved
 to the last position.  The "passed over" elements are moved up one element
 toward the start of the array.  The opposite takes place if <xMode> is
 FALSE, i.e., the element is moved to the first position.  If <xMode> is
 a numeric, the element is moved to that position.

 Example
 -------
 a := { 1, 2, 3 }
 ADarotate( a, 1, .t. )      // 2,3,1
 ADarotate( a, 2, .f. )      // 3,2,1
 ADarotate( a, 1, 2 )        // 2,3,1


 ADarr2str( <a>, <cDelimiter> ) --> NIL
 
 Converts an array of strings into a delimited string.

 Example
 -------
 a := { "The", "Quick", "Brown" }
 ? ADarr2str( a, "," )          // "The,Quick,Brown"


 ADashrink( <a> ) --> NIL
 
 Shrinks an array by removing trailing NIL elements.

 Example
 -------
 a := { 1,2,3,,, }
 b := { 1,,,,2,3 }
 ADashrink( a )  // 1,2,3
 ADashrink( b )  // 1,,,,2,3


 ADasize( [a], <nSize>, [xValue] ) --> <a>
 
 Resizes an array.  Optionally fills added elements with [xValue].  If
 [xValue] is not passed, it defaults to NIL.  If [a] is not passed, or if it
 is not an array, it is created.  In Frankie 1, [a] was not optional.

 Example:
 --------
 a := { 1, 2, 3 }
 ADasize( a, 5, 0 )
 /* The array becomes { 1, 2, 3, 0, 0 } */

 ADaswap( <a>, <n1>, <n2> ) --> NIL
 
 Swaps the values of two elements, <n1> and <n2> in an array, <a>.

 Example
 -------
 a := { 1,2,3,4,5 }
 ADaswap( a, 2, 4 )      // 1,4,3,2,5


 ADaview( <a> ) --> NIL
 
 Displays the elements of an array <a>, vertically scrolling if necessary.
 The displayed value of an element depends on its type:

 Numeric - converted to a string with str()
 Character - its face value.
 Date - converted to a string with dtoc()
 Logical - converted to either "T" or "F"
 Array - as "ARRAY".  Then if highlight it and press the Tab key, or click it
         with the mouse left button, it gets expanded into a lower-level
         viewer
 Block - "BLOCK"
 Nil - "NIL"

 If there are more elements than can be accommodated on the screen, the
 viewer will scroll vertically with the Up/Down button.  You may also scroll
 the highlighted element horizontally with the Left/Right key, or by holding
 the left mouse button toward the left or right end of the highlighted row.

 To close the window, press the Esc key, or click the right button.

 Example:
 --------
 a := {;
         date(),;
         1,;
         "1",;
         .T.,;
         NIL,;
         {|| NIL},;
         { 1, 2, 3 },;
         "The Frankie Library";
       }
 ADaview( a )

 ADaxmaxlen( <a>, <aPic> ) --> <nMaxLength>
 
 Returns the length of the longest TRANSFORMed element of an array, <a>.  The
 elements of the array may be of any type.

 <aPic> is an array of picture clauses.  These will be used to transform the
 <a> elements into strings before string length determinations are done.
 Thus <a>[1] will be transformed using <aPic>[1], <a>[2] with <aPic>[2], etc.
 If no clause was supplied for any particular element, that element will be
 transformed without the use of a picture.

 <nMaxLength> is the maximum length

 Example
 -------
 a := { 1, "xyz", "abc" }
 aPic := { "9999", "XXX", "@!" }
 ? ADaxmaxlen( a, aPic )     // 4
 aPic := {,,}
 ? ADaxmaxlen( a, aPic )     // 10


 ADstr2arr( <cString>, <cDelimiter> ) --> <a>
 

 Converts a delimited string, <cString> into an array.

 Example:
 --------
 cString := "The Frankie Library for Clipper"
 a := ADstr2arr( cString, " " )  // {;
                                 //        "The",;
                                 //        "Frankie",;
                                 //        "Library",;
                                 //        "for",;
                                 //        "Clipper";
                                 //  }

 a := ADstr2arr( cString, "e" )  // {;
                                 //        "Th",;
                                 //        " Franki",;
                                 //        " Library for Clipp",;
                                 //        "r";
                                 //   }


 Database Functions
 
 ADacolumns( [aFldPos] ) --> <aColumns>
 
 Creates an array of column objects from fields from the current database.
 [nFldPos] is an array of the database positions of the fields for which
 column objects are to be created.  If an element is zero, an empty column
 object is created for it.  If [aFldPos] is not passed, it will default to
 all fields.

 Special NOTE on memo fields
 ---------------------------
 1.  The retrieval block is {|| "< memo >"}

 Special NOTES on "empty columns"
 -------------------------------
 1.  The heading is an empty string.
 2.  The retrieval block is {|| "< calc >"}
 
 Special NOTES on the cargo instance variable:
 ---------------------------------------------
 The cargo instance variable of each column object is filled with an array
 of values useful to the Frankie database browser engine, ADdbview().  The
 array has 6 elements, listed below (NOT in chronological order)

 1 - NIL; reserved for the developer's use
 6 - the field position.  This value is taken form [aFldPos]
 5 - the field name.  If the field position is zero, the field name is an
     empty string.
 2 - field type.  If the field position is zero, the field type is reported
     as "U"
 3 - an alternate retrieval block.  This is specially intended for a memo
     field whose retrieval block is merely {|| "< memo >"}.  This element
     is filled with a block that retrieves the actual field value.  For 
     other data types, this is NIL.
 4 - an array of 4 elements whose values are all NIL.

 NOTE:  Except for element #1, these elements MUST NOT be directly changed.


 ADamemo() --> <aIsMemo>
 
 Checks for memo fields in the current database.  Returns an array of logical
 values: TRUE if the corresponding field is a memo, FALSE if otherwise.

 Example
 -------
 use demo
 aMemo := ADamemo()  // { .f., .f., .f., .t., .f. }


 ADgetrec( [nMemoLen] ) --> <aRec>
 
 Returns the current record as an array, <aRec>

 [nLenMemo] is an optional numeric value. If it is passed, the length of each
 memo field is compared with [nLenMemo].  If the length is less than
 [nLenMemo] the corresponding <aRec> element is padded with spaces so it will
 be [nLenMemo] in length.

 Example
 -------
 use MYDBF
 go bottom
 aRec := ADgetrec() // returns the last record as an array.  Each element
                    // in the array corresponds to a field in the record.
 if ADg_many( aRec )    // edits the record
   ADputrec( aRec )     // stores back the record if it was edited
 endif

 ADputrec( aRec ) --> NIL
 
 Stores an array, <aRec> into the current record.  Note that the number of
 elements in <aRec> must be equal to the number of fields in the database.
 Also, their corresponding data types must be the same.

 Example: See ADgetrec()


 ADzaprec() --> NIL
 
 Blanks the current record.

 Example:
 -------
 use MYDBF  // assume there are 4 fields
 go bottom
 ADzaprec()
 ? empty( fieldget(1) )    // .T.
 ? empty( fieldget(2) )    // .T.
 ? empty( fieldget(3) )    // .T.
 ? empty( fieldget(4) )    // .T.




 File Functions
 
 ADfeof( <nHandle> ) --> <lEOF>
 
 Checks if the file pointer of the file whose handle is <nHandle> is
 at the end of file.

 Example:  See ADfgetl()


 ADfgetl( <nHandle> --> <cLine>
 
 Returns a line from a file whose handle is <nHandle>.  The return value,
 <cLine> is stripped of the CRLF characters.  Returns a "" if it is at
 end of file.

 Example:
 --------
 func main()
 local h := fopen( "OVERVIEW.F" )

 do while !ADfeof( h )
    ? ADfgetl( h )
 enddo
 return nil


 ADfllength( [nLength] ) --> <nOld>
 
 Returns the current value of the line length used by ADfgetl(), and
 optionally resets it to a new value.  The default value is 256.  Increase it
 if you are reading a file with longer lines.

 Example:
 --------
 func main()
 local h := fopen( "OVERVIEW.F" )

 ADfllength( 512 )

 do while !ADfeof( h )
    ? ADfgetl( h )
 enddo
 return nil

 ^ADfprint( <cFile> ) --> NIL
 
 Prints <cFile> to LPT1.  It checks for the readiness of the printer before
 attempting to send data.  But it does not check for the existence of
 <cFile>.

 Example:
 --------
 if file( "myfile.txt" )
   ADfprint( "myfile.txt" )
 endif

 ADtemp_create( [cPath] ) --> <cFileName>
 
 Attempts to create temporary file of 0 byte length in compatibility mode.
 It will be created in [cPath] if it is passed, or in the root directory
 of the current drive if it is not passed.  It always returns <cFileName>,
 the name of the file that was attempted to be created, even when it fails
 to create it.  Use ADtemp_handle() to check if the file was actually created
 or not.

 Example:
 --------
 ? ADtemp_create("d:\temp")   // attempts to create a temp file in d:\temp
 ? ADtemp_handle()            // not -1 if successful


 ADtemp_handle() --> <nHandle>
 
 Returns the handle of the last temporary file created with ADtemp_create().
 Returns a -1 if the last attempt to create was not successful. 
 ADtemp_handle() must be called immediately after ADtemp_create() because its
 value is overwritten every time ADtemp_create() is called.

 Example:  See ADtemp_create()




 Keyboard and Mouse Functions
 
 ADm_activate( <lActive> ) --> <lStatus>
 
 Returns the status of the mouse, and optionally toggles it.


 ADm_awithin( <aRegion>, [mRow], [mCol] ) --> <lWithin>
 
 Checks if the mouse cursor lies within a screen area.

 <aRegion> is an array that describes the area where to check for the mouse
 cursor's presence.  It contains 4 elements representing the coordinates of
 the screen area, i.e., {nTop, nLeft, nBottom, nRight}.

 [mRow], [mCol] are the the row and column positions of the mouse cursor.
 If you do not pass these parameters, the current cursor's position will be
 substituted for them.  You can also first determine where the cursor is,
 then pass its location to the function.

 Examples:
 ---------
 ? ADm_awithin( {10,10,20,20} )

 aMouse := ADm_status()
 ? ADm_awithin( {10,10,20,20}, aMouse[2], aMouse[3] )
 

 ADm_button() --> <nButton>
 
 Returns the pressed button identifier.  The possible return values are:

 0 - no pressed button
 1 - left button
 2 - right button
 3 - both buttons


 ADm_col() --> <nColPosition>
 
 Returns the column postion of the mouse cursor


 ADm_hide() --> NIL
 
 Hides the mouse cursor.  This call is disregarded if the cursor is not
 visible.


 ADm_limits( <nT>, <nL>, <nB>, <nR> ) --> NIL
 
 Limits the movement of the mouse to <nT>, <nL>, <nB>, <nR>.


 ADm_move( <nRow>, <nCol> ) --> NIL
 
 Moves the mouse cursor to <nRow>, <nCol>.


 ADm_pwait() --> NIL
 
 Pauses the program until the mouse is clicked.


 ADm_row() --> <nRowPosition>
 
 Returns the row position of the mouse cursor.


 ADm_rwait() --> NIL
 
 Pauses the program until all the mouse buttons are released.


 ADm_show() --> NIL
 
 Shows the mouse cursor.  This call is disregarded if the cursor is visible.
 

 ADm_status( <aStatus> ) --> <nButton>
 
 Returns the pressed button, just like ADm_button().  But it also fills
 <aStatus> with mouse info:

 1 - pressed button
 2 - mouse cursor row position
 3 - mouse cursor column position

 NOTE: <aStatus> must be passed as an array of three elements.

 Example:
 --------
 func main(p)
 local a[3]

 do while .t.
   ? ADm_status(a)
   ? a[1], a[2], a[3]
 enddo
 return nil




 ADm_within( <nT>, <nL>, <nB>, <nR>, [mRow], [mCol] ) --> <lWithin>
 
 Checks if the screen location [mRow], [mCol] is within the screen area
 bounded by <nT>, <nL>, <nB>, <nR>.  If [mRow] OR [mCol] is not specified,
 the current mouse cursor positions are used.

 Example:
 --------
 /* Pause the program until the mouse cursor is at the top/left corner */
 func main()
 ADm_show()
 do while !ADm_within( 0,0,0,0 ); enddo
 ADm_hide()
 return nil

 ADwait( [nDuration], [bTimeOut] ) --> <aWait>
 
 Pauses program execution and waits for either a key press or mouse click.
 Resumes execution if either of this event happens.  The return value,
 <aWait> is an array of 4 elements:

 1 - the inkey code of the pressed key
 2 - the button identifier of the clicked button
 3 - the row position of the mouse cursor
 4 - the column position of the mouse cursor

 If [nDuration] is passed, the program pauses for up to nDuration] seconds.
 Program execution resumes after this period even if no event occurs.
 ADwait(0), therefore, functionally does not pause the program.  [bTimeOut]
 is a codeblock that gets EVALuated after [nDuration] seconds has elapsed and
 no event occurred.

 Examples:
 ---------
 aWait := ADwait()                      // waits indefinitely
 aWait := ADwait( 3, {||tone(100,1)} )  // beeps after 3 seconds of inactivity

 do while .t.                    // continues polling without pausing
   aWait := ADwait(0)           // program execution
   // other lines
 enddo




 Screen Functions
 
 ADblankscn( [cMsg], [nDuration] ) --> NIL
 
 Blanks the screen and displays a message, [cMsg], until a key is pressed, or
 a mouse button is clicked, at which time, the original screen is restored.
 If the screen is not restored within [nDuration] seconds, the message is
 moved to another location on the screen, and a new cycle is started.  [cMsg]
 defaults to "Press any key", while [nDuration] defaults to 10 seconds.

 Example:
 -------
 func main()
 local cString := "The Frankie Library for Clipper"
 local getlist[0]

 cls
 @10,10 adget cString
 ADread( getlist, {|| ADr_timeout( 3, {||ADblankscn()} )} )
 return nil

 ADbox( <nT>, <nL>, <nB>, <nR>, [cColor], [cFrame], [lShadow], [lExplode] )
      --> <aSavedScreen>
 
 Draws a box on the screen.  Returns an array, <aSavedScreen>, that defines
 the overwritten screen, which may be restored by passing <aSavedScreen> to
 ADrestscn().

 <nT>, <nL>, <nB>, <nR> are the coordinates of the box.

 [cColor] is a color string.  Defaults to the current standard color.

 [cFrame] is the box frame.  Defaults to "͸Գ ".

 [lShadow] is a logical value.  If it is TRUE, a shadow is dropped at the
 bottom/right corner.  Defaults to TRUE.

 [lExplode] is a logical value.  If it is TRUE, it the box explodes when
 drawn.  Defaults to TRUE.

 Example:
 --------
 aScn := ADbox( 10,10,20,69 )
 inkey(0)
 ADrestscn( aScn )


 ADchgcolor( <nT>, <nL>, <nB>, <nR>, <cColor> ) --> <aOverwrittenScreen>
 
 Changes the color of a screen section bounded by <nT>, <nL>, <nB>, <nR> to
 <cColor>.  Before changing the color, it saves the affected screen and
 returns it later as <aOverwrittenScreen> which can be passed to ADrestscn()
 to restore it.

 Example:
 --------
 func main()
 local aScn := ADchgcolor( 10,10,20,69, "R/B" )
 inkey(0)
 ADrestscn( aScn )
 return nil

 ADcls( [cFillChar], [cColor], [nT], [nL], [nB], [nR], [lResetColor] )
      --> <aRetval>
 
 Clears the screen.

 [cFillChar] - a single character to fill the cleared screen with.  Defaults
 to a space.

 [cColor] - color to shade the cleared area with.  Defaults to 
 "W+/BG,GR+/R,nil,nil,GR+/G" and "W/N,N/W,nil,nil,N/W" in color and mono,
 respectively.

 [nT], [nL], [nB], [nR] - boundaries of screen to be cleared.  Default to
 0, 0, maxrow() and maxcol(), respectively.

 [lResetColor] - is a logical value.  If it is TRUE, the default colors will
 be set to [cColor].  Defaults to FALSE.

 <aRetval> - the return value is an array of 2 elements:

 1 - the current color.  It is NIL if [lResetColor] is FALSE.
 2 - the overwritten screen.

 Example:
 --------
 func main()
 local aScn := ADcls( "", "R/W", 10,10,20,69 )[2]
 inkey(0)
 ADrestscn( aScn )
 return nil

 ADcsay( <nRow>, <nCol1>, <nCol2>, <cString>, [cColor] ) --> NIL
 
 Displays a string, <cString> on row <nRow> between two columns, <nCol1>, and
 <nCol2> in shades it in [cColor] if passed.

 Example:
 --------
 ADcsay( maxrow(), 0, maxcol(), "Middle of the Bottom Line" )

 ADdisparray( <aStringColor> ) --> ""
 
 Displays an array of strings in different colors at the current cursor
 location.

 <aStringColor> is an array of subarrays.  Each subarray has 2 elements:

   1 - the string to display
   2 - the color to display it in

 Example
 -------
 @10,10 ADdisparray({;
                        { "Red",             "R/W"      },;
                        { "Blue",            "B/W"      },;
                        { "Yellow",          "GR+/W"    },;
                        { "Green",           "G/W"      },;
                        { "Cyan",            "BG/W"     },;
                        { "Magenta",         "BR/W"     };
                    })



 ADdisplist( [cStringColor..] ) --> ""
 
 Displays a list of strings in different colors at the current cursor
 location.

 [cStringColor..] is a list of strings and color specifiers.  The list must
 be in this format: s1, c1, s2, c2, s3, c3, s4, c4 where the s's are the
 strings and the c's are their corresponding colors.  There can only be a
 maximum of 4 of such pairs.  If you need to have more, use ADdisparray()
 which supports an unlimited number of pairs.

 Example
 -------
 @10,10 say ADdisplist( "Red",             "R/W",;
                        "Blue",            "B/W",;
                        "Yellow",          "GR+/W",;
                        "Green",           "G/W";
                      ) +;
            ADdisplist( "Cyan",            "BG/W",;
                        "Magenta",         "BR/W";
                      )




 ADenhcolor() --> <cEnhancedColor>
 
 Returns the current Clipper enhanced color setting.

 Example
 -------
 ? ADenhcolor()      // "W/N"

 ADhorline( <nRow>, <nCol1>, <nCol2>, [cFrame], [cColor] ) --> NIL
 
 Draws a horizontal line.

 <nRow> is the row position to display the line.

 <nCol1> is the column position where the line starts.

 <nCol2> is the column position where the line ends.

 [cFrame] is an optional string of 3 line characters.  The first and last
 characters will be the first and last characters to be used for the line.
 The second character will be used for anything in between.  Defaults to
 "͵"

 [cColor] is the color to display it in.  Defaults to the current standard
 color setting.

 Example
 -------
 ADhorline( 10, 10, 14 )            // "͵"
 ADhorline( 11, 10, 14, "+-+" )     // "+---+"

 ADiscolor() --> <lColor>
 
 Returns a logical value, <lColor>, indicating the application's color mode.
 This typically is the value of iscolor(), unless "MONO" was passed as a Dos
 command line argument to force a mono display.


 ADlegend( <aPrompt>, [aTrigger], [aAltTrigger], [aColor], [nRow], [nCol1],;
      [nCol2], [nPad] ) --> <aRetval>
 
 Displays a 2-line formatted menu similar to:

 
    AppendBlank  Delete  Recall  Index  reFresh  Order    


 <aPrompt> - an array of prompt strings to be displayed on 2nd line.  In the
 above illustration, <aPrompt> would be { "AppendBlank", "Delete", "Recall",
 "Index", "reFresh",; "Order" }.  NOTE that there must be at least 2 options.

 [aTrigger] - an array of trigger positions.  Defaults to { 1, .., 1 }.  In
 the above illustration, [aTrigger] would be { 1,1,1,1,3,1 }.

 [aAltTrigger] - an array of alternate trigger key names.  Defaults to
 { "".."" }.  These names are displayed on the 1st line on top of their
 respective equivalents.  In the above example, had { "F2", "F3", "F4",
 "F5", "F6" } been passed, then the menu would have been:

 F1F2F3F4F5F6
    AppendBlank  Delete  Recall  Index  reFresh  Order    


 [aColor] - an array of { standard, trigger } colors.

 [nRow] - the row position of the first line,  Default = maxrow() - 1

 [nCol1] - the starting column,  Default = 0

 [nCol2] - the ending column.  Default = maxcol()

 [nPad] - the number of spaces to pad around a prompt string.  Default = 1

 <aRetval> - the return value is an array:

   1 - the overwritten screen.

   2 - an array of hot spots, one spot for each menu option.

   3 - an array of hot keys, two for each menu option.  In the above
       illustration, this array would be:

       {;
          K_a, K_d, K_r, K_i, K_f, K_o,;
          K_A, K_D, K_R, K_I, K_F, K_O;
       }


 Example:
 --------
 aLegend := ADlegend(;
                       { "AppendBlank", "Delete", "Recall", "Pack", "Index" },;
                       {  1,            1,        1,        1,      1       };
                    )

 ADmessage( <aMsg>, [nTop], [nLeft], [lPause], [lAddPrompt], [aColor],;
        cHelpID ) --> <xRetval>
 
 Displays a boxed message on the screen.

 <aMsg> is an array of strings to be displayed.

 [nTop], [nLeft] is the top/left corner of the box.  The box is centered if
 they are not passed.

 [lPause] - is a logical.  If it is TRUE, the program pauses execution until
 a key is pressed, or a mouse button is clicked, at which time the message is
 cleared and the program execution is resumed.  If it is FALSE, the app 
 program continues right after the message is displayed.  Defaults to TRUE.

 [lAddPrompt] - is a logical value.  If it is TRUE, the message is modified
 in accordance to the following rules:

   1.  If [lPause] is TRUE, another line "Press any key..." is added to the
       displayed message.

   2.  If [lPause] is FALSE, three dots, "..." are added to the last line.

 [cColor] - is an array of color strings:

   1 - color of the box and message lines.  Defaults to "GR+/RB" and "W/N"
       in color and mono, respectively.

   2 - color of "Press any key" if it is added.  Defaults to "B+/RB" and 
       "W+/N" in color and mono, respectively.

   3 - color of the "...".  Defaults to "G*/RB" and "W*/N" in color and mono,
       respectively.

 [cHelpID] - is an Help Identifier string used by the Help Facility.

 <xRetval> the return value.  Its type and meaning depend on the value of
 [lPause].  If [lPause] is TRUE, the return value is an array:

   1 - inkey code of the pressed key that resumes program execution

   2 - the mouse button that was clicked (0=None, 1=Left, 2=Right)

   3 - the row position of the mouse cursor

   4 - the column position of the mouse cursor

 If [lPause] is FALSE, the return value is the overwritten screen variable.



 Example:
 --------
 aMsg := { "This is a message" }
 ADmessage( aMsg, 10, 10,,, { "W+/G", "R/G", "GR+/G" } )

 ADmsg_bare( <nRow>, <nCol>, <cMsg>, [cColor], [lPause] ) --> <xRetval>
 
 Displays a bare message, that is, without any box.

 <nRow>, <nCol> are the coordinates of the location where the message is
 displayed.

 <cMsg> is a one-liner message string.

 [cColor] is the color to display the <cMsg> in.   Defaults to the standard
 color.

 [lPause] is a logical that specifies whether or not the program should
 pause after displaying the message, or not.  If it is TRUE (the default),
 the program pauses until a key is pressed, or a mouse button is clicked, at
 which time the overwritten screen is restored and the program resumes.

 <xRetval> is the return value.  If [lPause] is TRUE the return value is an
 array similar to ADwait()'s return value.  If it is FALSE, the return
 value is an array that the defines the overwritten screen.  This value may
 be used by ADrestscn() to restore the screen at some future time.

 Example
 -------
 aScn := ADmsg_bare( 10,10, "WAIT", "B*/R", .f. )
 // more code here
 ADrestscn( aScn )



 ADmsg_pop( xValue1, xValue2,.., xValue14 ) --> NIL
 
 Displays the ADccast'ed values of up to 14 passed values.  Then pauses prog
 execution until a key is pressed, or a button is clicked, at which time the
 display is cleared, and program execution is resumed.

 Example:
 --------
 local xx := date(), yy := 1, zz := "ABC"
 ADmsg_pop( xx, yy, zz )


 ADprintscn( [nT], [nL], [nB], [nR], [cFile], [cMode] ) --> NIL
 
 Prints the screen area bounded by [nT], [nL], [nB], [nR].  If these values
 are not passed, the entire screen is printed.  Output is sent to the
 [cFile].  [cMode] is either an "A" or "C".  If it is "C", then a new file is
 created.  If it is "A", [cFile] is appended if it is already existing, or
 created if it does not exist.  If [cFile] is "PRN", the output is sent
 directly to the printer.  If [cFile] is not passed, output is sent to
 "FRANKIE.SCN".  ADprintscn() does not check for printer-readiness.

 Example:
 --------
 ADprintscn( 10,10,20,69 )



 ADrestcsr( <aCursor> ) --> NIL
 
 Restores the cursor state saved by ADsavecsr().

 ADrestscn( <aScreen> ) --> <aOverwrittenScreen>
 
 Restores a previously saved screen, and at the same time saves the current
 screen.

 ADsavecsr( [nCursor] ) --> <aCursor>
 
 Saves the current status of the cursor, and optionally changes its shape to
 [nCursor] whose valid values are #defined in SETCURSR.CH.  The return value,
 <aCursor> may later be passed to ADrestcsr() to restore the saved state.
 <aCursor> is an array containing this elements:

 1 - the current cursor size
 2 - the current row position
 3 - the current column position

 Example:
 --------
 func main()
 local aCursor

 @ 24,0
 inkey(0)
 aCursor := ADsavecsr(4)
 @ 0,0
 inkey(0)
 ADrestcsr( aCursor )
 return nil

 ADsavescn( <nT>, <nL>, <nB>, <nR> ) --> <aSavedScreen>
 
 Saves the screen section bounded by <nT>, <nL>, <nB>, <nR>, and returns a
 value that may be passed to ADrestscn() to restore the saved screen.
 <aSavedScreen> is an array of 5 elements:

 1 - top
 2 - left
 3 - bottom
 4 - right
 5 - saved screen string

 Example:
 --------
 func main()
 local aScn := ADsavescn( 10,10,20,69 )
 cls
 inkey(0)
 ADrestscn( aScn )
 return nil

 ADsay( <nRow>, <nCol>, <cLine>, [cColor] ) --> NIL
 
 Displays a string, <cLine> at location <nRow>, <nCol> and shades it with
 [cColor].  If [cColor] is not passed, it defaults to the current standard
 color.  This function is functionally the same as:

 setpos( <nRow>, <nCol> ); dispout( <cLine>, [cColor] )

 Example:
 --------
 ADsay( 10,10, "Frankie", "R/W" )

 ADscn_hcenter( <nWidth> ) --> <nStartColumn>
 
 Returns the starting column position of an intention to horizontally center
 something on the screen.


 <nWidth> is the width of the "something" to be centered.

 <nStartRow> is the starting column position of the "something" to be
 centered.

 Example
 -------
 nHeight := 10
 nWidth := 10
 nTop := ADscn_vcenter( nHeight )
 nLeft := ADscn_hcenter( 10 )
 ADbox( nTop, nLeft, nTop + nHeight + 1, nLeft + nWidth + 1 )


 ADscn_vcenter( <nHeight> ) --> <nStartRow>
 
 Returns the starting row position of an intention to vertically center
 something on the screen.

 <nHeight> is the height of the "something" to be centered.

 <nStartRow> is the starting row position of the "something" to be centered.

 Example
 -------
 nHeight := 10
 nWidth := 10
 nTop := ADscn_vcenter( nHeight )
 nLeft := ADscn_hcenter( 10 )
 ADbox( nTop, nLeft, nTop + nHeight + 1, nLeft + nWidth + 1 )



 ADstdcolor() --> <cStandardColor>
 
 Returns the current Clipper standard color setting.

 Example
 -------
 ? ADenhcolor()      // "N/W"

 ADunscolor() --> <cUnselectedColor>
 
 Returns the current Clipper unselected color setting.

 Example
 -------
 ? ADunscolor()      // "W/N"


 String Functions
 
 ADat( <cSubstring>, <cString>, [nthOccur], [nActual] ) --> <nPos>
 
 Returns the position of <cSubString> within <cString>.  If [nthOccur] and
 [nActual] are not passed, then ADat() is similar to AT().  If [nthOccur] is
 passed, then the position of the [nthOcurr] occurrence is returned.  If there
 is no [nthOccur] occurrence, zero is returned.  If [nActual] is passed by
 reference, it is filled with the actual number of occurences from the
 beginning of cString until the [nthOccur] occurrence.

 Example:
 --------
 func main()
 local cString := "The Frankie Library for Clipper"
 local n := 0
 ? ADat( "e", cString )                  // 3
 
 ? ADat( "e", cString, 1 )               // 3
 ? ADat( "e", cString, 2 )               // 11
 ? ADat( "e", cString, 3 )               // 30
 ? ADat( "e", cString, 4 )               // 0,  (No 4th occurrence)
 
 ? ADat( "e", cString, 1, @n ), n        // 3,  1
 ? ADat( "e", cString, 2, @n ), n        // 11, 2
 ? ADat( "e", cString, 3, @n ), n        // 30, 3
 ? ADat( "e", cString, 4, @n ), n        // 0,  3 (No 4th occurrence,
                                         //        Only 3 occurrences)
 return nil
 
 ADccast( <xValue> ) --> <cString>
 
 Casts or converts <xValue> of any value to a character string.  The
 conversion rules are:
 
 Numeric - converted to a string with str()
 Character - its face value.
 Date - converted to a string with dtoc()
 Logical - converted to either "T" or "F"
 Array - "ARRAY"
 Block - "BLOCK"
 Nil - "NIL"
 
 Example:
 --------
 ? ADccast( 1 )          // "         1"
 ? ADccast( .t. )        // "T"
 ? ADccast( date() )     // "01/01/93"
 ? ADccast( {} )         // "ARRAY"
 ? ADccast( {||nil} )    // "BLOCK"
 ? ADccast( NIL )        // "NIL"
 
 ADn2s( n ) --> <cString>
 
 Converts a numeric value to a trimmed string. Equivalent to ALLTRIM( STR(n) ).

 Example:
 --------
 ? str( 1 )          // "          1"
 ? ADn2s( 1 )        // "1"
 
 ADparsel( <cString>, <cDelimiter> ) --> <cExtractedString>
 
 Parses a string, <cString> from the left and returns the extracted string.
 <cDelimiter> is the delimiter that "divides" <cString> into segments.  If
 <cDelimiter> does not occur in <cString>, <cString> is returned.  If <cString>
 is passed by reference, then it gets shortened.


 Example:
 --------
 func main()
 local cString := "The Frankie Library for Clipper"
 ? ADparsel( cString, "b" )      // "The Frankie Li"
 ? cString                       // "The Frankie Library for Clipper"
 ? ADparsel( cString, "a" )      // "The Fr"
 ? ADparsel( cString, "x" )      // "The Frankie Library for Clipper"
 
 ? ADparsel( @cString, "b" )     // "The Frankie Li"
 ? cString                       // "rary for Clipper"
 ? ADparsel( @cString, "a" )     // "r"
 ? cString                       // "ry for Clipper"
 return nil
 
 ADparser( <cString>, <cDelimiter> ) --> <cExtractedString>
 
 Parses a string, <cString> from the right and returns the extracted string.
 <cDelimiter> is the delimiter that "divides" <cString> into segments.  If
 <cDelimiter> does not occur in <cString>, <cString> is returned.  If <cString>
 is passed by reference, then it gets shortened.
 
 
 Example
 -------
 func main()
 local cString := "The Frankie Library for Clipper"
 ? ADparser( cString, "b" )      // "rary for Clipper"
 ? ADparser( cString, "a" )      // "ry for Clipper"
 ? ADparser( cString, "x" )      // "The Frankie Library for Clipper"
 
 ? ADparser( @cString, "b" )     // "rary for Clipper"
 ? cString                       // "The Frankie Li"
 ? ADparser( @cString, "a" )     // "nkie Li"
 ? cString                       // "The Fr"
 return nil

 ADrat( <cSubstring>, <cString>, [nthOccur], [nActual] ) --> <nPos>
 
 Returns the position of <cSubString> within <cString>.  Unlike ADat(), the
 search starts from the right.  If [nthOccur] and [nActual] are not passed,
 then ADrat() is similar to RAT().  If [nthOccur] is passed, then the position
 of the [nthOcurr] occurrence is returned.  If there is no [nthOccur] 
 occurrence, zero is returned.  If [nActual] is passed by reference, it is
 filled with the actual number of occurences from the beginning of cString
 until the [nthOccur] occurrence.

 Example
 -------
 func main()
 local cString := "The Frankie Library for Clipper"
 local n := 0
 ? ADrat( "e", cString )                  // 30

 ? ADrat( "e", cString, 1 )               // 30
 ? ADrat( "e", cString, 2 )               // 11
 ? ADrat( "e", cString, 3 )               // 3
 ? ADrat( "e", cString, 4 )               // 0,  (No 4th occurrence)
 
 ? ADrat( "e", cString, 1, @n ), n        // 30,  1
 ? ADrat( "e", cString, 2, @n ), n        // 11, 2
 ? ADrat( "e", cString, 3, @n ), n        // 3, 3
 ? ADrat( "e", cString, 4, @n ), n        // 0,  3 (No 4th occurrence,
                                         //        Only 3 occurrences)
 return nil
 
 ADstrcnt( <cString>, <cSubstring> ) --> <nOcuurrences>
 
 Returns the number of occurrences of <cSubString> in <cString>.
 
 Example
 -------
 func main()
 local cString := "The Frankie Library for Clipper"
 ? ADstrcnt( cString, "e" )               // 3
 ? ADstrcnt( cString, "e " )              // 2
 ? ADstrcnt( cString, "x" )               // 0
 return nil
 

 System Functions
 
 ADcurdrive() --> <cDrive>
 
 Returns the current drive as "A", "B", "C", etc.

 ADcurpath() --> <cPath>
 
 Returns the current path including the drive and the trailing "\", e.g.,
 "C:\", "C:\CLIPPER\SOURCE\".

 ADdrives( [nMode]) --> <xList>
 
 Returns a list of available drives.  The value of [nMode] determines the form
 of the return value <xList>.  Its valid values are #defined in FRANKIE.CH:

 #define DRIVE_STRING            1    // string of drive letters

    <xList> is a string of drive identifiers, e.g., "ABCD"

 #define DRIVE_LETTER_ONLY       2    // drive letters

    <xList> is an array of drive letters, e.g., { "A", "B", "C", "D" }

 #define DRIVE_LETTER_COLON      3    // drive letters + ":"

    <xList> is an array of drive letters plus colon, e.g.,
           { "A:", "B:", "C:", "D:" }

 #define DRIVE_LETTER_PATH       4    // drive letters + path

    <xList> is an array of drive letters plus path, e.g.,
           { "A:\", "B:\", "C:\CLIPPER5\FRANKIE\", "D:\UTIL\" }
 
 [nMode] defaults to DRIVE_LETTER_COLON

 ADisfloppy( <cDrive> ) --> <lReady>
 
 Returns the ready-status of a floppy drive.  <cDrive> is either "A" or "B".
 If <cDrive> refers to a hard drive or a logical drive, the function always
 returns a FALSE.

 ADisparam( <cString> ) --> <lPassed>
 
 Checks if <cString> was passed as Dos Command Line argument.  Checking is not
 case sensitive.

 Example
 -------
 ADisparam( "/f" )
 ADisparam( "/F" )       // is similar to the first example
 See also ADpname()

 ADnfloppy() --> <nFloppyDrives>
 
 Returns the number of floppy drives attached to the system.

 ADnlogical() --> <nLogicalDrives>
 
 Returns the number of logical drives attached to the system.  This number
 includes floppies, hard drives and virtual drives.

 ADprogpath() --> <cHomeDirectory>
 
 Returns the directory where the running application program was loaded from.

 ADpcount() --> <nCommandLineArgs>
 
 Returns the number of Dos Command Line arguments.

 Example: See ADpname()


 ADpname( <nthArg> ) --> <cArg>
 
 Returns the <nthArg> command line argument.

 Example
 -------
 If you entered the command "MYAPP //INFO /c", the following calls will yield:

 ? ADpcount()            // 2
 ? ADpname(0)            // ""
 ? ADpname(1)            // "//INFO"
 ? ADpname(2)            // "/c"
 ? ADisparam( "/c" )     // .t.
 ? ADisparam( "/C" )     // .t.
 ? ADisparam( "/" )      // .f.
 ? ADisparam( "c" )      // .f.


 ADpposition( <cParam> --> <nParamPos>
 
 Returns the position <nParam> of a Dos Command Line parameter <cParam>.

 Example
 -------
 If you entered the command "MYAPP //INFO /c", the following calls will yield:
 ? ADpposition( "//INFO" )     // 1
 ? ADpposition( "/c" )         // 2





 Miscellaneous Functions
 
 ADdevblock( [bDeveloper] ) --> <bOld>
 
 Returns the current Developer's Block.  Optionally sets it to a new value,
 [bDeveloper].  The Developer's Block is a codeblock that gets EVALuated
 whenever the Developer's key, ALT-0, is pressed during a Frankie engine
 pauses to wait for a user response.  The default block is a do-nothing block.

 Example
 -------
 ADdevblock( {||ADaview( ADtrace() )} )     // displays the call stack
 ADdevblock( {||ADmessage( { ADn2s( memory(0) ) } )} )  // displays
                                                        // avail memory

 ADnextmonth( <d> ) --> <dNextMonth>
 
 Returns the date exactly one month (not 30 days) from <d>.


 ADnotyet( [cMessage] ) --> NIL
 
 Displays a "Not Yet Implemented" message.  If [cMessage] is passed, it will
 be displayed also.

 ADprevmonth( <d> ) --> <dPrevMonth>
 
 Returns the date exactly one month (not 30 days) prior to <d>.


 ADtrace() --> <aStack>
 
 Creates a file called "TRACE.TXT" which contains a list of procedure calls.
 Returns an array consisting of the procedure calls.

 Example
 -------
 ADaview( ADtrace() )    // displays a scrollable array of the call stack
 ADfview( "trace.txt" )  // displays the newly created "trace.txt"
