{ Menu Generator

  Author: Michael H. Hughes;  Hereby placed in the Public Domain

  This program illustrates the function "menu" which generates a very nice
  kind of menu display.  This general type of menu has been common on Wang
  equipment for years, but has not caught on elsewhere.  It is a neater and
  quicker menu than most of the common types.

  Pass the function a list of descriptive lines, and it will display them
  on the screen along with a heading and instructions.  A pointer is displayed
  beside the first line of the list, and that line is highlighted.  Press
  a key (usually the space-bar) and the pointer will drop one line down the
  list.  Another key (backspace) will move the pointer back up the list.
  Pressing any character key will cause the routine to search for a line
  beginning with that character.  If found, the pointer will be positioned
  on that line.  Another key (return/enter) will cause the routine to exit
  and return the number of the selected line.  Another key (ESC) can be used
  as a "cancel" and will return a zero.

  Up to 40 items can be displayed.  The routine will position them
  automatically, using two columns if necessary.

  The routine requires a single-keystroke input routine which does not echo
  to the screen or require a return/enter to respond.  A function suitable
  for MSDOS/PCDOS machines is supplied.

  The display will look better if you insert the necessary code to turn the
  cursor off.

  Note the use of a typeless parameter and an absolute address to pass an
  array of uknown length to the routine.   }






Function menu(number: Integer; Var data): Integer; { Generate Menu Display }

  Const llen = 80;          { screen line length }
        slen = 20;          { maximum number items in one column }
        maxnumber = 40;     { maximum size of list array }

        movedownlist = 32;  { SPACE key }
        moveuplist = 8;     { BACKSPACE key }
        select = 13;        { ENTER/RETURN key }
        cancel = 27;        { ESC key }

  Type listtype=Array[0..maxnumber] Of String[30];

  Var list: listtype Absolute data;
      posn, len, margin, vmargin, maxlen: Integer;
      minlen, count, cnumber: Integer;
      ch: Char;
      chval: Byte;

  Function keyin: Byte;   { Single-Key Input Routine (MSDOS/PCDOS) }
  Var register: Record    { register pack }
       AX,BX,CX,DX,BP,SI,DI,DS,ES,FLAG: Integer;
     End;

  Begin
    register.AX := $0700; { call function 07 }
    MsDos(register);
    keyin := register.AX And $00FF;
  End;


  Begin  { Menu }
    { Display Heading }
    ClrScr; LowVideo;
    write(chr(27),'[5h'); { ANSI cursor-off command; change as required }
    writeln(list[0]);     { Title line }
    For posn:=1 To llen Do write('='); writeln;

    { Compute positioning parameters }
    If number > maxnumber Then number:=maxnumber;
    If number <= slen Then len:=number Else len:=slen;
    If number > slen Then margin:=1
    Else
      Begin
        maxlen:=0; minlen:=80;
        For count:=1 to number Do
          Begin
            If length(list[count]) > maxlen Then maxlen:=length(list[count]);
            If length(list[count]) < minlen Then minlen:=length(list[count])
          End;
        margin:=34-(maxlen+minlen) Div 4
      End;
    If number <= slen Then vmargin:=(slen-number) Div 2
    Else vmargin:=(slen-(number Div 2)) Div 2;
    vmargin:=vmargin+3;
    If number > slen Then
      Begin
        cnumber:=number Div 2;
        If odd(number) Then cnumber:=cnumber+1
      End
    Else cnumber:=number;

    { Display list }
    For posn:=1 To cnumber Do
      Begin
        gotoxy(margin+4,posn+vmargin);
        write(list[posn]);
        If ((number) > slen) And ((posn+cnumber) <= number) Then
          Begin
            gotoxy(45,posn+vmargin);
            write(list[posn+cnumber])
          End;
        writeln
      End;

    { Display Instructions }
    gotoxy(1,24); NormVideo;
    writeln('Press SPACE or BACKSPACE or First Letter of Line to Select');
    write('Press RETURN or ENTER to run your choice');

    { Pick menu }
    posn:=1;
    Repeat
      { Display current pick }
      gotoxy(((posn-1) Div cnumber)*40+margin,
                ((posn-1) Mod cnumber)+1+vmargin);
      NormVideo;
      write(' -> ',list[posn]);
      { Get Keyboard and clear current pick }
      chval:=keyin;
      gotoxy(((posn-1) Div cnumber)*40+margin,
                ((posn-1) Mod cnumber)+1+vmargin);
      LowVideo;
      If chval <> select Then write('    ',list[posn]);

      { Determine new Pick }
      If chval = select Then menu:=posn
      Else If chval = cancel Then menu:=0
      Else If chval = moveuplist Then
        If posn > 1 Then posn:=posn-1
        Else posn:=number
      Else If chval = movedownlist Then
        If posn < (number) Then posn:=posn+1
        Else posn:=1
      Else   { Check for first character of line }
        Begin
          count:=posn;
          ch:=UpCase(chr(chval));
          Repeat
            count:=succ(count);
            If count > number Then count:=1
          Until (count = posn) Or (ch = UpCase(copy(list[count],1,1)));
          posn:=count
        End
    Until (chval = select) Or (chval = cancel)
  End;



{ The following program illustrates the use of MENU as a basic program loader.
  The typed constants are used to set up the menu, and can be changed to
  provide any desired choice.  The program list is simply in the same order as
  the descriptive lines.  Lines[0] defines the menu heading while progs[0] is
  the program to be loaded if the "cancel" key is pressed.  }


Const maxlines = 4; { number of lines in menu }

      lines: Array[0..maxlines] Of String[30] =
         ('General Business Menu',
          'Accounts Receivable',
          'Accounts Payable',
          'Payroll',
          'General Ledger');

      progs: Array[0..maxlines] Of String[30] =  { list of programs to load }
         ('MAINMENU.COM',
          'AR.COM',
          'AP.COM',
          'PA.COM',
          'GL.COM');

Var nextprog: File;

Begin  { Main Program }
  assign(nextprog,progs[menu(maxlines,lines)]);
  execute(nextprog)
End.

