MODULE ButtonLib
! ========================================================================
!
! PURPOSE: To provide an easy-to-use, system transportable, push button
!          menu facility.
!
! DESCRIPTION:
!
!    This MODULE contains six routines for creating "push button" menus.
!    Any number of buttons can be created and made to appear anywhere on
!    the screen.  They may also contain any desired text.  Menu selection
!    is accomplished by using the mouse, or pressing a user defined key.
!
!    This MODULE does not affect the window scaling in your original,
!    calling program unit.  It accomplishes this by first measuring your
!    original coordinate system before switching to its own, then
!    switching back to yours after completing any of the routines
!    within the MODULE.
!
!
!
!                          LIBRARY ROUTINES
!
!
!                  PUBLIC                  PRIVATE*
!            ------------------       -----------------
!            CreateButton             ShowSelectedButton
!            PollButtons              ButtonGraphics
!            WaitForButton            ScaleButtonWindow
!            RenderButtons            RestoreOriginalWindow 
!            PurgeButtons
!            InitButtonWindow
!                 
!
! *PRIVATE ==> not available to the programmer from outside this MODULE
!
!
! ERRORS:
!
!   NUMBER   ROUTINE                DESCRIPTION
!  -------- ---------          -----------------------
!    900    PollButtons        Buttons not yet created
!           WaitForButton
!           RenderButtons
!           PurgeButtons
!    901    InitButtonWindow   Cannot re-initialize without first 
!                              invoking PurgeButtons
!    902    ScaleButtonWindow  Scaling variables not yet assigned
!    903    CreateButton       Button plus prompt falls outside window
!    904    CreateButton       Button falls outside window
!
!
!
!                         HOW TO USE ROUTINES
!
!
! -------------------------------------------------------------------------
! SUBROUTINE CreateButton(Text$, ControlKey$, Prompt$, Vertical, Horizontal)
! -------------------------------------------------------------------------
!    Creates on screen buttons at the location specified within the
!    active window of the calling program unit.  The width of the button
!    is automatically adjusted to make the Text$ fit inside.  In 
!    addition to being selectable by the mouse, buttons can be made to
!    respond to any single keyboard key, by passing that key's GET KEY
!    string value through the ControlKey$ paramater.  The prompt$ string
!    will cause any supporting text to be displayed to the right of the
!    button.  The Vertical and Horizontal parameters specify the location
!    of lower left corner of the button's containing text, using the same
!    character position numbers that are used in the SET CURSOR statement.
!
!    Here is an example that produces a button which appears in the 
!    center of an 80 column window.  By using other routines in this
!    MODULE, the button can be made to respond to either a mouse click 
!    or to the user pressing the letter A on the keyboard.
!
!       CALL CreateButton("Press ME", "A", "For results", 15, 30)
!
!    Button position correspond to integral character positions.  An error
!    is produced if you try to create buttons too close to the edges
!    of the active window in the calling program unit.
!
!    It is often desirable to make the button text and control key
!    correspond, like this:
!
!       CALL CreateButton("A", "A", "Press A for results", 10, 20)
!
!    Note that the button will respond to either lower or upper case
!    letters.
!
!    To make a button wider than its enclosed text, add extra spaces in
!    text string, either leading or trailing.  The text will always be
!    centered within the button to an accuracy of one pixel:
!
!       CALL CreateButton(" A", "A", "Press A for results", 10, 20)
!
!    To make a button respond to a keyboard function key, use as a 
!    ControlKey$ argument True BASIC's CHR$() function with the key's
!     GET KEY code, like this:
!
!       CALL CreateButton("Press F-1", CHR$(315), "For results", 10, 20)
!
!    GET KEY codes are listed in your True BASIC User's Guide.
!
!    If you don't want a control key for your button, pass the NULL string
!    as a ControlKey$ argument, like this:
!
!       CALL CreateButton("Click on ME", "", "For results", 10, 20)
!
!    If you don't want a prompt string for your button, pass the NULL 
!    string as a Prompt$ argument, like this:
!
!       CALL CreateButton("Click on ME", "", "", 10, 20)
!
!    To create blank buttons of any width, pass a string containing the
!    desired number of spaces:
!
!       CALL CreateButton("  ", "", "Press me for results", 10, 20)
!
!
!    The CreateButton SUBROUTINE automatically assigns a decimal sequence
!    number to each button you create according the order you create it.
!
!      CALL CreateButton("  ", "", "Choice 1", 10, 20) ! Assigned number 1
!      CALL CreateButton("  ", "", "Choice 2", 10, 20) ! Assigned number 2
!      CALL CreateButton("  ", "", "Choice 3", 10, 20) ! Assigned number 3
!
!    You may create as many buttons as you like.  Information about each
!    button is saved in dynamic stacks within this MODULE.  Dynamic stacks
!    are easy to implement in True BASIC by using the MAT REDIM statement.
!
!    Note that although this SUBROUTINE automatically renders your buttons
!    on the screen, the next time you want them displayed you must call
!    the RenderButtons SUBROUTINE.  Do not try to create the same
!    buttons twice, since that would simply add more button definitions
!    to those already in memory.  You can also render both buttons, and
!    any supporting text, yourself, by using BOX KEEP and BOX SHOW
!    instructions, within your project.  This is expecially useful when
!    your project uses a high number of buttons.  See Example_Three on the
!    ButtonLib disk for and demonstration of this.  You can have your
!    buttons rendered using any color register permitted on your platform
!    by using a SET COLOR instruction before calling the CreateButtons
!    SUBROUTINE.  You can also conceal the rendering process by first
!    setting the color register to the background, again if your platform
!    permits this, and then re-setting it after the CreateButtons
!    SUBROUTINE has completed the rendering process.  This technique is
!    demonstrated in Example_Three on the ButtonLib disk.
!
!    You may cancel button definitions at any time by using the
!    PurgeButtons SUBROUTINE of this MODULE.  You are then free to
!    create a new set of buttons.
!
!    Exceptions:
!
!    903    Button plus prompt falls outside window
!    904    Button falls outside window
!
!
!
! -------------------------------------------------------------------------
! FUNCTION PollButtons
! -------------------------------------------------------------------------
!    Verifies if the user has selected a button.  This FUNCTION returns
!    the decimal sequence number of whatever button the user has selected,
!    or a zero if none has been selected.  This is a polling function,
!    which means that it checks the condition of the buttons once and then
!    returns to your calling program unit.  Polling is used when you
!    want to do other things within your calling program unit while
!    simultaneously verifying whether or not the user selects a button.
!    Polling functions are normally used by placing them in a LOOP within
!    your calling program unit, like this:
!
!       DO
!
!          LET Button_Number = PollButtons
!
!          CALL Another_Routine
!
!       LOOP UNTIL Button_Number <> 0
!
!    Program execution will LOOP between checking the menu buttons and,
!    in this case, Another_Routine.  See Example_One on the library disk
!    for an example of polling a button menu.
!
!    The user's response is usually determined by using a multiple
!    direction switch, or SELECT CASE, like this:
!
!       SELECT CASE Button_Number
!
!          CASE 1
!             CALL Routine_One
!          CASE 2
!             CALL Routine_Two
!
!                 ...
!
!          CASE ELSE
!
!       END SELECT
!
!    One of your responsibilities when designing polling loops is to
!    properly clear the keyboard and mouse buffers of the computer at the
!    appropiate times.  This is done in Example_One by calling the
!    ClearBuffers SUBROUTINE, designed as part of that project.  Try
!    removing the CALL to ClearBuffers and then enter multiple mouse
!    clicks or key presses.  You will see that those extra entries can
!    sometimes interfere with the normal operation of a program.  The
!    exact placement of the CALL to the ClearBuffers SUBROUTINE within
!    your program may depend on your particular design.  Also, always
!    design your ClearBuffers SUBROUTINE as external (after the END
!    statement) so that its internal variables do not interfere with
!    the normal operation of your program.
!
!    Notice the PAUSE instruction in the Example_One program, which delays
!    processing of the user's selection of a menu button for a short period
!    of time.  This allows the user to see the graphics of the selected
!    button.  This is pure cosmetics and should be removed if you desire
!    faster button response.
!
!    Note that it is your responsibility to make sure that the button menu
!    is rendered on the screen before calling this FUNCTION.  Buttons are
!    rendered automatically when they are created, or by invoking the
!    RenderButtons SUBROUTINE after they have been created.  Do not try to
!    create the same buttons twice, since that will simply add more button
!    definitions to those already in memory.  You can also render your
!    buttons, along with any supporting text, by using BOX KEEP and BOX
!    SHOW instructions within your project.  See Example_Three on the
!    ButtonLib disk for a demonstration of this.
!
!    An error is generated if you invoke this FUNCTION without first
!    creating some buttons.
!
!    Exceptions: 900    Buttons not yet created.
!
!
!
! -------------------------------------------------------------------------
! FUNCTION WaitForButton
! -------------------------------------------------------------------------
!    Waits for the user to respond to a button menu.  This FUNCTION
!    returns the decimal sequence number of whatever button was selected.
!    This is a waiting function, which means that execution does not return
!    to the calling program unit until the user actually selects a button.
!
!    An error is produced if you call this SUBROUTINE without first
!    creating some buttons.
!
!    Note that it is your responsibility to make sure that the button menu
!    is rendered on the screen before calling this FUNCTION.  Buttons are
!    rendered automatically when they are created, or by invoking the
!    RenderButtons SUBROUTINE after they have been created.  Do not try to
!    create the same buttons twice, since that will simply add more button
!    definitions to those already in memory.  You can also render your
!    buttons, along with any supporting text, by using BOX KEEP and BOX
!    SHOW instructions within your project.  See Example_Three on the
!    ButtonLib disk for a demonstration of this.
!
!    Exceptions: 900    Buttons not yet created.
!
!
!
! -------------------------------------------------------------------------
! SUBROUTINE RenderButtons
! -------------------------------------------------------------------------
!    Renders previously created buttons.  This SUBROUTINE uses the 
!    faster executing BOX SHOW statement.  It retrieves whatever button
!    information it needs from the dynamic button stacks.
!
!    An error is produced if you call this SUBROUTINE without first
!    creating some buttons.
!
!    Exceptions: 900    Buttons not yet created.
!
!
!
! -------------------------------------------------------------------------
! SUBROUTINE PurgeButtons
! -------------------------------------------------------------------------
!    Removes all button definitions.  The original coordinate system of
!    the calling program unit is restored.  All SHARED variables of this
!    MODULE are cleared.  Use this SUBROUTINE before you create a new
!    set of buttons.
!
!
!
! -------------------------------------------------------------------------
! SUBROUTINE InitButtonWindow
! -------------------------------------------------------------------------
!    Scales button window so that Cartesian coordinates used in PLOT TEXT
!    match the character position numbers used in SET CURSOR and PRINT.
!    This routine also executes a:
!
!          SET TEXT JUSTIFY "LEFT", "BASE"
!
!    This SUBROUTINE is invoked automatically by the CreateButton
!    SUBROUTINE of this MODULE, so you don't really have to use it
!    yourself.  However, you may want to use it, to make the window
!    scaling within your calling program unit such that PLOT TEXT
!    instructions place text at exactly the same screen positions as
!    SET CURSOR using the same numbers.  Remember that the positions
!    of the horizontal and vertical position numbers in PLOT TEXT are
!    reversed compared to SET CURSOR.  See your True BASIC's reference
!    manual.
!
!    If you want to invoke this SUBROUTINE you must do so before calling
!    CreateButtons.  If you try to CALL this SUBROUTINE after having
!    created some buttons, an error occurs.  This is a feature designed to
!    protect you from losing the original scaling within your calling
!    program unit.  If you have already created some buttons, and you want
!    to call this SUBROUTINE, you must first purge the buttons by calling
!    the PurgeButtons SUBROUTINE from your calling program unit.
!
!    Exceptions: 
!
!       901    Cannot re-initialize without first invoking PurgeButtons.
!  
!
!  
! ========================================================================
! True BASIC, Inc
! 12 Commerce Avenue
! West Lebanon, New Hampshire 03784-9758
! (800) 872-2742,  (603) 298-8517
! ========================================================================

! ****************************
! PRIVATE routine declarations
! ****************************
PRIVATE ButtonGraphics         ! Render button outline and containing text
PRIVATE ShowSelectedButton     ! Render highlighted graphics
PRIVATE ScaleButtonWindow      ! Used to scale window in this MODULE
PRIVATE RestoreOriginalWindow  ! Return to scaling of calling program unit

! ****************************
! SHARED variable declarations
! ****************************
SHARE HOR(0)              ! Dynamic stack to store horizontal positions
SHARE VER(0)              ! Dynamic stack to store vertical positions
SHARE WIDTH(0)            ! Dynamic stack to store width postions
SHARE CONTROL$(0)         ! Dynamic stack to store controlling key
SHARE PROMPT$(0)          ! Dynamic stack to store prompt string
SHARE SNAPSHOT$(0)        ! Dynamic stack to store graphic data
SHARE SELECT_SNAPSHOT$(0) ! Dynamic stack to store selected graphic data
SHARE Number_of_Buttons   ! Keeps track of how many buttons are defined

SHARE XLeft        ! Original window scaling of calling program unit
SHARE XRight       ! Original window scaling of calling program unit
SHARE YBottom      ! Original window scaling of calling program unit
SHARE YTop         ! Original window scaling of calling program unit
SHARE Horiz$       ! Original window text mode
SHARE Vert$        ! Original window text mode
SHARE XMIN         ! Button window scaling, left edge
SHARE XMAX         ! Button window scaling, right edge
SHARE YMIN         ! Button window scaling, bottom edge
SHARE YMAX         ! Button window scaling, top edge
SHARE InitFlag     ! Reports if button window has been properly initialized

! *******************************
! SHARED variable initializations
! *******************************
LET InitFlag = 0


                   ! **********************************
                   ! ***   PUBLIC routines follow   ***
                   ! **********************************


! =========================================================================
! SUBROUTINE: CreateButton
! =========================================================================
!
! PURPOSE: 
!
!    Creates a new button at the location specified and containing a
!    specified text string.  The Width of the button is automatically
!    adjusted to make the text string fit.  The location of the button
!    corresponds to the lower left corner of the containing text string,
!    using the same position number system that is used in the SET CURSOR
!    statement. Each button can be linked to a keyboard key by passing
!    that key's character value as a string.  A prompt string can be
!    rendered to the right of each button.
!
!
! SCOPE: PUBLIC
!
!
! PARAMETERS:
!
!    INPUT:
!
!       A$ ....... Text string to be rendered inside the button
!       C$ ....... Corresponding keyboard key
!       P$ ....... Text string to be rendered outside the button
!       Y ........ Vertical character position measured in character rows
!                  from the top edge of active window in calling program
!                  unit.
!       X ........ Horizontal character position measured in character
!                  columns from left edge of active window in calling
!                  program unit.
!       
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       InitFlag ... Signals if window has been previously scaled.
!                    This flag is set in the InitButtonWindow SUBROUTINE.
!
!    OUTPUT:
!
!       The following variables are assigned values in this SUBROUTINE
!       and then used by other program units within this MODULE
!
!       Number_of_Buttons       ! Keeps track of number of buttons
!       HOR()                   ! Horizontal position array
!       VER()                   ! Vertical position array
!       WIDTH()                 ! Width array
!       SNAPSHOT$()             ! Graphic data array (BOX KEEP format)
!       SELECT_SNAPSHOT$        ! Selected graphic data array
!       CONTROL$()              ! Controlling key array
!
!
! ROUTINES CALLED:
!
!    InitButtonWindow
!    RestoreOriginalWindow
!    ButtonGraphics
!
!
! EXCEPTIONS:
!
!    903    Button plus prompt falls outside window
!    904    Button falls outside window
!
!
! OVERVIEW:
!
!    Information related to the new button is stored in dynamically
!    allocated stacks: HOR, VER, WIDTH, and SNAPSHOT$.  Note that the
!    MAT REDIM statement allows dynamic control of memory allocated to
!    an array, and is easily used to create a dynamic stack.
!
! =========================================================================
SUB CreateButton(A$, C$, P$, Y, X)

   ! ***************************************
   ! Make sure position is at integral value
   ! ***************************************
   LET X = INT(X)
   LET Y = INT(Y)

   ! ******************************************************************
   ! If this is the first button, the button window must be initialized
   ! ******************************************************************
   IF InitFlag = 0 OR Number_of_Buttons = 0 THEN
      CALL InitButtonWindow
   ELSE
      CALL ScaleButtonWindow
   END IF

   ! *******************************************************
   ! Test if button plus prompt string fits in button window
   ! *******************************************************
   IF X < (ROUND(XMIN) + 1) THEN                   ! Left edge of window
      CAUSE ERROR 904, "Button falls outside window."
   END IF
   IF P$ = "" THEN                                 ! NULL prompt string
      IF X + LEN(A$) > (ROUND(XMAX) - 1) THEN      ! Right edge of window
         CAUSE ERROR 904, "Button falls outside window."
      END IF
   ELSEIF (X + LEN(A$) + LEN(P$) + 4) > (ROUND(XMAX) - 1) THEN
      CAUSE ERROR 903, "Button plus prompt falls outside window."
   END IF

   IF Y < (ROUND(YMAX) + 2) THEN                    ! Top of window
      CAUSE ERROR 904, "Button falls outside window."
   END IF

   IF Y > ROUND(YMIN) - 1 THEN                       ! Bottom of window
      CAUSE ERROR 904, "Button falls outside window."
   END IF

   ! ************************************
   ! Update the number of defined buttons
   ! ************************************
   LET Number_of_Buttons = SIZE(HOR) + 1

   ! *******************************
   ! Increase size of dynamic stacks
   ! *******************************
   MAT REDIM HOR(Number_of_Buttons)
   MAT REDIM VER(Number_of_Buttons)
   MAT REDIM WIDTH(Number_of_Buttons)
   MAT REDIM SNAPSHOT$(Number_of_Buttons)
   MAT REDIM SELECT_SNAPSHOT$(Number_of_Buttons)
   MAT REDIM CONTROL$(Number_of_Buttons)
   MAT REDIM PROMPT$(Number_of_Buttons)

   ! **************************************
   ! Store button information on the stacks
   ! **************************************
   LET HOR(Number_of_Buttons)   = X
   LET VER(Number_of_Buttons)   = Y
   LET WIDTH(Number_of_Buttons) = LEN(A$)
   LET CONTROL$(Number_of_Buttons) = C$
   LET PROMPT$(Number_of_Buttons) = P$

   ! *******************************************
   ! Render button and prompt at desired postion
   ! *******************************************
   CALL ButtonGraphics(A$, X, Y)
   IF PROMPT$(Number_of_Buttons) <> "" THEN
      SET CURSOR Y, X + WIDTH(Number_of_Buttons) + 5
      PRINT PROMPT$(Number_of_Buttons);
   END IF

   ! *******************************************************
   ! Take snapshot of button to speed up rendering next time
   ! *******************************************************
   LET XL = X - 1.125
   LET XR = X + LEN(A$) + 1
   LET YB = Y + 0.5
   LET YT = Y - 1.25
   BOX KEEP XL, XR, YB, YT IN SNAPSHOT$(Number_of_Buttons)

   ! ***************************************************************
   ! Restore original window characteristics in calling program unit
   ! ***************************************************************
   CALL RestoreOriginalWindow


END SUB  ! End of CreateButton




! =========================================================================
! FUNCTION: PollButtons
! =========================================================================
!
! PURPOSE: 
!
!    Reports if user selects a button with the mouse, or presses a
!    controlling key.  This FUNCTION returns the button number if 
!    the user selects a button, a zero if otherwise.   This FUNCTION
!    polls the buttons once.  That is, it checks the condition of the
!    buttons and returns.  This allows you to incorporate other features
!    in your menu within you calling program unit.
!
!
! SCOPE: PUBLIC
!
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       The following variables are assigned values in other program
!       units within this module and then used within this FUNCTION
!
!       Number_of_Buttons    ! Number of buttons
!       HOR                  ! Horizontal position array
!       VER                  ! Vertical position array
!       WIDTH                ! Width array
!       CONTROL$             ! Controlling key
!
!
! RETURNED VALUE:
!
!    Number of button corresponding to the sequence in which it was
!    originally created by the CreateButton SUBROUTINE.
!
! 
! ROUTINES CALLED:
!
!    ScaleButtonWindow
!    RestoreOriginalWindow
!    ShowSelectedButton
!
!
! EXCEPTIONS:
!
!    900   Buttons not yet created.
!
!
! OVERVIEW:
!
!    This function searches the dynamic button stacks for a position or
!    control key match.  An error is generated if you have not previously
!    created any buttons.
!
! =========================================================================
FUNCTION PollButtons


   ! **************************************
   ! Check if buttons were properly created
   ! **************************************
   IF Number_of_Buttons = 0 THEN
      LET TestButtons = 0
      CAUSE ERROR 900, "Buttons not yet created."
   END IF

   ! ***************************************
   ! Scale button window for mouse selection
   ! ***************************************
   CALL ScaleButtonWindow

   ! ********************************************
   ! Start by assuming that no button is selected
   ! ********************************************
   LET SelectedButton = 0

   ! ****************************
   ! Read and interpret the mouse
   ! ****************************
   GET MOUSE XMouse, YMouse, StateMouse

   IF StateMouse <> 0 THEN

      ! ****************************
      ! Check all button definitions
      ! ****************************
      FOR I = 1 TO Number_of_Buttons
   
         ! ***************************************
         ! Check horizontal postion of mouse click
         ! ***************************************
         IF XMouse > (HOR(I)-.75) AND XMouse < (HOR(I)+WIDTH(I)+.75) THEN

            ! **************************************
            ! Check vertical position of mouse click
            ! **************************************
            IF YMouse < (VER(I)+.375) AND YMouse > (VER(I)-1.125) THEN

               ! ***********************
               ! User selected button #I
               ! ***********************
               LET SelectedButton = I

               ! *****************************
               ! Show selected button graphics
               ! *****************************
               CALL ShowSelectedButton(I)

               ! *******************************************
               ! Leave FOR LOOP because you found the button
               ! *******************************************
               EXIT FOR

            END IF  ! End of IF YMouse < VER(I) AND YMouse > VER(I) -1

         END IF  ! End of IF XMouse > HOR(I) AND XMouse < HOR(I) + WIDTH(I)
   
      NEXT I  ! End of FOR I = 1 TO Number_of_Buttons

      ! **********************************
      ! Clear mouse buffer of extra clicks
      ! **********************************
      DO     
         GET MOUSE XMouse, YMouse, StateMouse
      LOOP UNTIL StateMouse = 0

   END IF  ! End of IF StateMouse <> 0

   ! ******************************
   ! Leave if a button was selected
   ! ******************************
   IF SelectedButton <> 0 THEN

      ! ***************************************************************
      ! Restore original window characteristics in calling program unit
      ! ***************************************************************
      CALL RestoreOriginalWindow

      LET PollButtons = SelectedButton
      EXIT FUNCTION

   END IF

   ! *******************************
   ! Read and interpret the keyboard
   ! *******************************
   IF KEY INPUT THEN

      GET KEY K

      ! ****************************
      ! Check all button definitions
      ! ****************************
      FOR I = 1 TO Number_of_Buttons

         ! *************************
         ! Check key pressed by user
         ! *************************
         IF UCASE$(CHR$(K)) = CONTROL$(I) THEN

            ! ***********************
            ! User selected button #I
            ! ***********************
            LET SelectedButton = I

            ! *****************************
            ! Show selected button graphics
            ! *****************************
            CALL ShowSelectedButton(I)

            ! *******************************************
            ! Leave FOR LOOP because you found the button
            ! *******************************************
            EXIT FOR

         END IF  ! End of IF UCASE$(CHR$(K)) = CONTROL$(I)

      NEXT I  ! End of I = 1 TO Number_of_Buttons

      ! *******************************
      ! Clear keyboard of extra presses
      ! *******************************
      DO WHILE KEY INPUT
         GET KEY K
      LOOP

   END IF  ! End of IF KEY INPUT

   ! ***************************************************************
   ! Restore original window characteristics in calling program unit
   ! ***************************************************************
   CALL RestoreOriginalWindow

   ! *******************************************************
   ! Return value of selected button to calling program unit
   ! *******************************************************
   LET PollButtons = SelectedButton


END FUNCTION  ! End of TestButtons




! =========================================================================
! FUNCTION: WaitForButton
! =========================================================================
!
! PURPOSE: 
!
!    Reports if user selects a button with the mouse, or presses a
!    controlling key.  This FUNCTION returns the button number when 
!    the user selects a button.   This FUNCTION waits for the user
!    to press a button.  It does not return to the calling program
!    unit until the user selects a button.
!
!
! SCOPE: PUBLIC
!
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       The following variables are assigned values in other program
!       units within this MODULE and then used within this FUNCTION
!
!       Number_of_Buttons    ! Number of buttons
!       HOR                  ! Horizontal position array
!       VER                  ! Vertical position array
!       WIDTH                ! Width array
!       CONTROL$             ! Controlling key
!
!
! RETURNED VALUE:
!
!    Number of button corresponding to the sequence in which it was
!    originally created by invoking the CreateButton SUBROUTINE from
!    the calling program unit.
!
! 
! ROUTINES CALLED:
!
!    ScaleButtonWindow
!    RestoreOriginalWindow
!    ShowSelectedButton
!
!
! EXCEPTIONS:
!
!    900   Buttons not yet created.
!
!
! OVERVIEW:
!
!    This function searches the dynamic button stacks for a position or
!    control key match.  An error is generated if you have not previously
!    created any buttons.
!
! =========================================================================
FUNCTION WaitForButton


   ! **************************************
   ! Check of buttons were properly created
   ! **************************************
   IF Number_of_Buttons = 0 THEN
      LET TestButtons = 0
      CAUSE ERROR 900, "Buttons not yet created."
   END IF

   ! ***********************************
   ! Clear keyboard of premature presses
   ! ***********************************
   DO WHILE KEY INPUT
      GET KEY K
   LOOP
   
   ! **************************************
   ! Clear mouse buffer of premature clicks
   ! **************************************
   DO     
      GET MOUSE XMouse, YMouse, StateMouse
   LOOP UNTIL StateMouse = 0
   
   ! ***************************************
   ! Scale button window for mouse selection
   ! ***************************************
   CALL ScaleButtonWindow

   ! ********************************************
   ! Start by assuming that no button is selected
   ! ********************************************
   LET SelectedButton = 0

   ! **************************************
   ! LOOP waits until user selects a button
   ! **************************************
   DO

      ! ****************************
      ! Read and interpret the mouse
      ! ****************************
      GET MOUSE XMouse, YMouse, StateMouse
   
      IF StateMouse <> 0 THEN
   
         ! ****************************
         ! Check all button definitions
         ! ****************************
         FOR I = 1 TO Number_of_Buttons
      
         ! ***************************************
         ! Check horizontal postion of mouse click
         ! ***************************************
         IF XMouse > (HOR(I)-.75) AND XMouse < (HOR(I)+WIDTH(I)+.75) THEN

            ! **************************************
            ! Check vertical position of mouse click
            ! **************************************
            IF YMouse < (VER(I)+.375) AND YMouse > (VER(I)-1.125) THEN

                  ! ***********************
                  ! User selected button #I
                  ! ***********************
                  LET SelectedButton = I
   
                  ! *****************************
                  ! Show selected button graphics
                  ! *****************************
                  CALL ShowSelectedButton(I)
   
                  ! *******************************************
                  ! Leave FOR LOOP because you found the button
                  ! *******************************************
                  EXIT FOR
   
               END IF  ! End of IF YMouse < VER(I) AND YMouse > VER(I) -1
   
            END IF  ! End of IF XMouse > HOR(I) AND XMouse < HOR(I) + WIDTH(I)
      
         NEXT I  ! End of FOR I = 1 TO Number_of_Buttons
   
         ! **********************************
         ! Clear mouse buffer of extra clicks
         ! **********************************
         DO     
            GET MOUSE XMouse, YMouse, StateMouse
         LOOP UNTIL StateMouse = 0
   
      END IF  ! End of IF StateMouse <> 0

      ! **********************************************
      ! Leave the waiting loop if you found the button
      ! **********************************************
      IF SelectedButton <> 0 THEN
         EXIT DO
      END IF
   
      ! *******************************
      ! Read and interpret the keyboard
      ! *******************************
      IF KEY INPUT THEN
   
         GET KEY K
   
         ! ****************************
         ! Check all button definitions
         ! ****************************
         FOR I = 1 TO Number_of_Buttons
   
            ! *************************
            ! Check key pressed by user
            ! *************************
            IF UCASE$(CHR$(K)) = CONTROL$(I) THEN
   
               ! ***********************
               ! User selected button #I
               ! ***********************
               LET SelectedButton = I
   
               ! *****************************
               ! Show selected button graphics
               ! *****************************
               CALL ShowSelectedButton(I)
   
               ! *******************************************
               ! Leave FOR LOOP because you found the button
               ! *******************************************
               EXIT FOR
   
            END IF  ! End of IF UCASE$(CHR$(K)) = CONTROL$(I)
   
         NEXT I  ! End of FOR I = 1 TO Number_of_Buttons
   
         ! *******************************
         ! Clear keyboard of extra presses
         ! *******************************
         DO WHILE KEY INPUT
            GET KEY K
         LOOP
   
      END IF  ! End of IF KEY INPUT
   
   LOOP UNTIL SelectedButton <> 0

   ! ***************************************************************
   ! Restore original window characteristics in calling program unit
   ! ***************************************************************
   CALL RestoreOriginalWindow

   ! *******************************************************
   ! Return value of selected button to calling program unit
   ! *******************************************************
   LET WaitForButton = SelectedButton


END FUNCTION  ! End of WaitForButton




! =========================================================================
! SUBROUTINE: RenderButtons
! =========================================================================
!
! PURPOSE: 
!
!    Renders previously created buttons using faster executing
!    BOX SHOW format.
!
!
! SCOPE: PUBLIC
!
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       The following variables are assigned values in other program 
!       units within this MODULE and then used by this SUBROUTINE
!
!       Number_of_Buttons    ! Keeps track of number of buttons
!       HOR                  ! Horizontal position array
!       VER                  ! Vertical position array
!       SNAPSHOT$            ! Graphic data array (BOX KEEP format)
!
!
!
! EXCEPTIONS:
!
!    900   Buttons not yet created.
!
!
! ROUTINES CALLED:
!
!    ScaleButtonWindow
!    RestoreOriginalWindow
!
!
! OVERVIEW:
!
!    BOX SHOW graphic data is retreived from dynamic button stacks.  An
!    error is generated if you have not previously generated any buttons.
!
! =========================================================================
SUB RenderButtons


   ! *************************************************
   ! Make sure some buttons have been properly created
   ! *************************************************
   IF Number_of_Buttons > 0 THEN

      ! *************************************
      ! Scale button window for a mouse click
      ! *************************************
      CALL ScaleButtonWindow
   
      ! ******************
      ! Render all buttons
      ! ******************
      FOR I = 1 TO Number_of_Buttons
         BOX SHOW SNAPSHOT$(I) AT HOR(I)-1.125, VER(I)+.5
         IF PROMPT$(I) <> "" THEN
            SET CURSOR VER(I), HOR(I) + WIDTH(I) + 5
            PRINT PROMPT$(I);
         END IF
      NEXT I
   
      ! ***************************************
      ! Restore scaling of calling program unit
      ! ***************************************
      CALL RestoreOriginalWindow

   ELSE

      CAUSE ERROR 900, "Buttons not yet created."

   END IF


END SUB  ! End of RenderButtons




! =========================================================================
! SUBROUTINE: PurgeButtons
! =========================================================================
!
! PURPOSE: To delete button definitions
!
! SHARED VARIABLES
!
!    OUTPUT:
!
!       The following variables are cleared by this SUBROUTINE
!
!       XLeft         Original window scaling in calling program unit
!       XRight        Original window scaling in calling program unit
!       YBottom       Original window scaling in calling program unit
!       YTop          Original window scaling in calling program unit
!       Horiz$        Original text justification in calling program unit
!       Vert$         Original text justification in calling program unit
!       XMIN          Button window scaling, left edge
!       XMAX          Button window scaling, right edge
!       YMIN          Button window scaling, bottom edge
!       YMAX          Button window scaling, top edge
!       HOR(0)               Dynamic stack to store horizontal positions
!       VER(0)               Dynamic stack to store vertical positions
!       WIDTH(0)             Dynamic stack to store width postions
!       CONTROL$(0)          Dynamic stack to store controlling key
!       PROMPT$(0)           Dynamic stack to store prompt string
!       SNAPSHOT$(0)         Dynamic stack to store graphic data 
!       SELECT_SNAPSHOT$(0)  Dynamic stack to store selected grahpic data
!       Number_of_Buttons    Keeps track of how many buttons are defined
!       InitFlag             Reports if button window has been properly
!                            initialized
!
!
! ROUTINES CALLED: RestoreOriginalWindow
!
! Exceptions:  900    Buttons not yet created.
!
! =========================================================================
SUB PurgeButtons

   ! ***********************************************
   ! Verify that SHARED variables have been assigned
   ! ***********************************************
   IF InitFlag = 1 THEN

      ! ****************************
      ! Clear all button information
      ! ****************************
      MAT HOR   = 0
      MAT VER   = 0
      MAT WIDTH = 0
      
      FOR I = 1 TO Number_of_Buttons
         LET SNAPSHOT$(I) = ""
         LET SELECT_SNAPSHOT$(I) = ""
         LET CONTROL$(I)  = ""
         LET PROMPT$(I) = ""
      NEXT I

      LET Number_of_Buttons = 0
      LET InitFlag = 0
   
      ! **************************
      ! Reduce stack sizes to zero
      ! **************************
      MAT REDIM HOR(0)
      MAT REDIM VER(0)
      MAT REDIM WIDTH(0)
      MAT REDIM SNAPSHOT$(0)
      MAT REDIM SELECT_SNAPSHOT$(0)
      MAT REDIM CONTROL$(0)
      MAT REDIM PROMPT$(0)
   
      ! ***************************************
      ! Restore scaling of calling program unit
      ! ***************************************
      CALL RestoreOriginalWindow

      ! **************************
      ! Clear all SHARED variables
      ! **************************
      LET XLeft   = 0
      LET XRight  = 0
      LET YBottom = 0
      LET YTop    = 0
      LET Horiz$  = ""
      LET Vert$   = ""

   ELSE

      CAUSE ERROR 900, "Buttons not yet created."

   END IF


END SUB  ! End of PurgeButtons




! =========================================================================
! SUBROUTINE: InitButtonWindow
! =========================================================================
!
! PURPOSE: To scale the window such that the PLOT TEXT statement uses
!          the same positions numbers as SET CURSOR and PRINT.  That is,
!          both:
!
!                    PLOT TEXT, AT X,Y : A$
!
!          and:
!
!                    SET CURSOR Y,X
!                    PRINT A$
!
!          ... place the text on the screen in exactly the same place
!          within the active window of the calling program unit.
!          Remember that SET CURSOR and PLOT TEXT use horizontal and
!          vertical positions in reverse order.
!
!
! SCOPE: PUBLIC
!
!
! SHARED VARIABLES:
!
!    OUTPUT:
!
!       The following variables are assigned values in this SUBROUTINE
!       and then used by other program units within this MODULE
!
!       XLeft         Original window scaling in calling program unit
!       XRight        Original window scaling in calling program unit
!       YBottom       Original window scaling in calling program unit
!       YTop          Original window scaling in calling program unit
!       Horiz$        Original text justification in calling program unit
!       Vert$         Original text justification in calling program unit
!       SHARE XMIN    Button window scaling, left edge
!       SHARE XMAX    Button window scaling, right edge
!       SHARE YMIN    Button window scaling, bottom edge
!       SHARE YMAX    Button window scaling, top edge
!
!    INOUT:
!
!       The following variable can be either assigned within this program
!       unit, or simply read, depending on program logic.
!
!       InitFlag      To report if button window has already been scaled
!
!
! ROUTINES CALLED:
!
!    ScaleButtonWindow
!
!
! OVERVIEW:
!
!    This routine scales the window so that Cartesian coordinates match the
!    cursor position numbers used by the SET CURSOR statement.  Original
!    window scaling and text justification within the calling program unit
!    are saved and are restored automatically by all the routines within
!    this MODULE.
!
!    You may invoke this SUBROUTINE from your calling program unit, but
!    only before you create new buttons.  You may want to do this if you
!    want screen coordinates of PLOT TEXT to coincide with character
!    position numbers of SET CURSOR.  
!
!    Note that an error is produced it you try to invoke this SUBROUTINE
!    from your calling program unit after having created some buttons,
!    or after having already invoked this SUBROUTINE once.  This is a
!    feature protecting you from losing your calling program unit's
!    original coordinate system.
!
!
! Exceptions:  
!
!    901    Cannot re-initialize without first invoking PurgeButtons.
!
! =========================================================================
SUB InitButtonWindow


   ! ***************************************
   ! Execute only if not already initialized
   ! ***************************************
   IF InitFlag = 0 OR Number_of_Buttons = 0 THEN

      ! ************************************************************
      ! Measure and record window conditions in calling program unit
      ! ************************************************************
      ASK WINDOW XLeft, XRight, YBottom, YTop
      ASK MAX CURSOR MR, MC
      SET MARGIN MC
      ASK TEXT JUSTIFY Horiz$, Vert$
      ASK PIXELS XPIXELS, YPIXELS
   
      ! **************************************************************
      ! Calculate window scaling values to match text columns and rows
      ! **************************************************************
      LET DXT = XPIXELS/MC                   ! Text width in pixels
      LET DYT = YPIXELS/MR                   ! Text height in pixels
      LET XMIN = 1                           ! Left edge of window
      LET XMAX = MC + 1 - 1/DXT              ! Right edge of window
      LET YMIN = MR + 1/DYT                  ! Bottom edge of window
      LET YMAX = 2/DYT                       ! Top edge of window

      LET InitFlag = 1
      CALL ScaleButtonWindow   

   ELSE

      CAUSE ERROR 901, "Cannot re-initialize without first invoking PurgeButtons."

   END IF


END SUB  ! End of InitButtonWindow




                   ! ***********************************
                   ! ***   PRIVATE routines follow   ***
                   ! ***********************************



! =========================================================================
! SUBROUTINE: ButtonGraphics
! =========================================================================
!
! PURPOSE: 
!
!    To render a new button with enclosed text
!
!
! SCOPE: PRIVATE
!
!
! PARAMATERS:
!
!    INPUT:  
!
!       A$   Text string to enclose in button
!       X    Horizontal position of lower left corner of text from left
!            edge of screen
!       Y    Vertical position of lower left corner of text from top edge
!            of screen
!
!
! SHARED VARIABLES:
!
!
! OVERVIEW:
!
!    Information related to the new button is stored in dynamically
!    allocated stacks: HOR, VER, WIDTH, and SNAPSHOT$.  Note that the
!    MAT REDIM statement allows dynamic control of memory allocated to
!    an array.  Thus you can easily implement dynamic stacks.
!
! =========================================================================
SUB ButtonGraphics(A$, X, Y)

   LET W = LEN(A$)

   ! ********************************
   ! Calculate inside edges of button
   ! ********************************
   LET InsideLeftEdge   = 0.625
   LET InsideRightEdge  = W + 0.5
   LET InsideBottomEdge = 0.25
   LET InsideTopEdge    = 1
   LET BXMIN = X - InsideLeftEdge
   LET BXMAX = X + InsideRightEdge
   LET BYMIN = Y + InsideBottomEdge
   LET BYMAX = Y - InsideTopEdge

   ! *********************************
   ! Calculate outside edges of button
   ! *********************************
   LET OutsideLeftEdge   = BXMIN - 0.5
   LET OutsideRightEdge  = BXMAX + 0.5
   LET OutsideBottomEdge = BYMIN + 0.25
   LET OutsideTopEdge    = BYMAX - 0.25

   ! *********************
   ! Draw inside of button
   ! *********************
   PLOT BXMIN, BYMIN;
   PLOT BXMAX, BYMIN;
   PLOT BXMAX, BYMAX;
   PLOT BXMIN, BYMAX;
   PLOT BXMIN, BYMIN

   ! **********************
   ! Draw outside of button
   ! **********************
   PLOT OutsideLeftEdge,  OutsideBottomEdge;
   PLOT OutsideRightEdge, OutsideBottomEdge;
   PLOT OutsideRightEdge, OutsideTopEdge;
   PLOT OutsideLeftEdge,  OutsideTopEdge;
   PLOT OutsideLeftEdge,  OutsideBottomEdge

   ! ***********
   ! Draw shadow
   ! ***********
   PLOT BXMIN - 0.25, BYMIN + 0.125;
   PLOT BXMIN - 0.25, BYMAX - 0.125;
   PLOT BXMAX + 0.25, BYMAX - 0.125;
   PLOT BXMAX + 0.25, BYMIN + 0.125

   ! **********************************
   !      Place test in the button
   ! Remove leading and trailing spaces 
   ! **********************************
   SET TEXT JUSTIFY "CENTER", "BASE"
   PLOT TEXT, AT X + W/2 - 0.125, Y : RTRIM$(LTRIM$(A$))
   SET TEXT JUSTIFY "LEFT", "BASE"


END SUB  ! End of ButtonGraphics




! =========================================================================
! SUBROUTINE: ShowSelectedButton
! =========================================================================
!
! PURPOSE: To give visual confirmation that a button has been selected
!
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       HOR(0)               Dynamic stack to store horizontal positions
!       VER(0)               Dynamic stack to store vertical positions
!       WIDTH(0)             Dynamic stack to store width postions
!
!
!    INOUT:
!
!       Selected_Button$
!
! =========================================================================
SUB ShowSelectedButton(I)

   IF SELECT_SNAPSHOT$(I) = "" THEN

      ! ********************************
      ! Calculate inside edges of button
      ! ********************************
      LET InsideLeftEdge = 0.625
      LET InsideRightEdge = WIDTH(I) + 0.5
      LET InsideBottomEdge = 0.25
      LET InsideTopEdge = 1
      LET BXMIN = HOR(I) - InsideLeftEdge
      LET BXMAX = HOR(I) + InsideRightEdge
      LET BYMIN = VER(I) + InsideBottomEdge
      LET BYMAX = VER(I) - InsideTopEdge
   
      ! *********************************
      ! Calculate outside edges of button
      ! *********************************
      LET OutsideLeftEdge   = BXMIN - 0.5
      LET OutsideRightEdge  = BXMAX + 0.5
      LET OutsideBottomEdge = BYMIN + 0.25
      LET OutsideTopEdge    = BYMAX - 0.25

      ! ************************
      ! Delete unselected button
      ! ************************
      BOX CLEAR OutsideLeftEdge, OutsideRightEdge, OutsideBottomEdge, OutsideTopEdge

      ! *********************
      ! Draw inside of button
      ! *********************
      PLOT BXMIN, BYMIN;
      PLOT BXMAX, BYMIN;
      PLOT BXMAX, BYMAX;
      PLOT BXMIN, BYMAX;
      PLOT BXMIN, BYMIN
   
      ! **********************
      ! Draw outside of button
      ! **********************
      PLOT OutsideLeftEdge,  OutsideBottomEdge;
      PLOT OutsideRightEdge, OutsideBottomEdge;
      PLOT OutsideRightEdge, OutsideTopEdge;
      PLOT OutsideLeftEdge,  OutsideTopEdge;
      PLOT OutsideLeftEdge,  OutsideBottomEdge
   
      ! ***********
      ! Draw shadow
      ! ***********
      PLOT BXMIN - 0.25, BYMAX;
      PLOT BXMIN - 0.25, BYMIN + 0.125;
      PLOT BXMAX + 0.25, BYMIN + 0.125;
      PLOT BXMAX + 0.25, BYMAX

      ! ***********************************
      ! Render solid area on face of button
      ! ***********************************
      LET XL = HOR(I)
      LET XR = HOR(I) + WIDTH(I) - 0.125
      LET YB = VER(I)
      LET YT = VER(I) - 0.75
      BOX AREA XL, XR, YB, YT

      ! **********************************************
      ! Take a shapshot of the button to use next time
      ! **********************************************
      BOX KEEP OutsideLeftEdge, OutsideRightEdge, OutsideBottomEdge, OutsideTopEdge IN Selected_Button$

   ELSE

      ! ****************************************************
      ! Offset of lower left corner of button is -1.125, 0.5
      ! ****************************************************
      BOX SHOW SELECT_SNAPSHOT$(I) AT HOR(I) - 1.125, VER(I) + 0.5

   END IF


END SUB  ! End of ShowSelectedButton




! =========================================================================
! SUBROUTINE: RestoreOriginalWindow
! =========================================================================
!
! PURPOSE: 
!
!    To restore original window conditions in calling program unit.
!
!
! SCOPE: PRIVATE
!
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       The following variables are assigned values in other program 
!       units within this MODULE and then used by this SUBROUTINE
!
!       XLeft        ! Original window scaling in calling program unit
!       XRight       ! Original window scaling in calling program unit
!       YBottom      ! Original window scaling in calling program unit
!       YTop         ! Original window scaling in calling program unit
!       Horiz$       ! Original text justification in calling program unit
!       Vert$        ! Original text justification in calling program unit
!
! =========================================================================
SUB RestoreOriginalWindow

   SET WINDOW XLeft, XRight, YBottom, YTop
   SET TEXT JUSTIFY Horiz$, Vert$

END SUB




! =========================================================================
! SUBROUTINE: ScaleButtonWindow
! =========================================================================
!
! PURPOSE: 
!
!    To scale window as needed by this MODULE.
!
!
! SCOPE: PRIVATE
!
!
! SHARED VARIABLES:
!
!    INPUT:
!
!       The following variables are assigned values in other program 
!       units within this MODULE and then used by this SUBROUTINE
!
!       SHARE XMIN   ! Button window scaling, left edge
!       SHARE XMAX   ! Button window scaling, right edge
!       SHARE YMIN   ! Button window scaling, bottom edge
!       SHARE YMAX   ! Button window scaling, top edge
!
!
! EXCEPTIONS:
!
!    902   Scaling variables not yet assigned.
!
! 
! OVERVIEW:
!
!    An error is generated if window scaling variables have not yet
!    been assigned by InitButtonWindow SUBROUTINE.  Note that this is
!    a PRIVATE function, so you don't have to worry about this unless
!    you actually modify this MODULE.
!
! =========================================================================
SUB ScaleButtonWindow


   IF InitFlag = 1 THEN

      ! ******************************************************
      ! Scale window using SHARED variables that were assigned
      ! values in the InitButtonWindow SUBROUTINE.
      ! ******************************************************
      SET WINDOW XMIN, XMAX, YMIN, YMAX
      SET TEXT JUSTIFY "LEFT", "BASE"

   ELSE

      CAUSE ERROR 902, "Scaling variables not yet assigned."

   END IF


END SUB  ! End of ScaleButtonWindow




END MODULE
