Contributor: DAVID ADAMSON { Here is a good scrolling menu bar program written in TP 5.5. The code is very clean and well commented. } program exemenu; { version 2.2 } (****************************************** 1991 J.C. Kessels **** This is freeware. No guarantees whatsoever. You may change it, use it, copy it, anything you like. J.C. Kessels Philips de Goedelaan 7 5615 PN Eindhoven Netherlands ********************************************************************) {$M 3000,0,0} { No heap, or we can't use 'exec'. } uses dos; const (* English version: *) StrCopyright = 'EXEMENU v2.2, 1991 J.C. Kessels';{ Name of program. } StrBusy = 'Busy....'; { Program is busy message. } StrHelp = 'Enter=Start ESC=Stop'; { Bottom-left help message.} StrStart = 'Busy starting program: '; { Start a program message. } { Wrong DOS version message. } StrDos = 'Sorry, this program only works with DOS versions 3.xx and above.'; { Unrecognised error message. } StrError = 'EXEMENU: unrecognised error caused program termination.'; StrExit = 'That''s it, folks!'; { Exit message. } (* Dutch version: *) (* StrCopyright = 'EXEMENU v2.2, 1991 J.C. Kessels'; { Naam van het programma.} StrHelp = 'Enter=Start ESC=Stop'; { Bodem-links hulp boodschap.} StrBusy = 'Bezig....'; { Ik ben bezig boodschap.} { Bij het starten van een programma. } StrStart = 'Bezig met starten van: '; { Foutboodschap als de DOS versie niet goed is. } StrDos = 'Sorry, dit programma werkt slechts met DOS versie 3.xx en hoger.'; { Onbekende fout boodschap. } StrError = 'EXEMENU: door onbekende fout voortijdig be‰indigd.'; StrExit = 'Exemenu is ge‰indigd.'; { Stop EXEMENU boodschap. } *) DirMax = 1000; { Number of entries in directory array. } type Str90 = string[90]; { We don't need anything longer than this. } var VidStore : array[0..3999] of char; { Video screen storage. } Dir : array[1..DirMax] of record {The directory is loaded into this array.} attr : byte; { 1: directory, 2: file.} name : NameStr; { Name of file/directory. } ext : ExtStr; { Extension of file. } end; DirTop : word; { Last active entry in Dir array. } DirHere : word; { Current selection in Dir array. } DirPath : pathstr; { The path of the Loaded directory. } OldPath : PathStr; { The current directory at startup of EXEMENU. } BasicPath : PathStr; { The path to the basic interpreter. } OldCursor : word; { Saved cursor shape. } xy : word; { Cursor on the screen. } colour : byte; { Colour for the screen. } vidseg : word; { Segment of the screen RAM. } regs : registers; { Registers to call the BIOS. } Inkey : word; { The last pressed key. } keyflags : byte absolute $0040:$0017; { BIOS keyboard flags. } ExitSave : pointer; { Address of exit procedure. } ExitMsg : Str90; { Message to display when exiting. } DTA : SearchRec; { FindFirst-FindNext buffer. } function Left(s : Str90; width : byte) : Str90; {Return Width characters from input string. Add trailing spaces if necessary.} begin if width > length(s) then Fillchar(s[length(s)+1],width-length(s),32); s[0] := chr(width); Left := s; end; procedure FixupDir; { Fixup the DirPath string. } var drive : char; i, j : word; begin i := pos(':',DirPath); { Strip the drive from the path. } if i = 0 then begin if (length(Dirpath) > 0) and (Dirpath[1] = '\') then DirPath := copy(OldPath,1,2) + DirPath else if OldPath[length(OldPath)] = '\' then DirPath := OldPath + DirPath else DirPath := OldPath + '\' + DirPath; i := pos(':',DirPath); end; drive := DirPath[1]; delete(DirPath,1,i); while pos('..',DirPath) <> 0 do { Remove embedded ".." } begin i := pos('..',DirPath); j := i + 2; if i > 1 then dec(i); if (i > 1) and (DirPath[i] = '\') then dec(i); while (i > 1) and (DirPath[i] <> '\') do dec(i); delete(DirPath,i,j-i); end; { Remove embedded ".\" } while pos('.\',DirPath) <> 0 do delete(DirPath,pos('.\',DirPath),2); if pos('\',DirPath) = 0 { If no subdirectories.... } then DirPath := '\' else begin { Else strip filename from the path.... } i := pos('.',DirPath); if i > 0 then begin while (i > 0) and (DirPath[i] <> '\') do dec(i); if i > 0 then DirPath := copy(DirPath,1,i) else DirPath := '\'; end; if DirPath[length(DirPath)] <> '\' { maybe add '\' at the end.... } then DirPath := DirPath + '\'; end; DirPath := drive + ':' + DirPath; { Add the drive back to the directory. } { Translate the Dirpath into all uppercase. } for i := 1 to length(DirPath) do DirPath[i] := upcase(DirPath[i]); end; procedure Show(s : Str90); { Display string "s" at "xy", using "colour". This routine uses DMA into the video memory. } begin Inline( $8E/$06/>VIDSEG/ {mov es,[>vidseg] ; Fetch video segment in ES.} $8B/$3E/>XY/ {mov di,[>xy] ; Fetch video offset in DI.} $8A/$26/>COLOUR/ {mov ah,[>colour] ; Fetch video colour in AH.} $1E/ {push ds ; Setup DS to stack segment.} $8C/$D1/ {mov cx,ss} $8E/$D9/ {mov ds,cx} $8A/$8E/>S/ {mov cl,[bp+>s] ; Fetch string size in CX.} $30/$ED/ {xor ch,ch} $8D/$B6/>S+1/ {lea si,[bp+>s+1] ; Fetch string address in SI.} $E3/$04/ {jcxz l2 ; Skip if zero length.} {l1:} $AC/ {lodsb ; Fetch character from string.} $AB/ {stosw ; Show character.} $E2/$FC/ {loop l1 ; Next character.} {l2:} $1F/ {pop ds ; Restore DS.} $89/$3E/>XY); {mov [>xy],di ; Store new XY.} end; procedure ShowMenu(Message : Str90); { Display the screen, with borders, a "Message" in line 2, and the loaded directory in the rest of the screen. } var i : word; { Work variable. } s : Str90; { Work variable. } pagetop : word; { Top of the page in the Dir array. } row : word; { The display row we are busy with. } begin xy := 0; { First line. } colour := $13; if length(StrCopyright) > 76 then i := 76 else i := length(StrCopyright); s[0] := chr((76 - i) div 2); Fillchar(s[1],ord(s[0]),'Í'); Show('É'+s+'µ'); colour := $1B; Show(copy(StrCopyright,1,i)); colour := $13; s[0] := chr(76 - length(s) - length(StrCopyright)); Fillchar(s[1],ord(s[0]),'Í'); Show('Æ'+s+'»º '); colour := $1E; { Second line. } Show(left(Message,76)); colour := $13; { Third line. } Show(' ºÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ'); { Display all the directory entries, using the current cursor position to calculate the top-left of the page. } pagetop := DirHere - DirHere mod 105 + 1; for i := pagetop to pagetop + 20 do begin colour := $13; Show('º '); colour := $1E; row := 0; while row <= 84 do begin if i+row <= DirTop then if Dir[i+row].attr = 1 then Show(left(Dir[i+row].name,14)) else Show(left(Dir[i+row].name,8) + '.' + left(Dir[i+row].ext,5)) else Show(' '); row := row + 21; end; colour := $13; Show(' º'); end; colour := $13; { Last line. } Show('ÈÍ͵'); colour := $1B; if length(StrHelp) > 74 then i := 74 else i := length(StrHelp); Show(copy(StrHelp,1,i)); colour := $13; s[0] := chr(74-i); Fillchar(s[1],ord(s[0]),'Í'); Show('Æ'+s+'¼'); end; procedure ShowBar(here : word; onoff : boolean); { Display (onoff = true) or remove (onoff = false) the cursor bar at the screen location that shows the "here" entry in the Dir array. Every entry has a fixed location on the screen. } var i : word; begin i := Here mod 105 - 1; { Calculate position on screen. } xy := 484 + (i div 21) * 28 + (i mod 21) * 160; if onoff { Setup the proper colour. } then colour := $70 else colour := $1E; if Here <= DirTop { Display the Dir entry. } then if Dir[Here].attr = 1 then Show(left(Dir[Here].name,12)) { Directories without a dot. } else Show(left(Dir[Here].name,8) + '.' + left(Dir[Here].ext,3)) else Show(' '); { Empty entries. } colour := $1E; { Reset the colour. } end; procedure InitVideo; { Initialise the video. If not 80x25 then switch to it. Store the screen. Hide the cursor. } var i : byte; begin regs.ah := $0F; { If not text mode 3 or 7, then switch to it. } intr($10,regs); i := regs.al and $7F; regs.ah := $03; { Save current cursor shape. BH is active page. } intr($10,regs); OldCursor := regs.cx; if (i <> 3) and (i <> 7) then begin regs.al := 3; regs.ah := 0; intr($10,regs); i := 3; end; if i <> 7 { Compute video segment. } then vidseg := $B800 + (memw[$0040:$004E] shr 4) else vidseg := $B000 + (memw[$0040:$004E] shr 4); move(mem[vidseg:0],VidStore[0],4000); { Store current screen. } regs.cx := $2000; { Hide cursor. } regs.ah := 1; intr($10,regs); colour := $1E; { Reset attribute. } xy := 0; { Reset cursor. } end; procedure ResetVideo; { Reset the video back to it's original contents. Show the cursor. } begin move(VidStore[0],mem[vidseg:0],4000); { Restore screen. } regs.cx := OldCursor; { Reset original cursor chape. } regs.ah := 1; intr($10,regs); end; {$F+} procedure ExitCode; { Reset display upon exit. This also works for error exit's. } begin ResetVideo; { Reset the original display contents. } if ExitMsg <> '' then writeln(ExitMsg); { Show exit message. } ChDir(OldPath); { Restore current path. } ExitProc := ExitSave; { Reset previous exit procedure. } end; {$F-} procedure LoadDir; { Load the "DirPath" directory into memory. } var i : word; { Work variable. } s : pathstr; { Work variable. } name : NameStr; { Name of current file. } ext : ExtStr; { Extension of current file. } attr : byte; { Attribute of current file. } begin colour := $1E; { Show "busy" message. } xy := 164; Show(left(StrBusy,76)); FixupDir; { Cleanup the DirPath string. } DirTop := 0; { Reset pointers into the Dir array.} DirHere := 1; FindFirst(DirPath+'*.*',AnyFile,DTA); { Find first file. } while (DosError = 3) and (length(DirPath) > 3) do { If path not found....} begin i := length(DirPath); { then strip last directory from path. } if i > 3 then dec(i); while (i > 3) and (DirPath[i] <> '\') do dec(i); DirPath := copy(DirPath,1,i); FindFirst(DirPath+'*.*',AnyFile,DTA); { And try again. } end; while DosError = 0 do { For all the files. } begin attr := 0; if (DTA.attr and Directory) = Directory then begin { Setup for directories. } name := DTA.name; ext := ''; if DTA.name <> '.' then attr := 1; { Ignore '.' directory. } if DTA.name = '..' then name := '..'; end else begin for i := 1 to length(DTA.name) do { Translate filename to lowercase. } if DTA.name[i] IN ['A'..'Z'] then DTA.name[i] := chr(ord(DTA.name[i])+32); i := pos('.',DTA.name); { Split filename in name and extension. } if i > 0 then begin name := copy(DTA.name,1,i-1); ext := copy(DTA.name,i+1,length(DTA.name)-i); end else begin name := DTA.name; ext := ''; end; { Ignore unrecognised extensions. } if (ext = 'com') and (DTA.name <> 'command.com') then attr := 2; if (ext = 'exe') and (DTA.name <> 'exemenu.exe') then attr := 2; if (ext = 'bat') and (DTA.name <> 'autoexec.bat') then attr := 2; if (ext = 'bas') and (BasicPath <> '') then attr := 2; end; { If recognised extension or directory, then load into memory. } if attr > 0 then begin i := 1; while (i <= DirTop) and { Find location where to insert (sort). } ((attr > Dir[i].attr) or ((attr = Dir[i].attr) and (name > Dir[i].name)) or ((attr = Dir[i].attr) and (name = Dir[i].name) and (ext > Dir[i].ext))) do inc(i); if DirTop < DirMax then inc(DirTop); if i < DirTop then { Move entries up, to create entry. } move(Dir[i],Dir[i+1],sizeof(Dir[1]) * (DirTop - i)); if i <= DirMax then { Fill the entry. } begin Dir[i].name := name; Dir[i].ext := ext; Dir[i].attr := attr; end; end; FindNext(DTA); { Next item. } end; { Analyse the results. If nothing found (maybe disk error), and if we are in a subdirectory, then at least add the parent directory. } if (DirTop = 0) and (length(DirPath) > 3) then begin Dir[1].name := '..'; Dir[1].ext := ''; Dir[1].attr := 1; DirTop := 1; end; end; procedure ExecuteProgram; { Execute the program at "DirHere". } var ProgramPath : pathstr; { Path to the program to execute. } begin { Return from this subroutine if there is no program at the cursor. } if (DirHere < 1) or (DirHere > DirTop) or (Dir[DirHere].attr <> 2) then exit; colour := $1E; { Show "busy" message. } xy := 164; Show(left(StrBusy,76)); { Setup path to the program. } ProgramPath := DirPath + Dir[DirHere].name + '.' + Dir[DirHere].ext; FindFirst(ProgramPath,AnyFile,DTA); { Test if the path to the program exists. } if DosError <> 0 then exit; { Exit if error. } ResetVideo; { Reset the video screen. } writeln(StrStart,ProgramPath); { Show startup message. } ChDir(copy(DirPath,1,length(DirPath)-1)); { Change to the directory. } SwapVectors; { Start program. } if Dir[DirHere].ext = 'bat' { .BAT files trough the COMMAND.COM. } then Exec(getenv('COMSPEC'),'/C '+ProgramPath) else if Dir[DirHere].ext = 'bas' { .BAS trough the basic interpreter. } then Exec(BasicPath,ProgramPath) else Exec(ProgramPath,''); { Others directly. } SwapVectors; InitVideo; { Initialise the video. } ShowMenu(StrBusy); { Draw screen with "busy" message. } { Reset keyboard flags. } keyflags := keyflags and $0F; {Capslock, Numlock, ScrollLock and Insert off.} fillchar(regs,sizeof(regs),#0); { Clear registers. } regs.ah := 1; { Activate new setting. } intr($16,regs); regs.ah := 1; { Clear the keyboard buffer.} intr($16,regs); while (regs.flags and fzero) = 0 do begin regs.ah := 0; intr($16,regs); regs.ah := 1; intr($16,regs); end; Inkey := 13; end; var i : word; { Workvariable. } s : Str90; { Workvariable. } OldHere, OldPageTop : word; { Determine if cursor has moved. } begin DirPath := ''; { No directory loaded right now. } DirTop := 0; { No directory loaded right now. } ExitMsg := StrError; { Reset error message. } getdir(0,OldPath); { Save current directory. } ExitSave := ExitProc; { Setup exit procedure. } ExitProc := @ExitCode; InitVideo; { Initialise the video. } ShowMenu(StrBusy); { Draw screen with "busy" message. } if lo(DosVersion) < 3 then { Test DOS version. } begin ExitMsg := StrDos; halt(1); end; { Determine what directory to search for programs. Default is the current directory. Otherwise the first argument after EXEMENU is used as starting path. } if paramcount = 0 then DirPath := OldPath else DirPath := paramstr(1); { Find the basic interpreter somewhere in the path. If not found, then basic programs will not be listed. } BasicPath := Fsearch('GWBASIC.EXE',GetEnv('PATH')); if BasicPath = '' then BasicPath := Fsearch('GWBASIC.COM',GetEnv('PATH')); if BasicPath = '' then BasicPath := Fsearch('BASIC.EXE',GetEnv('PATH')); if BasicPath = '' then BasicPath := Fsearch('BASIC.COM',GetEnv('PATH')); if BasicPath = '' then BasicPath := Fsearch('BASICA.EXE',GetEnv('PATH')); if BasicPath = '' then BasicPath := Fsearch('BASICA.COM',GetEnv('PATH')); if BasicPath <> '' then BasicPath := FExpand(BasicPath); LoadDir; { Load the directory into memory. } ShowMenu(DirPath); { Display the directory. } ShowBar(DirHere,true); { Highlight the current choice. } { The main loop, exited only when the user presses ESC. } repeat { Wait for a key to be pressed. Place the scancode in the Inkey variable. } regs.ah := 0; intr($16,regs); Inkey := regs.ax; if lo(Inkey) = 13 then { Process ENTER key. } begin ShowBar(DirHere,false); { Remove cursor bar. } s := ''; { No item stored. } { If cursor points to a program....} if DirHere <= DirTop then if Dir[DirHere].attr = 2 then begin { Store the item to execute, so we can move the cursor back to it. } s := Dir[DirHere].name + '.' + Dir[DirHere].ext; ExecuteProgram; { Then execute the program....} end else if Dir[DirHere].name <> '..' { Else goto the directory....} then DirPath := fexpand(DirPath+Dir[DirHere].name) + '\' else begin { Or goto the parent directory. } i := length(DirPath) - 1; while (i >= 1) and (DirPath[i] <> '\') do dec(i); {Store the directory we just left, so we can move the cursor to it.} s := copy(DirPath,i+1,length(DirPath)-i-1); if i > 0 then DirPath := copy(DirPath,1,i) else DirPath := '\'; end; LoadDir; { Reload the directory. } { If an item was stored, then find it, and move the cursor to it. } if s <> '' then begin DirHere := 1; if pos('.',s) = 0 then while (DirHere < DirTop) and (Dir[DirHere].name <> s) do inc(DirHere) else while (DirHere < DirTop) and (Dir[DirHere].name + '.' + Dir[DirHere].ext <> s) do inc(DirHere); if (DirHere <= DirTop) and ( ((pos('.',s) = 0) and (Dir[DirHere].name <> s)) or ((pos('.',s) > 0) and (Dir[DirHere].name + '.' + Dir[DirHere].ext <> s)) ) then DirHere := 1; end; ShowMenu(DirPath); { Show the menu. } ShowBar(DirHere,true); { Show cursor bar. } end; { Process cursor movement keys. } OldHere := DirHere; {Remember current cursor, to determine if it has moved.} if (Inkey = $4800) and (DirHere > 1) then dec(DirHere); { arrow-up.} if (Inkey = $5000) and (DirHere < DirTop) then inc(DirHere); {arrow-down.} if (Inkey = $4D00) or (lo(Inkey) = 9) then {arrow-right or tab.} if DirHere + 21 <= DirTop then DirHere := DirHere + 21 else DirHere := DirTop; if (Inkey = $4B00) or (Inkey = $0F00) then { arrow-left or shift-tab. } if DirHere > 21 then DirHere := DirHere - 21 else DirHere := 1; if (Inkey = $5100) and (DirHere < DirTop) then { pgdn. } if DirTop > 105 then if DirHere + 105 < DirTop then DirHere := DirHere + 105 else DirHere := DirTop else if (DirHere - 1) mod 21 = 20 then if DirHere + 21 <= DirTop then DirHere := DirHere + 21 else DirHere := DirTop else if DirHere - (DirHere - 1) mod 21 + 20 < DirTop then DirHere := DirHere - (DirHere - 1) mod 21 + 20 else DirHere := DirTop; if (Inkey = $4900) and (DirHere > 1) then { pgup. } if DirTop > 105 then if DirHere > 105 then DirHere := DirHere - 105 else DirHere := 1 else if (DirHere - 1) mod 21 = 0 then if DirHere > 21 then DirHere := DirHere - 21 else DirHere := 1 else DirHere := DirHere - (DirHere - 1) mod 21; if Inkey = $4700 then DirHere := 1; { home. } if Inkey = $4F00 then DirHere := DirTop; { end. } if lo(Inkey) > 31 then {Process a character inkey. } begin i := 1; while (i <= DirTop) and (Dir[i].name[1] <> chr(lo(Inkey))) do inc(i); if i <= DirTop then DirHere := i; end; if DirHere = 0 then DirHere := 1; { Correct for empty list. } { If the cursor has moved off the screen, then redraw the menu. } if OldHere - OldHere mod 105 + 1 <> DirHere - DirHere mod 105 + 1 then begin ShowBar(OldHere,false); ShowMenu(DirPath); ShowBar(DirHere,true); OldHere := DirHere; end; if OldHere <> DirHere then { If the cursor has moved, then redraw it. } begin ShowBar(OldHere,false); ShowBar(DirHere,true); end; until lo(Inkey) = 27; { Until ESC key pressed. } ExitMsg := StrExit; { Exit with message. } end.