UNIT ListPlus; { S. Ockers 2/18/93 last update 3/17/94 } { Usefull routines for menu type applications } {$F+} { necessary to get function key hook to work } INTERFACE USES Crt,Dos; TYPE str11 = String[11]; str25 = String[25]; str80 = String[80]; str255 = String[255]; ByteArray = ARRAY[0..0] OF Byte; FKeyProc = PROCEDURE(place:integer); CONST BxFg : Integer = White; { Box around list, foreground } BxBg : Integer = Cyan; { Box around list, background } FilFg : Byte = YELLOW; { Filled area foreground color } FilBg : Byte = BLUE; { Filled area background color } FilCh : Char = #32; { Character used for menu background } TitFg : Byte = WHITE; { Foreground color of title area } TitBg : Byte = RED; { Background color of title area } HiFg : Byte = WHITE; { Forground color of highlight } HiBg : Byte = MAGENTA; { Background color of highlight } ShFg : Byte = LIGHTGRAY; { Foreground color of SHADOW } ShBg : Byte = BLACK; { Background color of SHADOW } PtrFg : Byte = LIGHTRED; { Color of menu pointers and letter highlight } PrFg : Byte = YELLOW; { Prompt foreground color } Msghigh : Integer = 0; { Height of message window } Msgwide : Integer = 0; { Width of message window } SelOset : Byte = 4; { Offset of selection text from left edge } TimeUnits : Byte = 3; { # of time units to return } FKeyCh = #187; { Function key which calls passed procedure } DirSelect : Str11 = '*.*'; { Directory selection mask } SelectStr : Str25 = ''; { Letter selection string } REALONLY : Boolean = FALSE; { Input limited to real # chars } INTONLY : Boolean = FALSE; { Input limited to 0-9 & - } ESCAPED : Boolean = FALSE; { Tells if the Esc key was pressed } FULLDATE : Boolean = TRUE; { Return an extended version of date? } LTRSELECT : Boolean = FALSE; { Allow letter selection in lists? } ALPHASELECT : Boolean = FALSE; { Allow alphabet selection in lists? } ALLOWPICK : Boolean = FALSE; { Allow pick list } Centered : Boolean = FALSE; { Center lines in message window } Shadowed : Boolean = TRUE; { Draw shadow with messages? } OldWait : LongInt = 0; { Used to check rollover in wait routine } NewWait : LongInt = 0; { value to look for in wait routine } Waittime : LongInt = 0; { last time detected in CheckWait } { ************* some constants used to describe various keys } LARR = #203; { Left Arrow } RARR = #205; { Right Arrow } UARR = #200; { Up Arrow } DARR = #208; { Down Arrow } ESC = #27; { Escape Key } BACKSP = #8; { Backspace Key } DEL = #211; { Delete Key } INS = #210; { Insert Key } ENTER = #13; { Enter Key } HOME = #199; { Home Key } ENDKEY = #207; { End Key } PGUP = #201; { Page Up Key } PGDN = #209; { Page Down Key } { and some used to describe mouse movements } MUP = #128; { Mouse moved up } MDOWN = #129; { Mouse moved down } MLEFT = #130; { Mouse moved left } MRIGHT = #131; { Mouse moved right } MRIGHTB = #132; { Mouse right button pressed } MLEFTB = #133; { Mouse left button pressed } VAR Display : ^ByteArray; PickRay : ARRAY[0..31] OF BYTE; moused : Boolean; PROCEDURE WriteAt(col,row,fg,bg:Byte;S:Str80); PROCEDURE FastWrite(col,row,attr : Byte ;S:Str80); PROCEDURE WriteCenter(row,fg,bg:Byte;S:Str80); PROCEDURE WriteBetween(x1,x2,row,fg,bg : Byte; S : Str80); PROCEDURE PlainWrite(col,row : Byte ;S:Str80); FUNCTION Replicate(nbr:Byte;ch:Char) : Str80; PROCEDURE ClearLine(nbr,attr : Byte ); PROCEDURE BtmLine( s : Str80); PROCEDURE Attrib(col,row,nbr,attr:Byte); FUNCTION GetKey : Char; PROCEDURE FlushKey; PROCEDURE Box (left, top, right, bottom, BxFg, BxBg, style : Integer); PROCEDURE Fill(left,top,right,bottom,attr,fchar : Byte); PROCEDURE FBox (left, top, right, bottom, BxFg, BxBg, style : Integer); PROCEDURE Button(x1,y1,x2,y2,hishad,lowshad,centbut : Integer); PROCEDURE cursOff; PROCEDURE cursOn; FUNCTION NowTime : str11; FUNCTION WaitUp : Boolean; PROCEDURE SetWait( sec : Integer); FUNCTION Date : Str80; PROCEDURE Msg(msgx,msgy,MsgFg,MsgBg,Filch:Integer;txt:String); PROCEDURE MsgBx(x,y,Fg,Bg,BxFg,BxBg,Style: Integer; txt:String); PROCEDURE MsgButton(x,y,hi,lo,cent,txtFg :Integer; txt:String); PROCEDURE TempMsg(x,y,Fg,Bg,Fil,count:Integer;txt:Str80); PROCEDURE TempMsgBx(x,y,Fg,Bg,BxFg,BxBg,Fil,count:Integer;txt:Str80); FUNCTION IntToStr(I : LongInt; places : Byte) : Str80; FUNCTION RealToStr(r : real; width, decp: ShortInt) : Str80; PROCEDURE Uhuh; FUNCTION ReadStr(x,y,RdFg,RdBg,leng : Integer; prompt,instr : Str80) : Str80; FUNCTION ReadYN(x,y,YNFg,YNBg : Integer; prompt : Str80) : Boolean; PROCEDURE ReadReal(x,y,RRFg,RRBg,leng : Integer; prompt : Str80 ; VAR strt : Real; lo,hi : Real) ; PROCEDURE ReadInt(x,y,RIFg,RIBg,leng : Integer; prompt : Str80 ; VAR strt : Integer; lo,hi : Integer); PROCEDURE DrawShadow(x1,y1,x2,y2 : Byte); FUNCTION Showlist(ListX,ListY,ListW,ListH,Titlesize : Byte; VAR listray; maxrow : Byte; HelpProc : FKeyProc) : Byte; PROCEDURE Dummy(dumdum : Integer); FUNCTION ShowDirectory : Str80; FUNCTION DirChoice(path:pathstr):pathstr; IMPLEMENTATION VAR reg : registers; {**********************************************************************} { Fast Screen writing procedures - no snow control } { Write a string (S) at col,row using fg and bg colors. } PROCEDURE WriteAt(col,row,fg,bg : Byte ;S:Str80); VAR j : Byte; start : Word; attr : Byte; BEGIN start := Pred(row)*160+2*Pred(col); attr := 16*bg+fg ; FOR j:= 1 TO Length(S) DO BEGIN Display^[start] := ord(S[j]); Inc(start); Display^[start] := attr; Inc(start); END; END; { WriteAt } {**********************************************************************} { Write a string (S) at col,row using attribute, attr. } PROCEDURE FastWrite(col,row,attr : Byte ;S:Str80); VAR j : Byte; start : Word; BEGIN start := Pred(row)*160+2*Pred(col); FOR j:= 1 TO Length(S) DO BEGIN Display^[start] := ord(S[j]); Inc(start); Display^[start] := attr; Inc(start); END; END; { FastWrite } {**********************************************************************} { Write a string(S) centered in row using colors fg & bg. } PROCEDURE WriteCenter(row,fg,bg:Byte;S:Str80); VAR start : Byte; BEGIN start := 40 - Length(S) DIV 2; WriteAt(start,row,fg,bg,S); END; { WriteCenter } {**********************************************************************} { Write a string (S) centered between x1 and x2 at row } PROCEDURE WriteBetween(x1,x2,row,fg,bg : Byte; S : Str80); BEGIN WriteAt(x1+(x2-x1-Length(S)) DIV 2,row,fg,bg,S); END; { WriteBetween } {**********************************************************************} { Write a string (S) at col,row using default text colors } PROCEDURE PlainWrite(col,row : Byte ;S:Str80); VAR j : Byte; start : Word; BEGIN start := Pred(row)*160+2*Pred(col); FOR j:= 1 TO Length(S) DO BEGIN Display^[start] := ord(S[j]); Inc(start); Display^[start] := Textattr; Inc(start); END; END; { PlainWrite } {**********************************************************************} { Change the attribute of nbr characters starting at col,row } PROCEDURE Attrib(col,row,nbr,attr : Byte); VAR j : Byte; start : Word; BEGIN start := Pred(row)*160+2*Pred(col)+1; FOR j:= 0 TO Pred(nbr) DO BEGIN Display^[start] := attr; Inc(start,2); END; END; {**********************************************************************} { Return a string nbr long of characters ch. } FUNCTION Replicate(nbr:Byte;ch:Char) : Str80; VAR temp : Str80; BEGIN IF nbr = 0 THEN temp :='' ELSE BEGIN IF nbr >80 THEN nbr:=1; FillChar(temp,nbr+1,ch); temp[0]:=Chr(nbr) END; { Else } Replicate := temp; END; { Replicate } {**********************************************************************} { Clear line nbr using attribute attr } PROCEDURE ClearLine(nbr,attr : Byte ); BEGIN WriteAt(1,nbr,attr MOD 16,attr DIV 16,Replicate(80,' ')); END; { ClearLine } {**********************************************************************} { Put prompt message at bottom line of screen. } PROCEDURE BtmLine( s : Str80); BEGIN ClearLine(25,Black); FastWrite(1,25,White,s); END; { BtmLine } {**********************************************************************} { Get a key from the keyboard add 128 to special keys } FUNCTION GetKey : Char; CONST mvXp = 340; { pos mouse movement x dir before detection } mvXm = 300; { likewise minus x } mvYp = 113; { likewise pos y } mvYm = 93; { likewise minus y } VAR ch : Char; but : Byte; col,row : Word; PROCEDURE Akey; { Procedure to use when no mouse present } BEGIN ch := ReadKey; IF ch = #0 THEN BEGIN ch := ReadKey; ch := Chr( Ord(ch) + 128 ); END; { If } GetKey := ch; END; { Akey } BEGIN IF NOT Moused THEN BEGIN Akey; Exit END; but:=0; col:=319; row:=103; reg.ax := 4; { set mouse cursor to center of screen } reg.cx := 319; reg.dx := 103; Intr($33,reg); REPEAT reg.ax := 3; Intr($33,reg); { get mouse status } col := reg.cx; { get the initial positions } row := reg.dx; but := reg.bx UNTIL KeyPressed OR (col > mvXp) OR (col < mvXm) OR (row > mvYp) OR (row < mvYm) OR (but <> 0); IF KeyPressed THEN Akey ELSE BEGIN REPEAT { wait for mouse button release } reg.ax := 3; Intr($33,reg); { get mouse status } UNTIL reg.bx = 0; IF (but AND 1) = 1 THEN BEGIN GetKey := MLEFTB; Exit; END; IF (but AND 2) = 2 THEN BEGIN GetKey := MRIGHTB; Exit; END; IF row > mvYp THEN GetKey := MDOWN; IF row < mvYm THEN GetKey := MUP; IF col > mvXp THEN GetKey := MRIGHT; IF col < mvXm THEN GetKey := MLEFT; END; { Else } END; { GetKey } {**********************************************************************} { Flush keyboard buffer } PROCEDURE FlushKey; BEGIN reg.ah := 1; { check for keystroke } Intr ($16,reg); IF (reg.flags AND $0040) = 0 THEN { if chars in buffer } REPEAT reg.ah := 0; { char is ready to go, read it } Intr ($16, reg); reg.ah := 1; { check for another } Intr ($16, reg); UNTIL (reg.flags AND $0040) <> 0; END; { FlushKey } {**********************************************************************} { Draw a box using indicated corners ans style : 1-single line } { 2-double,3-single top dbl sides, 4- dbl top single sides, } { 0 - don't draw box. Any other - use IBM char set. } PROCEDURE Box (left, top, right, bottom, BxFg, BxBg, style : Integer); CONST border : ARRAY[1..4,0..5] OF Char = (( #196,#179,#218,#191,#217,#192), (#205,#186,#201,#187,#188,#200), (#196,#186,#214,#183,#189,#211), (#205,#179,#213,#184,#190,#212)); VAR r, c : Byte; BEGIN IF left = 0 THEN BEGIN { autocentering, width = right } left := (80 - right) DIV 2; right := left + right; END; IF top = 0 THEN BEGIN { autocentering, height = bottom } top := (25 - bottom) DIV 2; bottom := top + bottom; END; IF style <> 0 THEN BEGIN { Draw horizontals } IF style IN [1..4] THEN WriteAt(left,top,BxFg,BxBg, Replicate(right-left,border[style,0])) ELSE WriteAt(left,top,BxFg,BxBg, Replicate(right-left,Chr(style))); IF style IN [1..4] THEN WriteAt(left,bottom,BxFg,BxBg, Replicate(right-left,border[style,0])) ELSE WriteAt(left,bottom,BxFg,BxBg, Replicate(right-left,Chr(style))); { Draw verticals } FOR r := (top+1) TO bottom DO BEGIN IF style in [1..4] THEN BEGIN WriteAt(left,r,BxFg,BxBg,border[style,1]); WriteAt(right,r,BxFg,BxBg,border[style,1]); END ELSE BEGIN WriteAt(left,r,BxFg,BxBg,Chr(style)); WriteAt(right,r,BxFg,BxBg,Chr(style)); END; { Else } END; { For r } { Draw Corners } IF style in [1..4] THEN BEGIN WriteAt(left,top,BxFg,BxBg,border[style,2]); WriteAt(right,top,BxFg,BxBg,border[style,3]); WriteAt(left,bottom,BxFg,BxBg,border[style,5]); WriteAt(right,bottom,BxFg,BxBg,border[style,4]); END ELSE BEGIN WriteAt(left,top,BxFg,BxBg,Chr(style)); WriteAt(right,top,BxFg,BxBg,Chr(style)); WriteAt(left,bottom,BxFg,BxBg,Chr(style)); WriteAt(right,bottom,BxFg,BxBg,Chr(style)); END; END; { If style <> 0 } END; { Box } {**********************************************************************} { Fill an area give by corners with an attribute given by } { attr. If fchar > 0 then filled with that character else just attr. } PROCEDURE Fill(left,top,right,bottom,attr,fchar : Byte); VAR j,leng : Byte; BEGIN leng := Succ(right-left); FOR j:=0 TO (bottom-top) DO BEGIN IF fchar > 0 THEN WriteAt(left,top+j,FilFg,FilBg,Replicate(Succ(right-left),Chr(fchar))); Attrib(left,top+j,leng,attr); END; { Do } END; {**********************************************************************} { As Box but center filled with Bx colors } { If style > 4 then box filled with style char. else blank } PROCEDURE FBox (left, top, right, bottom, BxFg, BxBg, style : Integer); BEGIN Box (left, top, right, bottom, BxFg, BxBg, style); IF style > 4 THEN Fill(Succ(left),Succ(top),Pred(right),Pred(bottom), 16*BxBg+BxFg,style) ELSE Fill(Succ(left),Succ(top),Pred(right),Pred(bottom), 16*BxBg+BxFg,32); END; { FBox } {**********************************************************************} { Draw a button with corners x1,y1 and x2,y2 } PROCEDURE Button(x1,y1,x2,y2,hishad,lowshad,centbut : Integer); VAR j : Word; BEGIN Fill(x1,y1,x2,y2,16 * CENTBUT,32); { Clear out area } FOR j := y1 TO y2 DO BEGIN FastWrite(x1,j,HISHAD,#219); { left edge } FastWrite(x2,j,LOWSHAD,#219); { right edge } END; { For } Fastwrite(Succ(x1),y1,HISHAD + 16 * CENTBUT,Replicate(Pred(x2-x1),#223)); Fastwrite(Succ(x1),y2,LOWSHAD + 16 * CENTBUT,Replicate(Pred(x2-x1),#220)); END; { Button } {**********************************************************************} { Turns off hardware cursor } PROCEDURE CursOff; BEGIN reg.ah := 3; { get cursor shape } reg.bh := 0; { NOTE: works in page 0 only } Intr (16,reg); reg.ch := reg.ch OR $20; { turn on bit 5 } reg.ah := 1; Intr (16, reg); { tell BIOS } END; { cursOff } {**********************************************************************} { Turns hardware cursor back on } PROCEDURE CursOn; BEGIN { As above except } reg.ah := 3; reg.bh := 0; Intr (16,reg); reg.ch := reg.ch AND $DF; { turn off bit 5 } reg.ah := 1; Intr (16, reg); END; { cursOn } {**********************************************************************} { Return a string giving the current time. Global Constant TimeUnits } { determines how much of the time is returned. } FUNCTION NowTime : str11; VAR hr,min,sec,hsec : Word; temp : String[2]; timestr : str11; LABEL OutTime; BEGIN GetTime(hr,min,sec,hsec); Str(hr,timestr); IF TimeUnits = 1 THEN GOTO OutTime; Str(min,temp); timestr := timestr+':'+temp; IF TimeUnits = 2 THEN GOTO OutTime; Str(sec,temp); timestr := timestr+':'+temp; IF TimeUnits = 3 THEN GOTO OutTime; Str(hsec,temp); timestr := timestr+':'+temp; OutTime: NowTime := timestr; END; { NowTime } {************************************************************************} FUNCTION WaitUp : Boolean; { check if timeout has occured } BEGIN waittime := MemL[$40:$6C]; IF (waittime < NewWait) AND (waittime >= OldWait) THEN WaitUp := FALSE ELSE WaitUp := TRUE; END; { WaitUp } {************************************************************************} PROCEDURE SetWait( sec : Integer); { set WaitTime to value at end of wait } { times are in 18ths of a second } BEGIN { NOTE: doesn't work over midnight } OldWait := MemL[$40:$6C]; { used to check rollover } NewWait := OldWait + sec * 18; { value to be looking for } END; { SetWait } {************************************************************************} { Return a string containing the date. The global constant FULLDATE } { determines whether an extended version of the date is returned. } FUNCTION Date : Str80; CONST wkdays : ARRAY[1..7] OF Str11 = (' Monday ',' Tuesday ', ' Wednesday ',' Thursday ',' Friday ',' Saturday ',' Sunday '); months : ARRAY[1..12] OF Str11 = ('January ','February ','March ', 'April ','May ','June ','July ','August ','September ', 'October ','November ','December '); VAR yr,mo,da,daofwk : Word; BEGIN GetDate(yr,mo,da,daofwk); IF NOT FULLDATE THEN Date := IntToStr(mo,1)+'/'+IntToStr(da,1)+'/'+IntToStr(yr,1) ELSE Date := wkdays[daofwk]+months[mo]+IntToStr(da,1)+' ,'+IntToStr(yr,1); END; { Date } {************************************************************************} { Put a message in a blank box with upper left corner msgx,msgy } { with colors MsgFg,MsgBg. There is one blank line above and below the } { message. 0's give autocentering. Globals Msghigh & Msgwide updated. } { Lines of the message txt are separated by ~'s. } PROCEDURE Msg(msgx,msgy,MsgFg,MsgBg,Filch:Integer;txt:String); VAR temp : String; j,posit,delta : Integer; BEGIN temp := txt; Msghigh := 1; { determine # lines and width of box } posit := Pos('~',temp); IF (posit = 0) THEN Msgwide := Length(txt)+1 { If just one line } Else Msgwide := 1; REPEAT { determine widest line } posit := Pos('~',temp); IF posit > Msgwide THEN Msgwide := posit; Delete(temp,1,posit); Inc(Msghigh); UNTIL posit = 0; IF Length(temp) > Msgwide THEN Msgwide := Length(temp)+1; { last big? } IF msgx = 0 THEN msgx := 39 - MsgWide DIV 2; { autocentering } IF msgy = 0 THEN msgy := 12 - Msghigh DIV 2; Fill(msgx,msgy,msgx+Msgwide,msgy+Msghigh,MsgFg+16*MsgBg,Filch); temp := txt; { Write in lines } j := Succ(msgy); delta := 0; REPEAT posit := Pos('~',temp); IF Centered THEN { if message centered the figure offset } IF posit = 0 THEN delta := (MsgWide - Length(temp)) DIV 2 ELSE delta := (MsgWide - posit) DIV 2; IF posit = 0 THEN WriteAT(Succ(msgx)+delta,j,MsgFg,MsgBg,temp) { write last line } ELSE { write out line } WriteAT(Succ(msgx)+delta,j,MsgFg,MsgBg,Copy(temp,1,Pred(posit))); Delete(temp,1,posit); Inc(j); UNTIL posit = 0; IF Shadowed THEN DrawShadow(msgx,msgy,msgx+Msgwide,msgy+Msghigh); END; { Msg } {**********************************************************************} { Display message in a box ( as Msg with a box around it ) } PROCEDURE MsgBx(x,y,Fg,Bg,BxFg,BxBg,Style: Integer; txt:String); BEGIN Msg(x,y,Fg,Bg,32,txt); IF x = 0 THEN x := 39 - MsgWide DIV 2; { autocentering } IF y = 0 THEN y := 12 - Msghigh DIV 2; Box(Pred(x),Pred(y),Succ(x)+msgwide,Succ(y)+msghigh,BxFg,BxBg,Style); IF Shadowed THEN DrawShadow(Pred(x),Pred(y),Succ(x)+msgwide,Succ(y)+msghigh); END; { MsgBx } {**********************************************************************} { Display Button with message in middle } PROCEDURE MsgButton(x,y,hi,lo,cent,txtFg :Integer; txt:String); BEGIN Msg(x,y,0,0,32,txt); { necessary to set height & width } IF x = 0 THEN x := 39 - MsgWide DIV 2; { autocentering } IF y = 0 THEN y := 12 - Msghigh DIV 2; Button(Pred(x),Pred(y),Succ(x)+MsgWide,Succ(y)+MsgHigh,hi,lo,cent); Msg(x,y,txtFg,cent,32,txt); END; { MsgButton } {************************************************************************} { Put a message in the center of the screen until a key pressed or } { a timeout of count seconds elapses . If count = 0, just key } { press is recognized. Msgx or msgy = 0 give autocentering. } PROCEDURE TempMsg(x,y,Fg,Bg,Fil,count:Integer;txt:Str80); VAR saveptr : ^ByteArray; timed : Boolean; BEGIN IF count = 0 THEN timed := FALSE ELSE timed := TRUE; count := count * 1000; { change from seconds to milliseconds } GetMem(SavePtr,4000); { memory to save screen } Move(Display^,SavePtr^,4000); { save screen } Msg(x,y,Fg,Bg,Fil,txt); REPEAT IF timed THEN Dec(count); Delay(1); UNTIL (count < 0) OR KeyPressed; Move(SavePtr^,Display^,4000); { restore screen } FreeMem(SavePtr,4000); { return memory } END; { TempMsg } {************************************************************************} { As TempMsg but with box around message window } PROCEDURE TempMsgBx(x,y,Fg,Bg,BxFg,BxBg,Fil,count:Integer;txt:Str80); VAR saveptr : ^ByteArray; timed : Boolean; BEGIN IF count = 0 THEN timed := FALSE ELSE timed := TRUE; count := count * 1000; { change from seconds to milliseconds } GetMem(SavePtr,4000); { memory to save screen } Move(Display^,SavePtr^,4000); { save screen } MsgBx(x,y,Fg,Bg,BxFg,BxBg,Fil,txt); REPEAT IF timed THEN Dec(count); Delay(1); UNTIL (count < 0) OR KeyPressed; Move(SavePtr^,Display^,4000); { restore screen } FreeMem(SavePtr,4000); { return memory } END; { TempMsgBx } {**********************************************************************} { Convert an integer into a string } FUNCTION IntToStr(I : LongInt; places : Byte) : Str80; VAR temp : String[11]; len : Byte; BEGIN Str(i,temp); len := Length(temp); IF len < places THEN temp := Replicate(places - len, #32) + temp; IntToStr := temp; END; { Int_To_Str } {**********************************************************************} { Convert a real # to a String with width and decp decimal places. } FUNCTION RealToStr(r : real; width ,decp: ShortInt) : Str80; VAR temp : Str25; BEGIN Str(r:width:decp,temp); REPEAT IF temp[1] = ' ' THEN Delete(temp,1,1); UNTIL temp[1] <> ' '; RealToStr := temp; END; { RealToStr } {**********************************************************************} { Produces an Uhuh Sound } PROCEDURE Uhuh; VAR j : Byte; BEGIN FOR j:=1 TO 2 DO BEGIN Sound(50); Delay(100); NoSound; Delay(50); END; END; { Uhuh } {**********************************************************************} { Input a string of length leng, preceded by prompt, initally } { set to instr. If the first character input is not a cursor } { movement character the string is first cleared } FUNCTION ReadStr(x,y,RdFg,RdBg,leng : Integer; prompt,instr : Str80) : Str80; VAR startin,endin,cursor,regattr,curattr : Byte; ch : Char; workstr : Str80; firstchar : Boolean; saveptr : ^ByteArray; PROCEDURE UpDate; BEGIN IF firstchar THEN BEGIN workstr := ''; cursor := startin; WriteAt(cursor,y,FilFg,FilBg,Replicate(leng,'.')); END; IF cursor < endin THEN BEGIN WriteAt(cursor,y,FilFg,FilBg,ch); Inc(cursor); Attrib(cursor,y,1,curattr); workstr:=workstr+ch; END ELSE IF cursor = endin THEN BEGIN WriteAt(cursor,y,HiFg,HiBg,ch); workstr[0] := Chr(leng); workstr[leng] := ch; END; { Else } firstchar := FALSE; END; { UpDate } BEGIN IF x=0 THEN x := (80 - leng - Length(prompt) - Length(instr)) DIV 2; IF y=0 THEN y := 13; { auto centering } startin := x + Length(Prompt); endin := Pred(startin + leng); cursor := startin + Length(instr); regattr := PrFg+16*FilBg; curattr := HiFg + 16*HiBg; firstchar := TRUE; ESCAPED := FALSE; GetMem(SavePtr,4000); { memory to save screen } Move(Display^,SavePtr^,4000); { save screen } IF cursor > endin THEN BEGIN instr[0] := Chr(Ord(instr[0])-Pred(cursor-endin)); cursor := endin; END; workstr := instr; WriteAt(x,y,PrFg,FilBg,prompt + Replicate(leng,'.')); WriteAt(startin,y,FilFg,FilBg,instr); Attrib(cursor,y,1,curattr); Box(Pred(x),Pred(y),RdFg,RdBg,Succ(endin),Succ(y),2); DrawShadow(Pred(x),Pred(y),Succ(endin),Succ(y)); REPEAT ch := GetKey; CASE ch OF LARR,DEL,BACKSP : IF cursor > startin THEN BEGIN IF cursor = endin THEN BEGIN WriteAt(cursor,y,PrFg,FilBg,'.'); Delete(workstr,Length(workstr),1); END ELSE Attrib(cursor,y,1,regattr); Dec(cursor); WriteAt(cursor,y,HiFg,HiBg,'.'); Delete(workstr,Length(workstr),1); firstchar := FALSE; END ELSE write(#7); #24 : BEGIN { Ctrl-X } WriteAt(startin,y,PrFg,FilBg,Replicate(leng,'.')); workstr := ''; cursor := startin; Attrib(cursor,y,1,curattr); END; #32..#125 : BEGIN { a normal character } IF NOT REALONLY AND not INTONLY THEN Update ELSE IF REALONLY AND (ch IN ['-','.','0'..'9','E','e']) THEN Update ELSE IF INTONLY AND (ch IN ['-','0'..'9']) THEN UpDate ELSE Uhuh; END; ENTER,ESC : ; { to prevent Uhuh when entered } ELSE Uhuh; END; { Case } UNTIL ch IN [#13,#27]; IF ch = ESC THEN ESCAPED := TRUE; IF ch = ENTER THEN ReadStr := workstr ELSE ReadStr := instr; Move(SavePtr^,Display^,4000); { restore screen } FreeMem(SavePtr,4000); { return memory } END; { ReadStr } {**********************************************************************} { Get a yes or no response to prompt. Yes returns TRUE } FUNCTION ReadYN(x,y,YNFg,YNBg : Integer; prompt : Str80) : Boolean; VAR ch : Char; BEGIN IF x = 0 THEN x := (78-Length(prompt)) DIV 2; IF y = 0 THEN y := 13; Fbox(Pred(x),Pred(y),x + Length(prompt),Succ(y),YNFg,YNBg,2); WriteAt(x,y,Black,Cyan,prompt); REPEAT ch := GetKey; CASE UpCase(ch) OF 'Y' : ReadYN := TRUE; 'N',ESC : ReadYN := FALSE; END; { Case } UNTIL UpCase(ch) IN ['Y','N',ESC]; END; { ReadYN } {**********************************************************************} { Input a real number between values lo and hi. Default is } { strt. Prompt may precede input at column x, row y. } PROCEDURE ReadReal(x,y,RRFg,RRBg,leng : Integer; prompt : Str80 ; VAR strt : Real; lo,hi : Real) ; VAR errcode : Integer; tempstr,savestr : Str11; LABEL GetOut; BEGIN REALONLY := TRUE; REPEAT savestr := RealToStr(strt,20,2); tempstr := ReadStr(x,y,RRFg,RRBg,leng,prompt,savestr); IF tempstr = savestr THEN Goto GetOut; Val(tempstr,strt,errcode); IF (strt>hi) OR (strt 0 THEN TempMsg(0,0,Yellow,Red,32,3,'Error! Please Re-Enter '); UNTIL (strt <= hi) AND (strt >= lo) AND (errcode=0); GetOut: REALONLY := FALSE; END; { ReadReal } {**********************************************************************} { As for real # except integer instead } PROCEDURE ReadInt(x,y,RIFg,RIBg,leng : Integer; prompt : Str80 ; VAR strt : Integer; lo,hi : Integer); VAR errcode : Integer; tempstr,savestr : Str11; LABEL GetOut; BEGIN INTONLY := TRUE; REPEAT savestr := IntToStr(strt,1); tempstr := ReadStr(x,y,RIFg,RIBg,leng,prompt,savestr); IF tempstr = savestr THEN Goto GetOut; Val(tempstr,strt,errcode); IF (strt > hi) OR (strt < lo) THEN TempMsg(0,0,Yellow,Red,32,3,'Value must be between '+ IntToStr(lo,1)+ ' and '+ IntToStr(hi,1)); IF errcode > 0 THEN TempMsg(0,0,Yellow,Red,32,3,'Error! Please Re-Enter '); UNTIL (strt <= hi) AND (strt >= lo) AND (errcode=0); GetOut: INTONLY := FALSE; END; { ReadInt } {**********************************************************************} { Draw a shadow outside boxed area } PROCEDURE DrawShadow(x1,y1,x2,y2 : Byte); VAR MnuShAtt,j : Byte; BEGIN { SHADOW for BOXED menu } MnuShAtt := ShFg + 16 * ShBg; Attrib(Succ(x1),Succ(y2),Succ(x2-x1),MnuShAtt); FOR j:=Succ(y1) TO y2 DO Attrib(Succ(x2),j,1,MnuShAtt); END; { Shadow } {**********************************************************************} { The following function presents a scrolling list and returns } { the number of the item selected. It is passed the upper left } { coordinates and the width and height of the list window. It } { is also passed a pointer to the list and its size. A zero in } { ListX, ListY or ListW causes centering } FUNCTION Showlist(ListX,ListY,ListW,ListH,Titlesize : Byte; VAR listray; maxrow : Byte; HelpProc : FKeyProc) : Byte; VAR tempray : ARRAY[0..0] OF Str80 ABSOLUTE listray; ch : Char; toprow,currow,attrsave,j,k : Byte; changed : Boolean; blanks : Str25; ltrpos : Array[1..25] OF Byte; { position of letter to hilite } saveptr : ^ByteArray; LABEL OutOfList; PROCEDURE UpdateList; VAR j : Byte; BEGIN FOR j:=0 TO Pred(ListH)-titlesize DO BEGIN IF j+toprow 0) THEN Attrib(ListX+Pred(seloset)+ltrpos[toprow-titlesize+Succ(j)], ListY+j+titlesize,1,PtrFg+16*FilBg); IF ALLOWPICK THEN BEGIN k := toprow+j-titlesize; IF (PickRay[k DIV 8] AND (1 SHL ( k MOD 8 ))) > 0 THEN WriteAt(Listx+2,ListY+j+titlesize,FilFg,FilBg,#251); END; { If } END; { For } IF (currow=titlesize) OR (currow=maxrow) THEN Box(Pred(ListX),Pred(ListY),ListX+ListW,ListY+ListH,BxFg,BxBg,1); IF toprow>titlesize THEN WriteAt(ListX+ListW,ListY,BxFg,BxBg,#24); { up arrow } IF toprow+ListH Length(SelectStr) THEN ltrpos[j] := 0 ELSE ltrpos[j] := Pos(SelectStr[j],tempray[j+Pred(titlesize)]); IF ListW = 0 THEN BEGIN IF titlesize > 0 THEN FOR j := 0 TO Pred(titlesize) DO IF Length(tempray[j]) > ListW THEN ListW := Length(tempray[j]); FOR j := titlesize TO maxrow DO IF Length(tempray[j])+2*seloset > ListW THEN ListW := Length(tempray[j])+2*seloset; END; { If ListW = 0 } IF ListX = 0 THEN ListX := 40 - ListW DIV 2; IF ListY = 0 THEN ListY := 12 - ListH DIV 2; IF titlesize > 0 THEN FOR j:= 0 TO Pred(titlesize) DO CASE tempray[j,1] OF '^' : BEGIN { center title line } blanks := Replicate((ListW-Length(tempray[j])) DIV 2,' '); Delete(tempray[j],1,1); tempray[j] := blanks + tempray[j]; END; '&' : BEGIN { duplicate character across width } Delete(tempray[j],1,1); tempray[j] := Replicate(ListW,tempray[j,1]); END; END; { Case } attrsave := TextAttr; GetMem(SavePtr,4000); { memory to save screen } Move(Display^,SavePtr^,4000); { save screen } DrawShadow(Pred(ListX),Pred(ListY),ListX+ListW,ListY+ListH); IF titlesize > 0 THEN FOR j:= 0 TO Pred(titlesize) DO WriteAt(ListX,ListY+j,TitFg,TitBg,tempray[j]+ Replicate(ListW-Length(tempray[j]),' ')); UpDateList; toprow := titlesize; REPEAT ch := GetKey; IF ch = FKeyCh THEN HelpProc(Succ(currow-titlesize)); changed := FALSE; CASE ch OF #208,#129 : IF currow < maxrow THEN BEGIN Inc (currow); changed := TRUE; END; #200,#128 : IF currow> titlesize THEN BEGIN Dec (currow); changed := TRUE; END; #32 : IF ALLOWPICK THEN BEGIN { Flip appropriate bit } k := currow-titlesize; j := k DIV 8; PickRay[j] := PickRay[j] XOR (1 SHL (k MOD 8)); changed := TRUE; END; END; IF changed THEN { Only update when change has been made } BEGIN IF currow > Pred(toprow+ListH-titlesize) THEN Inc(toprow); IF currow < toprow THEN Dec(toprow); UpDateList; END; IF ALPHASELECT THEN FOR j := titlesize TO maxrow DO { Check first letter choosing } IF Upcase(tempray[j,1])=UpCase(ch) THEN BEGIN toprow := j; currow := j; UpdateList; END; { If } IF LTRSELECT THEN FOR j := 1 TO Length(SelectStr) DO IF UpCase(SelectStr[j]) = UpCase(ch) THEN BEGIN k := Pred(j)+titlesize; currow := k; IF (ktoprow+Pred(ListH)-titlesize) THEN toprow := k; UpdateList; END; { If } UNTIL ch IN [#27,#13,#132,#133]; IF ch IN [#27,#132] THEN ShowList := 0 ELSE ShowList := Succ(currow-titlesize); OutOfList: TextAttr := attrsave; Move(SavePtr^,Display^,4000); { restore screen } FreeMem(SavePtr,4000); { return memory } END; { ShowList } {**********************************************************************} { This is a dummy procedure used in calling ShowList when you } { don't need a procedure hooked in. } PROCEDURE Dummy(dumdum : Integer); BEGIN END; { Dummy } {**********************************************************************} { This function displays a filelist of the current directory and } { returns the filname selected } FUNCTION ShowDirectory : Str80; VAR fileStuff : SearchRec; filelist : ARRAY[0..100] OF Str80; j,choice,attrsave : Byte; unpacked : DateTime; saveptr : ^ ByteArray; BEGIN filelist[0] := ' Directory of : '+DirSelect; attrsave := TextAttr; GetMem(saveptr,4000); { memory to 2nd save screen } Move(Display^,saveptr^,4000); { save screen } j:=1; ESCAPED := FALSE; FindFirst(DirSelect,AnyFile,fileStuff); WHILE DosError = 0 DO WITH FileStuff DO BEGIN UnPackTime(time,unpacked); IF (Attr AND 16) = 16 THEN filelist[j] := '['+name+'] '+ Replicate(30-Length(name),' ') + IntToStr(unpacked.month,1)+ '/'+IntToStr(unpacked.day,1)+'/'+ IntToStr(unpacked.year,1) ELSE filelist[j] := name + Replicate(20-Length(name),' ')+ ' (' + IntToStr(size,1) + ') '+ Replicate(8-Length(IntToStr(size,1)),' ')+ IntToStr(unpacked.month,1)+'/'+IntToStr(unpacked.day,1)+'/'+ IntToStr(unpacked.year,1); Inc(j); FindNext(fileStuff); END; { DO } choice := ShowList(0,0,60,6,1,filelist,j-1,Dummy); IF choice = 0 THEN BEGIN ESCAPED := TRUE; ShowDirectory := ''; END ELSE ShowDirectory := Copy(filelist[choice],1, Pred(Pos(' ',filelist[choice]))); TextAttr := attrsave; Move(saveptr^,Display^,4000); { restore screen } FreeMem(saveptr,4000); { return memory } END; { ShowDirectory } {**********************************************************************} { Get a directory selection } FUNCTION DirChoice(path:pathstr):pathstr; TYPE postype = (drive,sub,fname); CONST ltedge : ARRAY[drive..fname] OF Word = ( 18, 31, 48 ); cursize : ARRAY[drive..fname] OF Word = ( 7, 12, 16 ); VAR DirInfo : SearchRec; FileRay : ARRAY[1..100] OF String[12]; SubRay : ARRAY[1..20] OF String[8]; j,attr,attrsave : Word; ch : Char; moved : Boolean; pos : postype; tot,line,curs : ARRAY[drive..fname] OF Word; s : pathstr; dir : DirStr; name : NameStr; ext : ExtStr; saveptr : ^ ByteArray; LABEL NewPath,Getout; BEGIN { dirchoice } attrsave := TextAttr; GetMem(saveptr,4000); { memory to 2nd save screen } Move(Display^,saveptr^,4000); { save screen } dirchoice := ''; escaped := FALSE; GetDir(0,s); tot[drive] := 0; WHILE IoResult = 0 DO { determine total # drives } BEGIN Inc(tot[drive]); {$I-} ChDir(Chr(tot[drive]+64)+':'); {$I+} END; Dec(tot[drive]); { starts at 0 } ChDir(s); { back to inital path } Newpath: pos := fname; FOR j := 1 TO 100 DO { zero out filename array } FileRay[j] := ''; FOR j := 1 TO 20 DO { zero out subdirchoice array } SubRay[j] := ''; tot[fname] := 0; tot[sub] := 0; FindFirst('*.*',AnyFile,DirInfo); WHILE DosError = 0 DO BEGIN IF DirInfo.attr = 16 THEN BEGIN Inc(tot[sub]); SubRay[tot[sub]] := DirInfo.Name; IF DirInfo.Name = '..' THEN SubRay[tot[sub]] := ''; IF DirInfo.Name = '.' THEN Dec(tot[sub]); { don't list this } END; FindNext(DirInfo); END; { While } FindFirst(path,AnyFile,DirInfo); WHILE DosError = 0 DO BEGIN IF DirInfo.attr <> 16 THEN BEGIN Inc(tot[fname]); FileRay[tot[fname]] := DirInfo.Name; END; FindNext(DirInfo); END; { While } attr := White+16 * Blue; line[fname] := 1; curs[fname] := 0; line[sub] := 1; curs[sub] := 0; line[drive] := 1; curs[drive] := 0; moved := TRUE; FBox(14,6,66,21,White,Blue,2); DrawShadow(14,6,66,21); Fastwrite(14,10,attr, '‚€€€ Drive €€€å€ Subdirectory €å€€€€€ Filename €€€€€¦'); FastWrite(28,21,attr,#207); FastWrite(45,21,attr,#207); FBox(15,7,65,9,White,Cyan,32); FastWrite(35,7,White+16*Cyan,'DIRECTORY'); FastWrite(18,8,Black+16*Cyan,'Mask: '+path); FastWrite(48,9,Black+16*Cyan,#17#217 + ' Select'); BtmLine('Select a file using arrow keys and Enter.'); GetDir(0,s); FastWrite(18,9,Black+16*Cyan,'Path: '+s); attr := Black+16 * LightGray; REPEAT IF moved THEN BEGIN FBox(15,11,65,20,Black,LightGray,32); FOR j := 11 TO 20 DO { Vertical Lines } BEGIN FastWrite(28,j,White+16*Blue,#179); FastWrite(45,j,White+16*Blue,#179); END; FOR j := 1 TO 10 DO IF line[drive]+j <= Succ(tot[drive]) THEN Fastwrite(ltedge[drive] +2,10+j,attr,'['+Chr(j+64)+']'); FOR j := 1 TO 10 DO FastWrite(ltedge[sub] +2,10+j,attr,SubRay[Pred(line[sub]+j)]); FOR j := 1 TO 10 DO IF line[fname] + j < 100 THEN FastWrite(ltedge[fname] +2,10+j,attr,Fileray[Pred(line[fname]+j)]); IF tot[fname] = 0 THEN BEGIN FastWrite(ltedge[fname] +5,13,attr,'No Files'); FastWrite(ltedge[fname] +6,14,attr,'Found'); END; { If } Attrib(ltedge[pos],11+curs[pos],cursize[pos],yellow+16*magenta); moved := FALSE; END; { If moved } ch := GetKey; CASE ch OF ENTER,MLEFTB : CASE pos OF drive : BEGIN ChDir(Chr(curs[drive]+65)+':'); Goto NewPath; END; sub : BEGIN GetDir(0,s); FSplit(s,dir,name,ext); IF SubRay[line[sub]+curs[sub]] = '' THEN ChDir('..') ELSE ChDir(FExpand(SubRay[line[sub]+curs[sub]])); Goto NewPath; END; fname : BEGIN GetDir(0,s); FSplit(s,dir,name,ext); dirchoice := FExpand( FileRay[line[fname] + curs[fname]]); Goto GetOut; END; END; { Case } LARR,MLEFT : BEGIN { left arrow } Dec(pos); IF pos < drive THEN pos := fname; moved := TRUE; END; RARR,MRIGHT : BEGIN { right arrow } Inc(pos); IF pos > fname THEN pos := drive; moved := TRUE; END; DARR,MDOWN : IF (curs[pos] < 9) AND (line[pos]+curs[pos] < tot[pos]) THEN { DnArr } BEGIN Attrib(ltedge[pos],11+curs[pos],cursize[pos],Black+16 * LightGray); Inc(curs[pos]); Attrib(ltedge[pos],11+curs[pos],cursize[pos],yellow+16 * magenta); END { If fline } ELSE IF (line[pos] + 9 < 100) AND (line[pos]+curs[pos] < tot[pos]) THEN BEGIN Inc(line[pos]); moved := true; END; UARR,MUP : IF curs[pos] > 0 THEN { UpArr } BEGIN Attrib(ltedge[pos],11+curs[pos],cursize[pos],Black+16*LightGray); Dec(curs[pos]); Attrib(ltedge[pos],11+curs[pos],cursize[pos],yellow+16*magenta); END { If fline } ELSE IF line[pos] > 1 THEN BEGIN Dec(line[pos]); moved := true; END; END; { Case } UNTIL ch IN [ESC , MRIGHTB]; Escaped := TRUE; GetOut: TextAttr := attrsave; Move(saveptr^,Display^,4000); { restore screen } FreeMem(saveptr,4000); { return memory } END; { DirChoice } {**********************************************************************} { The initilization section of this unit detects if a mouse is } { present and sets up a pointer ( display ) to the video screen } BEGIN moused := FALSE; IF MemW[$0000:$00CC] <> 0 THEN BEGIN reg.ax := 0; Intr($33,reg); IF reg.ax <> 0 THEN moused := TRUE; END; { IF } IF LastMode = 7 THEN Display := Ptr($B000, 0) ELSE Display := Ptr($B800, 0); END.