[Back to MENU SWAG index]  [Back to Main SWAG index]  [Original]

{
I saw someone could use a menu unit like the one used with RA/FD/ALLFIX..
Well... I have one... :

úoO BEGIN Menus.Pas Ooú
(* This unit is (c) 1995 by Archangel/DMA
   You can use this unit, or parts of it in your own programs as
   long as you mention my name somewhere (I didn't code it all for
   fun you know =])

   It's a pretty straightforward unit, if you don't get it, try to
   look at the code some more and probably you'll understand. Or you
   can read the comments.

   About comments, there are not much comments since I'm not good at
   commenting my own sources...

*)
{$G+,F+,X+,V-,R-,O+}
{$M 8192,0,128000}                           { Set up some local stack space }
Unit RAMenu;
Interface
Type
  SaveRecord = Record                         { Record used for 'pushscreen' }
                 Case UseDisk: Boolean of                        { Use disk? }
                   TRUE : (FName: string[12]);              { Yes? What file }
                   FALSE: (MemPtr: Pointer);                    { No? Where? }
               End;
  SaveStackType = array[1..50] of SaveRecord;                   { Save stack }
  SubMenuRecord = record                     { Record used to store submenus }
                    ItemName : string[40];        { Name of item (displayed) }
                    ItemProc : Procedure;     { Pointer to procedure to exec }
                    ItemHelp : string[79];                    { The helpline }
                  end;
  SubMenuType = array[1..20] of SubMenuRecord;                 { One submenu }
  MainMenuRecord = record             { Record used to store main menu items }
                     ItemName : string[20];
                     ItemHelp : string[79];
                     SubMenu  : SubMenuType;      { Submenu of this mainmenu }
                   end;
  MainMenuType = array[1..10] of MainMenuRecord;  { The total menu structure }
  VRecord = record                                       { Video memory type }
              VChar : char;
              VAttr : byte;
            End;
  VMemType = array[1..25,1..80] of VRecord;                   { Video memory }
  TabType = array[1..10] of Byte;                             { A table type }
  TabType2 = array[1..20] of Byte;                            { A table type }

Var
  Menu        : ^MainMenuType;
  XTab        : TabType;
  SubRemP     : TabType2;
  ColorMem    : VMemType absolute $B800:0000;
  MonoMem     : VMemType absolute $B000:0000;
  Mono        : Boolean;
  VideoSegment: Word;
  OldExit     : Pointer;
  CommentColor: byte;                                   { Color of helplines }
Const
  StackMaxMem : Byte=10;

(* STACKMAXMEM

   This is something to consider, this constant holds the amount of screens
   saved in memory before the unit starts saving on disks. Since I've only
   built in total screen saving, 10 screens will take up 40000 bytes. If
   you need that memory either set it to a lower value or set it to '1'.

*)

Function GetAttr(X,Y: Byte): Byte;
{ Get attribute from a position }
Procedure SetAttr(X,Y,Attr: byte);
{ Set attribute on a position }

Procedure VWriteCh(Ch: char;x,y: byte);
{ Write a character to the video memory with the attribute stored in
  'textattr'}
Procedure VWriteStr(S: string;fg,bg,x,y: byte);
{ Write a string to the video memory with the colors 'fg' and 'bg' and
  start writing on 'x', 'y' }
Procedure Color(fg,bg: byte);
{ Set the current colors to 'fg' and 'bg' }

Procedure DrawBox(x1,y1,x2,y2,bfg,bbg,wfg,wbg: byte;Title: string;TFG,TBG:
byte);{ Draw a box, variables are:

  x1,y1,x2,y2: Upper left and lower right corners
  bfg,bbg    : Color of the box
  wfg,wbg    : Color of the inside of the box
  Title      : A title to give to the box, special chars are:
               '!'  - Put title on upper left side
               '@'  - Centre title on upper side
               '#'  - Put title on upper right side
  Tfg,Tbg    : Color of the title

  Example:

  DrawBox(1,1,80,25,11,0,7,0,'!Upper left',15,0);
}

Procedure ErrorMessage(Message: string);
{ Displays an errormessage }
Procedure Message(Message: string);
{ Displays a normal message }
Function AskBox(S,T: String;bfg,bbg,tfg,tbg: Byte): Boolean;
{ Ask a Yes/No question, returns TRUE on yes }

Procedure CursorOff;
{ Turns cursor off }
Procedure CursorOn;
{ Turns cursor on }

Function TopMenu: Byte;
{ Start the mainmenu }
Procedure SubMenu(Var Which: Byte);
{ Execute a submenu }
Procedure SetXTab;
{ Used to get a table for the menu positions }

Procedure PopScreen;
{ Save a screen to savestack}
Procedure PushScreen;
{ Restore a screen from savestack }

Procedure HelpLine(S: string);
{ Prints a helpline on line 25 }
Procedure ClearHelp;
{ Clear line 25}

Implementation
Uses Crt,Dos;
Var
  SaveCurHi   : Byte; { High scan line of cursor }
  SaveCurLo   : Byte; { Low scanline of cursor }

  RemTop      : Byte; { Temp save for top menu position }
  SaveStack   : ^SaveStackType; { Screen save stack }
  SaveStackPtr: Byte; { Screen save stack pointer }

Procedure WaitKey;  { Waits for a key }
Begin
  ReadKey;
End;

Function FStr(A: Longint): string; { Turns a longint to a string }
Var
  Temp : string;
Begin
  Str(A,Temp);
  FStr :=Temp;
End;

Function FVal(S: string): Longint; { Turns a string into a longint }
Var
  Temp: Longint;
  Code: Integer;
Begin
  Val(S,Temp,Code);
  FVal :=Temp;
End;

Function ForceBack(s: string): string; { Adds a '\' to a string if it's not
there }Begin
  If S[length(s)]<>'\' then S :=s+'\';
  ForceBack :=s;
End;

Function LZ(w : Word) : String;
Var
  S : String;
begin
  Str(w:0,s);
  if Length(s)=1 then s := '0' + s;
  LZ := s;
end;

Procedure ClrScr; { Clears the screen, keeping the current unit colors }
Var
  Bak : byte;
Begin
  Bak :=TextAttr;
  TextColor(7);
  TextBackGround(0);
  Crt.ClrScr;
  TextAttr :=Bak;
End;

Function Expand(S: string;Len: Byte): String; { Expand a string }
Begin
  Expand :=S;
  If Length(S)>Len then Exit;
  While Length(s)<Len do S :=S+' ';
  Expand :=S;
End;

Function BasePath: String; { Get the program's own path }
Var
  P: PathStr;
  D: DirStr;
  N: NameStr;
  E: ExtStr;
Begin
  P :=ParamStr(0);
  FSplit(P,D,N,E);
  BasePath :=ForceBack(FExpand(D));
End;

Procedure SetXTab; { Set the XTab for the menus }
Var
  Tel : byte;
Begin
  For Tel :=2 to 10 do XTab[Tel]
:=XTab[Tel-1]+Length(Menu^[Tel-1].ItemName)+2;End;

Procedure PushScreen;
Var
  SaveStackFile : File of VMemType;
Begin
  If SaveStackPtr=50 then
  Begin
    ErrorMessage('Screen save stack overflow');
    Halt(10);
  End;
  If (MaxAvail<10000) or (SaveStackPtr>StackMaxMem) then
  With SaveStack^[SaveStackPtr] do
  Begin
    UseDisk :=TRUE;
    FName :=BasePath+'SAV'+FStr(SaveStackPtr)+'.TMP';
    Assign(SaveStackFile,FName);
    {$i-}
    Rewrite(SaveStackFile);
    {$i+}
    If IOResult<>0 then
    Begin
      ErrorMessage('Cannot open temporary file for writing');
      Halt(10);
    End;
    Case Mono of
      TRUE : Write(SaveStackFile,MonoMem);
      FALSE: Write(SaveStackFile,ColorMem);
    End;
    Close(SaveStackFile);
  End
  Else With SaveStack^[SaveStackPtr] do
  Begin
    GetMem(MemPtr,4000);
    Case Mono of
      TRUE : Move(MonoMem,MemPtr^,4000);
      FALSE: Move(ColorMem,MemPtr^,4000);
    End;
  End;
  Inc(SaveStackPtr);
End;

Procedure PopScreen;
Var
  SaveStackFile : File of VMemType;
  Temp          : VMemType;
Begin
  If SaveStackPtr=1 then Exit;
  Dec(SaveStackPtr);
  With SaveStack^[SaveStackPtr] do
  Begin
    If UseDisk then
    Begin
      Assign(SaveStackFile,FName);
      {$i-}
      Reset(SaveStackFile);
      {$i+}
      If IOResult<>0 then
      Begin
        ErrorMessage('Cannot open temporary file for reading');
        Halt(10);
      End;
      Read(SaveStackFile,Temp);
      Close(SaveStackFile);
      Case Mono of
        TRUE : Move(Temp,MonoMem,4000);
        FALSE: Move(Temp,ColorMem,4000);
      End;
    End else
    Begin
      Case Mono of
        TRUE : Move(MemPtr^,MonoMem,4000);
        FALSE: Move(MemPtr^,ColorMem,4000);
      End;
      FreeMem(MemPtr,4000);
      MemPtr :=NIL;
    End;
  End;
End;

Procedure CursorOff; assembler;
ASM
  MOV           AX,0300h
  MOV           BH,0
  INT           10h
  MOV           [SaveCurHi],CH
  MOV           [SaveCurLo],CL
  MOV           AX,0100h
  MOV           CX,2000h
  INT           10h
END;

Procedure CursorOn; assembler;
ASM
  MOV           AX,0100h
  MOV           CH,[SaveCurHi]
  MOV           CL,[SaveCurLo]
  INT           10h
END;

Procedure Color(fg,bg: byte);
Begin
  TextColor(Fg);
  TextBackground(bg);
End;

Function GetAttr(X,Y: Byte): Byte;
Begin
  Case Mono of
    TRUE : GetAttr :=MonoMem[Y,X].VAttr;
    FALSE: GetAttr :=ColorMem[Y,X].VAttr;
  End;
End;

Procedure SetAttr(X,Y,Attr: byte);
Begin
  Case Mono of
    TRUE : MonoMem[Y,X].VAttr :=Attr;
    FALSE: ColorMem[Y,X].VAttr :=Attr;
  End;
End;

Function MakeAttr(Fg,Bg: Byte): Byte; { Creates an attribute out of a
foreground/background color }Begin
  MakeAttr :=Fg+16*Bg;
End;

Procedure SetAttrRange(X1,X2,Y,Attr: Byte); { Sets the attribute over a range
}Var
  Tel : Byte;
Begin
  For Tel :=1 to x2-x1 do SetAttr(x1-1+Tel,Y,Attr);
End;

Procedure VWrite(Ch: char;x,y: byte);
Begin
  Case Mono of
    TRUE : With MonoMem[Y,X] do
           Begin
             VChar :=Ch;
             VAttr :=TextAttr;
           End;
    FALSE: With ColorMem[Y,X] do
           Begin
             VChar :=Ch;
             VAttr :=TextAttr;
           End;
  End;
End;

Procedure VWriteCh(Ch: char;x,y: byte);
Begin
  VWrite(ch,x,y);
End;

Procedure VWriteStr(S: string;fg,bg,x,y: byte);
Var
  Tel : byte;
  Bak : byte;
Begin
  Bak :=TextAttr;
  Color(Fg,Bg);
  For Tel :=1 to length(s) do VWrite(S[Tel],x-1+tel,y);
  TextAttr :=Bak;
End;

{ Returns the appropriate shade color for the 'drawbox' routine }
Function ReturnShade(X,Y: byte): Byte;
Var
  TA : Byte;
  FG : Byte;
  BG : Byte;
Begin
  TA :=GetAttr(x,y);
  BG :=TA SHR 4;
  FG :=TA-BG;
  If Fg>8 then
  Begin
    Dec(Fg,8);
    Bg :=0;
  End
  else
  Begin
    Fg :=8;
    Bg :=0;
  End;
  ReturnShade :=Fg+(16*Bg);
End;

Procedure DrawBox(x1,y1,x2,y2,bfg,bbg,wfg,wbg: byte;Title: string;TFG,TBG:
byte);Var
  Tel,Tel2: byte;
  A,B: Word;
Begin
  A :=WindMax;
  B :=WindMin;
  Color(wfg,wbg);
  Window(x1,y1,x2,y2);
  Crt.ClrScr;
  WindMax :=A;
  WindMin :=B;
  Color(bfg,bbg);
  For Tel :=1 to x2-x1 do
  Begin
    VWriteCh('Í',x1-1+tel,y1);
    VWriteCh('Í',x1-1+tel,y2);
  End;
  For Tel :=1 to y2-y1 do
  Begin
    Color(bfg,bbg);
    VWriteCh('³',x1,y1-1+tel);
    Color(bfg,bbg);
    VWriteCh('³',x2,y1-1+tel);
  End;
  VWriteCh('¾',x2,y2);
  VWriteCh('¸',x2,y1);
  VWriteCh('Ô',x1,y2);
  VWriteCh('Õ',x1,y1);
  For Tel :=1 to x2-x1 do SetAttr(x1+Tel,y2+1,ReturnShade(x1+Tel,y2+1));
  For Tel :=1 to (y2-y1)+1 do SetAttr(x2+1,y1+Tel,ReturnShade(x2+1,y1+Tel));
  If Title<>'' then
  Begin
    If Title[1]='!' then VWriteStr(' '+Copy(Title,2,Length(Title)-1)+'',Tfg,Tbg,x1+2,y1);
    If Title[1]='@' then VWriteStr(''+Copy(Title,2,Length(Title)-1)+' ',Tfg,Tbg,x2-Length(Title)-2,y1);
    If Title[1]='#' then VWriteStr(' '+Copy(Title,2,Length(Title)-1)+'',Tfg,Tbg,x1+((x2-x1) div 2)-(Length(Title) div 2),y1);
    End;
End;

Procedure HelpLine(S: string);
Begin
  VWriteStr(Expand(S,79),CommentColor,0,2,25);
End;

Procedure ClearHelp;
Begin
  VWriteStr(Expand(' ',79),7,0,2,25);
End;

Procedure Message(Message: string);
Const
  Prompt = 'Press any key';
Var
  A   : Byte;
Begin
  PushScreen;
  ClearHelp;
  Message :=Message+' - '+Prompt;
  A :=40-(Length(Message) div 2);
  DrawBox(a,11,a+Length(Message)+1,15,12,4,14,4,'',15,4);
  VWriteStr(Message,14,4,a+1,13);
  WaitKey;
  PopScreen;
End;

Procedure ErrorMessage(Message: string);
Const
  Prompt = 'Press any key';
Var
  A   : Byte;
Begin
  PushScreen;
  ClearHelp;
  Message :=Message+' - '+Prompt;
  A :=40-(Length(Message) div 2);
  DrawBox(a,11,a+Length(Message)+1,15,12,4,14,4,'!ERROR',15,4);
  VWriteStr(Message,14,4,a+1,13);
  WaitKey;
  PopScreen;
End;

Function AskBox(S,T: String;bfg,bbg,tfg,tbg: Byte): Boolean;
Var
  A   : Byte;
  Ch  : Char;
Begin
  PushScreen;
  A :=40-(Length(S) div 2);
  DrawBox(a,12,a+Length(S)+1,14,Bfg,Bbg,Tfg,Tbg,T,Tfg,TBg);
  VWriteStr(S,Tfg,Tbg,a+1,13);
  Repeat
    Ch :=UpCase(ReadKey);
  Until Ch in ['Y','N'];
  AskBox :=(Ch='Y');
  PopScreen;
End;

{ Used internally for the submenu's }
Function GetLastX(SubRec: SubMenuType): Byte;
Var
  Temp : Byte;
  Tel  : Byte;
Begin
  Temp :=0;
  For Tel :=1 to 20 do with SubRec[Tel] do If ItemName<>'' then If
Length(ItemName)>Temp then Temp :=Length(ItemName);  GetLastX :=Temp;
End;

{ Used internally for the submenu's }
Function GetLastY(SubRec: SubMenuType): Byte;
Var
  Temp : Byte;
  Tel  : Byte;
Begin
  Temp :=0;
  For Tel :=1 to 20 do With SubRec[Tel] do If ItemName<>'' then Inc(Temp);
  GetLastY :=Temp;
End;

{ Used internally for the main menu }
Function TopItems: Byte;
Var
  Tel : Byte;
  Temp: Byte;
Begin
  Temp :=0;
  For Tel :=1 to 10 do If Menu^[Tel].ItemName<>'' then Inc(Temp);
  TopItems :=Temp;
End;

{ Draws the main menu }
Procedure DrawTop;
Var
  Tel : Byte;
Begin
  For Tel :=1 to TopItems do VWriteStr(Menu^[Tel].ItemName,7,0,XTab[Tel],1);
End;

{ Draws a sub menu }
Procedure DrawMenu(Which: Byte;Var LastX,LastY: Byte);
Var
  Tel    : Byte;
Begin
  LastX :=GetLastX(Menu^[Which].SubMenu);
  LastY :=GetLastY(Menu^[Which].SubMenu);
  DrawBox(XTab[Which],2,XTab[Which]+LastX+1,2+LastY+1,11,0,7,0,'',0,0);
  For Tel :=1 to LastY do
VWriteStr(Menu^[Which].SubMenu[Tel].ItemName,7,0,XTab[Which]+1,2+Tel);
DrawTop;  VWriteStr(Menu^[Which].ItemName,1,3,XTab[Which],1);
End;

Procedure SubMenu(Var Which: Byte);
Var
  MPos  : Byte;
  OPos  : Byte;
  LastY : Byte;
  LastX : Byte;
  TI    : Byte;
  OW    : Byte;
Begin
  DrawTop;
  PushScreen;
  TI :=TopItems;
  MPos :=SubRemP[Which];
  OPos :=MPos;
  OW :=0;
  DrawMenu(Which,LastX,LastY);
  While True Do
  Begin

VWriteStr(Expand(Menu^[Which].SubMenu[MPos].ItemName,LastX),1,7,XTab[Which]+1,2+MPos);
HelpLine(Menu^[Which].SubMenu[MPos].ItemHelp);
    If MPos<>OPos then
VWriteStr(Expand(menu^[Which].SubMenu[OPos].ItemName,LastX),7,0,XTab[Which]+1,2 +OPos);
OPos :=MPos;    OW :=Which;
    Case ReadKey of
      #0: Case ReadKey of
            #80: If MPos<LastY then Inc(MPos) else MPos :=1;
            #77: Begin
                   PopScreen;
                   PushScreen;
                   SubRemP[Which] :=MPos;
                   If Which<TI then Inc(Which) else Which :=1;
                   DrawMenu(Which,LastX,LastY);
                   RemTop :=Which;
                   MPos :=SubRemP[Which];
                   OPos :=MPos;
                 End;
            #75: Begin
                   PopScreen;
                   PushScreen;
                   SubRemP[Which] :=MPos;
                   If Which>1 then Dec(Which) else Which :=TI;
                   DrawMenu(Which,LastX,LastY);
                   RemTop :=Which;
                   MPos :=SubRemP[Which];
                   OPos :=MPos;
                 End;
            #72: If MPos>1 then Dec(MPos) else MPos :=LastY;
          End;
     #13: Begin
            SubRemP[Which] :=MPos;
            PushScreen;
            Menu^[Which].SubMenu[MPos].ItemProc;
            PopScreen;
          End;
     #27: Begin
            SubRemP[Which] :=MPos;
            PopScreen;
            Exit;
          End;
    End;
  End;
End;

Function TopMenu: Byte;
Var
  MPos,OPos: Byte;
  TI       : Byte;
Begin
  DrawTop;
  PushScreen;
  MPos :=RemTop;
  OPos :=MPos;
  TI :=TopItems;
  While True do
  Begin
    VWriteStr(Menu^[MPos].ItemName,1,3,XTab[MPos],1);
    HelpLine(Menu^[MPos].ItemHelp);
    If OPos<>MPos then VWriteStr(Menu^[OPos].ItemName,7,0,XTab[OPos],1);
    OPos :=MPos;
    Case ReadKey of
      #0: Case ReadKey of
            #77: If MPos<TI then Inc(MPos) else MPos :=1;
            #75: If MPos>1 then Dec(MPos) else MPos :=TI;
            #80: Begin
                   TopMenu :=MPos;
                   RemTop :=MPos;
                   Exit;
                 End;
          End;
     #13: Begin
            TopMenu :=MPos;
            RemTop :=MPos;
            Exit;
          End;
    End;
  End;
End;

Procedure ExitProcedure;
Var
  Tel : byte;
  T   : file;
Begin
  Color(7,0);
  Crt.ClrScr;
  CursorOn;
End;

Var
  Tel : byte;

Begin
  Case LastMode of
    7: VideoSegment :=$B000;
    3: VideoSegment :=$B800;
    else VideoSegment :=$B800;
  End;
  OldExit :=ExitProc;
  ExitProc :=@ExitProcedure;
  CursorOff;
  SaveStackPtr :=1;
  XTab[1] :=2;
  FillChar(SubRemP,SizeOf(SubRemP),1);
  RemTop :=1;
  New(Menu);
  New(SaveStack);
  FillChar(SaveStack^,SizeOf(SaveStack^),0);
  FillChar(Menu^,SizeOf(Menu^),0);
  CommentColor :=7;
End.

[Back to MENU SWAG index]  [Back to Main SWAG index]  [Original]