home *** CD-ROM | disk | FTP | other *** search
/ QBasic & Borland Pascal & C / Delphi5.iso / Basic / Samples / BOZOL2 / BOZOL2.ZIP / DB.BAS < prev    next >
Encoding:
BASIC Source File  |  1994-02-08  |  34.4 KB  |  1,022 lines

  1. '       DBASE III COMPATIBLE DATA FILE INTERFACE for PowerBASIC 3.0+
  2. '
  3. ' dBASE interface, screen field editing, and indexing routines by Erik Olson
  4. ' with Joe Vest's BT() BTree subroutine and a modified field input routine
  5. ' by David Zarnitsky.  Special thanks to Bob Zale for making me do this.
  6.  
  7. ' Routine list (detailed descriptions follow)
  8.  
  9. ' dBASE .DBF file access
  10. '    dBUse(STRING,INTEGER)
  11. '    dBGetRecord(DWORD,INTEGER)
  12. '    dBGetCField$(STRING,INTEGER)
  13. '    dBGetNField!(STRING,INTEGER)
  14. '    dBPutRecord(DWORD,INTEGER)
  15. '    dBPutCField(STRING, STRING, INTEGER)
  16. '    dBPutNField(STRING, SINGLE, INTEGER)
  17.  
  18. ' utilities
  19. '    dBGetASCII$()
  20. '    dBGetARRAY(STRING ARRAY,INTEGER)
  21. '
  22. ' index support
  23. '    dBSetIndexTo(IX$,Fld$,e%)
  24. '    dBCreateIndex(IX$, Fld$, e%)
  25. '    dBSearchIndex(Findme$,e%)
  26. '    dBSkip(NS%, e%)
  27. '    dBGotoTop(e%)
  28. '    dBGotoBottom(e%)
  29.  
  30. ' screen editing
  31. '    dBCreateFormat ()
  32. '    dBSetFormatTo (FormatFileName$,Ecode%)
  33. '    dBView ()
  34. '    dBEditFields (Ecode%)
  35. '    dBEditRecord (RecNum???,E%)
  36. '    dBAppendRecord (E%)
  37.  
  38. %FALSE = 0
  39. %TRUE = NOT %FALSE
  40. %INSERTSCAN = 3           ' Change these two to change shape of cursor
  41. %OVERWRITESCAN = 6        ' The higher the number, the smaller the cursor
  42.  
  43.  
  44. ' SUB or FUNCTION declaration            Example use and description
  45. '====================================    ===========================
  46. DECLARE SUB dBUse(STRING,INTEGER)      ' dBUse "TEST.DBF", ErrorCode%
  47.                        '  ErrorCode returns
  48.                        '   1 - file not found
  49.                        '   2 - Zero byte file
  50.                        '   3 - File has no fields
  51.                        '   4 - not a dBASE file
  52.  
  53. DECLARE SUB dBGetRecord(DWORD,INTEGER) ' dBGetRecord R???, ErrorCode%
  54.                        ' ErrorCode returns
  55.                        '   1 - database not open
  56.                        '   2 - record exceeds size
  57.                        '   3 - record => zero
  58.  
  59. DECLARE FUNCTION dBGetCField$(STRING,INTEGER)
  60.                        ' ErrorCode 1 if no such field
  61.                        ' A$=dBGetCField$("PHONE",e%)
  62.                        ' returns the string value of a
  63.                        ' character field
  64.  
  65. DECLARE FUNCTION dBGetNField!(STRING,INTEGER)
  66.                        ' A! = dBGetNField!("TOTAL",e%)
  67.                        ' ErrorCode 1 if no such field
  68.                        ' Returns a single precision number
  69.                        ' of a numeric field with proper
  70.                        ' decimal places
  71.  
  72. DECLARE SUB dBPutRecord(DWORD,INTEGER) ' dBPutRecord(R???,ErrorCode%)
  73.                        ' Returns error 1 if no dbase open
  74.                        ' Returns error 2 if record too hi
  75.                        ' Puts the current record in memory
  76.                        ' into the database at the record
  77.                        ' specified.  If record number is
  78.                        ' 1 higher than NumberOfRecords???
  79.                        ' or if it is 0 then the record will
  80.                        ' be appended to the database
  81.  
  82. DECLARE SUB dBPutCField(STRING, STRING, INTEGER)
  83.                        ' dBPutCField "NAME", "Erik", Ecode%
  84.                        ' returns error if no such field
  85.                        ' places a string value into a
  86.                        ' character field in memory
  87.  
  88. DECLARE SUB dBPutNField(STRING, SINGLE, INTEGER)
  89.                        ' dBPutNField "AGE", 27, Ecode%
  90.                        ' returns error if no such field
  91.                        ' places a numeric value into a
  92.                        ' character field in memory.  Numeric
  93.                        ' argument is formatted according to
  94.                        ' the design of the field
  95.  
  96. DECLARE SUB dBCreateFormat ()          ' runs a mini program to create a
  97.                        ' data entry screen format.  The
  98.                                        ' current format or a default format
  99.                                        ' (of up to 44 fields) is created.
  100.                                        ' you then move the fields around
  101.                                        ' on the screen with the arrow
  102.                                        ' keys and press ENTER when finished.
  103.  
  104. DECLARE SUB dBSetFormatTo(FormatFileName$,Ecode%)
  105.                     ' dBSetFormatTo "SCREEN1.FRM", E%
  106.                                         ' Loads screen edit format file and
  107.                                         ' returns.  If not successful error
  108.                                         ' code returns 1 for file not found.
  109.                                         ' If filename is nul string then
  110.                                         ' the current format is cleared.
  111.                                         ' Ecode% returns 1 if the format
  112.                                         ' file is not found.
  113.  
  114. DECLARE SUB dBView ()            ' Uses the current screen format to
  115.                     ' simply display the current record.
  116.                                         ' it does not pause.
  117.  
  118. DECLARE SUB dBEditFields(Ecode%)        ' uses the current screen format to
  119.                     ' display and then allow editing of
  120.                                         ' the current record in typical
  121.                                         ' dBASE fashion.  CTRL-END or F10
  122.                                         ' terminates and updates the record.
  123.                                         ' ESCAPE terminates and does not
  124.                                         ' update the record.
  125.  
  126. DECLARE SUB dBEditRecord(RecNum???,E%)  ' Gets a record and allows fullscreen
  127.                     ' editing using current screen format
  128.                                         ' or default screen format if no
  129.                                         ' current format is set.  e% returns
  130.                                         ' 1 if the specified record does not
  131.                                         ' exist.
  132.  
  133. DECLARE SUB dBAppendRecord(E%)          ' Creates a blank record and allows
  134.                     ' full screen editing.  If the record
  135.                                         ' is not aborted it will be appended
  136.                                         ' to the database.  Uses the current
  137.                                         ' screen format or default format if
  138.                                         ' no format is set.  e% returns 1 if
  139.                                         ' the record cannot be appended to
  140.                                         ' the database for whatever reason.
  141.  
  142. DECLARE FUNCTION dBGetASCII$()         ' A$ = dBGetASCII$
  143.                        ' returns a comma delimited ASCII
  144.                        ' record of the entire dBASE record
  145.                        ' currently in memory
  146.  
  147. DECLARE SUB dBGetARRAY(STRING ARRAY,INTEGER)
  148.                        ' dBGetARRAY DB$,e%
  149.                        ' fills the specified array with
  150.                        ' consecutive fields from the entire
  151.                        ' dBASE record currently in memory.
  152.                        ' ErrorCode 1 is array is too small
  153.  
  154. DECLARE SUB dBSetIndexTo(IX$,Fld$,e%)      ' Set index to file in IX$.  You must
  155.                                         ' specify the field which is being
  156.                                         ' indexed in order to properly update
  157.                                         ' the index during append or edit
  158.                                         ' operations.  The index must have
  159.                                         ' already been created using
  160.                                         ' dBCreateIndex.  E% returns 1 if the
  161.                                         ' database is not open, 2 if the
  162.                                         ' specified field is not in the
  163.                                         ' database, 3 if the index file
  164.                                         ' does not exist
  165.  
  166. DECLARE SUB dBCreateIndex(IX$, Fld$, e%)' Creates an index file specified in
  167.                     ' IX$.  You must specify the field
  168.                                         ' to index in FLD$.  As the file is
  169.                                         ' being indexed, record numbers are
  170.                                         ' printed to the screen at the
  171.                                         ' current cursor location.  e%
  172.                                         ' returns 1 if the database is not
  173.                                         ' open, 2 if the field does not
  174.                                         ' exist, 3 if the index can't be
  175.                                         ' created on disk, 4 if there is
  176.                                         ' an error reading the database,
  177.                                         ' 5 if the user aborts with ESC,
  178.                                         ' 6 if there is an internal error
  179.                                         ' extracting the field from the
  180.                                         ' record, or 7 if there is an error
  181.                                         ' writing to the index file (like
  182.                                         ' the disk fills up).
  183.  
  184. DECLARE SUB dBSearchIndex(Findme$,e%)   ' The current index (specified in
  185.                     ' dBSetIndexTo) is searched for
  186.                                         ' a match or closest match (next
  187.                                         ' higher) to the string in Findme$.
  188.                                         ' Index searches are case-INsensative
  189.                                         ' When a match or closest match is
  190.                                         ' found, the actual indexed field is
  191.                                         ' returned in FindMe$, so you can
  192.                                         ' test it against what was originally
  193.                                         ' passed to it.  The matching or
  194.                                         ' closest matching record is loaded.
  195.                                         ' IF NO INDEX HAS BEEN SET, this
  196.                                         ' routine will prompt if you want
  197.                                         ' to sequentially scan the database
  198.                                         ' for a match in any field.  e%
  199.                                         ' returns 1 if no database is open,
  200.                                         ' or if there is an error reading
  201.                                         ' the index or database.  Not too
  202.                                         ' specific, huh?
  203.  
  204. DECLARE SUB dBSkip(NS%, e%)        ' Skips the number of records
  205.                     ' specified in NS%, either physically
  206.                                         ' of via the index if one has been
  207.                                         ' set.  Notice NS% is an integer.
  208.                                         ' e% returns 1 if something goes
  209.                                         ' wrong in the skip operation.  If
  210.                                         ' you skip physically beyond the end
  211.                                         ' or before record 1, you will get
  212.                                         ' the highest record, or record 1.
  213.  
  214. DECLARE SUB dBGotoTop(e%)        ' Goes to record 1 or to the first
  215.                     ' record in the index if one has
  216.                                         ' been set.  e% returns 1 if there
  217.                                         ' is an error in this operation or
  218.                                         ' -2 if there is an index error
  219.  
  220. DECLARE SUB dBGotoBottom(e%)        ' Goes to the last record in the
  221.                     ' database or to the last record in
  222.                                         ' the index if one has been set.
  223.                                     ' e% returns 1 if there is an error
  224.                                         ' or -2 if the index returns an
  225.                                         ' error.
  226.  
  227. OPTION BINARY BASE 1
  228.  
  229. 'THE FOLLOWING STRUCTURES ARE DIMENSIONED AS SHARED.  USE THEM IN GOOD HEALTH
  230.  
  231. TYPE DBaseHeaderRecord
  232.         Ver AS BYTE         ' dBASE version
  233.        Year AS BYTE         ' year
  234.       Month AS BYTE         ' month
  235.         Day AS BYTE         ' day of last update
  236. NumberOfRecords AS DWORD        ' number of records in this database
  237.          offset AS WORD         ' length of header
  238.        Size AS WORD         ' length of record
  239.       Blank AS STRING * 20  ' reserved for future use
  240. END TYPE
  241.  
  242. TYPE DBaseFieldRecord
  243.    FieldName AS STRING * 11  ' name of the field in ASCII
  244.    FieldType AS STRING * 1   ' Type CNLM or D
  245.      FDA AS DWORD        ' field data address - we don't need this
  246.     FLen AS BYTE         ' Length, we'll need this!
  247.     DecC AS BYTE         ' number of decimals in numeric field
  248.       Blank9 AS STRING * 14  ' reserved for future use
  249. END TYPE
  250.  
  251. TYPE DBStructureRecord
  252.     FieldName AS STRING * 11
  253.     FieldType AS STRING * 1
  254.     FieldLength AS BYTE
  255.     FieldOffset AS INTEGER
  256.     FieldDecimals AS BYTE
  257.     END TYPE
  258.  
  259. TYPE DBaseEditFormat
  260.         FieldName AS STRING * 11
  261.         FieldType AS STRING * 1
  262.         FieldLength AS BYTE
  263.         FieldRow AS INTEGER
  264.         FieldCol AS INTEGER
  265.         FieldFG AS INTEGER
  266.         FieldBG AS INTEGER
  267. END TYPE
  268.  
  269.  
  270. DIM DBH AS DBaseHeaderRecord
  271. DIM DBF AS DBaseFieldRecord
  272. DIM DBS(256) AS DBStructureRecord
  273. DIM DBE(256) AS DBaseEditFormat
  274.  
  275. SHARED DBH, DBF, DBS(), dBaseOpen%, RecNum???, NumberOfFields?, RecordBlock$
  276. SHARED DBE(), NumberOfRecords???, Index$, IndexField$, IndexField?
  277. SHARED Bt.Update.Always%, Act.Keys$
  278. ' THE FOLLOWING VARIABLES ARE SHARED AND CONTAIN USEFUL STATUS INFORMATION
  279.  
  280.  BT.Update.Always% = -1 ' for Vest BTree indexing
  281.     dBaseOpen% = 0  ' Integer contains buffer number if database open
  282.      RecNum??? = 0  ' Current record number
  283.    NumberOfFields? = 0  ' Number of fields in current database
  284.       RecordBlock$ = "" ' Contains binary image of current record
  285.       ErrCode% = 0  ' Return code used by subs and functions for errors
  286. NumberOfRecords??? = 0  ' Total number of records in the current database
  287.         Index$ = "" ' Name of current index if open
  288.        IndexField$ = "" ' Name of current indexed field if index open
  289.        IndexField? = 0  ' Field number of current indexed field if ...
  290.  
  291. '=========================================================================
  292. '                         Test program goes here
  293. '=========================================================================
  294.  
  295.  
  296.  
  297. '=========================================================================
  298. '         dBASE III Plus file interface subroutines begin here
  299. '=========================================================================
  300. SUB dBSetIndexTo(IX$,Fld$,e%)
  301. e%=0
  302. ' Make sure a database is open
  303. IF dBASEOpen%=0 THEN e%=1:EXIT SUB
  304.  
  305. ' close existing index if it is open
  306. IF IX$="" OR Index$<>"" THEN Index$="":_
  307.    CALL BT("","Q","","","","",r%)
  308. IF IX$="" THEN EXIT SUB
  309. ' verify filename exists
  310. IF DIR$(IX$)="" THEN e%=3:EXIT SUB
  311.  
  312. ' verify field exists in database
  313. Fld%=0:Fld$=UCASE$(Fld$)
  314.     FOR y%=1 TO NumberOfFields?
  315.             IF INSTR(DBS(y%).FieldName,Fld$)=1 THEN Fld%=y%:EXIT FOR
  316.     NEXT y%
  317. IF Fld%=0 THEN e%=2:EXIT SUB
  318. Index$=IX$:IndexField$=Fld$:IndexField?=Fld%
  319. END SUB
  320.  
  321. SUB dBCreateIndex(IX$, Fld$, e%)
  322. Bt.Update.Always%=0
  323. ' Make sure a database is open
  324. IF dBASEOpen%=0 THEN e%=1:GOTO ExitSub
  325.  
  326. ' close existing index if it is open
  327. IF IX$="" OR Index$<>"" THEN Index$="":_
  328.    CALL BT("","Q","","","","",r%)
  329. IF IX$="" THEN EXIT SUB
  330.  
  331. ' verify field exists in database
  332. Fld%=0:Fld$=UCASE$(Fld$)
  333.     FOR y%=1 TO NumberOfFields?
  334.             IF INSTR(DBS(y%).FieldName,Fld$)=1 THEN Fld%=y%:EXIT FOR
  335.     NEXT y%
  336. IF Fld%=0 THEN e%=2:GOTO EXITSUB
  337. Index$=IX$:IndexField$=Fld$:IndexField?=Fld%
  338.  
  339. ' Create the index and build it.
  340. K$=SPACE$(DBS(Fld%).FieldLength):D$=CHR$(0,0,0,0)
  341. CALL BT(Index$,"C",K$,D$,RK$,RD$,R%)
  342. IF NOT R% THEN E%=3:GOTO EXITSUB ' could not create index
  343. x%=CSRLIN:y%=POS(0)
  344. For y???=1 TO NumberOfRecords???
  345.     dBGetRecord Y???, e%
  346.         IF e% THEN e%=4:EXIT FOR
  347.     IF INSTAT THEN A$=INKEY$:IF A$=CHR$(27) THEN e%=5:EXIT FOR
  348.  
  349.         ' ====================
  350.         ' remove the UCASE$ here if you do not want the index to be
  351.         ' create as case insensative.
  352.         K$=UCASE$(dBGetCField$(Indexfield$, e%))
  353.         '  ^^^^^^____________________________ ^
  354.  
  355.         IF e% THEN e%=6:EXIT FOR
  356.         D$=MKDWD$(Y???)  ' must know the record number!
  357.     CALL BT(Index$,"A",K$,D$,RK$,RD$,r%)
  358.         IF NOT r% THEN e%=7:EXIT FOR
  359.         LOCATE x%,y%:PRINT Y???;
  360.         NEXT y???
  361.     CALL BT(Index$,"Q","","","","",r%)
  362. ExitSub:
  363.         SELECT CASE e%
  364.         CASE 1
  365.                 PRINT "No database in USE."
  366.         CASE 2
  367.                 PRINT "Field name not found."
  368.         CASE 3
  369.                 PRINT "Could not create file."
  370.                 CASE 4
  371.                 PRINT "Invalid record number."
  372.         CASE 5
  373.                 PRINT "**ABORTED**"
  374.         CASE 6
  375.                 PRINT "Error finding field data."
  376.             CASE 7
  377.                 PRINT "Error writing to index file."
  378.                 CASE ELSE
  379.             PRINT
  380.         END SELECT
  381. BT.Update.Always%=-1
  382. END SUB
  383.  
  384. SUB dBSearchIndex(Findme$,e%)
  385. e%=0
  386. IF dBaseOpen%=0 THEN e%=1:EXIT SUB
  387. IF Index$="" THEN
  388.         INPUT "Index not open, scan database? (Y/N): ",YN$
  389.     IF UCASE$(YN$)="Y" THEN
  390.         ' scan the whole database for a match
  391.         FOR y???=1 TO NumberOfRecords???
  392.                 dBGetRecord y???, e%
  393.                     IF e% THEN EXIT FOR
  394.                     IF INSTR(FindMe$,RecordBlock$) THEN EXIT FOR
  395.         NEXT y???
  396.         IF y???=>NumberOfRecords THEN _
  397.                 print "Last Record.  Press a key...":DO:LOOP WHILE INKEY$=""
  398.         END IF
  399. ELSE
  400.         Findme$=UCASE$(Findme$)
  401.     CALL BT(Index$,"S", Findme$, D$, RK$, RD$, r%)
  402.         'IF NOT r% THEN e%=2:EXIT SUB
  403.         FindMe$=RK$
  404.         R???=CVDWD(RD$)
  405.         IF R???>0 THEN CALL dBGetRecord(R???,e%)
  406. END IF
  407. END SUB
  408.  
  409. SUB dBSkip(NS%, e%)
  410. e%=0
  411. IF LEN(INDEX$) THEN
  412.         DO
  413.     IF NS%<0 THEN BT Index$,"P","","",K$,D$,r%:INCR NS% ELSE _
  414.                       BT Index$,"N","","",K$,D$,r%:DECR NS%
  415.         IF NOT r% THEN e%=1:EXIT SUB
  416.         IF INSTAT THEN IF A$=CHR$(27) THEN NS%=0
  417.         LOOP WHILE NS%<>0
  418.         dBGetRecord CVDWD(D$), e%
  419. ELSE
  420.     RN???=RecNum??? + NS%
  421.         IF RN???<0 THEN RN???=1
  422.         IF RN??? > NumberOfRecords??? THEN RN???=NumberOfRecords???
  423.         dBGetRecord RN???,e%
  424. END IF
  425. END SUB
  426.  
  427. SUB dBGotoTop (e%)
  428. e%=0
  429. IF LEN(INDEX$) THEN
  430.     BT Index$,"F","","",K$,D$,r%
  431.         IF NOT r% THEN e%=-2:EXIT SUB
  432.         DBGetRecord CVDWD(D$),e%
  433. ELSE
  434.     DBGetRecord 1, e%
  435. END IF
  436. END SUB
  437.  
  438. SUB dBGotoBottom (e%)
  439. e%=0
  440. IF LEN(INDEX$) THEN
  441.     BT Index$,"L","","",K$,D$,r%
  442.         IF NOT r% THEN e%=-2:EXIT SUB
  443.         DBGetRecord CVDWD(D$),e%
  444. ELSE
  445.     DBGetRecord NumberOfRecords???, e%
  446. END IF
  447. END SUB
  448.  
  449. SUB dBEditRecord (RN???, e%)
  450. e%=0
  451.     dBGetRecord RN???, e%
  452.         IF e% THEN EXIT SUB
  453.  
  454. ' remove entry from index
  455. IF LEN(INDEX$) THEN
  456.     BT Index$,"D",UCASE$(DBGetCField$(IndexField$,e%)),MKDWD$(RN???),"","",r%
  457.     IF NOT r% THEN PRINT "Error accessing index file"
  458. END IF
  459.  
  460.         ' edit the record
  461.         DBEditFields e%
  462.  
  463. ' replace entry in index
  464. IF LEN(INDEX$) THEN
  465.     BT Index$,"A", UCASE$(DBGetCField$(IndexField$,e%)),MKDWD$(RN???),"","",r%
  466.     IF NOT r% THEN PRINT "Error updating index file"
  467. END IF
  468. END SUB
  469.  
  470. SUB dBAppendRecord (e%)
  471.     e%=0
  472.         IF dBaseOpen%=0 THEN e%=1:EXIT SUB
  473.     Recnum???=0
  474.         RecordBlock$=SPACE$(LEN(RecordBlock$))
  475.     DbEditFields e%
  476.     IF Recnum???>0 AND LEN(INDEX$) THEN
  477.             BT Index$, "A", UCASE$(DBGetCField$(IndexField$,e%)),MKDWD$(RecNum???),"","",r%
  478.                 IF NOT r% THEN PRINT "Error appending index file."
  479.         END IF
  480. END SUB
  481.  
  482. SUB dBDefaultFormat
  483. ' Create a default field edit format.
  484. IF dBaseOpen%=0 THEN EXIT SUB
  485. ERASE DBE()
  486. k%=1
  487. FOR y%=1 to NumberOfFields?
  488.         INCR j%:IF j%=20 THEN j%=1:k%=k%+40:IF K%=81 THEN EXIT FOR
  489.     DBE(y%).FieldName = DBS(y%).FieldName
  490.         DBE(y%).FieldType = DBS(y%).FieldType
  491.         DBE(y%).FieldLength = DBS(y%).FieldLength
  492.         DBE(y%).FieldRow = j%
  493.         DBE(y%).FieldCol = k%+(11-LEN(RTRIM$(DBS(y%).FieldName,CHR$(0))))
  494.     DBE(y%).FieldFG = 0
  495.         DBE(y%).FieldBG = 7
  496. NEXT y%
  497. END SUB
  498.  
  499. SUB dBCreateFormat
  500. IF dBaseOpen%=0 THEN PRINT "No Database is in USE.":EXIT SUB
  501. DO
  502. CLS
  503. DBView
  504. LOCATE 23,1:COLOR 7,0:INPUT "Press ENTER to Accept or Fieldname to change: ",F$
  505. IF F$="" THEN
  506.     B%=FREEFILE
  507.         LOCATE 23,1:PRINT SPACE$(80);
  508.         LOCATE 23,1:INPUT "Enter format filename: ",F$
  509.         IF F$="" THEN F$="NONAME.FMT"
  510.         OPEN F$ FOR RANDOM SHARED AS #B% LEN=LEN(DBE(1))
  511.         Fld%=1
  512.         DO UNTIL DBE(Fld%).FieldLength=0
  513.             PUT #B%, Fld%, DBE(Fld%)
  514.                 INCR Fld%
  515.         LOOP
  516.         EXIT LOOP
  517. ELSE
  518. Fld%=0
  519. F$=UCASE$(F$)
  520.     FOR y%=1 TO NumberOfFields?
  521.             IF INSTR(DBS(y%).FieldName,F$)=1 THEN Fld%=y%:EXIT FOR
  522.     NEXT y%
  523. IF Fld%=0 THEN LOCATE 23,1:PRINT SPACE$(80):LOCATE 23,1:PRINT "BAD FIELD NAME":SOUND 50,4:DELAY 2:ITERATE LOOP
  524. LOCATE 23,1:PRINT SPACE$(80);:LOCATE 23,1:PRINT "Use arrow keys to place new field position"
  525. X%=DBE(Fld%).FieldRow
  526. Y%=DBE(Fld%).FieldCol
  527. F$=RTRIM$(DBE(Fld%).FieldName,CHR$(0))+":"+STRING$(DBE(Fld%).FieldLength,176)
  528. ' edit field location
  529. DBSCRNFIND X%, Y%, F$
  530. IF X%=0 THEN EXIT LOOP
  531. DBE(Fld%).FieldRow = X%
  532. DBE(Fld%).FieldCol = Y%
  533. END IF
  534. LOOP
  535. END SUB
  536.  
  537. SUB dBSetFormatTo(FormatFileName$,Ecode%)
  538. Ecode%=0
  539. IF FormatFileName$="" THEN ERASE DBE():EXIT SUB
  540. IF Dir$(FormatFileName$)="" THEN Ecode%=1:EXIT SUB
  541. B%=FREEFILE
  542. OPEN FormatFileName$ FOR RANDOM SHARED AS #B% LEN=LEN(DBE(1))
  543. FOR y%=1 TO LOF(B%)\LEN(DBE)
  544.     GET #B%, y%, DBE(y%)
  545. NEXT y%
  546. CLOSE #B%
  547. END SUB
  548.  
  549. SUB dBView
  550. Fld%=1
  551. of%=(PBVScrnTxtAttr AND &HF) ' get the original foreground and background
  552. ob%=(PBVScrnTxtAttr \ &H10)  ' colors, in case they change.
  553. DO UNTIL DBE(Fld%).FieldLength=0
  554.     LOCATE DBE(Fld%).FieldRow,DBE(Fld%).FieldCol,0
  555.         COLOR of%,ob%
  556.         PRINT RTRIM$(DBE(Fld%).FieldName,chr$(0))+":";
  557.         r%=CSRLIN:c%=POS(0)
  558.         COLOR DBE(Fld%).FieldFG,DBE(Fld%).FieldBG
  559.         PRINT SPACE$(DBE(Fld%).FieldLength)
  560.     LOCATE r%,c%:
  561.         IF DBE(Fld%).FieldType="N" THEN
  562.             PRINT dBGetNField!((DBE(Fld%).FieldName),E%);
  563.                 IF E% THEN PRINT "???";
  564.     ELSE
  565.             PRINT dBGetCField$((DBE(Fld%).FieldName),E%);
  566.                 IF E% THEN PRINT "???";
  567.     END IF
  568.     INCR Fld%
  569. LOOP
  570. COLOR of%, ob%
  571. END SUB
  572.  
  573.  
  574. SUB dBEditFields(Ecode%)
  575. Ecode%=0
  576. Fld%=1 ' start with the first field on the screen
  577. of%=(PBVScrnTxtAttr AND &HF) ' get the original foreground and background
  578. ob%=(PBVScrnTxtAttr \ &H10)  ' colors, in case they change.
  579. ' Now make one pass and DRAW the fields on the screen with defaults
  580. DO UNTIL DBE(Fld%).FieldLength=0
  581.     LOCATE DBE(Fld%).FieldRow,DBE(Fld%).FieldCol,0
  582.         COLOR of%,ob%
  583.         PRINT RTRIM$(DBE(Fld%).FieldName,chr$(0))+":";
  584.         r%=CSRLIN:c%=POS(0)
  585.         COLOR DBE(Fld%).FieldFG,DBE(Fld%).FieldBG
  586.         PRINT SPACE$(DBE(Fld%).FieldLength)
  587.     LOCATE r%,c%:
  588.         IF DBE(Fld%).FieldType="N" THEN
  589.             PRINT dBGetNField!((DBE(Fld%).FieldName),E%);
  590.                 IF E% THEN PRINT "???";
  591.     ELSE
  592.             PRINT dBGetCField$((DBE(Fld%).FieldName),E%);
  593.                 IF E% THEN PRINT "???";
  594.     END IF
  595.  
  596.     INCR Fld%
  597. LOOP
  598.  
  599.  
  600. Fld%=1 ' start with the first field on the screen
  601. ' Now go back and edit the fields
  602. DO UNTIL DBE(Fld%).FieldLength=0
  603.     LOCATE DBE(Fld%).FieldRow,DBE(Fld%).FieldCol,0
  604.         COLOR of%,ob%
  605.         PRINT RTRIM$(DBE(Fld%).FieldName,CHR$(0))+":";
  606.         r%=CSRLIN:c%=POS(0)
  607.         IF DBE(Fld%).FieldType="N" THEN
  608.                 num%=-1
  609.             ED$=STR$(dBGetNField!((DBE(Fld%).FieldName),E%))
  610.                 IF E% THEN ED$="???"
  611.                     ELSE
  612.                 num%=0
  613.             ED$= dBGetCField$((DBE(Fld%).FieldName),E%)
  614.                 IF E% THEN ED$="???"
  615.     END IF
  616.  
  617.         ED$=DBGET$(r%, c%, (DBE(Fld%).FieldLength), (DBE(Fld%).FieldFG),_
  618.                   (DBE(Fld%).FieldBG), ED$, -1, num%,KeyFlag%)
  619.  
  620.         IF num% THEN
  621.             dBPutNField (DBE(Fld%).FieldName), VAL(ED$), E%
  622.         ELSE
  623.             dBPutCField (DBE(Fld%).FieldName),ED$,E%
  624.     END IF
  625.  
  626.     SELECT CASE KeyFlag%
  627.             CASE 10
  628.                     DBPutRecord RecNum???, E%
  629.                         EXIT LOOP
  630.         CASE 5
  631.                     EXIT LOOP
  632.         CASE 0,2,6
  633.                     INCR Fld%
  634.                         IF Fld%>NumberOfFields? THEN Fld%=NumberOfFields?
  635.                 CASE 4,8
  636.                     DECR Fld%
  637.                         IF Fld%=0 THEN Fld%=1
  638.         END SELECT
  639. LOOP
  640. Color Of%, Ob%
  641.  
  642. END SUB
  643.  
  644.  
  645.  
  646. SUB dBPutCField(FieldName$, FieldData$, Ecode%)
  647. Ecode% = 1
  648.     FieldName$=UCASE$(FieldName$)
  649. FOR nof? = 1 TO NumberOfFields?
  650.     IF LEFT$(DBS(nof?).FieldName,LEN(FieldName$)) = FieldName$ THEN
  651.                 IF LEN(FieldData$)>DBS(nof?).FieldLength THEN FieldData$=LEFT$(FieldData$,DBS(nof?).FieldLength)
  652.         MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  653.         DBS(nof?).FieldLength) = FieldData$ + _
  654.                 Space$(DBS(nof?).FieldLength-LEN(FieldData$))
  655.         Ecode% = 0
  656.         EXIT FOR
  657.     END IF
  658. NEXT nof?
  659. END SUB
  660.  
  661. SUB dBPutNField(FieldName$, FieldData!, Ecode%)
  662.     Ecode% = 1
  663.     FieldName$=UCASE$(FieldName$)
  664.  
  665. FOR nof? = 1 TO NumberOfFields?
  666.     IF LEFT$(DBS(nof?).FieldName,LEN(FieldName$)) = FieldName$ THEN
  667.     Pattern$ = STRING$(DBS(nof?).FieldLength,"#")
  668.     IF DBS(nof?).FieldDecimals > 0 THEN
  669.     MID$(Pattern$,LEN(Pattern$)-(DBS(nof?).FieldDecimals),1)="."
  670.     END IF
  671.     FieldData$ = USING$(Pattern$,FieldData!)
  672.     MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  673.     DBS(nof?).FieldLength) = FieldData$
  674.         Ecode% = 0
  675.         EXIT FOR
  676.     END IF
  677. NEXT nof?
  678.  
  679. END SUB
  680.  
  681.  
  682. SUB dBPutRecord(RN???,Ecode%)
  683. Ecode% = 0
  684. IF dBaseOpen% = 0 THEN Ecode% = 1: Exit Sub
  685.                          ' Error Code 1 = Database file not open
  686. GET #dBaseOpen%, 1, DBH
  687. IF RN??? > DBH.NumberOfRecords + 1 THEN RN???=0
  688. IF RN???<1 OR RN???=DBH.NumberOfRecords+1 THEN RN???=DBH.NumberOfRecords+1 :_
  689.  DBH.NumberOfRecords = RN???:LastRec%=1: NumberOfRecords???=RN???
  690. R$=MID$(RecordBlock$,2)
  691. IF LEN(R$)<DBH.Size+1 THEN R$=R$+SPACE$(DBH.Size+1-LEN(R$))
  692. IF LastRec%=1 THEN R$=R$+CHR$(26)
  693. PUT #dBaseOpen%, DBH.offset + ((RN??? * DBH.Size) - DBH.Size)+1 , R$
  694. IF DBH.NumberOfRecords = RN??? THEN _
  695.           e$ = CHR$(26) + CHR$(10): PUT #dBaseOpen%, SEEK(dBaseOpen%) + 1, e$
  696. DBH.Day   = VAL(MID$(DATE$, 4, 2))
  697. DBH.Month = VAL(LEFT$(DATE$, 2))
  698. DBH.Year  = VAL(RIGHT$(DATE$, 2))
  699.  
  700. PUT #dBaseOpen%, 1, DBH
  701.  
  702. END SUB
  703.  
  704.  
  705. SUB dBGetARRAY(DB$(),Ecode%)
  706.  
  707. IF UBOUND(DB$()) < NumberOfFields? THEN Ecode% = 1:EXIT SUB
  708.                     ' Error code 1, array not big enough
  709. FOR nof? = 1 TO NumberOfFields?
  710.     IF INSTR("CLD",DBS(nof?).FieldType) THEN
  711.         DB$(nof?) = MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  712.         DBS(nof?).FieldLength)
  713.     ELSE
  714.         DB$(nof?) = STR$(val(MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  715.      DBS(nof?).FieldLength)) * (10 ^ DBS(nof?).FieldDecimals))
  716.     END IF
  717. NEXT nof?
  718. END SUB
  719.  
  720.  
  721.  
  722. FUNCTION dBGetASCII$
  723. A$=""
  724. FOR nof? = 1 TO NumberOfFields?
  725.     IF INSTR("CLD",DBS(nof?).FieldType) THEN
  726.         A$ = A$ + CHR$(34)+MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  727.         DBS(nof?).FieldLength)+CHR$(34)
  728.     ELSE
  729.         A$ = A$ + STR$(val(MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  730.      DBS(nof?).FieldLength)) * (10 ^ DBS(nof?).FieldDecimals))
  731.     END IF
  732.     IF nof? < NumberOfFields? THEN A$ = A$ + ","
  733. NEXT nof?
  734. dBGetASCII$ = A$
  735. END FUNCTION
  736.  
  737.  
  738.  
  739.  
  740. FUNCTION dBGetCField$ (FieldName$, Ecode%)
  741. Ecode% = 1
  742.     FieldName$=UCASE$(FieldName$)
  743. FOR nof? = 1 TO NumberOfFields?
  744.     IF LEFT$(DBS(nof?).FieldName,LEN(FieldName$)) = FieldName$ THEN
  745.         dBGetCField$ = MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  746.         DBS(nof?).FieldLength)
  747.         Ecode% = 0
  748.         EXIT FOR
  749.     END IF
  750. NEXT nof?
  751. END FUNCTION
  752.  
  753. FUNCTION dBGetNField!(FieldName$,Ecode%)
  754.     Ecode% = 1
  755.     FieldName$=UCASE$(FieldName$)
  756. FOR nof? = 1 TO NumberOfFields?
  757.     IF LEFT$(DBS(nof?).FieldName,LEN(FieldName$)) = FieldName$ THEN
  758.     dBGetNField! = val(MID$(RecordBlock$, DBS(nof?).Fieldoffset,_
  759.      DBS(nof?).FieldLength)) '* (10 ^ -DBS(nof?).FieldDecimals)
  760.         Ecode% = 0
  761.         EXIT FOR
  762.     END IF
  763. NEXT nof?
  764.  
  765. END FUNCTION
  766.  
  767.  
  768. SUB DBGetRecord (Rn???, Ecode%)
  769. Ecode% = 0
  770. IF dBaseOpen% = 0 THEN Ecode% = 1: EXIT SUB              ' database not open
  771. GET #dBaseOpen%, 1, DBH
  772. IF Rn??? > DBH.NumberOfRecords THEN Ecode% = 2: EXIT SUB   ' record too high
  773. IF Rn??? < 1 THEN Ecode% = 2: EXIT SUB                     ' record too low
  774.  
  775. SEEK #dBaseOpen%, DBH.offset + (Rn??? * DBH.Size) - DBH.Size
  776. GET$ dBaseOpen%, DBH.Size + 2, RecordBlock$
  777. RecNum???=RN???
  778. END SUB  ' dBGetRecord
  779.  
  780.  
  781.  
  782. SUB dBUse (FileName$, Ecode%)
  783. Ecode% = 0: Recnum??? = 0
  784. IF dBaseOpen% THEN CLOSE #dBaseOpen%: dBaseOpen% = 0
  785.                                   'if database file is open, then close it.
  786. FileName$ = UCASE$(FileName$)
  787. IF INSTR(FileName$, ".") = 0 THEN FileName$ = FileName$ + ".DBF"
  788. IF DIR$(FileName$) = "" THEN Ecode% = 1: EXIT SUB
  789.                                                   ' error 1=file not found
  790.  
  791. LET dBaseOpen% = 81
  792. OPEN FileName$ FOR BINARY ACCESS READ WRITE SHARED AS #dBaseOpen%
  793. IF LOF(dBaseOpen%) = 0 THEN CLOSE #dBaseOpen%:dBaseOpen%=0:Ecode%=2:EXIT SUB
  794.                                                 ' Error 2=file is 0 length
  795.  
  796. GET #dBaseOpen%, 1, DBH
  797. IF DBH.Year > 99 OR DBH.Month > 12 OR DBH.Month = 0 OR_
  798.    DBH.Day > 31 OR DBH.Day = 0 THEN CLOSE #dBaseOpen%:_
  799.    dBaseOpen% = 0: Ecode% = 4: EXIT SUB
  800.                                               ' Error 4 = not a dBASE file
  801.  
  802. ' establish number of fields by (dbh.offset-len(dbheader))\32
  803. NumberOfRecords??? = DBH.NumberOfRecords
  804. NumberOfFields? = (DBH.offset - LEN(DBH)) \ 32
  805. IF NumberOfFields?<1 THEN Ecode% = 3:CLOSE #dBaseOpen%:dBaseOpen%=0:Exit SUB
  806.                                 ' Error 3 = no fields in database structure
  807.  
  808.  
  809. ' Load the field definition header
  810. DBS(1).FieldOffset = 3
  811. FOR nof? = 1 TO NumberOfFields?
  812.     GET #dBaseOpen%, SEEK(dBaseOpen%), DBF
  813.  
  814.         DBS(nof?).FieldName     = DBF.FieldName
  815.         DBS(nof?).FieldType     = DBF.FieldType
  816.         DBS(nof?).FieldLength   = DBF.FLen
  817.         DBS(nof?+1).FieldOffset = DBS(nof?).FieldOffset + DBF.FLen
  818.         DBS(nof?).FieldDecimals = DBF.DecC
  819. NEXT nof?
  820. CALL dBDefaultFormat  ' set default screen format
  821. RecordBlock$=SPACE$(DBH.Size+2)
  822. END SUB 'dBUse
  823.  
  824.  
  825. FUNCTION DBGET$(y%,x%,length%,fg%,bg%,whole$,ins%,num%,keyflag%)
  826. LOCAL tscan%, exitflag%, curpos%, tempwhole$, first%
  827.   ofg%=(PBVSCRNTXTATTR AND &HF)
  828.   ofb%=PBVSCRNTXTATTR / &H10
  829.   keyflag% = 0
  830.   tempwhole$ = whole$
  831.   first% = %TRUE
  832.   LOCATE y%,x% : COLOR fg%,bg% : PRINT SPACE$(length%)
  833.   exitflag% = %FALSE
  834.   curpos% = 0
  835.  
  836.   DO
  837.     IF ins% THEN tscan% = %INSERTSCAN ELSE tascn% = %OVERWRITESCAN
  838.     LOCATE y%,x% : PRINT whole$+SPACE$(length%-LEN(whole$))
  839.     LOCATE y%,x%+curpos%,1,tscan%,7
  840.  
  841.     ky$ = GETKEY$("")
  842.     IF ky$ < CHR$(31) THEN first% = %FALSE
  843.     SELECT CASE ky$
  844.       CASE > CHR$(31)
  845.         IF num% THEN
  846.           IF ky$ > CHR$(62) THEN EXIT SELECT
  847.         END IF
  848.         IF first% THEN
  849.           whole$ = ky$
  850.           curpos% = 1
  851.           first% = %FALSE
  852.           EXIT SELECT
  853.         END IF
  854.         IF ins% THEN
  855.           IF curpos% < LEN(whole$) THEN
  856.             whole$ = RTRIM$(whole$)
  857.             IF LEN(whole$) < length% THEN
  858.               whole$ = LEFT$(whole$,curpos%)+ky$+RIGHT$(whole$,LEN(whole$)-curpos%)
  859.               INCR curpos%,1
  860.               IF curpos% = length% THEN DECR curpos%,1
  861.             END IF
  862.           ELSE
  863.             whole$ = whole$ + ky$
  864.             INCR curpos%,1
  865.             IF curpos% = length% THEN DECR curpos%,1
  866.           END IF
  867.         ELSE
  868.           IF curpos% < LEN(whole$) THEN
  869.             MID$(whole$,curpos%+1) = ky$
  870.           ELSE
  871.             whole$ = whole$ + ky$
  872.           END IF
  873.           INCR curpos%,1
  874.           IF curpos% = length% THEN DECR curpos%,1
  875.         END IF
  876.       CASE CHR$(0,75)'**** LEFT ****
  877.         IF curpos% <> 0 THEN DECR curpos%,1
  878.       CASE CHR$(0,77)'**** RIGHT ****
  879.         IF curpos% <> length%-1 THEN INCR curpos%,1
  880.         IF curpos% > LEN(whole$) THEN whole$=whole$+" "
  881.       CASE CHR$(0,71)'**** HOME ****
  882.         curpos% = 0
  883.       CASE CHR$(0,79)'**** END ****
  884.            whole$ = RTRIM$(whole$)
  885.            curpos% = LEN(whole$)
  886.            IF LEN(whole$) = length% THEN DECR curpos%,1
  887.       CASE CHR$(0,82)'**** INS ****
  888.         ins% = NOT ins%
  889.         IF tscan% = 3 THEN tscan% = 6 ELSE tscan% = 3
  890.       CASE CHR$(0,83)'**** DEL ****
  891.         IF curpos% > LEN(whole$)-1 THEN EXIT SELECT
  892.         whole$ = LEFT$(whole$,curpos%) + RIGHT$(whole$,LEN(whole$)-curpos%-1)
  893.       CASE CHR$(8)'**** BACKSPACE ****
  894.         IF curpos% <> 0 THEN
  895.           whole$ = LEFT$(whole$,curpos%-1) + RIGHT$(whole$,LEN(whole$)-curpos%)
  896.           DECR curpos%,1
  897.         END IF
  898.       CASE CHR$(13)'**** ENTER ****
  899.         exitflag% = %TRUE
  900.         keyflag% = 0
  901.       CASE CHR$(27)'**** ESC ****
  902.         exitflag% = %TRUE
  903.         keyflag% = 5
  904.         whole$ = tempwhole$
  905.       CASE CHR$(0,72)'**** UP ARROW ****
  906.         exitflag% = %TRUE
  907.         keyflag% = 8
  908.       CASE CHR$(0,80)'**** DOWN ARROW ****
  909.         exitflag% = %TRUE
  910.         keyflag% = 2
  911.       CASE CHR$(9)'**** TAB ****
  912.         exitflag% = %TRUE
  913.         keyflag% = 6
  914.       CASE CHR$(0,15)'**** SHFT-TAB ****
  915.         exitflag% = %TRUE
  916.         keyflag% = 4
  917.           CASE CHR$(0,117),CHR$(0,68)
  918.               exitflag%=%TRUE
  919.                 keyflag%=10
  920.  
  921.     END SELECT
  922.  
  923.   LOOP UNTIL exitflag%
  924.   COLOR ofg%, obg%
  925.   DBGET$ = RTRIM$(whole$)
  926.  
  927. END FUNCTION
  928.  
  929. FUNCTION getkey$(mstr$)
  930.   IF mstr$ = "" THEN
  931.     DO
  932.       k$ = INKEY$
  933.     LOOP UNTIL k$ <> ""
  934.   ELSE
  935.     DO
  936.       k$ = INKEY$
  937.     LOOP UNTIL INSTR(k$,ANY mstr$)
  938.   END IF
  939.   getkey$ = k$
  940. END FUNCTION
  941.  
  942. SUB DBSCRNFIND(X%, Y%, F$)
  943. 'arrows around F$ on the screen. and returns the ultimate coordinates.
  944. REG 1, 15*256
  945. CALL INTERRUPT &H10
  946. IF Reg(1) - (Reg(1)\256) * 256 = 7 THEN Address=&HB000 else Address=&HB800
  947. DEF SEG = ADDRESS
  948. O$=PEEK$(0,4000)
  949.  
  950. DO   ' a deer, a female deer
  951.     LOCATE X%, Y%:COLOR 20,0:PRINT F$;
  952.         COLOR 7,0
  953.         LOCATE 23,1:PRINT SPACE$(80);
  954.         LOCATE 23,1:PRINT "Use arrows to re-position field.  ENTER finishes, ESC aborts.";
  955.     KB$="" : WHILE KB$=""    ' create a polling loop instead of SLEEPing
  956.                 KB$=INKEY$
  957.         WEND
  958.         POKE$ 0,O$
  959.         SELECT CASE KB$
  960.  
  961.             CASE CHR$(0,71) '  home
  962.                             Y%=1
  963.             CASE CHR$(0,72) '  up arrow
  964.                             DECR X%:IF X%=0 THEN X%=22
  965.             CASE CHR$(0,73) '  page up
  966.                             X%=1
  967.             CASE CHR$(0,75) '  left arrow
  968.                             DECR Y%:IF Y%=0 THEN Y%=79-LEN(F$)
  969.             CASE CHR$(0,77) '  right arrow
  970.                             INCR Y%:IF Y%>79-LEN(F$) THEN Y%=1
  971.             CASE CHR$(0,79) '  end
  972.                             Y%=79-LEN(F$)
  973.             CASE CHR$(0,80) '  down arrow
  974.                             INCR X%:IF X%=23 THEN X%=1
  975.             CASE CHR$(0,81) '  page down
  976.                             X%=22
  977.             CASE CHR$(0,82) '  Insert
  978.             CASE CHR$(0,83) '  Delete
  979.             CASE CHR$(0,59) '  f1
  980.             CASE CHR$(0,60) '  f2
  981.             CASE CHR$(0,61) '  f3
  982.             CASE CHR$(0,62) '  f4
  983.             CASE CHR$(0,63) '  f5
  984.             CASE CHR$(0,64) '  f6
  985.             CASE CHR$(0,65) '  f7
  986.             CASE CHR$(0,66) '  f8
  987.             CASE CHR$(0,67) '  f9
  988.             CASE CHR$(0,68) '  f10
  989.                             FINISHED=-1
  990.             CASE CHR$(0,115) ' CTL-Left arrow
  991.                             Y%=Y%-8:IF Y%<1 THEN Y%=1
  992.             CASE CHR$(0,116) ' CTL-Right arrow
  993.                             Y%=Y%+8:IF Y%>79-LEN(F$) THEN y%=79-LEN(F$)
  994.             CASE CHR$(0,117) ' CTL-END
  995.                             FINISHED=-1
  996.             CASE CHR$(0,118) ' CTL-PgDn
  997.             CASE CHR$(0,119) ' CTL-HOME
  998.                             X%=1:Y%=1
  999.             CASE CHR$(0,132) ' CTL-PgUp
  1000.             CASE CHR$(3)  '  CTL-C ETX
  1001.                             X%=0:FINISHED=-1
  1002.             CASE CHR$(9)  '  CTL-I TAB
  1003.                             Y%=Y%+8:IF Y%>79-LEN(F$) THEN y%=79-LEN(F$)
  1004.             CASE CHR$(13)  ' CTL-M CARRIAGE RETURN
  1005.                             FINISHED=-1
  1006.             CASE CHR$(16)  ' CTL-P DLE
  1007.             CASE CHR$(21)  ' CTL-U NAK
  1008.             CASE CHR$(27)  ' Escape ESC
  1009.                             X%=0:FINISHED=-1
  1010.  
  1011.             END SELECT
  1012.  
  1013.  
  1014. LOOP WHILE NOT FINISHED
  1015. POKE$ 0, O$
  1016. DEF SEG
  1017.  
  1018. END SUB
  1019.  
  1020.  
  1021. $INCLUDE "BTREE.BAS"
  1022.