unit databox; { This is a unit to let you open data-entry boxes on the screen for quick 'n' easy data entry. It operates on variables of type "string", "integer", "word", "byte", "longint" and "boolean". There are two main routines to call here: OpenBox(x, y, data, temp, type) -- to open a data entry box on the screen ReadBoxes -- to read all data entry boxes The parameters for "OpenBox": x, y -- the coordinates where the box should appear on the screen data -- the variable you want to do data entry on type -- an character indicating what type of variable you're working on. Valid "types" are: 'S' -- String 'I' -- Integer 'W' -- Word 'L' -- LongInt 'Y' -- Byte 'B' -- Boolean temp -- a string "template" indicating the size of the data entry field and the data acceptable at each position. The following characters mean the following: 'X' -- accept any character ( strings ) '!' -- accept any character, but capitalize ( strings ) '9' -- accept only digits and minus signs ( numeric ) 'T' -- accept only 'T' and 'F' ( boolean ) 'Y' -- accept only 'T', 'F', 'Y' and 'N' ( boolean ) All of these template characters are valid on strings. For numeric fields, the whole template gets converted to all 9's; for boolean, the template will either be a single 'T' or 'Y' (it defaults to 'T'). Examples: OpenBox(12, 10, counter, '99999', 'I'); -- is for an integer variable "counter". It opens a data entry box at position (12, 10), and is five characters across. OpenBox(1, 14, yes_or_no, 'Y', 'b') -- opens a data entry box for a boolean variable "yes_or_no", and will accept only a "Y" or an "N" as input. OpenBox(1, 25, namestring, '!XXXXXXXXXXXXXXXX', 's') -- opens a data entry box for a string variable "namestring"; it will automatically capitalize the first letter, and accept every other character entered "as is". When you have opened all your data boxes, call "ReadBoxes" to allow the user to actually input into the boxes. Once you are done, the boxes "close" so you can't do any more data entry on them. There is also a "ClearBoxes" procedure to manually "close" open boxes, and a "Qwrite" procedure for doing direct video writes. Oh, I'm Lou Duchez, and if you could leave my name somewhere in the code I'd appreciate it. I'll never be rich off of public domain code like this, so at least help me get famous ... } { ------------------------------------------------------- } interface const boxforeground: byte = 1; boxbackground: byte = 7; procedure qwrite(x, y: byte; s: string; f, b: byte); procedure openbox(x, y: byte; var data; template: string; datatype: char); procedure clearboxes; procedure readboxes; { ------------------------------------------------------- } implementation uses crt; { for "checkbreak" and "readkey" functions } const maxboxes = 255; { open up to 255 data boxes simultaneously } type boxrecord = record { holds all the data we need } x, y: byte; { position to display on screen } template: string; { describes size and type of data field } dataptr: pointer; { points to data } datatype: char; { type of data we're pointing to } end; var boxes: array[1 .. maxboxes] of ^boxrecord; { all the data boxes } boxcount, thisbox, boxpos, boxlength: byte; boxstring: string; boxmodified: boolean; { ------------------------------------------------------- } procedure qwrite(x, y: byte; s: string; f, b: byte); { direct video writes } { x, y: coordinates to display string at } { s: the string to display } { f, b: the foreground and background colors to display in } type videolocation = record { video memory locations } videodata: char; { character displayed } videoattribute: byte; { attributes } end; var cnter: byte; videosegment: word; vidptr: ^videolocation; videomode: byte absolute $0040:$0049; scrncols: byte absolute $0040:$004a; monosystem: boolean; begin { Find the memory location where the string will be displayed at, according to the monitor type and screen location. Then associate the pointer VIDPTR with that memory location: VIDPTR is a pointer to type VIDEOLOCATION. Insert the screen data and attribute; now go to the next character and video location. } monosystem := (videomode = 7); if monosystem then videosegment := $b000 else videosegment := $b800; vidptr := ptr(videosegment, 2*(scrncols*(y - 1) + (x - 1))); for cnter := 1 to length(s) do begin vidptr^.videoattribute := (b shl 4) + f; vidptr^.videodata := s[cnter]; inc(vidptr); end; end; { ------------------------------------------------------- } procedure movecursor(boxnum, position: byte); { Positions cursor. } var tmpx, tmpy: byte; begin tmpx := (boxes[boxnum]^.x - 1) + (position - 1); tmpy := (boxes[boxnum]^.y - 1); asm mov ah, 02h { Move cursor here. I don't use GOTOXY because it } mov bh, 00h { is window-dependent. } mov dh, tmpy mov dl, tmpx int 10h end; end; { ------------------------------------------------------- } procedure openbox(x, y: byte; var data; template: string; datatype: char); var i: byte; datastring, tempstring: ^string; begin if boxcount < maxboxes then begin { If we have room for another data } inc(boxcount); { box, allocate memory for it from } new(boxes[boxcount]); { the heap and fill its fields. } boxes[boxcount]^.x := x; boxes[boxcount]^.y := y; boxes[boxcount]^.dataptr := @data; boxes[boxcount]^.template := template; boxes[boxcount]^.datatype := upcase(datatype); case upcase(datatype) of { "Fix" data entry template as needed. Make sure the string data and the template are of the same length. Numeric templates should consist of all 9's. Boolean templates should be either 'Y' or 'T'. } 'S': begin datastring := boxes[boxcount]^.dataptr; tempstring := addr(boxes[boxcount]^.template); while length(datastring^) < length(tempstring^) do datastring^ := datastring^ + ' '; while length(tempstring^) < length(datastring^) do tempstring^ := tempstring^ + ' '; end; 'W', 'I', 'L', 'Y': for i := 1 to length(template) do boxes[boxcount]^.template[i] := '9'; 'B': begin boxes[boxcount]^.template[0] := #1; if not (boxes[boxcount]^.template[1] in ['Y', 'T']) then boxes[boxcount]^.template := 'T'; end; end; end; end; { ------------------------------------------------------- } procedure clearboxes; { Free up all memory for "box" data. } begin while boxcount > 0 do begin dispose(boxes[boxcount]); dec(boxcount); end; end; { ------------------------------------------------------- } procedure fixstring(boxnumber: byte); { Adjusts string for displaying } var i: byte; { so that each character adheres to } begin { the corresponding template char. } for i := 1 to length(boxstring) do case upcase(boxes[boxnumber]^.template[i]) of 'X': ; '!': boxstring[i] := upcase(boxstring[i]); '9': if not (boxstring[i] in ['-', '0' .. '9']) then boxstring[i] := ' '; 'T': case upcase(boxstring[i]) of 'Y', 'T': boxstring[i] := 'T'; 'N', 'F': boxstring[i] := 'F'; else boxstring[i] := ' '; end; 'Y': case upcase(boxstring[i]) of 'Y', 'T': boxstring[i] := 'Y'; 'N', 'F': boxstring[i] := 'N'; else boxstring[i] := ' '; end; end; qwrite(boxes[boxnumber]^.x, boxes[boxnumber]^.y, boxstring, boxforeground, boxbackground); end; { ------------------------------------------------------- } procedure displaybox(boxnumber: byte); { Convert data to string and display. } var lentemplate: byte; pntr: pointer; begin pntr := boxes[boxnumber]^.dataptr; lentemplate := length(boxes[boxnumber]^.template); case boxes[boxnumber]^.datatype of 'S': boxstring := string(pntr^); 'I': str(integer(pntr^): lentemplate, boxstring); 'W': str(word(pntr^): lentemplate, boxstring); 'Y': str(byte(pntr^): lentemplate, boxstring); 'L': str(longint(pntr^): lentemplate, boxstring); 'B': if boolean(pntr^) then boxstring := 'T' else boxstring := 'F'; end; fixstring(boxnumber); end; { ------------------------------------------------------- } procedure deletekey; { delete: remove character at cursor and shift over } var i: byte; begin boxmodified := true; for i := boxpos to boxlength - 1 do boxstring[i] := boxstring[i + 1]; boxstring[boxlength] := ' '; end; procedure backspace; { backspace: back up one and delete if we're } begin { still in the same box } boxpos := boxpos - 1; if boxpos = 0 then begin dec(thisbox); boxpos := 255; end else deletekey; end; { Enter, Tab, and Shift-Tab move you to the beginning of prev/next box } procedure enterkey; begin inc(thisbox); boxpos := 1; end; procedure tab; begin inc(thisbox); boxpos := 1; end; procedure reversetab; begin dec(thisbox); boxpos := 1; end; { PgUp, PgDn, Esc take you out of editing; "Esc" indicates that the "current" box should not be updated } procedure pageup; begin thisbox := 0; end; procedure pagedown; begin thisbox := 0; end; procedure esckey; begin thisbox := 0; boxmodified := false; end; { Up / Down } procedure moveup; begin dec(thisbox); end; procedure movedown; begin inc(thisbox); end; procedure moveleft; { Move left; if we go too far left, move up } begin dec(boxpos); if (boxpos = 0) then begin boxpos := 255; moveup; end; end; procedure moveright; { Move right; if we go too far right, move down } begin inc(boxpos); if (boxpos > boxlength) then begin boxpos := 1; movedown; end; end; procedure literalkey(keyin: char); { accept character into field } var i: byte; goodkey, insmode: boolean; keyboardstat: byte absolute $0040:$0017; begin case upcase(boxes[thisbox]^.template[boxpos]) of { does char match tmplt? } '9': goodkey := (keyin in ['-', '0'..'9']); 'T': goodkey := (upcase(keyin) in ['T', 'F']); 'Y': goodkey := (upcase(keyin) in ['T', 'F', 'Y', 'N']); else goodkey := true; end; if goodkey then begin { character matches template -- use it } boxmodified := true; insmode := (keyboardstat and $80 = $80); if insmode then begin i := length(boxstring); { "Insert" mode: make space for new char } while i > boxpos do begin boxstring[i] := boxstring[i - 1]; dec(i); end; end; boxstring[boxpos] := keyin; { enter character and move to the right } moveright; end; end; { ------------------------------------------------------- } procedure readbox; { get data input on the box specified by THISBOX } var keyin: char; startingbox, i: byte; pntr: pointer; dummyint: integer; numstring: string; begin boxmodified := false; { "housekeeping" here } startingbox := thisbox; displaybox(thisbox); boxlength := length(boxstring); if boxpos > boxlength then boxpos := boxlength; { cursor positioning } if boxpos < 1 then boxpos := 1; while (thisbox = startingbox) and (boxpos >= 1) and (boxpos <= boxlength) do begin { process field } fixstring(startingbox); movecursor(startingbox, boxpos); keyin := readkey; { Interpret keystrokes here } case keyin of #0: case readkey of #15: reversetab; #72: moveup; #73: pageup; #75: moveleft; #77: moveright; #80: movedown; #81: pagedown; #83: deletekey; end; #8: backspace; #9: tab; #13: enterkey; #27: esckey; else literalkey(keyin); end; end; if boxmodified then begin { If data was changed, update variable } { This section handles numeric decoding. Since "Val" gets real uppity if there are spaces in the middle of your string, these couple loops isolates the first section of the data entry string surrounded by spaces. Then "Val" processes that part. } i := 1; while (i <= length(boxstring)) and (boxstring[i] = ' ') do inc(i); numstring[0] := #0; while (i <= length(boxstring)) and (boxstring[i] <> ' ') do begin inc(numstring[0]); numstring[length(numstring)] := boxstring[i]; inc(i); end; pntr := boxes[startingbox]^.dataptr; { Put the updated data back into its original variable. } case boxes[startingbox]^.datatype of 'S': string(pntr^) := boxstring; 'I': val(numstring, integer(pntr^), dummyint); 'W': val(numstring, word(pntr^), dummyint); 'Y': val(numstring, byte(pntr^), dummyint); 'L': val(numstring, longint(pntr^), dummyint); 'B': boolean(pntr^) := (upcase(boxstring[1]) = 'Y') or (upcase(boxstring[1]) = 'T'); end; end; { Do a final data display. } displaybox(startingbox); movecursor(startingbox, boxlength + 1); end; { ------------------------------------------------------- } procedure readboxes; { gets data input on all boxes } var oldcheckbreak: boolean; begin oldcheckbreak := checkbreak; checkbreak := false; for thisbox := 1 to boxcount do displaybox(thisbox); { display data boxes } thisbox := 1; boxpos := 1; while (thisbox >= 1) and (thisbox <= boxcount) do readbox; clearboxes; checkbreak := oldcheckbreak; end; { ------------------------------------------------------- } begin { initialize to "no boxes" } boxcount := 0; end. ============================================================================== TEST PROGRAM: ============================================================================== program datatest; uses databox, crt; var i: integer; s: string; w: word; b: boolean; l: longint; y: byte; begin clrscr; i := 10; openbox(1, 1, i, '999999', 'i'); w := 10; openbox(1, 3, w, '999999', 'w'); s := 'SpamBurger'; openbox(1, 5, s, '!xxxxxxxxxxxxxxx', 's'); readboxes; gotoxy(1, 18); writeln(i); writeln(w); writeln(s); b := false; openbox(1, 7, b, 'Y', 'b'); l := 10; openbox(1, 9, l, '9999999999', 'l'); y := 20; openbox(1,11, y, '9999999999', 'y'); readboxes; gotoxy(1, 21); writeln(b); writeln(l); writeln(y); end.