A seldom-scrolling browser
The original browser program presented in Branches #1 calls BTPSEQ and repaints the entire screen every time an up (U) or down (D) command is input by the user, just to get the current item of interest to show up inside the two horizontal "hairlines" of hyphens. Wouldn't it be quicker to leave the data in place and only move the hyphens? What's the value of moving up or down an item at a time, anyway? Also, although BROWSE.NAMES makes it easy to find any item, how can we interface to other programs to manipulate the item?
Here is a browser that answers all those questions, using a design slightly different from BROWSE.NAMES:
FIND.NAME001 EQU AMC$FNAME TO 1 ;* Data file002 EQU AMC$LNAME TO 2 ;* attributes003 EQU first.row TO 2 ;* Screen004 EQU middle.row TO 11 ;* row005 EQU last.row TO 21 ;* positions006 EQU top.down TO 1 ;* Constant flags007 EQU bottom.up TO 2 ;* to control008 EQU middle.out TO 3 ;* painting direction009 EQU root TO "LNAME" ;* B-tree name010 clear = @(-1) ;* To clear screen011 eos = @(-3) ;* To clear to end of screen012 eol = @(-4) ;* To clear to end of line013 bell = CHAR(7) ;* To beep014 PROCREAD BUFFER ELSE BUFFER="" ;* Latest command015 PROCWRITE "" ;* Clear it in case of STOP016 OPEN "B-TREE" TO BFILE ELSE STOP017 OPEN "NAMES" TO NFILE ELSE STOP018 NEXT.ID = FIELD(BUFFER," ",2) ;* Id number, if any019 PRINT clear:"ID Numbers":SPACE(5):"Names"020 PRINT STR("-",10):SPACE(5):STR("-",50):021 DIRECTION = top.down ;* Will paint top to bottom022 READ ITEM FROM NFILE, NEXT.ID ELSE ITEM = ""023 100 CALL BTPFIND(root, BFILE, NFILE, NEXT.ID, ITEM, NODE.ID, NODE.POS)024 200 ID.LIST = "" ;* For first.row to last.row ids025 BEGIN CASE026 CASE DIRECTION = top.down ; ROW = first.row027 CASE DIRECTION = bottom.up ; ROW = last.row028 CASE 1 ; ROW = middle.row ;* Dir must be middle.out029 END CASE030 STILL.ROOM = 1 ;* So LOOP can get started031 LOOP WHILE (NEXT.ID # "") AND STILL.ROOM DO032 READ ITEM FROM NFILE, NEXT.ID ELSE ITEM = ""033 PRINT @(0,ROW):NEXT.ID "R#10":SPACE(5):034 PRINT ITEM<AMC$LNAME>:", ":ITEM<AMC$FNAME>:eol:035 ID.LIST<ROW> = NEXT.ID ;* Remember id on this row036 BEGIN CASE037 CASE DIRECTION = top.down ;* Painting down screen038 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"NEXT",NEXT.ID)039 ROW = ROW+1 ; STILL.ROOM = ROW <= last.row040 CASE DIRECTION = bottom.up ;* Painting up screen041 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"PREV",NEXT.ID)042 ROW = ROW-1 ; STILL.ROOM = ROW >= first.row043 CASE 1 ;* Painting up then down from middle044 IF ROW <= middle.row THEN ;* Up from middle045 IF ROW = middle.row THEN ;* Remember middle046 MIDDLE.ID = NODE.ID ; MIDDLE.POS = NODE.POS047 END048 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"PREV",NEXT.ID)049 ROW = ROW-1 ; STILL.ROOM = ROW >= first.row050 IF (NEXT.ID = "") OR NOT(STILL.ROOM) THEN ;* Top051 GOSUB 400 ; ROW = middle.row ;* Force bottom052 END ;* half053 END054 IF ROW >= middle.row THEN ;* Down from middle055 IF ROW = middle.row THEN ;* Remember middle056 NODE.ID = MIDDLE.ID ; NODE.POS = MIDDLE.POS057 END058 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"NEXT",NEXT.ID)059 ROW = ROW+1 ; STILL.ROOM = ROW <= last.row060 PRINT @(0,ROW): ;* Pos for eos if no more names061 END062 END CASE063 REPEAT064 BEGIN CASE ;* Done painting, clear as needed065 CASE DIRECTION = top.down066 PRINT eos: ; ROW = first.row067 CASE DIRECTION = bottom.up068 GOSUB 400 ; ROW = last.row069 DIRECTION = top.down ; NEXT.ID = NEXT.SAVE070 NODE.ID = TOP.NODE.ID ; NODE.POS = TOP.NODE.POS071 CASE 1072 PRINT eos:; ROW = middle.row ; DIRECTION = top.down073 END CASE074 300 PRINT @(11,ROW):">>>": ;* Show moving cursor075 PRINT @(0,22):"Number or name or return or ":076 PRINT "N)ew C)hange L)ist U)p D)own E)xit":eol:077 INPUT COMMAND: ; PRINT @(11,ROW):SPACE(3):078 BEGIN CASE079 CASE COMMAND="" ; IF NEXT.ID # "" THEN GO TO 200080 CASE COMMAND = "E" ; STOP081 CASE COMMAND = "U" ;* Move cursor up082 IF ID.LIST<ROW-1> # "" THEN ROW = ROW-1 ELSE083 ITEM.ID = ID.LIST<ROW> ;* Get id at top of screen084 READ ITEM FROM NFILE, ITEM.ID ELSE ITEM = ""085 NODE.ID.SAVE = NODE.ID ;* In case086 NODE.POS.SAVE = NODE.POS ;* U)p fails087 CALL BTPFIND(root, BFILE, NFILE, ITEM.ID, ITEM, NODE.ID, NODE.POS)088 TOP.NODE.ID = NODE.ID ;* In case089 TOP.NODE.POS = NODE.POS ;* U)p succeeds090 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"PREV", PRIOR.ID)091 IF PRIOR.ID # "" THEN ;* Found prior item092 NEXT.SAVE = ITEM.ID ;* Start pos for next RETURN093 DIRECTION = bottom.up ;* Paint from bottom up094 NEXT.ID = PRIOR.ID ;* Start painting with this id095 GO TO 200096 END ELSE ;* No prior item, restore state097 NODE.ID = NODE.ID.SAVE; NODE.POS = NODE.POS.SAVE098 END099 END100 CASE COMMAND = "D" ;* Move cursor down101 IF ID.LIST<ROW+1> # "" THEN ROW = ROW+1 ELSE102 IF NEXT.ID # "" THEN GO TO 200 ;* Paint next page103 END104 CASE (COMMAND="N") OR (COMMAND="C") OR (COMMAND="L")105 PROCWRITE (COMMAND:" ":ID.LIST<ROW>) ; STOP106 CASE COMMAND MATCHES "0N" ;* Assume id number input107 LOCATE(COMMAND, ID.LIST; POS) THEN ROW=POS ELSE108 READ ITEM FROM NFILE, COMMAND ELSE ITEM = ""109 IF ITEM # "" THEN ;* Assume it's a valid item110 NEXT.ID = COMMAND ;* OK to clobber NEXT.ID now111 GO TO 100 ;* Show page starting with new item112 END113 PRINT bell: ;* Missing or invalid number114 END115 CASE 1 ;* Input must be a name, search for it116 ITEM = "" ; NEXT.ID = "" ;* Dummy item for find117 ITEM<AMC$LNAME> = FIELD(COMMAND,",",1)118 ITEM<AMC$FNAME> = FIELD(COMMAND,",",2)119 DIRECTION = middle.out120 GOTO 100 ;* Go do find121 END CASE122 GO TO 300 ;* Get next command123 *124 400 * Done painting upwards, clear rest of lines125 FOR I = ROW TO first.row STEP -1 ; PRINT @(0,I):eol: ; NEXT I126 RETURN127 *128 ENDFIND.NAME lets you browse the items in the sample NAMES file used in previous issues of Branches. When using FIND.NAME, a "cursor" of three angle brackets (>>>) points to the current item of interest. When an up (U) or down (D) command is used, FIND.NAME simply moves the cursor, and only scrolls the display when the cursor attempts to move off the top or bottom of the displayed chunk of names. FIND.NAME also moves the cursor to avoid scrolling when an ID number is typed and the number is already somewhere on the display. In that case, the cursor simply jumps to the ID number requested.
FIND.NAME accepts three commands (C, L, and N) for which it assumes other programs exist to perform some desired action in those cases. FIND.NAME also expects to be imbedded in a proc, so it uses PROCREAD and PROCWRITE to communicate with those other programs via the proc input buffer. For example, if the user inputs the C command, FIND.NAME assumes the user wants to change the currently selected item, so PROCWRITE is used to save the letter C and the number of the current item in the proc buffer. After the PROCWRITE, FIND.NAME then STOPs to let some other program get control and perform the necessary change.
Similarly, if the user inputs an L, FIND.NAME assumes the user wants to list more information about the currently selected item, so it saves L and the number of the current item in the proc buffer, then STOPs so some other program can do the listing.
If the user inputs an N, FIND.NAME assumes the user wants to create a new item, so it PROCWRITEs the letter N. Although the current item number is also saved in the proc buffer, it is of no importance for the N command.
A proc to combine FIND.NAME with other code for handling C, L, and N commands should look like the following:
FIND.EDIT.LIST001 PQ002 10 HFIND.NAME003 P004 T C005 IF # A1 X006 IF A1 = L GO 20007 HEDIT.NAME008 P009 GO 10010 20 HLIST NAMES011 A'2012 P013 O014 OHit RETURN+015 IP:016 GO 10The FIND.EDIT.LIST proc begins by executing the FIND.NAME program to give the user a chance to position the >>> cursor to an item of interest. FIND.NAME will STOP only after the user inputs a C, L, N, or E command, after which the FIND.EDIT.LIST proc clears the screen.
If the user types the E command, the proc buffer will be empty, so the FIND.EDIT.LIST proc also terminates.
If the user types the L command, the proc goes to label 20, where a standard LIST command is constructed around the current item number extracted from the proc buffer. Note that the LIST command can easily be extended to display anything of interest about the current item.
If the user types the C or N commands, the FIND.EDIT.LIST proc executes the EDIT.NAME program, which then has the opportunity to change the current item or create a new one, depending on whether it finds the letter C or N in the proc buffer. Here is a simple "skeleton" version of EDIT.NAME to show how the proc buffer is interpreted:
EDIT.NAME001 PROCREAD BUFFER ELSE STOP002 COMMAND = FIELD(BUFFER," ",1)003 ID = FIELD(BUFFER," ",2)004 PRINT "This program should..."005 IF COMMAND = "N" THEN006 PRINT "...create a new item with a new ID number."007 END ELSE PRINT "...edit existing item #":ID:"."008 PRINT ; PRINT "Ready to return to browser..."009 PRINT "Begin browsing at what ID number":010 INPUT ID011 PROCWRITE " ":ID012 STOP013 ENDBefore EDIT.NAME terminates, it saves an optional ID number in the proc buffer just like FIND.NAME does, so that when the FIND.EDIT.LIST proc loops back up and executes FIND.NAME again, FIND.NAME will do a PROCREAD to find the ID number last used by EDIT.NAME, and display the NAMES list beginning at that point.
The combination of FIND.NAME, EDIT.NAME, and FIND.EDIT.LIST demonstrate how browsers and application programs can be combined to create very effective work environments for users. Users can browse, select an item of interest, immediately transfer to some other program or proc to manipulate or display the item, then resume browsing where they left off.