{***********************************************************************}
{									}
{ __ReadMnu   Return a menu selection					}
{									}
{ function  __ReadMnu(	MenuPtr : _MenuPtr;				}
{		      FirstItem : _ItemPtr;				}
{			Options : word; 				}
{			var Key : _Keys) : _ItemPtr;			}
{									}
{	      MenuPtr	Pointer to the menu descriptor returned by	}
{			__MakeMnu when the menu was constructed.	}
{	      FirstItem Pointer to an _Item record upon which the high- }
{			light bar will initially rest (NIL for default).}
{	      Options	Bit flags for options (see below).		}
{	      Key	Variable of type _Keys which represents the	}
{			last key pressed when the menu was transmitted. }
{			Key may be used as an index into the		}
{			Unit_Key._KeyTable array of type _KeySeq to	}
{			obtain the character and scancode.		}
{									}
{ Description This function returns the menu selection	entered by the	}
{	      user at the keyboard.  Typically, the highlight bar is	}
{	      moved to an item to make a selection and the menu 	}
{	      transmitted by pressing return, or a selection is made	}
{	      by pressing a key corresponding to an item's highlighted	}
{	      letter or number.  Items are defined by __ItemMnu, and	}
{	      the actions of various keystrokes by __KeyMnu.  __ReadMnu }
{	      returns a pointer to the menu item selected, or NIL if an }
{	      error is encountered or the selection is aborted. 	}
{									}
{	      Before calling __ReadMnu, a menu record (of type _Menu)	}
{	      must be created and 'linked' (with pointers in		}
{	      the _Menu record structure) to two linked lists:	one of	}
{	      item records (type _Item) and one of key records (type	}
{	      _Key).  The menu must also be associated with the window	}
{	      (type Unit_win._Window) in which it be displayed. 	}
{	      These structures, which together define a single menu),	}
{	      are created by calls to __MakeWin, __MakeMnu, __ItemMnu	}
{	      or __LItemMnu, and __KeyMnu.  Alternatively, a single	}
{	      call to __BuildMnu will create all the structures 	}
{	      required for a vertical or horizontal menu, using a	}
{	      parameter of type _MenuDefaults to define many of the	}
{	      menu's characteristics.  Finally, before calling		}
{	      __ReadMnu, the menu must be displayed with __DispMnu.	}
{									}
{	      __ReadMnu works with any style (horizontal, vertical,	}
{	      Lotus or grid), and provides several options as defined	}
{	      by the Options parameter.  One or more of the following	}
{	      may be specified: 					}
{									}
{	      _MDEFAULT_MNU  Use none of the following options. 	}
{	      _MREMOVE_MNU   Removes the menu from the screen on	}
{			     transmit.					}
{	      _MKEEP_HIGHLIGHT_MNU					}
{			     Highlighted item remains so when menu is	}
{			     transmitted (until menu is removed from	}
{			     the screen).				}
{	      _MBEEP_TRANSMIT_MNU					}
{			     Beep sounds on transmit (as defined by	}
{			     _TransmitPitch and _TransmitDelay).	}
{	      _MBEEP_UNKNOWN_MNU					}
{			     Beep sounds on undefined keystrokes (as	}
{			     defined by _UnknownPitch and _UnknownDelay)}
{	      _MTRANSMIT_UNKNOWN_MNU					}
{			     Menu is transmitted on unknown keystrokes. }
{	      _MTRANSMIT_ALL_MNU					}
{			     Menu is transmitted after every keystroke. }
{			     This option allows you to examine every	}
{			     keystroke while the menu remains on the	}
{			     screen.					}
{	      _MTRANSMIT_MNU Menu is transmitted immediately.  Used to	}
{			     reposition highlight bar.			}
{									}
{	      Overview of __ReadMnu program control:			}
{									}
{		1.  Validate input parameters/menu integrity;		}
{		    exit on error, setting _MenuError and returning	}
{		    NIL as the function value.				}
{									}
{		2.  Repeat						}
{		      Get a keystroke from the user			}
{		      Check keystroke against menu's list of _Keys	}
{		      If found						}
{			 Respond, based on found _Key definition	}
{			 (move highlight bar, transmit, abort, etc.)	}
{		      Else						}
{			 Respond, based on Options			}
{			 (do nothing, beep, etc.)			}
{		    Until menu is transmitted.				}
{									}
{		3.  Cleanup, depending on options (remove menu from	}
{		    screen, beep, etc.) and set function return 	}
{		    value to pointer to _Item selected, or NIL if	}
{		    menu is aborted.  Also return last key pressed.	}
{									}
{	      On error _MenuError may be set to:  _InvalidWindow	}
{						  _InvalidMenu		}
{						  _InvalidKey		}
{						  _InvalidItem		}
{						  _NoMenuExit		}
{						  _NoMenuItems		}
{									}
{ Version     4.00 (C)Copyright Blaise Computing Inc. 1987		}
{_______________________________________________________________________}

function __ReadMnu(  MenuPtr : _MenuPtr;
		   FirstItem : _ItemPtr;
		      Options: word;
		     var Key : _Keys) : _ItemPtr;

  {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  {  RetCorr_Keys  Return a _Keys type that corresponds to a particular }
  {  combintation of character and scan code. Given ChSc, search the	}
  {  array Unit_Key._Keytable for a match.  It is the index into the	}
  {  enumerated type _Keys.						}
  {_____________________________________________________________________}

  function RetCorr_Keys(ChSc: _KeySeq) : _Keys;

    var
      Index : word;

    begin {RetCorr_Keys}

      Index := 0;
      while ((word(_KeyTable[_Keys(Index)]) <> word(ChSc)) and
						(word(ChSc) <> $A300)) do
	Inc(Index);
      RetCorr_Keys := _Keys(Index)

    end;  { RetCorr_Keys }


  {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  {  FindKey	   Find a key record in the linked list of keys based	}
  {  on its unique character, scan code.				}
  {_____________________________________________________________________}

  function FindKey(MenuPtr : _MenuPtr; ChSc : _KeySeq) : _KeyPtr;

    var
      Found   : boolean;
      TempPtr : _KeyPtr;

    begin {FindKey}

      Found   := FALSE;
      FindKey := NIL;

      TempPtr := MenuPtr^._MKeysPtr;

      while ((TempPtr <> NIL) and (not Found)) do
	if (TempPtr^._KChSc._ChScan = ChSc._ChScan) then
	   begin
	     Found   := TRUE;
	     FindKey := TempPtr
	   end
	else
	   TempPtr := TempPtr^._NextKey

    end;  {FindKey}


  {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  {  NextIPtr	   Find an item record in the linked list of items	}
  {  specified by the _Motion field of a _Key record:			}
  {									}
  {  _Next and	     Simply default to the next or previous item in the }
  {  _Previous	     linked list, as pointed to by the _NextItem and	}
  {		     _PrevItem fields.	Wrap to the other end of at	}
  {		     head or tail.					}
  {									}
  {  _First and      Select the head or tail of the list, respectively, }
  {  _Last	     unless that item is protected or not visible, in	}
  {		     which case search the list (forward or backward	}
  {		     as appropriate) for the first visible, un- 	}
  {		     protected item.					}
  {									}
  {  _NextCh	     Select the next item whose highlighted letter	}
  {		     is equal to Ch (upper or lower case).  Search the	}
  {		     list forward from the current item, wrapping to	}
  {		     the head of the list if necessary.  If no match is }
  {		     found, return the current item.			}
  {									}
  {  _Up, _Down,     Select the next item in the direction, unless	}
  {  _Right, _Left   the current item is last in the chosen direction,	}
  {		     or no items can be found on direction side of	}
  {		     the current item, in which case wrap around and	}
  {		     return the furthest item in the opposite		}
  {		     direction.   For an item to be chosen, it must	}
  {		     begin in the same column (_Up and _Down) or row	}
  {		     (_Right and _Left) as the current item.		}
  {									}
  { _Goto	     Select the item pointed to by the _KeyItem field	}
  {		     of CurKey.  If the pointer is NIL or invalid, set	}
  {		     this function's return value to NIL and set the	}
  {		     _MenuError variable.  If the item pointed to is	}
  {		     protected or not visible, return the current item. }
  {_____________________________________________________________________}

  function NextIPtr(CurKey  : _KeyPtr;
		    CurItem : _ItemPtr;
		    MenuPtr : _MenuPtr;
		    Ch	    : char) : _ItemPtr;

    type
      CRArray = array[0..1] of shortint;

    var
      Cur, Tmp		: CRArray;
      TempPtr		: _ItemPtr;
      Axis, Cross	: word;
      DirFlag		: integer;
      TempStr		: string;
      HiCh		: char;
      Size,Save,Current : integer;

    begin {NextIPtr}

      case CurKey^._Motion of

	_Up,_Down,_Left,_Right :
	  begin

	    { Set Axis for row (right and left arrows) or column	}
	    { (up and down).  Also get size of window.			}

	    with CurKey^ do
	      begin
		if ((_Motion = _Up) or (_Motion = _Down)) then
		   begin
		     Axis := 1;        { Refer to columns		}
		     Size := MenuPtr^._MWinPtr^._Dim._YLower -
			     MenuPtr^._MWinPtr^._Dim._YUpper + 1
		   end
		else
		   begin	       { Refer to rows			}
		     Axis := 0;
		     Size := MenuPtr^._MWinPtr^._Dim._XLower -
			     MenuPtr^._MWinPtr^._Dim._XUpper + 1
		   end;

		Cross := 1 - Axis;     { Refer to the other		}

		if ((_Motion = _Up) or (_Motion = _Left)) then
		   DirFlag := 1
		else
		   DirFlag := -1

	      end;

	    TempPtr  := MenuPtr^._MItemsPtr;
	    NextIPtr := CurItem;
	    Cur      := CRArray(CurItem^._ColRow);
	    Save     := size;

	    { Now scan linked list of items, looking for active, unpro- }
	    { tected items on the same axis (column or row).  Starting	}
	    { with the current item, find the item which is logically	}
	    { next in the direction chosen--wrapping to the other end	}
	    { of the window if necessary.				}

	    repeat
	      Tmp := CRArray(TempPtr^._ColRow);

	      { Skip protected, non-active items			}

	      if ((not TempPtr^._Protect) and (TempPtr^._Active)) then
		 if (Cur[Axis] = Tmp[Axis]) then
		    begin
		      Current := (DirFlag * (Cur[Cross] -
				       Tmp[Cross]) + Size - 1) mod Size;
		      if (Current < Save) then
			 begin
			   NextIPtr := TempPtr;
			   Save     := Current
			 end
		     end;
	      TempPtr := TempPtr^._NextItem;
	    until (TempPtr = NIL);

	    Exit		       { NextIPtr has been set		}

	  end; { _Up,_Down,_Left,_Right }

	_Next:
	  repeat
	    CurItem := CurItem^._NextItem;
	    if (CurItem = NIL) then
	       CurItem := MenuPtr^._MItemsPtr
	  until ((not CurItem^._Protect) and (CurItem^._Active));
	_Previous:
	  repeat
	    CurItem := CurItem^._PrevItem;
	    if (CurItem = NIL) then
	       CurItem := MenuPtr^._MITailPtr;
	  until ((not CurItem^._Protect) and (CurItem^._Active));
	_First:
	  begin
	    CurItem  := MenuPtr^._MItemsPtr;
	    while ((CurItem^._Protect) or (not CurItem^._Active)) do
		   CurItem := CurItem^._NextItem
	  end;
	_Last:
	  begin
	    CurItem := MenuPtr^._MITailPtr;
	    while ((CurItem^._Protect) or (not CurItem^._Active)) do
		  CurItem := CurItem^._PrevItem
	  end;
	_NextCh:
	  begin
	    TempPtr := CurItem;
	    repeat
	      TempPtr := TempPtr^._NextItem;
	      if (TempPtr = NIL) then
		 TempPtr := MenuPtr^._MItemsPtr;
	      TempStr := string(TempPtr^._ItemStrPtr^);
	      HiCh    := TempStr[TempPtr^._HiChPos]
	    until ( ((TempPtr = CurItem) or
			      (UpCase(HiCh) = UpCase(Ch))) and
					(TempPtr^._Active) and
					       (not TempPtr^._Protect) );
	    CurItem := TempPtr
	  end;
	_Goto:
	  if ((CurKey^._KeyItem = NIL) or
			  (not ValidSig(CurKey^._KeyItem,ITEMSIG_))) then
	     CurItem := NIL
	  else
	     if ((not CurKey^._KeyItem^._Protect) and
					(CurKey^._KeyItem^._Active)) then
		CurItem := CurKey^._KeyItem;

      end;  {case CurKey^._Motion}

      NextIPtr := CurItem

    end;  {NextIPtr}


  {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  {  MoveBar	   Move the menu highlight bar, which means writing the }
  {  highlight attributes at its new position and the window attributes }
  {  at its old position.  Also change the Lotus description line if	}
  {  there is one.							}
  {_____________________________________________________________________}

  procedure MoveBar(CurItemPtr,PrevItemPtr : _ItemPtr);

    begin {MoveBar}

      { First place highlight bar on current menu item. 		}

      if ((PrevItemPtr <> CurItemPtr) or (PrevItemPtr = NIL)) then
	with CurItemPtr^ do
	  __WrAttWin(_Col,_Row,_Len,MenuPtr^._BarFore,MenuPtr^._BarBack);

      { Next remove highlight bar from previous position:		}

      if ((PrevItemPtr <> CurItemPtr) and (PrevItemPtr <> NIL)) then
	with PrevItemPtr^ do
	  begin
	    __WrAttWin(_Col,_Row,_Len,
		       MenuPtr^._MWinPtr^._Fore,
		       MenuPtr^._MWinPtr^._Back);
	    if (_HiChPos > 0) then
	       __WrAttWin(_Col + _HiChPos - 1, _Row,1,_HiChFore,_HiChBack)
	  end;

      { Now write out Lotus-style description, if any.			}

      if (PrevItemPtr <> NIL) then     { Remove previous message	}
	with PrevItemPtr^ do
	  if (_LDescPtr <> NIL) then   { Something to remove		}
	     __FastWin(_LDescCol,
		       _LDescRow,
		       Ptr(_VectorAddr(_LDescPtr)._Seg,
			   _VectorAddr(_LDescPtr)._Ofs + 1),
		       _LDescLen,
		       MenuPtr^._LDescBack,
		       MenuPtr^._LDescBack,1);

      with CurItemPtr^ do
	if (_LDescPtr <> NIL) then
	   __FastWin(_LDescCol,
		     _LDescRow,
		     Ptr(_VectorAddr(_LDescPtr)._Seg,
			 _VectorAddr(_LDescPtr)._Ofs + 1),
		     _LDescLen,
		     MenuPtr^._LDescFore,
		     MenuPtr^._LDescBack,1);

  end;	{ MoveBar }


  var
    CurItemPtr	: _ItemPtr;
    PrevItemPtr : _ItemPtr;
    HoldItem	: _ItemPtr;
    KPtr	: _KeyPtr;
    ChSc	: _KeySeq;
    Transmitted : boolean;
    Found	: boolean;
    CursorOff	: boolean;
    Check	: boolean;

  begin { __ReadMnu }

    __ReadMnu := NIL;
    Key       := _Key_c_2;		{ Initially NUL 		}

    { First determine whether we are pointing at a valid menu:		}

    if (not ValidSig(MenuPtr,MENUSIG_)) then
       Exit;

    { Make sure the menu provides a way to exit.  Either Options pro-	}
    { vides that every keystroke transmits the menu, or one of the	}
    { _Key records defines a keystroke which will transmit or abort the }
    { menu.								}

    if (Options and _MTRANSMIT_ALL_MNU = 0) then
       begin
	 KPtr := MenuPtr^._MKeysPtr;
	 Found := FALSE;

	 repeat
	   if ( ((KPtr^._Action and _KTRANSMIT_MNU <> 0) or
	      (KPtr^._Action and _KABORT_MNU	<> 0)) and
	      (KPtr^._Action and _KDISABLE_MNU = 0) ) then
	      Found := TRUE
	   else
	      Found := FALSE;
	   KPtr := KPtr^._NextKey;
	until ((KPtr = NIL) or (Found));

	if (not Found) then
	   begin
	     SetMenuError(_NoMenuExit);
	     Exit
	   end;
       end;

    { First ensure that the menu has an items list attached.		}

    if (MenuPtr^._MItemsPtr = NIL) then
       begin
	 SetMenuError(_NoMenuItems);
	 Exit
       end;

    { Validate FirstItem:  It must have a proper item signature, and it }
    { must be part of the correct linked list of items--that is, it must}
    { be attached to the menu we are about to read.			}

    if (FirstItem <> NIL) then
       begin
	 CurItemPtr := FirstItem;
	 while (CurItemPtr^._PrevItem <> NIL) do
	   CurItemPtr := CurItemPtr^._PrevItem;  { Find head		}
	 if ((CurItemPtr <> MenuPtr^._MItemsPtr) or
	    (not ValidSig(FirstItem,ITEMSIG_))) then
	    begin
	      BeepUnknown;
	      SetMenuError(_InvalidItem);
	      Exit
	    end
       end;

    { If FirstItem is set to NIL, we default to the first in the list	}
    { (a convenience for simple menus): 				}

    if (FirstItem = NIL) then
       CurItemPtr := MenuPtr^._MItemsPtr
    else
       CurItemPtr := FirstItem;

    { Finally check to see if the initial item initial item is un-	}
    { protected and currently visible.	If it is neither, find the	}
    { first unprotected, active item, starting with FirstItem.	If	}
    { there is no place for the highlight bar to go, set an error code	}
    { and exit. 							}

    if ((CurItemPtr^._Protect) or (not CurItemPtr^._Active)) then
       begin
	 HoldItem := CurItemPtr;
	 repeat
	   CurItemPtr	:= CurItemPtr^._NextItem;
	   if (CurItemPtr = NIL) then			{ At end, so	}
	      CurItemPtr := MenuPtr^._MItemsPtr 	{ back to start.}
	 until ( ((not CurItemPtr^._Protect) and (CurItemPtr^._Active))
		 or (CurItemPtr = HoldItem) );
	if (CurItemPtr = HoldItem) then
	   begin
	     SetMenuError(_NoMenuItems);
	     Exit
	   end
       end;

    { Make the menu current; if it is not possible (it has not been	}
    { displayed), return a function value of NIL, set the returned	}
    { _KeySeq parameter's fields to the NUL character and 0, and set	}
    { _MenuError appropriately. 					}

    if (not __TopWin(MenuPtr^._MWinPtr)) then
       begin
	 SetMenuError(_InvalidWindow);
	 Exit
       end;

    { Remember whether cursor was on, and turn it off unless requested	}
    { to leave it on.							}

    CursorOff := not (__IsCurScn);
    __COffScn(Options and _MCURSORON_MNU = 0);

    { The following repeat loop accepts keystrokes from the user, looks }
    { up the character/scan code combinations in the linked list of keys}
    { and takes appropriate action, based on the contents of the _Key	}
    { record:  moving the highlight bar to a different item, selecting	}
    { a menu item, aborting the menu, etc.				}

    Transmitted := FALSE;
    PrevItemPtr := NIL;
    MoveBar(CurItemPtr,PrevItemPtr);	{ Initial highlight bar 	}

    if (Options and _MTRANSMIT_MNU <> 0) then
       ChSc._ChScan := $0300
    else
       repeat

	 ChSc._Ch  := __ExInKey(TRUE,ChSc._ScanCode);  { Get a keystroke}

	 KPtr := FindKey(MenuPtr,ChSc); 	   { Look for key record}
	 if ((KPtr <> NIL) and			   { Found a match.	}
	    (not (KPtr^._Action and _KDISABLE_MNU <> 0)))  then
	    begin

	      Transmitted := (KPtr^._Action and _KTRANSMIT_MNU <> 0) or
		  (Options and _MTRANSMIT_ALL_MNU <> 0)   or
			     (KPtr^._Action and _KABORT_MNU <> 0);
	      if (KPtr^._Action and _KABORT_MNU <> 0) then
		 CurItemPtr := NIL;
	      if (KPtr^._Action and _KBEEP_MNU <> 0) then
		 BeepKnown;

	      if (KPtr^._Motion <> _NoMove) then
		 begin
		   PrevItemPtr := CurItemPtr;
		   CurItemPtr  := NextIPtr(KPtr,CurItemPtr,
						       MenuPtr,ChSc._Ch);
		   if (CurItemPtr = NIL) then
		      begin
			CurItemPtr := PrevItemPtr; { Don't move 	}
			SetMenuError(_InvalidItem) { Set error; no exit }
		      end
		 end;
	    end {key found}
	 else
	    begin
	      if (Options and _MBEEP_UNKNOWN_MNU <> 0) then
		 BeepUnknown;
	      Transmitted := (Options and _MTRANSMIT_UNKNOWN_MNU <> 0)
	    end;

	 MoveBar(CurItemPtr,PrevItemPtr)

       until (Transmitted);

    __ReadMnu := CurItemPtr;	       { Returns NIL for _KABORT_MNU	}
    MenuPtr^._MLastPtr := CurItemPtr;

    if ((Options and _MKEEP_HIGHLIGHT_MNU) = 0) then
       with CurItemPtr^ do
	 begin
	   __WrAttWin(_Col,_Row,_Len,
		      MenuPtr^._MWinPtr^._Fore,MenuPtr^._MWinPtr^._Back);
	   if (_HiChPos > 0) then
	      __WrAttWin(_Col + _HiChPos - 1, _Row,1,_HiChFore,_HiChBack)
	 end;

    if ((Options and _MBEEP_TRANSMIT_MNU) <> 0) then
       BeepTransmit;

    if ((Options and _MREMOVE_MNU) <> 0) then
       Check := __RemMnu(MenuPtr);

    Key := RetCorr_Keys(ChSc);	       { Find the corresponding key name}
    __COffScn(CursorOff)	       { Reestablish cursor visibility	}

  end; {__ReadMnu}
