{*******************************************************} { } { Turbo Pascal Version 6.0 } { Optional FormLine Unit } { for use with Turbo Vision } { } { Copyright (c) 1991 J. John Sprenger } { } {*******************************************************} unit FormLine; {$O+,F+,S+} interface uses {Turbo Pascal Run-Time Library Units} Crt, {Turbo Vision Standard Units} Objects, Drivers, Views, Dialogs, App, {Turbo Vision Accessory Units} StdDlg, MsgBox; const { flError, flCharOk and flFormatOK are constants used } { by tFormatLine.CheckPicture. flError is returned } { when an error is found. flCharOk when an character } { is found to be appropriate. And flFormatOk when the } { entire input string is found acceptable. } flError = $0000; flCharOK = $0001; flFormatOK = $0002; { flCharError is passed to tFormatLine.ReportError } { when a character does not fit the proper form. } { flFormatError is used when the format is not } { satisfied even though input so far is acceptable. } flCharError = 1; flFormatError = 2; { CommandSet represents the characters used in Format } { Line Pictures. These match those used by Paradox. } CommandSet = ['[','{','?','&','@','!','#','{',',',']', '}','*']; type { tFormatLine } { tFormatLine is the improved tInputLine object which } { accepts Paradox-form Picture strings to ensure that } { data will be entered in an acceptable form. } pFormatLine = ^tFormatLine; tFormatLine = object( tInputLine) Picture : string; constructor Init(var Bounds : tRect; AMaxLen : integer; Pic : string); function Valid(command : word) : boolean; virtual; procedure HandleEvent(var Event : tEvent); virtual; function CheckPicture(var s, Pic : string; var CPos : integer):word; procedure ReportError( kind : word); virtual; end; { tMoneyFormatLine } { tMoneyFormatLine is an input line intended for use } { real number fields associated with money. Input is } { preceded with a "$" sign and terminated with a "." } { followed by the appropriate fractional value. } pMoneyFormatLine = ^tMoneyFormatLine; tMoneyFormatLine = object( tFormatLine ) constructor Init(var Bounds : tRect; AMaxlen : integer); procedure SetData(var Rec); virtual; procedure GetData(var Rec); virtual; function DataSize : word; virtual; end; { tPhoneFormatLine } { tPhoneFormatLine is for phone number fields. Normal } { 10-digit numbers are entered in the following form } { (###) ###-####. International numbers are entered } { digit after digit with spaces and hyphens where the } { user deems appropriate. } pPhoneFormatLine = ^tPhoneFormatLine; tPhoneFormatLine = object( tFormatLine ) constructor Init(var Bounds : tRect; AMaxLen : integer); procedure SetData(var Rec); virtual; procedure GetData(var Rec); virtual; end; { tRealFormatLine } { tRealFormatLine is used for real number fields. It } { can handle both decimal and scientific notations. } pRealFormatLine = ^tRealFormatLine; tRealFormatLine = object ( tFormatLine ) constructor Init(var Bounds : tRect; AMaxLen : integer); procedure SetData(var Rec); virtual; procedure GetData(var Rec); virtual; function DataSize : word; virtual; end; { tIntegerFormatLine } { tIntegerFormatLine is used for integer fields. It } { accepts signed integers. } pIntegerFormatLine = ^tIntegerFormatLine; tIntegerFormatLine = object( tFormatLine ) constructor Init(var Bounds : tRect; AMaxLen : integer); procedure SetData(var Rec); virtual; procedure GetData(var Rec); virtual; function DataSize : word; virtual; end; { tNameFormatLine } { tNameFormatLine accepts words and capitalizes the } { first character of each word. This would be used } { proper names and addresses. } pNameFormatLine = ^tNameFormatLine; tNameFormatLine = object( tFormatLine ) constructor Init(var Bounds : tRect; AMaxLen : integer); end; { tZipFormatLine } { tZipFormatLine is used for ZIP and Postal Code } { fields. It handles U.S. and Canadian format codes. } pZipFormatLine = ^tZipFormatLine; tZipFormatLine = object( tFormatLine ) constructor Init(var Bounds : tRect; AMaxLen : integer); end; implementation { Function Copy represents a bit of syntatic sugar for } { the benefit of the author. It changes the Copy func. } { so that its parameters represent start and end points } { rather than a start point followed by a quantity. } function Copy(s : string; start, stop : integer) : string; begin if stop < start then Copy:='' else Copy:=System.Copy(s,start,stop-start+1); end; { Function FindMatch recursively locates the matching } { grouping characters for "{" and "[". } function FindMatch(P : string) : integer; var i:integer; match:boolean; c:char; begin i:=2; match:=false; while (i<=length(P)) and not match do begin if ((p[i]=']') and (p[1]='[')) or ((p[i]='}') and (p[1]='{')) then match:=true; if p[i]='{' then i:=i+FindMatch(Copy(p,i,length(p))) else if p[i]='[' then i:=i+FindMatch(Copy(p,i,length(P))) else inc(i); end; FindMatch:=i-1; end; { tFormatLine.ReportError handles errors found when the } { user keys inappropriate characters or presses ENTER } { when input is incomplete. } procedure tFormatLine.ReportError(kind:word); var w : word; Pic : pstring; begin Pic:=newstr(Picture); case kind of flCharError : begin sound(220); delay(200); nosound; end; flFormatError : begin w:=MessageBox('Error in Formatted Input Line'+ ' '+ '%s'+ ' '+ '(Using Paradox Picture Format)', @Pic,mfError+mfOkButton); end; end; DisposeStr(Pic); end; { tFormatLine.Valid overrides TView's Valid and reports } { any format errors if the user accepts the input string } { before the entire format requirements have been met. } function tFormatLine.Valid(command: word):boolean; var result:word; begin result:=CheckPicture(Data^,Picture,CurPos); if (result and flFormatOK)=0 then begin ReportError(flFormatError); Select; DrawView; Valid:=false; end else Valid:=true; end; { tFormatLine.CheckPicture is the function that inspects } { the input string passed as S against the Pic string } { which holds the Paradox-form Picture. If an error is } { found the position of the error is placed in CPos. } function tFormatLine.CheckPicture(var s, Pic : string; var CPos : integer) : word; var Resolved : integer; TempIndex : integer; { Function CP is the heart of tFormatLine. It } { determines if the string, s passed to it fits the } { requirements of the picture, Pic. The number of } { characters successful resolved is returned in the } { parameter resolved. When groups or repetitions are } { encountered CP will call itself recursively. } function CP(var s : string; Pic : string; var CPos : integer; var Resolved : integer) : word; const CharMatchSet = ['#','?','&','@','!']; var i : integer; index : integer; result : word; commit : boolean; Groupcount : integer; { Procedure Succeed resolves defaults and } { default requests } procedure Succeed; var t : integer; found : boolean; begin if (s[i]=' ') and (Pic[index]<>' ') and (Pic[index]<>',') then begin t:=index; found:=false; while (t<=length(pic)) and not found do begin if not (Pic[t] in (CharMatchSet+ ['*','[','{',',',']','}'])) then begin if pic[t]=';' then inc(t); s[i]:=Pic[t]; found:=true; end; inc(t); end; end; if (i>length(s)) then while not (Pic[index] in (CharMatchSet+['*','[','{',',',']','}'])) and (index<=length(Pic)) and not(Pic[index-1] in ['}',',',']']) do begin if Pic[index]=';' then inc(index); s[i]:=Pic[index]; if i>length(s) then begin CPos:=i; s[0]:=char(i); end; inc(i); inc(index); end; end; { Function AnyLeft returns true if their are no required } { characters left in the Picture string. } function AnyLeft : boolean; var TempIndex : integer; begin TempIndex:=index; while ((Pic[TempIndex]='[') or (Pic[TempIndex]='*')) and (TempIndex<=Length(Pic)) and (Pic[TempIndex]<>',') do begin if Pic[TempIndex]='[' then Tempindex:=Tempindex+FindMatch(Copy(Pic,index, Length(Pic))) else begin if not (Pic[TempIndex+1] in ['0'..'9']) then begin inc(TempIndex); if Pic[TempIndex] in ['{','['] then tempIndex:=TempIndex+ FindMatch(Copy(pic,index,length(pic))) else inc(TempIndex); end; end; end; AnyLeft:=(TempIndex<=length(Pic)) and (Pic[TempIndex]<>','); end; { Function CharMatch determines if the current character } { matches the corresponding character mask in the } { Picture string. Alters the character if necessary. } function CharMatch : word; var result : word; begin result:=flError; case Pic[index] of '#': if s[i] in ['0'..'9'] then result:=flCharOk; '?': if s[i] in ['A'..'Z','a'..'z'] then result:=flCharOk; '&': if s[i] in ['A'..'Z','a'..'z'] then begin result:=flCharOk; s[i]:=upcase(s[i]); end; '@': result:=flCharOk; '!': begin result:=flCharOk; s[i]:=upcase(s[i]); end; end; if result<>flError then commit:=true; CharMatch:=result; end; { Function Literal handles characters which are needed } { by the picture by otherwise used as format specifiers. } { All such characters are preceded by the ';' in the } { picture string. } function Literal : word; var result : word; begin inc(index); if s[i]=Pic[index] then result:=flCharOk else result:=flError; if result<>flError then commit:=true; Literal:=result; end; { Function Group handles required and optional groups } { in the picture string. These are designated by the } (* "{","}" and "[","]" character pairs. *) function Group:word; var result: word; TempS: string; TempPic: string; TempCPos: integer; PicEnd: integer; TempIndex: integer; SwapIndex:integer; SwapPic : string; begin TempPic:=Copy(Pic,index,length(Pic)); PicEnd:=FindMatch(TempPic); TempPic:=Copy(TempPic,2,PicEnd-1); TempS:=Copy(s,i,length(s)); TempCPos:=1; result:=CP(TempS,TempPic,TempCPos,TempIndex); if result=flCharOK then inc(GroupCount); if (result=flFormatOK) and (groupcount>0) then dec(GroupCount); if result<>flError then result:=flCharOk; SwapIndex:=index; index:=TempIndex; SwapPic:=Pic; Pic:=TempPic; if not AnyLeft then result:=flCharOk; pic:=SwapPic; index:=SwapIndex; if i>1 then s:=copy(s,1,i-1)+TempS else s:=TempS; CPos:=Cpos+TempCPos-1; if Pic[index]='[' then begin if result<>flError then i:=i+TempCPos-1 else dec(i); result:=flCharOK; end else i:=i+TempCPos-1; index:=index+PicEnd-1; Group:=result; end; { Function Repetition handles repeated that may be } { repeated in the input string. The picture string } { indicates this possiblity with "*" character. } function Repetition:word; var result:word; count:integer; TempPic:string; TempS:string; TempCPos:integer; TempIndex:integer; SwapIndex:integer; SwapPic:string; PicEnd:integer; commit:boolean; procedure MakeCount; var nstr:string; code:integer; begin if Pic[index] in ['0'..'9'] then begin nstr:=''; repeat nstr:=nstr+Pic[index]; inc(index); until not(Pic[index] in ['0'..'9']); val(nstr,count,code); end else count:=512; end; procedure MakePic; begin if Pic[index] in ['{','['] then begin TempPic:=copy(Pic,index,length(Pic)); PicEnd:=FindMatch(TempPic); TempPic:=Copy(TempPic,2,PicEnd-1); end else begin if Pic[index]<>';' then begin TempPic:=''+Pic[index]; PicEnd:=3; if index=1 then pic:='{'+pic[index]+'}'+ copy(pic,index+1,length(pic)) else pic:=copy(pic,1,index-1)+ '{'+pic[index]+'}'+ copy(pic,index+1,length(pic)); end else begin TempPic:=Pic[index]+Pic[index+1]; PicEnd:=4; if index=1 then pic:='{'+pic[index]+ pic[index+1]+'}'+ copy(pic,index+1,length(pic)) else pic:=copy(pic,1,index-1)+'{'+pic[index]+ pic[index+1]+'}'+copy(pic,index+1, length(pic)); end; end; end; begin inc(index); MakeCount; MakePic; result:=flCharOk; while (count<>0) and (result<>flError) and (i<=length(s)) do begin commit:=false; TempS:=Copy(s,i,length(s)); TempCPos:=1; result:=CP(TempS,TempPic,TempCPos,TempIndex); if result=flCharOK then inc(GroupCount); if (result=flFormatOK) and (groupcount > 0) then dec(GroupCount); if result<>flError then result:=flCharOk; SwapIndex:=Index; Index:=TempIndex; SwapPic:=Pic; Pic:=TempPic; if (not AnyLeft) then result:=flCharOk; Pic:=SwapPic; index:=SwapIndex; if i>1 then s:=copy(s,1,i-1)+TempS else s:=TempS; Cpos:=Cpos+TempCpos-1; if (count>255) then begin if result<>flError then begin i:=i+TempCpos-1; if not commit then commit:=true; result:=flCharOk; end else dec(i); end else i:=i+TempCPos-1; inc(i); dec(count); end; dec(i); index:=index+PicEnd-1; if result=flError then if (count>255) and not commit then result:=flCharOk; repetition:=result; end; begin{ of function CP} i:=1; index:=1; result:=flCharOk; commit:=false; Groupcount:=0; while (i<=length(s)) and (result<>flError) do begin if index>length(Pic) then result:=flError else begin if s[i]=' ' then Succeed; if Pic[index] in CharMatchSet then result:=CharMatch else if Pic[index]=';' then result:=Literal else if (Pic[index]='{') or (Pic[index]='[') then result:=Group else if Pic[index]='*' then result:=Repetition else if Pic[index] in [',','}',']'] then result:=flError else if Pic[index]=s[i] then begin result:=flCharOk; commit:=true; end else result:=flError; if (result = flError) and not commit then begin TempIndex:=Index; while (TempIndex<=length(Pic)) and ((Pic[TempIndex]<>',') and (Pic[TempIndex-1]<>';')) do begin if (Pic[TempIndex]='{') or (Pic[TempIndex]=']') then Index:=FindMatch( Copy( Pic, TempIndex,length(Pic)))+TempIndex-1; inc(TempIndex); end; if Pic[TempIndex]=',' then begin if Pic[TempIndex-1]<>';' then begin result:=flCharOk; index:=TempIndex; inc(index); end; end; end else if result<>flError then begin inc(i); inc(index); Succeed; end; end; end; Resolved:=index; if (result=flCharOk) and (GroupCount=0) and (not AnyLeft or ((Pic[index-1]=',') and (Pic[index-2]<>';'))) then result:=flFormatOk; CPos:=i-1; CP:=result; end; begin{ of function CheckPicture} Resolved:=1; CheckPicture:=CP(s,Pic,CPos,Resolved); end; { tFormatLine.Init simply sets up the inputline and then } { sets up the Picture string for use by CheckPicture. } constructor tFormatLine.Init(var Bounds: tRect; AMaxLen: integer; Pic : string); begin tInputLine.Init(Bounds,AMaxLen); Picture:=Pic; end; { tFormatLine.HandleEvent intercepts character key } { presses and handles inserting these characters into } { Data field. Insertion only occures if a call to } { tFormatLine.CheckPicture is successful else } { tFormatLine.ReportError is called. All other events } { are passed on to tInputLine.HandleEvent. } procedure TFormatLine.HandleEvent(var Event: TEvent); var TempData : string; TempCurPos : integer; I : integer; begin if State and sfSelected <> 0 then if Event.What=evKeyDown then if Event.CharCode in [' '..#255] then begin TempData:=Data^; if State and sfCursorIns<>0 then Delete(TempData,CurPos+1,1) else begin if SelStart<>SelEnd then begin Delete(TempData,SelStart+1 ,SelEnd-SelStart); CurPos:=SelStart; end; end; if Length(TempData) CurPos then FirstPos:=CurPos; I:=CurPos-Size.X+2; if FirstPos0 then ReportError(flFormatError); end; procedure tMoneyFormatLine.SetData; var Figure : real absolute Rec; TempData : string; i,decimal, count : integer; begin str(Figure:0:2,TempData); i:=pos('.',TempData); count:=0; while (i<>1) do begin inc(count); dec(i); if count=3 then begin insert(',',TempData,i); count:=0; end; end; if TempData[1]=',' then delete(TempData,1,1); Data^:='$'+TempData; end; function tMoneyFormatLine.DataSize : word; begin DataSize:=sizeof(real); end; constructor tPhoneFormatLine.Init; begin tFormatLine.Init(Bounds,AMaxLen, '(###) ###-####,#*{#, ,-#}'); end; procedure tPhoneFormatLine.GetData; var i : integer; Default : string absolute Rec; begin for i:=length(Data^) downto 1 do if Data^[i] in [' ','-','(',')'] then Delete(Data^,i,1); Default:=Data^; end; procedure tPhoneFormatLine.SetData; var i:integer; Default : string absolute Rec; begin if length(Default)=10 then Default:='('+Copy(Default,1,3)+') '+Copy(Default,4,6)+ '-'+Copy(Default,7,10); Data^:=Default; end; constructor tRealFormatLine.Init; begin tFormatLine.Init(Bounds, AMaxLen, '[+,-]#*#[[.*#][{E,e}[+,-]#[#][#][#]]]'); end; procedure tRealFormatLine.GetData; var Result : real absolute Rec; code : integer; begin val(Data^, Result, code); if code<>0 then ReportError(flFormatError); end; procedure tRealFormatLine.SetData; var Default : real absolute Rec; begin if Default>1E6 then str(Default,Data^) else str(Default:0:8,Data^); end; function tRealFormatLine.DataSize : word; begin DataSize:=sizeof(Real); end; constructor tIntegerFormatLine.Init; begin tFormatLine.Init(Bounds,AMaxLen,'[+,-]#*#'); end; procedure tIntegerFormatLine.SetData; var Default : integer absolute Rec; begin str(Default,Data^); end; procedure tIntegerFormatLine.GetData; var Result : integer absolute Rec; code : integer; begin val(Data^,Result,code); if code<>0 then ReportError(flFormatError); end; function tIntegerFormatLine.DataSize : word; begin DataSize:=sizeof(integer); end; constructor tNameFormatLine.Init; begin tFormatLine.Init(Bounds,AMaxLen,'*[![*?][@][ ]]'); end; constructor tZipFormatLine.Init; begin tFormatLine.Init(Bounds,AMaxLen,'#####[-####],&#& #&#'); end; end.