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.NAME
001 EQU AMC$FNAME TO 1 ;* Data file
002 EQU AMC$LNAME TO 2 ;* attributes
003 EQU first.row TO 2 ;* Screen
004 EQU middle.row TO 11 ;* row
005 EQU last.row TO 21 ;* positions
006 EQU top.down TO 1 ;* Constant flags
007 EQU bottom.up TO 2 ;* to control
008 EQU middle.out TO 3 ;* painting direction
009 EQU root TO "LNAME" ;* B-tree name
010 clear = @(-1) ;* To clear screen
011 eos = @(-3) ;* To clear to end of screen
012 eol = @(-4) ;* To clear to end of line
013 bell = CHAR(7) ;* To beep
014 PROCREAD BUFFER ELSE BUFFER="" ;* Latest command
015 PROCWRITE "" ;* Clear it in case of STOP
016 OPEN "B-TREE" TO BFILE ELSE STOP
017 OPEN "NAMES" TO NFILE ELSE STOP
018 NEXT.ID = FIELD(BUFFER," ",2) ;* Id number, if any
019 PRINT clear:"ID Numbers":SPACE(5):"Names"
020 PRINT STR("-",10):SPACE(5):STR("-",50):
021 DIRECTION = top.down ;* Will paint top to bottom
022 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 ids
025 BEGIN CASE
026 CASE DIRECTION = top.down ; ROW = first.row
027 CASE DIRECTION = bottom.up ; ROW = last.row
028 CASE 1 ; ROW = middle.row ;* Dir must be middle.out
029 END CASE
030 STILL.ROOM = 1 ;* So LOOP can get started
031 LOOP WHILE (NEXT.ID # "") AND STILL.ROOM DO
032 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 row
036 BEGIN CASE
037 CASE DIRECTION = top.down ;* Painting down screen
038 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"NEXT",NEXT.ID)
039 ROW = ROW+1 ; STILL.ROOM = ROW <= last.row
040 CASE DIRECTION = bottom.up ;* Painting up screen
041 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"PREV",NEXT.ID)
042 ROW = ROW-1 ; STILL.ROOM = ROW >= first.row
043 CASE 1 ;* Painting up then down from middle
044 IF ROW <= middle.row THEN ;* Up from middle
045 IF ROW = middle.row THEN ;* Remember middle
046 MIDDLE.ID = NODE.ID ; MIDDLE.POS = NODE.POS
047 END
048 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"PREV",NEXT.ID)
049 ROW = ROW-1 ; STILL.ROOM = ROW >= first.row
050 IF (NEXT.ID = "") OR NOT(STILL.ROOM) THEN ;* Top
051 GOSUB 400 ; ROW = middle.row ;* Force bottom
052 END ;* half
053 END
054 IF ROW >= middle.row THEN ;* Down from middle
055 IF ROW = middle.row THEN ;* Remember middle
056 NODE.ID = MIDDLE.ID ; NODE.POS = MIDDLE.POS
057 END
058 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"NEXT",NEXT.ID)
059 ROW = ROW+1 ; STILL.ROOM = ROW <= last.row
060 PRINT @(0,ROW): ;* Pos for eos if no more names
061 END
062 END CASE
063 REPEAT
064 BEGIN CASE ;* Done painting, clear as needed
065 CASE DIRECTION = top.down
066 PRINT eos: ; ROW = first.row
067 CASE DIRECTION = bottom.up
068 GOSUB 400 ; ROW = last.row
069 DIRECTION = top.down ; NEXT.ID = NEXT.SAVE
070 NODE.ID = TOP.NODE.ID ; NODE.POS = TOP.NODE.POS
071 CASE 1
072 PRINT eos:; ROW = middle.row ; DIRECTION = top.down
073 END CASE
074 300 PRINT @(11,ROW):">>>": ;* Show moving cursor
075 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 CASE
079 CASE COMMAND="" ; IF NEXT.ID # "" THEN GO TO 200
080 CASE COMMAND = "E" ; STOP
081 CASE COMMAND = "U" ;* Move cursor up
082 IF ID.LIST<ROW-1> # "" THEN ROW = ROW-1 ELSE
083 ITEM.ID = ID.LIST<ROW> ;* Get id at top of screen
084 READ ITEM FROM NFILE, ITEM.ID ELSE ITEM = ""
085 NODE.ID.SAVE = NODE.ID ;* In case
086 NODE.POS.SAVE = NODE.POS ;* U)p fails
087 CALL BTPFIND(root, BFILE, NFILE, ITEM.ID, ITEM, NODE.ID, NODE.POS)
088 TOP.NODE.ID = NODE.ID ;* In case
089 TOP.NODE.POS = NODE.POS ;* U)p succeeds
090 CALL BTPSEQ(BFILE,NODE.ID,NODE.POS,"PREV", PRIOR.ID)
091 IF PRIOR.ID # "" THEN ;* Found prior item
092 NEXT.SAVE = ITEM.ID ;* Start pos for next RETURN
093 DIRECTION = bottom.up ;* Paint from bottom up
094 NEXT.ID = PRIOR.ID ;* Start painting with this id
095 GO TO 200
096 END ELSE ;* No prior item, restore state
097 NODE.ID = NODE.ID.SAVE; NODE.POS = NODE.POS.SAVE
098 END
099 END
100 CASE COMMAND = "D" ;* Move cursor down
101 IF ID.LIST<ROW+1> # "" THEN ROW = ROW+1 ELSE
102 IF NEXT.ID # "" THEN GO TO 200 ;* Paint next page
103 END
104 CASE (COMMAND="N") OR (COMMAND="C") OR (COMMAND="L")
105 PROCWRITE (COMMAND:" ":ID.LIST<ROW>) ; STOP
106 CASE COMMAND MATCHES "0N" ;* Assume id number input
107 LOCATE(COMMAND, ID.LIST; POS) THEN ROW=POS ELSE
108 READ ITEM FROM NFILE, COMMAND ELSE ITEM = ""
109 IF ITEM # "" THEN ;* Assume it's a valid item
110 NEXT.ID = COMMAND ;* OK to clobber NEXT.ID now
111 GO TO 100 ;* Show page starting with new item
112 END
113 PRINT bell: ;* Missing or invalid number
114 END
115 CASE 1 ;* Input must be a name, search for it
116 ITEM = "" ; NEXT.ID = "" ;* Dummy item for find
117 ITEM<AMC$LNAME> = FIELD(COMMAND,",",1)
118 ITEM<AMC$FNAME> = FIELD(COMMAND,",",2)
119 DIRECTION = middle.out
120 GOTO 100 ;* Go do find
121 END CASE
122 GO TO 300 ;* Get next command
123 *
124 400 * Done painting upwards, clear rest of lines
125 FOR I = ROW TO first.row STEP -1 ; PRINT @(0,I):eol: ; NEXT I
126 RETURN
127 *
128 END
FIND.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.LIST
001 PQ
002 10 HFIND.NAME
003 P
004 T C
005 IF # A1 X
006 IF A1 = L GO 20
007 HEDIT.NAME
008 P
009 GO 10
010 20 HLIST NAMES
011 A'2
012 P
013 O
014 OHit RETURN+
015 IP:
016 GO 10
The 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.NAME
001 PROCREAD BUFFER ELSE STOP
002 COMMAND = FIELD(BUFFER," ",1)
003 ID = FIELD(BUFFER," ",2)
004 PRINT "This program should..."
005 IF COMMAND = "N" THEN
006 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 ID
011 PROCWRITE " ":ID
012 STOP
013 END
Before 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.