(*----------------------------------------------------------------------*)
(*           PIBMENUS.PAS   --- Menu Routines for Turbo Pascal          *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*  Author:  Philip R. Burns                                            *)
(*  Date:    January, 1985                                              *)
(*  Version: 1.0                                                        *)
(*  Systems: For MS-DOS on IBM PCs and close compatibles only.          *)
(*           Note:  I have checked these on Zenith 151s under           *)
(*                  MSDOS 2.1 and IBM PCs under PCDOS 2.0.              *)
(*                                                                      *)
(*  History: These routines represent my substantial upgrading of the   *)
(*           simple menu routines written by Barry Abrahamsen which     *)
(*           I believe appeared originally in the TUG newsletter.       *)
(*           The windowing facility provides windows similar to those   *)
(*           implemented in QMODEM by John Friel III.                   *)
(*                                                                      *)
(*           Suggestions for improvements or corrections are welcome.   *)
(*           Please leave messages on Gene Plantz's BBS (312) 882 4145  *)
(*           or Ron Fox's BBS (312) 940 6496.                           *)
(*                                                                      *)
(*           If you use this code in your own programs, please be nice  *)
(*           and give all of us credit.                                 *)
(*                                                                      *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*  Needs:  These routines need the include files MINMAX.PAS,           *)
(*          GLOBTYPE.PAS, and ASCII.PAS.  These files are not           *)
(*          included here, since Turbo regrettably does not allow       *)
(*          nested includes.                                            *)
(*                                                                      *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*  What these routines do:                                             *)
(*                                                                      *)
(*    These routines provide a straight-forward menu-selection          *)
(*    facility, similar to that used in programs like Lotus.  A pop-up  *)
(*    window holds the menu.  The menu is contained in a frame.  The    *)
(*    items are displayed within the frame.  The currently selected     *)
(*    item is highlighted in reverse video.  You move up and down in    *)
(*    the list of menu items by using the up and down arrow keys, or    *)
(*    the space bar.  To make a selection, hit the Enter (Return) key.  *)
(*                                                                      *)
(*    Note that code for stacked windows is available here.  You may    *)
(*    want to modify this to use compile-time window spaces, or remove  *)
(*    the current push-down stack structure.                            *)
(*                                                                      *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*  Use:                                                                *)
(*                                                                      *)
(*     (1)  Define a variable of type Menu_Type, say, MYMENU.           *)
(*                                                                      *)
(*     (2)  Define the following entries in MYMENU:                     *)
(*                                                                      *)
(*             Menu_Size    --- Number of entries in this menu          *)
(*             Menu_Title   --- Title for the menu                      *)
(*             Menu_Row     --- Row where menu should appear (upper LHC *)
(*             Menu_Column  --- Column where menu should appear         *)
(*             Menu_Width   --- Width of menu                           *)
(*             Menu_Height  --- Height of menu                          *)
(*             Menu_Default --- Ordinal of the default menu entry       *)
(*             Menu_Tcolor  --- Color to display menu text              *)
(*             Menu_Bcolor  --- Color for menu background               *)
(*             Menu_Fcolor  --- Color for menu frame box                *)
(*                                                                      *)
(*     (3)  Now for each of Menu_Size Menu_Entries, define:             *)
(*             Menu_Text   --- Text of menu item                        *)
(*                                                                      *)
(*     (4)  Call  Menu_Display_Choices  to display menu.  The default   *)
(*          menu choice will be highlighted.                            *)
(*                                                                      *)
(*     (5)  Call  Menu_Get_Choice  to retrieve menu choice.  The up and *)
(*          down arrows, and the space bar, can be used to move         *)
(*          through the menu items.  Each item is highlighted in turn.  *)
(*          Whichever item is highlighted when a carriage return is     *)
(*          entered is returned as the chosen item.                     *)
(*                                                                      *)
(*----------------------------------------------------------------------*)

                   (* Character Constants *)

Const

   Up_arrow         = ^E;    (* move up in menu code   *)
   Down_arrow       = ^X;    (* move down in menu code *)
   Space_bar        = #32;   (* space bar              *)
   Ch_cr            = #13;   (* Carriage return *)
   Ch_esc           = #27;   (* Escape *)
   Bell             = #07;   (* Bell *)

   Max_Menu_Items   =  10;   (* Maximum number of menu choices *)

   Dont_Erase_Menu  = FALSE;
   Erase_Menu       = TRUE;


                   (* Menu Types *)
Type

   String40   = String[40]         (* Menu entry string type               *);
   String60   = String[60]         (* Menu title string type               *);

   Menu_Entry = Record
      Menu_Item_Text   : String40; (* Text of entry                        *)
      Menu_Item_Row    : Integer;  (* Row position of menu item            *)
      Menu_Item_Column : Integer;  (* Column position of menu item         *)
   End;

   Menu_Type = Record
      Menu_Size     : 1 .. Max_Menu_Items;    (* No. of items in menu      *)
      Menu_Title    : String60;               (* Menu title                *)
      Menu_Row      : Integer;                (* Row position of menu      *)
      Menu_Column   : Integer;                (* Column position of menu   *)
      Menu_Width    : Integer;                (* Width of menu             *)
      Menu_Height   : Integer;                (* Height of menu            *)
      Menu_Default  : 1 .. Max_Menu_Items;    (* Default value position    *)
      Menu_TColor   : Integer;                (* Foreground text color     *)
      Menu_BColor   : Integer;                (* BackGround color          *)
      Menu_FColor   : Integer;                (* Frame color               *)

                                              (* Menu items themselves     *)
      Menu_Entries  : Array[ 1 .. Max_Menu_Items ] Of Menu_Entry;
   End;

                   (* Screen Types *)

Const
     Color_Screen_Address   = $B800;   (* Address of color screen          *)
     Mono_Screen_Address    = $B000;   (* Address of mono screen           *)
     Screen_Length          = 4000;    (* 80 x 25 x 2 = screen area length *)
     Max_Saved_Screen       = 5;       (* Maximum no. of saved screens     *)

Type
                                              (* A screen image            *)
   Screen_Type       = Array[ 1 .. Screen_Length ] Of Byte;

   Screen_Ptr        = ^Screen_Image_Type;
   Screen_Image_Type = Record
                          Screen_Image: Screen_Type;
                       End;

                                              (* Screen stack entries      *)
   Saved_Screen_Ptr  = ^Saved_Screen_Type;
   Saved_Screen_Type = Record
                          Screen_Image  : Screen_Type;
                          Screen_Row    : Integer;
                          Screen_Column : Integer;
                          Screen_X1     : Integer;
                          Screen_Y1     : Integer;
                          Screen_X2     : Integer;
                          Screen_Y2     : Integer;
                       End;


                   (* Screen Variables *)

Var
                                              (* Memory-mapped screen area *)
   Actual_Screen        : Screen_Ptr;

                                              (* Saves screen behind menus *)
   Saved_Screen         : Saved_Screen_Ptr;

                                              (* Stack of saved screens    *)
   Saved_Screen_List    : Array[ 1 .. Max_Saved_Screen ] Of Saved_Screen_Ptr;

                                              (* Depth of saved screen stack *)
   Current_Saved_Screen : 0 .. Max_Saved_Screen;

(*----------------------------------------------------------------------*)
(*    Color_Screen_Active --- Determine if color or mono screen         *)
(*----------------------------------------------------------------------*)

Function Color_Screen_Active : Boolean;

(*                                                                      *)
(*     Function:   Color_Screen_Active                                  *)
(*                                                                      *)
(*     Purpose:    Determines if color or mono screen active            *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Color_Active := Color_Screen_Active : Boolean;                *)
(*                                                                      *)
(*           Color_Active --- set to TRUE if the color screen is        *)
(*                            active, FALSE if the mono screen is       *)
(*                            active.                                   *)
(*                                                                      *)
(*     Calls:   INTR                                                    *)
(*                                                                      *)

Var
   Regs : Record       (* 8088 registers *)
             Ax, Bx, Cx, Dx, Bp, Si, Di, Ds, Es, Flags : Integer
          End;

Begin  (* Color_Screen_Active *)

   Regs.Ax := 15 SHL 8;

   INTR( $10 , Regs );

   Color_Screen_Active := ( Regs.Ax AND $FF ) <> 7;

End    (* Color_Screen_Active *);

(*----------------------------------------------------------------------*)
(*        Get_Screen_Address --- Get address of current screen          *)
(*----------------------------------------------------------------------*)

Procedure Get_Screen_Address( Var Actual_Screen : Screen_Ptr );

(*                                                                      *)
(*     Procedure:  Get_Screen_Address                                   *)
(*                                                                      *)
(*     Purpose:    Gets screen address for current type of display      *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Get_Screen_Address( Var Actual_Screen : Screen_Ptr );         *)
(*                                                                      *)
(*           Actual_Screen --- pointer whose value receives the         *)
(*                             current screen address.                  *)
(*                                                                      *)
(*     Calls:   Color_Screen_Active                                     *)
(*              PTR                                                     *)
(*                                                                      *)

Begin  (* Get_Screen_Address *)

   If Color_Screen_Active Then
      Actual_Screen := PTR( Color_Screen_Address , 0 )
   Else
      Actual_Screen := PTR( Mono_Screen_Address , 0 );

End    (* Get_Screen_Address *);

(*----------------------------------------------------------------------*)
(*                Video Display Control Routines                        *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*       RvsVideoOn  --- Turn On Reverse Video                          *)
(*       RvsVideoOff --- Turn Off Reverse Video                         *)
(*                                                                      *)
(*----------------------------------------------------------------------*)

Procedure RvsVideoOn( Foreground_Color, Background_Color : Integer );

Begin (* RvsVideoOn *)

   TextColor     ( Background_color );
   TextBackGround( Foreground_color );

End   (* RvsVideoOn *);

(*----------------------------------------------------------------------*)

Procedure RvsVideoOff( Foreground_Color, Background_Color : Integer );

Begin (* RvsVideoOff *)

   TextColor     ( Foreground_color );
   TextBackGround( Background_color );

End   (* RvsVideoOff *);


(*----------------------------------------------------------------------*)
(*                TURBO Pascal Window Location Routines                 *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*  These routines and constants give the four corners of the current   *)
(*  Turbo window:                                                       *)
(*                                                                      *)
(*    Lower right-hand corner: (Lower_Right_Column, Lower_Right_Row)    *)
(*    Upper left_hand corner:  (Upper_Left_Column, Upper_Right_Column)  *)
(*                                                                      *)
(*----------------------------------------------------------------------*)

                                   (* Lower right corner of     *)
                                   (* current TURBO window      *)
Var
   Lower_Right_Column  : Byte ABSOLUTE Cseg:$016A;
   Lower_Right_Row     : Byte ABSOLUTE Cseg:$016B;

(*----------------------------------------------------------------------*)
(*    Upper_Left_Column ---  Upper Left Col. Position of current window *)
(*----------------------------------------------------------------------*)

Function Upper_Left_Column : Integer;

(*                                                                      *)
(*     Function:   Upper_Left_Column                                    *)
(*                                                                      *)
(*     Purpose:    Returns upper left col. pos. of current TURBO window *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Pos := Upper_Left_Column : Integer;                           *)
(*                                                                      *)
(*     Calls:   Mem                                                     *)
(*                                                                      *)

Begin  (* Upper_Left_Column *)

   Upper_Left_Column := Mem[ Dseg:$0156 ] + 1;

End    (* Upper_Left_Column *);

(*----------------------------------------------------------------------*)
(*    Upper_Left_Row ---  Upper Left Row Position of current window     *)
(*----------------------------------------------------------------------*)

Function Upper_Left_Row : Integer;

(*                                                                      *)
(*     Function:   Upper_Left_Row                                       *)
(*                                                                      *)
(*     Purpose:    Returns upper left row pos. of current TURBO window  *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Pos := Upper_Left_Row : Integer;                              *)
(*                                                                      *)
(*     Calls:   Mem                                                     *)
(*                                                                      *)

Begin  (* Upper_Left_Row *)

   Upper_Left_Row := Mem[ Dseg:$0157 ] + 1;

End    (* Upper_Left_Row *);

(*----------------------------------------------------------------------*)
(*                Set/Reset Text Color Routines                         *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*   These routines set and reset the global text foreground and        *)
(*   background colors.                                                 *)
(*                                                                      *)
(*----------------------------------------------------------------------*)

                   (* Global Text Color Variables *)

Var
   Global_ForeGround_Color : Integer;
   Global_BackGround_Color : Integer;

(*----------------------------------------------------------------------*)
(*    Set_Global_Colors --- Reset global foreground, background cols.   *)
(*----------------------------------------------------------------------*)

Procedure Set_Global_Colors( ForeGround, BackGround : Integer );

(*                                                                      *)
(*     Procedure:  Set_Global_Colors                                    *)
(*                                                                      *)
(*     Purpose:    Sets global text foreground, background colors.      *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Set_Global_Colors( ForeGround, BackGround : Integer );        *)
(*                                                                      *)
(*           ForeGround --- Default foreground color                    *)
(*           BackGround --- Default background color                    *)
(*                                                                      *)
(*     Calls:   TextColor                                               *)
(*              TextBackGround                                          *)
(*                                                                      *)

Begin  (* Set_Global_Colors *)

   Global_ForeGround_Color := ForeGround;
   GLobal_BackGround_Color := BackGround;

   TextColor     ( Global_ForeGround_Color );
   TextBackground( Global_BackGround_Color );

End    (* Set_Global_Colors *);

(*----------------------------------------------------------------------*)
(*  Reset_Global_Colors --- Reset global foreground, background cols.   *)
(*----------------------------------------------------------------------*)

Procedure Reset_Global_Colors;

(*                                                                      *)
(*     Procedure:  Reset_Global_Colors                                  *)
(*                                                                      *)
(*     Purpose:    Resets text foreground, background colors to global  *)
(*                 defaults.                                            *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Reset_Global_Colors;                                          *)
(*                                                                      *)
(*     Calls:   TextColor                                               *)
(*              TextBackGround                                          *)
(*                                                                      *)

Begin  (* Reset_Global_Colors *)

   TextColor     ( Global_ForeGround_Color );
   TextBackground( Global_BackGround_Color );

End    (* Reset_Global_Colors *);

(*----------------------------------------------------------------------*)
(*                 Screen Manipulation Routines                         *)
(*----------------------------------------------------------------------*)
(*                                                                      *)
(*   These routines save and restore screen images in support of the    *)
(*   windowing facility.  Also, the current screen image can be printed *)
(*   and text extracted from the screen memory.                         *)
(*                                                                      *)
(*----------------------------------------------------------------------*)

(*----------------------------------------------------------------------*)
(*       Get_Screen_Text_Line --- Extract text from screen image        *)
(*----------------------------------------------------------------------*)

Procedure Get_Screen_Text_Line( Var Text_Line     : AnyStr;
                                    Screen_Line   : Integer;
                                    Screen_Column : Integer );

(*                                                                      *)
(*     Procedure:  Get_Screen_Text_Line                                 *)
(*                                                                      *)
(*     Purpose:    Extracts text from current screen image              *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Get_Screen_Text_Line( Var Text_Line     : AnyStr;             *)
(*                                  Screen_Line   : Integer;            *)
(*                                  Screen_Column : Integer );          *)
(*                                                                      *)
(*           Text_Line        --- receives text extracted from screen   *)
(*           Screen_Line      --- line on screen to extract             *)
(*           Screen_Column    --- starting column to extract            *)
(*                                                                      *)
(*     Calls:   None                                                    *)
(*                                                                      *)
(*     Remarks:                                                         *)
(*                                                                      *)
(*        Only the text -- not attributes -- from the screen is         *)
(*        returned.                                                     *)
(*                                                                      *)

Var
   First_Pos  : Integer;
   Last_Pos   : Integer;
   I          : Integer;

Begin  (* Print_Screen *)

   Screen_Line   := Max( Min( Screen_Line   , 25 ) , 1 );
   Screen_Column := Max( Min( Screen_Column , 80 ) , 1 );

   Text_Line     := '';
   First_Pos     := ( ( Screen_Line - 1 ) * 80 + Screen_Column ) * 2;
   Last_Pos      := Screen_Line * 160;

   Repeat
      Text_Line := Text_Line + CHR( Actual_Screen^.Screen_Image[ First_Pos ] );
      First_Pos := First_Pos + 2;
   Until ( First_Pos > Last_Pos );

End    (* Print_Screen *);

(*----------------------------------------------------------------------*)
(*                Print_Screen --- Print current screen image           *)
(*----------------------------------------------------------------------*)

Procedure Print_Screen;

(*                                                                      *)
(*     Procedure:  Print_Screen                                         *)
(*                                                                      *)
(*     Purpose:    Prints current screen image (memory mapped area)     *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Print_Screen;                                                 *)
(*                                                                      *)
(*     Calls:   None                                                    *)
(*                                                                      *)
(*     Remarks:                                                         *)
(*                                                                      *)
(*        Only the text from the screen is printed.                     *)
(*                                                                      *)

Var
   I         : Integer;
   Text_Line : String[80];

Begin  (* Print_Screen *)

   For I := 1 To 25 Do
      Begin
         Get_Screen_Text_Line( Text_Line, I, 1 );
         Writeln( Lst , Text_Line );
      End;

End    (* Print_Screen *);

(*----------------------------------------------------------------------*)
(*                Save_Screen --- Save current screen image             *)
(*----------------------------------------------------------------------*)

Procedure Save_Screen( Var Saved_Screen_Pointer : Saved_Screen_Ptr );

(*                                                                      *)
(*     Procedure:  Save_Screen                                          *)
(*                                                                      *)
(*     Purpose:    Saves current screen image (memory mapped area)      *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Save_Screen( Var Saved_Screen_Pointer : Saved_Screen_Ptr );   *)
(*                                                                      *)
(*           Saved_Screen_Pointer  --- pointer to record receiving      *)
(*                                     screen image, window location,   *)
(*                                     and current cursor location.     *)
(*                                                                      *)
(*     Calls:   Move                                                    *)
(*              Upper_Left_Column                                       *)
(*              Upper_Left_Row                                          *)
(*                                                                      *)
(*     Remarks:                                                         *)
(*                                                                      *)
(*        This version doesn't check for stack overflow.                *)
(*        Caveat Programmer.                                            *)
(*                                                                      *)

Begin  (* Save_Screen *)

   Current_Saved_Screen := Current_Saved_Screen + 1;
   New( Saved_Screen_Pointer );
   Saved_Screen_List[ Current_Saved_Screen ] := Saved_Screen_Pointer;

   With Saved_Screen_Pointer^ Do
      Begin

         Screen_X1     := Upper_Left_Column;
         Screen_Y1     := Upper_Left_Row;
         Screen_X2     := Lower_Right_Column;
         Screen_Y2     := Lower_Right_Row;

         Screen_Row    := WhereY;
         Screen_Column := WhereX;

         Move( Actual_Screen^.Screen_Image, Screen_Image, Screen_Length );

      End;

End   (* Save_Screen *);

(*----------------------------------------------------------------------*)
(*              Restore_Screen --- Restore saved screen image           *)
(*----------------------------------------------------------------------*)

Procedure Restore_Screen( Var Saved_Screen_Pointer : Saved_Screen_Ptr );

(*                                                                      *)
(*     Procedure:  Restore_Screen                                       *)
(*                                                                      *)
(*     Purpose:    Restores previously saved screen image.              *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Restore_Screen( Var Saved_Screen_Pointer: Saved_Screen_Ptr ); *)
(*                                                                      *)
(*           Saved_Screen_Pointer  --- pointer to record with saved     *)
(*                                     screen image, window location,   *)
(*                                     and cursor location.             *)
(*                                                                      *)
(*     Calls:   Window                                                  *)
(*              Move                                                    *)
(*              GoToXY                                                  *)
(*                                                                      *)
(*     Remarks:                                                         *)
(*                                                                      *)
(*        All saved screen pointers from the last saved down to the     *)
(*        argument pointer are popped from the saved screen list.       *)
(*                                                                      *)

Begin  (* Restore_Screen *)

   With Saved_Screen_Pointer^ Do
      Begin

         Window( Screen_X1, Screen_Y1, Screen_X2, Screen_Y2 );

         Move( Screen_Image, Actual_Screen^.Screen_Image, Screen_Length );

         GoToXY( Screen_Column, Screen_Row );

      End;

   While( Saved_Screen_List[ Current_Saved_Screen ] <> Saved_Screen_Pointer ) Do
      Current_Saved_Screen := Current_Saved_Screen - 1;

   If Current_Saved_Screen > 0 Then
      Current_Saved_Screen := Current_Saved_Screen - 1;

   Dispose( Saved_Screen_Pointer );

End    (* Restore_Screen *);

(*----------------------------------------------------------------------*)
(*                Draw_Menu_Frame --- Draw a Frame                      *)
(*----------------------------------------------------------------------*)

Procedure Draw_Menu_Frame( UpperLeftX,  UpperLeftY,
                           LowerRightX, LowerRightY : Integer;
                           Frame_Color, Title_Color : Integer;
                           Menu_Title: AnyStr );

(*                                                                      *)
(*     Procedure:  Draw_Menu_Frame                                      *)
(*                                                                      *)
(*     Purpose:    Draws a titled frame using PC graphics characters    *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Draw_Menu_Frame( UpperLeftX,  UpperLeftY,                     *)
(*                         LowerRightX, LowerRightY,                    *)
(*                         Frame_Color, Title_Color : Integer;          *)
(*                         Menu_Title: AnyStr );                        *)
(*                                                                      *)
(*           UpperLeftX,  UpperLeftY  --- Upper left coordinates        *)
(*           LowerRightX, LowerRightY --- Lower right coordinates       *)
(*           Frame_Color              --- Color for frame               *)
(*           Title_Color              --- Color for title text          *)
(*           Menu_Title               --- Menu Title                    *)
(*                                                                      *)
(*     Calls:   GoToXY                                                  *)
(*              Window                                                  *)
(*              ClrScr                                                  *)
(*                                                                      *)
(*     Remarks:                                                         *)
(*                                                                      *)
(*        The area inside the frame is cleared after the frame is       *)
(*        drawn.  If a box without a title is desired, enter a null     *)
(*        string for a title.                                           *)

Var
   I  : Integer;
   L  : Integer;
   LT : Integer;

Begin (* Draw_Menu_Frame *)

                                   (* Move to top left-hand corner of menu *)
   GoToXY( UpperLeftX, UpperLeftY );

   L  := LowerRightX - UpperLeftX;
   LT := LENGTH( Menu_Title );
                                   (* Adjust title length if necessary *)
   If LT > ( L - 5 ) Then Menu_Title[0] := CHR( L - 5 );

                                   (* Color for frame                  *)
   TextColor( Frame_Color );
                                   (* Write upper left hand corner and title *)
   If LT > 0 Then
      Begin
         Write('Õ[ ');
         TextColor( Title_Color );
         Write( Menu_Title );
         TextColor( Frame_Color );
         Write(' ]');
      End
   Else
      Write('ÕÍÍÍÍ');
                                   (* Draw remainder of top of frame *)

   For I := ( UpperLeftX + LT + 5 ) To ( LowerRightX - 1 ) Do Write('Í');

   Write('¸');
                                  (* Draw sides of frame *)

   For I := UpperLeftY+1 To LowerRightY-1 Do
      Begin
         GoToXY( UpperLeftX  , I );  Write( '³' );
         GoToXY( LowerRightX , I );  Write( '³' );
      End;

                                  (* Draw bottom of frame     *)

   GoToXY( UpperLeftX, LowerRightY );
   Write( 'Ô' );

   For I := UpperLeftX+1 To LowerRightX-1 Do Write( 'Í' );
   Write( '¾' );

                                   (* Establish scrolling window area *)

   Window( UpperLeftX+1, UpperLeftY+1, LowerRightX-1, LowerRightY-1 );

                                   (* Clear out the window area       *)
   Clrscr;
                                   (* Ensure proper color for text    *)
   TextColor( Title_Color );

End   (* Draw_Menu_Frame *);

(*----------------------------------------------------------------------*)
(*                Menu_Click --- Make short click noise                 *)
(*----------------------------------------------------------------------*)

Procedure Menu_Click;

(*                                                                      *)
(*     Procedure:  Menu_Click                                           *)
(*                                                                      *)
(*     Purpose:    Clicks Terminal Bell                                 *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Menu_Click;                                                   *)
(*                                                                      *)
(*     Calls:    Sound                                                  *)
(*               Delay                                                  *)
(*               NoSound                                                *)
(*                                                                      *)

Begin (* Menu_Click *)

  Sound( 2000 );
  Delay( 10 );
  NoSound;

End   (* Menu_Click *);

(*----------------------------------------------------------------------*)
(*                Menu_Beep --- Ring Terminal Bell                      *)
(*----------------------------------------------------------------------*)

Procedure Menu_Beep;

(*                                                                      *)
(*     Procedure:  Menu_Beep                                            *)
(*                                                                      *)
(*     Purpose:    Rings Terminal Bell                                  *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Menu_Beep;                                                    *)
(*                                                                      *)
(*     Calls:    None                                                   *)
(*                                                                      *)

Begin (* Menu_Beep *)

   Write( Bell );

End   (* Menu_Beep *);

(*----------------------------------------------------------------------*)
(*                Menu_Turn_On --- Highlight Menu Choice                *)
(*----------------------------------------------------------------------*)

Procedure Menu_Turn_On( Menu: Menu_Type; Menu_Item : Integer );

(*                                                                      *)
(*     Procedure:  Menu_Turn_On                                         *)
(*                                                                      *)
(*     Purpose:    Highlight a menu item using reverse video            *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Menu_Turn_On( Menu: Menu_Type; Menu_Item : Integer );         *)
(*                                                                      *)
(*           Menu      : Menu containing item to highlight              *)
(*           Menu_Item : Menu entry to highlight                        *)
(*                                                                      *)
(*     Calls:    GoToXY                                                 *)
(*               RvsVideoOn                                             *)
(*               RvsVideoOff                                            *)
(*                                                                      *)

Begin (* Menu_Turn_On *)

   With Menu.Menu_Entries[ Menu_Item ] Do
      Begin

         GoToXY( Menu_Item_Column, Menu_Item_Row );

         RvsVideoOn( Menu.Menu_Tcolor, Menu.Menu_Bcolor );

         Write( Menu_Item_Text );

         RvsVideoOff( Menu.Menu_Tcolor, Menu.Menu_Bcolor );

      End;

End   (* Menu_Turn_On *);

(*----------------------------------------------------------------------*)
(*                Menu_Turn_Off --- UnHighlight Menu Choice             *)
(*----------------------------------------------------------------------*)

Procedure Menu_Turn_Off( Menu: Menu_Type; Menu_Item : Integer );

(*                                                                      *)
(*     Procedure:  Menu_Turn_Off                                        *)
(*                                                                      *)
(*     Purpose:    Removes highlighting from menu item                  *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Menu_Turn_Off( Menu : Menu_Type; Menu_Item : Integer );       *)
(*                                                                      *)
(*           Menu        : Menu containing item to unhighlight          *)
(*           RvsVideoOff : Menu entry to un-highlight                   *)
(*                                                                      *)
(*     Calls:    GoToXY                                                 *)
(*               NormVideo                                              *)
(*                                                                      *)

Begin (* Menu_Turn_Off *)

   With Menu.Menu_Entries[ Menu_Item ] Do
      Begin

         GoToXY( Menu_Item_Column , Menu_Item_Row );

         RvsVideoOff( Menu.Menu_Tcolor, Menu.Menu_Bcolor );

         Write( Menu_Item_Text );

      End;

End   (* Menu_Turn_Off *);

(*----------------------------------------------------------------------*)
(*                Menu_IBMCh --- Interpret IBM keyboard chars.          *)
(*----------------------------------------------------------------------*)

Procedure Menu_IBMCh( Var C : Char );

(*                                                                      *)
(*     Procedure:  Menu_IBMCh                                           *)
(*                                                                      *)
(*     Purpose:    Interpret IBM keyboard chars.                        *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Menu_IBMCh( Var C : Char );                                   *)
(*                                                                      *)
(*           C --- On input, char following escape;                     *)
(*                 on output, char revised to Wordstar command code.    *)
(*                                                                      *)
(*     Calls:   None                                                    *)
(*                                                                      *)

Begin  (* Menu_IBMCh *)

   Read( Kbd , C );

   Case C Of

      'H' : C := Up_arrow;
      'P' : C := Down_arrow;

   End;

End   (* Menu_IBMCh *);

(*----------------------------------------------------------------------*)
(*                Menu_Display_Choices --- Display Menu Choices         *)
(*----------------------------------------------------------------------*)

Procedure Menu_Display_Choices( Menu : Menu_Type );

(*                                                                      *)
(*     Procedure:  Menu_Display_Choices                                 *)
(*                                                                      *)
(*     Purpose:    Displays Menu Choices                                *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Menu_Display_Choices( Menu : Menu_Type );                     *)
(*                                                                      *)
(*           Menu --- Menu record to be displayed.                      *)
(*                                                                      *)
(*     Calls:   ClsScr                                                  *)
(*              GoToXY                                                  *)
(*              Draw_Menu_Frame                                         *)
(*              Save_Screen                                             *)

Var
   I    : Integer;
   J    : Integer;
   XL   : Integer;
   YL   : Integer;
   XR   : Integer;
   YR   : Integer;
   MaxX : Integer;
   MaxY : Integer;

Begin (* Menu_Display_Choices *)

                                   (* Establish menu size *)

   XL := Menu.Menu_Column;
   YL := Menu.Menu_Row;

   XR := LENGTH( Menu.Menu_Title ) + XL - 1;
   YR := YL;

   MaxX := Menu.Menu_Width;
   MaxY := Menu.Menu_Height;

   For I := 1 To Menu.Menu_Size Do
      With Menu.Menu_Entries[I] Do
      Begin
         If Menu_Item_Row > MaxY Then MaxY := Menu_Item_Row;
         J := LENGTH( Menu_Item_Text ) + Menu_Item_Column - 1;
         If J > MaxX Then MaxX := J;
      End;

   J := XL + MaxX - 1;
   If J > XR Then XR := J;

   J := YL + MaxY - 1;
   If J > YR Then YR := J;

   XL := XL - 4;
   If XL < 0 Then XL := 0;

   YL := YL - 1;
   If YL < 0 Then YL := 0;

   YR := YR + 1;
   If YR > 25 Then YR := 25;

   If XR > 80 Then XR := 80;

                                   (* Save current screen image *)
   Save_Screen( Saved_Screen );

                                   (* Draw the menu frame       *)
   Draw_Menu_Frame( XL, YL, XR, YR, Menu.Menu_FColor, Menu.Menu_TColor,
                    Menu.Menu_Title );

                                   (* Display Menu Entries *)

   For I := 1 To Menu.Menu_Size Do
      With Menu.Menu_Entries[I] Do
         Begin
            GoToXY( Menu_Item_Column , Menu_Item_Row );
            Write( Menu_Item_Text );
         End;
                                   (* Highlight Default Choice *)

   Menu_Turn_On( Menu, Menu.Menu_Default );

End   (* Menu_Display_Choices *);

(*----------------------------------------------------------------------*)
(*                Menu_Get_Choice --- Get Menu Choice                   *)
(*----------------------------------------------------------------------*)

Function Menu_Get_Choice( Menu: Menu_Type; Erase_After: Boolean ) : Integer;

(*                                                                      *)
(*     Function:  Menu_Get_Choice                                       *)
(*                                                                      *)
(*     Purpose:   Retrieves Menu Choice from current menu               *)
(*                                                                      *)
(*     Calling Sequence:                                                *)
(*                                                                      *)
(*        Ichoice := Menu_Get_Choice( Menu       : Menu_Type;           *)
(*                                    Erase_After: Boolean ) : Integer; *)
(*                                                                      *)
(*           Menu        --- Currently displayed menu                   *)
(*           Erase_After --- TRUE to erase menu after choice found      *)
(*           Ichoice     --- Returned menu item chosen                  *)
(*                                                                      *)
(*      Calls:   Menu_Click                                             *)
(*               Menu_IBMCh                                             *)
(*               Menu_Turn_Off                                          *)
(*               Menu_Turn_On                                           *)
(*                                                                      *)
(*      Remarks:                                                        *)
(*                                                                      *)
(*         The current menu item is highlighted in reverse video.       *)
(*         It may be chosen by hitting the return key.  Movement        *)
(*         to other menu items is done using the up-arrow and           *)
(*         down-arrow.                                                  *)
(*                                                                      *)

Var
   C       : Char;
   Current : Integer;
   Last    : Integer;

Begin  (* Menu_Get_Choice *)

   Current := Menu.Menu_Default;

   Last    := Current - 1;
   If Last < 1 Then Last := Menu.Menu_Size;

   Repeat  (* Loop until return key hit *)

                                   (* Read a character *)
      Read( Kbd , C );
      Menu_Click;
                                   (* Convert character to menu code *)
      If C = Ch_Esc Then Menu_IBMCh( C );
                                   (* Process character *)
      Case C Of

         Down_arrow,
         Space_bar     : Begin (* Move down menu *)
                            Last    := Current;
                            Current := Current + 1;
                            If Current > Menu.Menu_Size Then
                               Current := 1;
                         End;

         Up_arrow      : Begin (* Move up menu *)
                            Last    := Current;
                            Current := Current - 1;
                            If Current < 1 Then
                               Current := Menu.Menu_Size;
                         End   (* Move up menu *);

         Else
            If C <> ch_cr Then Menu_Beep;

      End (* Case of C *);
                                   (* Highlight new menu choice *)

      If C In [ Up_arrow, Down_arrow, Space_bar ] Then
         Begin
            Menu_Turn_Off( Menu, Last    );
            Menu_Turn_On ( Menu, Current );
         End;

   Until C = ch_cr;

                                   (* Return index of chosen value *)
   Menu_Get_Choice := Current;

                                   (* Erase menu from display      *)
   If Erase_After Then
      Begin                        (* Restore previous screen      *)
         Restore_Screen( Saved_Screen );
                                   (* Restore global colors        *)
         Reset_Global_Colors;
      End;

End   (* Menu_Get_Choice *);
