1 !+******************************************************************** 2 ! SAMP -- The FMS V2 Sample Application Program 3 !********************************************************************* 10! 11! COPYRIGHT (c) 2004 BY 12! HEWLETT PACKARD DEVELOPMENT COMPANY L.P., 13! 14! THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED 15! ONLY IN ACCORDANCE WITH THE TERMS OF SUCH LICENSE AND WITH THE 16! INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR ANY OTHER 17! COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY 18! OTHER PERSON. NO TITLE TO AND OWNERSHIP OF THE SOFTWARE IS HEREBY 19! TRANSFERRED. 20! 21! THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE 22! AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY HEWLETT PACKARD. 23! 24! 25! HEWLETT PACKARD ASSUMES NO RESPONSIBILITY FOR THE USE OR RELIABILITY 26! OF ITS SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY HEWLETT PACKARD. 27! 28! Author: S.P.Simon 29! 110 ! Data definitions 115 ! 120 ! FMS related 125 !- 130 DIM WORKSPACE%( 3 ) !General workspace 135 DIM CHECKWKSP%( 3 ) !Check workspace 140 DIM TCA%( 3 ) !Terminal Control Area 145 DIM MENU_FORM%( 500 ) !Storage for memory resident form 150 DIM CHECK_FORM%( 750 ) !Storage for memory resident form 155 DIM DPOSIT_FORM%( 500 ) !Storage for memory resident form 200 !- 205 ! Account (Read in from file) 210 !+ 215 MAP( ACCOUNT ) ACCOUNT$ = 151 220 MAP( ACCOUNT ) ACCTNO$ = 5, ACCDATE$ = 7, LAST$ = 20, FIRST$ = 15, & MIDDLE$ = 15, STREET$ = 30, CITY$ = 20, STATE$ = 2, & ZIP$ = 5, HOMEPH$ = 10, WORKPH$ = 10, OPW$ = 12 222 MAP( TACCOUNT ) TEMPACCOUNT$ = 151 230 !+ 235 ! Deposit data (Read via FDV$GETAL) 240 !- 245 MAP( DEPOSIT ) DEPOSIT$ = 60 250 MAP( DEPOSIT ) DEP.DATE$ = 7, & DEP.CURBAL$ = 6, & DEP.AMT$ = 6, & DEP.NEWBAL$ = 6, & DEP.MEMO$ = 35 260 !+ 265 ! Money. 270 ! Note that all money is kept internally as integers (in cents). 275 ! It is only when the quantities are output that they look like 280 ! dollars, since all the money fields have periods as field 285 ! markers in the right places and they are right justified or 287 ! fixed decimal. 290 ! 305 ! Register data. 310 ! It would be most convenient to be able to define an array 315 ! of structures for the register, but it can't be done 320 ! in BASIC (it can be done for some other languages). What's one 325 ! instead is to define a single structure into which to put data via 330 ! structure names and also an array of strings. After data has been 335 ! be put into the structure, it is copied to the array for convenience 340 ! in scrolling. 345 !- 350 MAP ( REGISTERITEM ) REGITEM$ = 64 355 MAP ( REGISTERITEM ) RI.NUM$ = 4, & RI.DATE$ = 7, & RI.MEMPAYTO$ =35, & RI.AMTDEP$ = 6, & RI.AMTPAY$ = 6, & RI.BALANCE$ = 6 380 DECLARE INTEGER CONSTANT REGSIZE = 50 385 DIM REGARRAY$( REGSIZE ) 500 !+ 505 ! Other variables 510 ! TERMINATOR% Terminator returned by FDV 515 ! BALANCE% Balance in account, numeric 522 ! SBALANCE% Starting balance 523 ! AMTPAY% Check payment amount 525 ! TOTDEP% Total deposits made in this session 530 ! TOTPAY% Total checks payed in this session 535 ! OPTION$ Choice returned from menu 540 ! FMSSTATUS% Status for last FDV call 545 ! RMSSTATUS% RMS Status for last FDV call 550 ! LASTREGNUM% Last number used in the register (1...REGSIZE) 555 ! LASTCHNUM% Last check number used 560 ! FIRSTL$ First line on the form of the check image 561 ! (from named data) 565 ! LASTL$ Last line on the form of the check image 566 ! (from named data) 570 ! LINE$ Line return as image of form for check print 575 ! I% Index into lines of check 580 ! NSCROL$ Number lines in scrolled area (from named data) 582 ! NSCROL% " 585 ! CURLINE% Line of check register that cursor is now on 590 ! MINWINDOW% Smallest line of register being displayed 591 ! on the scrolled area 595 ! MAXWINDOW% Largest line of register beig displayed 596 ! on the scrolled area 600 ! FAKE$ Value returned from fake field in scrolled area 610 ! PASSWORD$ Password from account 615 ! JUNK$ Temporary storage for return from GETAL 620 ! INSOVR% Insert/overstrike mode 625 ! DONE$ Form done message for Deposit 627 ! FIELDNAME$ Name of field from FDV$GETAF 630 !- 900 !+ 905 ! FMS terminator codes 915 !- 920 DECLARE INTEGER CONSTANT & FDV$K_FT_NTR = 0, !RETURN or ENTER & FDV$K_FT_SNX = 6, !Tab out of scroll line & FDV$K_FT_SPR = 7, !Bcksp out of scr line & FDV$K_FT_SFW = 8, !Down arrow (scroll fwd)& FDV$K_FT_SBK = 9, !Up arrow (scroll back) & FDV$K_KP_PER = 110, !Keypad period & FDV$K_KP_0 = 112 !Keypad zero 995 !******************************************************************** 996 ! SAMP main routine 997 !******************************************************************** 1000 !+ 1005 ! Initialize FMS 1010 ! Attach default terminal 1015 ! Attach normal and check workspaces (order important for help 1017 ! and refresh during CHECK/CHKDON time--try switching and see). 1020 ! Open form library, attach to channel 1 1025 ! Set keypad mode to application 1027 ! Set signal mode to bell (default, but it's fun to do) 1030 !- 1040 CALL FDV$ATERM( TCA%(), 12, 2% ) \ C=FN.GETSTA 1042 CALL FDV$AWKSP( CHECKWKSP%(), 2000% ) \ C=FN.GETSTA 1045 CALL FDV$AWKSP( WORKSPACE%(), 2000% ) \ C=FN.GETSTA 1050 CALL FDV$LOPEN( 'FMS$EXAMPLES:SAMP', 1% ) \ C=FN.GETSTA 1055 CALL FDV$SPADA( 1% ) 1060 CALL FDV$SSIGQ( 0% ) 1072 !+ 1074 ! Set all future calls to return status to the two status recording 1076 ! variables FMSSTATUS% and RMSSTATUS% without having to call the 1078 ! the FDV$STAT routine. 1080 !- 1082 CALL FDV$SSRV( FMSSTATUS%, RMSSTATUS% ) 1086 !+ 1088 ! Read in a few forms from the form library onto the dynamic 1090 ! resident form list. You may be able to detect the difference 1091 ! in the form to form access times for those forms which have to be 1092 ! accessed from the form library on disk and those forms which are 1093 ! on the dynamic or static memory resident form list. See the 1094 ! installation notes for this program (the LINK command) to see 1095 ! which forms are on the static memory resident form list. 1096 !- 1097 CALL FDV$READ( 'MENU', MENU_FORM%(), 2000%, SIZE_MENU% ) 1098 CALL FDV$READ( 'CHECK', CHECK_FORM%(), 3000%, SIZE_CHECK% ) 1099 CALL FDV$READ( 'DEPOSIT', DPOSIT_FORM%(), 2000%, SIZE_CHECK% ) 1101 !+ 1105 ! Initialize account information 1110 !- 1115 C=FN.INACCT 1125 !+ 1130 ! Put up welcome form, wait for response 1135 !- 1140 CALL FDV$CDISP( 'WELCOME' ) \ C=FN.SRVCHK 1141 CALL FDV$WAIT 1155 !+ 1160 ! Process all menu requests 1165 !- 1170 C=FN.MENU 1180 !+ 1185 ! Clean up and leave: 1186 ! Close form library. 1187 ! Reset keypad to numeric. 1188 ! Delete a form from dynamic mem. res. form list just to show how. 1189 ! Detach workspaces (not really necessary since DTERM would do it). 1190 ! Detach terminal. 1195 !- 1200 CALL FDV$LCLOS 1205 CALL FDV$SPADA( 0% ) 1207 CALL FDV$DEL( 'MENU' ) 1208 CALL FDV$DWKSP( WORKSPACE%() ) 1212 CALL FDV$DWKSP( CHECKWKSP%() ) 1215 CALL FDV$DTERM( TCA%() ) 1220 GOTO 15999 4000 DEF FN.INACCT 4001 !+******************************************************************** 4010 ! Subroutine INACCT 4015 ! Read from file SAMP.DAT into internal variables. 4020 ! Set up the workspace for checks and fill in the check form 4025 ! with the account's name, address, and account number. 4030 !-******************************************************************** 4034 4035 !+ 4040 ! Open file, get account data 4045 !- 4047 ON ERROR GOTO 4500 4050 OPEN 'FMS$EXAMPLES:SAMP.DAT' FOR INPUT AS FILE #5%, ACCESS READ 4055 LINPUT #5%, ACCOUNT$ 4065 !+ 4070 ! Read the remaining records into the check register, counting them. 4075 ! The last record has the current balance, and some record has the 4080 ! last check number used (not necessarily the last record). 4085 ! Note that in BASIC the record is read into the array and reference 4090 ! to the check number is via a substring rather than symbolically. 4091 ! Other languages may access differently. 4095 !- 4100 LASTCHNUM% = 0 4105 LASTREGNUM% = 0 4107 WHILE LASTREGNUM% < REGSIZE 4110 LINPUT #5%, REGARRAY$( LASTREGNUM% + 1 ) 4112 LASTREGNUM% = LASTREGNUM% + 1 4115 IF SEG$( REGARRAY$( LASTREGNUM% ), 1, 4 ) <> SPACE$(4) THEN LASTCHNUM% = VAL( SEG$( REGARRAY$( LASTREGNUM% ), 1, 4 ) ) 4120 NEXT 4130 !+ 4135 ! Reached here without hitting end of file, should probably print 4140 ! message or something, except that this is just a demo. 4145 ! As it is, just fall through and ignore remaining records. 4150 !- 4160 4200 !+ 4210 ! Reach here as result of end of file--last record tried didn't read. 4211 ! Check for data file in error. 4212 ! Take balance from last record read. 4213 ! Set session sums to zero to say no activity yet. 4215 !- 4220 IF LASTREGNUM% = 0 THEN PRINT "DATA FILE IN ERROR" STOP 4225 BALANCE% = VAL( SEG$( REGARRAY$( LASTREGNUM% ), 59, 64 ) ) 4230 SBALANCE% = BALANCE% 4235 TOTDEP% = 0 4240 TOTPAY% = 0 4245 !+ 4250 ! Set up the check workspace once so we don't have to do it every time. 4255 !- 4260 C=FN.FMTCHK 4265 4270 FNEXIT 4495 4500 !+ 4505 ! Error handler for BASIC I/O 4510 ! If it's end of file, just close file and resume at 4200. 4520 ! Otherwise, revert to BASIC error handling 4530 !- 4535 IF ERR = 11% THEN CLOSE #5% RESUME 4200 ELSE ON ERROR GOTO 0 4550 FNEND 4600 DEF FN.FMTCHK 4601 !+******************************************************************** 4605 ! Subroutine FMTCHK 4610 ! Format account data onto check form in the check workspace. 4615 !-******************************************************************** 4645 CALL FDV$SWKSP( CHECKWKSP%() ) 4650 CALL FDV$LOAD( 'CHECK' ) 4655 CALL FDV$PUT( TRM$( FIRST$ ) + SP + SEG$( MIDDLE$, 1, 1 ) + '.' + & SP + LAST$ , 'NAME' ) 4660 CALL FDV$PUT( STREET$, 'STREET' ) 4665 CALL FDV$PUT( TRM$( CITY$ ) + ', ' + STATE$ + SP + ZIP$ , 'CSZ' ) 4670 CALL FDV$PUT( HOMEPH$, 'HOMEPH' ) 4675 CALL FDV$PUT( ACCTNO$, 'ACCTNO' ) 4685 CALL FDV$SWKSP( WORKSPACE%() ) 4695 FNEND 5000 DEF FN.MENU 5001 !+******************************************************************** 5005 ! Subroutine MENU 5010 ! Accept inputs from the menu form and dispatch to the 5015 ! appropriate routine. Repeat until option 1 (exit) is 5020 ! chosen. The UARs in the form guarantee that we get back 5023 ! only inputs '1'-'5' with the correct terminators. 5024 ! Options are: 5025 ! 1 => Exit 5026 ! 2 => Write checks 5027 ! 3 => Make deposit 5028 ! 4 => View register 5029 ! 5 => View account data 5030 !-******************************************************************** 5035 5040 OPTION$ = ' ' ! Extend string variable before FMS call (BASIC only) 5045 WHILE 1 = 1 5050 CALL FDV$CDISP( 'MENU' ) \ C=FN.SRVCHK 5070 CALL FDV$GET( OPTION$, TERMINATOR%, 'OPTION' ) 5075 ON VAL( OPTION$ ) GOTO 5082, 5090, 5100, 5110, 5120 5081 ! Option 1: Exit 5082 FNEXIT 5085 ! Option 2: Write checks 5090 C=FN.WRITCH \ GOTO 5130 5095 ! Option 3: Make a deposit 5100 C=FN.MAKDEP \ GOTO 5130 5105 ! Option 4: View register 5110 C=FN.VUEREG \ GOTO 5130 5115 ! Option 5: View account data 5120 C=FN.VUEACT \ GOTO 5130 5130 NEXT 5140 FNEND 11000 DEF FN.WRITCH 11001 !+******************************************************************** 11005 ! Subroutine WRITCH 11010 ! Write one or more checks 11015 !-******************************************************************** 11017 11018 !+ 11019 ! Turn on LED 3 on the VT100 during this routine, just to show how. 11020 !- 11021 CALL FDV$LEDON( 3% ) 11022 11025 !+ 11027 ! Mark WORKSPACE not displayed so it doesn't show up during a refresh. 11030 ! Put up CHECK form from already loaded workspace 11032 ! and display current balance 11035 !- 11037 CALL FDV$NDISP 11040 CALL FDV$SWKSP( CHECKWKSP%() ) 11045 CALL FDV$DISPW 11050 11070 CALL FDV$PUT( FN.CENTS$( BALANCE% ), 'BALANCE' ) 11075 !+ 11080 ! Process checks until a keypad period is read 11085 !- 11090 TERMINATOR% = 0 11095 UNTIL TERMINATOR% = FDV$K_KP_PER 11100 C=FN.ONECHK ! Process one check 11105 C=FN.ENDCHK ! Give options for continuing 11110 NEXT 11120 !+ 11125 ! Turn off LED 3 on VT100 11130 !- 11135 CALL FDV$LEDOF( 3% ) 11140 CALL FDV$SWKSP( WORKSPACE%() ) 11150 11155 FNEND 11300 DEF FN.ONECHK 11301 !+******************************************************************** 11305 ! Subroutine ONECHK -- Process one check 11310 ! If input is terminated by kpd period, return with no action 11315 ! Else deduct from balance and enter into register. 11317 ! Note that a UAR in the form guarantees that the amount of 11318 ! the check is always less than or equal to the balance. 11319 ! Note that the form function key UAR allows only kpd period 11320 ! as terminator (other than FDV$K_FT_NTR). 11321 !-******************************************************************** 11322 CALL FDV$PUT( STR$( LASTCHNUM% + 1 ), 'NUMBER' ) 11325 CALL FDV$GETAL( JUNK$, TERMINATOR% ) 11330 IF TERMINATOR% = FDV$K_KP_PER THEN FNEXIT 11332 11335 !+ 11340 ! If the check wouldn't fit in the register, don't process, just 11345 ! give error message, wait for acknowledgement, and return 11350 !- 11355 IF LASTREGNUM% = REGSIZE THEN CALL FDV$PUTL( "Register full, can't enter check" ) CALL FDV$WAIT FNEXIT 11360 11365 !+ 11370 ! Get amount from check. 11372 ! Update balance (in memory and on screen) and session sums. 11375 ! Transfer form values to register item. 11385 !- 11390 CALL FDV$RET( RI.AMTPAY$, 'AMTPAY' ) 11395 AMTPAY% = VAL( RI.AMTPAY$) 11400 BALANCE% = BALANCE% - AMTPAY% 11405 TOTPAY% = TOTPAY% + AMTPAY% 11407 11410 CALL FDV$PUT( FN.CENTS$( BALANCE% ), 'BALANCE' ) 11415 CALL FDV$RET( RI.BALANCE$, 'BALANCE') !Avoid need to format RI.BALANCE$ 11420 11425 RI.AMTDEP$ = '' 11430 CALL FDV$RET( RI.NUM$, 'NUMBER' ) 11435 CALL FDV$RET( RI.DATE$, 'DATE' ) 11440 CALL FDV$RET( RI.MEMPAYTO$, 'PAYTO' ) !Note: not from check's MEMO 11445 11450 !+ 11455 ! Update register array and counters 11460 ! (Note that the two step update (form->regitem->regarray) 11465 ! is necessary in BASIC, not necessarily in every language). 11470 !- 11475 LASTREGNUM% = LASTREGNUM% + 1 11480 LASTCHNUM% = LASTCHNUM% + 1 11485 REGARRAY$( LASTREGNUM% ) = REGITEM$ 11490 11495 FNEND ! 11600 DEF FN.ENDCHK 11601 !+******************************************************************** 11605 ! Subroutine ENDCHK 11610 ! Finish off check processing by giving operator 11615 ! three options: 11620 ! RETURN Write another check 11625 ! KPD 0 Print the check into file SAMPCH.DAT 11630 ! KPD . Return to menu 11631 ! Check to see if check write was aborted by kpd per. 11632 ! If so, then don't give any further choice, just abort. 11633 ! Note that form function key UAR allows only the above 11634 ! terminators to get through. 11635 !-******************************************************************** 11640 IF TERMINATOR% = FDV$K_KP_PER THEN FNEXIT 11645 !+ 11650 ! Tell the operator that the check has been paid by overlaying with 11655 ! a new form, using the normal workspace, thereby saving the check 11660 ! workspace in case another check is to be written. 11665 !- 11675 CALL FDV$SWKSP( WORKSPACE%() ) 11685 CALL FDV$DISP( 'CHECK_DONE' ) \ C=FN.SRVCHK 11700 !+ 11705 ! Wait for operator to enter either KPD period, NTR, or KPD zero. 11707 ! Print the check as many times as requested. 11710 ! (Note that a UAR on the form guarantees that only those terminators 11715 ! are accepted). 11720 ! Process accordingly. 11730 !- 11735 CALL FDV$WAIT( TERMINATOR% ) 11740 WHILE TERMINATOR% = FDV$K_KP_0 11745 C=FN.PRICHK ! Print the check 11750 CALL FDV$WAIT( TERMINATOR% ) 11755 NEXT 11765 !+ 11770 ! If choice is to quit, 11771 ! then mark check wksp undisplayed so it doesn't appear during refresh, 11772 ! else mark normal workspace (occupied by CHECK_DONE form) undisplayed 11773 ! so it doesn't show during refresh and then clear its lines. 11774 ! (Clearing the space occupied by the CHECK_DONE form, lines 20-23 11775 ! is better done by overlaying with a blank form to 11776 ! avoid having to know the line numbers to clear). 11780 !- 11785 IF TERMINATOR% = FDV$K_KP_PER THEN CALL FDV$SWKSP( CHECKWKSP%() ) CALL FDV$NDISP ELSE CALL FDV$NDISP CALL FDV$CLEAR( 20%, 4% ) CALL FDV$SWKSP( CHECKWKSP%() ) 11795 !+ 11800 ! Going to write another check now or eventually, so: 11815 ! Clear out operator entered fields. 11820 !- 11845 CALL FDV$PUTD( 'AMTPAY' ) 11850 CALL FDV$PUTD( 'MEMO' ) 11855 CALL FDV$PUTD( 'PAYTO' ) 11865 FNEND 11900 DEF FN.PRICHK 11901 !+******************************************************************** 11905 ! Subroutine PRICHK 11910 ! Print the check into the file SAMPCH.DAT 11915 ! Use the check workspace, then switch back to the normal wksp 11920 ! to keep things clean. 11921 !-******************************************************************** 11922 !+ 11923 ! Open check writing file. Note there's a new version for every check. 11924 ! Switch workspaces 11925 !+ 11926 OPEN 'SAMPCH.DAT' FOR OUTPUT AS FILE # 2%, ACCESS WRITE, RECORDSIZE 80 11927 CALL FDV$SWKSP( CHECKWKSP%() ) 11930 !+ 11932 ! Get the top and bottom lines of the check from the named data 11934 ! (first two characters). 11935 ! Must pre-extend BASIC dynamic string variables before calling FMS 11936 !- 11937 FIRSTL$ = ' ' 11938 LASTL$ = ' ' 11940 CALL FDV$RETDN( 'FIRST', FIRSTL$ ) \ C=FN.SRVCHK 11945 CALL FDV$RETDN( 'LAST', LASTL$ ) \ C=FN.SRVCHK 11960 !+ 11965 ! Get lines from form. 11967 ! Convert to line printer style. 11970 ! Write to file. 11975 !- 11980 FOR I% = VAL( SEG$( FIRSTL$, 1, 2 ) ) TO VAL( SEG$( LASTL$, 1, 2 )) 11981 LINE$ = SPACE$( 80 ) ! Pre-extend character variable (BASIC only) 11982 CALL FDV$RETFL( I%, LINE$, LINELENGTH% ) 11986 PRINT #2%, SEG$( LINE$, 1, LINELENGTH% ) 11988 NEXT I% 11990 CALL FDV$PUTL( 'Check written to file' ) 11992 CLOSE #2% 11993 CALL FDV$SWKSP( WORKSPACE%() ) 11995 FNEND 12000 DEF FN.MAKDEP 12001 !+******************************************************************** 12005 ! Subroutine MAKDEP 12010 ! Make a deposit, enter into check register 12015 ! Cancel on keypad period. 12017 ! Note that the form function key UAR allows only kpd period. 12020 ! 12035 ! Put up deposit form with current balance 12040 !-******************************************************************** 12050 CALL FDV$CDISP( 'DEPOSIT' ) \ C=FN.SRVCHK 12065 CALL FDV$PUT( FN.CENTS$( BALANCE% ), 'CURBAL' ) 12075 !+ 12080 ! Get deposit amount and memo from operator. 12085 ! Abort on kpd period. 12090 !- 12095 CALL FDV$GETAL( DEPOSIT$, TERMINATOR% ) 12100 IF TERMINATOR% = FDV$K_KP_PER THEN FNEXIT 12110 !+ 12115 ! Have deposit information now. If no room in check register 12120 ! must abort. 12125 !- 12130 IF LASTREGNUM% = REGSIZE THEN CALL FDV$PUTL( "Register full, can't enter deposit" ) CALL FDV$WAIT FNEXIT 12155 !+ 12160 ! Add to balance and session sum. 12161 ! Check for overflow (program and form keep only six digits). 12162 ! Display new balance. 12163 ! Make entry in register. 12165 !- 12170 BALANCE% = BALANCE% + VAL( DEP.AMT$ ) 12172 TOTDEP% = TOTDEP% + VAL( DEP.AMT$ ) 12174 IF BALANCE% >= 1000000 THEN BALANCE% = BALANCE% - 1000000 CALL FDV$PUTL( "Overflow in bank computer, only 6 digits kept" ) CALL FDV$WAIT 12180 CALL FDV$PUT( FN.CENTS$( BALANCE% ), 'NEWBAL' ) 12185 RI.NUM$ = '' ! Blank since it's not a check 12190 RI.DATE$ = DEP.DATE$ 12195 RI.MEMPAYTO$ = DEP.MEMO$ 12197 RI.AMTDEP$ = DEP.AMT$ 12200 RI.AMTPAY$ = '' 12210 CALL FDV$RET( RI.BALANCE$, 'NEWBAL' ) ! Avoids need to format RI.BALANCE$ 12215 LASTREGNUM% = LASTREGNUM% + 1 12220 REGARRAY$( LASTREGNUM% ) = REGITEM$ 12221 !+ 12222 ! Sample of how to keep message texts stored with the form rather 12223 ! than in a program. This is especially useful for multi-lingual 12224 ! environments: only the form text and the form named data must 12225 ! be changed and nothing in the program. The trick is to store the 12226 ! response text in named data. This is the only example of how to do 12227 ! it in this program, but all messages could be stored like this. 12228 ! Message intent is: "Deposit made, press RETURN or ENTER to continue." 12229 !- 12230 DONE$ = SPACE$(80) !Pre-extend string (BASIC only) 12232 CALL FDV$RETDN( 'DONE', DONE$ ) 12234 CALL FDV$PUTL( DONE$ ) 12235 CALL FDV$WAIT 12240 FNEND 13000 DEF FN.VUEREG 13001 !+******************************************************************** 13005 ! Subroutine VUEREG 13010 ! View the check register and scroll through it. 13015 ! Also display totals for current session. 13030 ! 13035 ! Put up register form. 13037 ! Check for current session totals overflow. If so, output 'OVRFLO' 13040 ! Put out summary of this session into indexed(4) fields. 13045 !-******************************************************************** 13047 CALL FDV$CDISP( 'REGISTER' ) \ C=FN.SRVCHK 13048 IF TOTDEP% < 1000000 THEN DEPDSP$ = FN.CENTS$( TOTDEP% ) ELSE DEPDSP$ = 'OVRFLO' 13049 IF TOTPAY% < 1000000 THEN PAYDSP$ = FN.CENTS$( TOTPAY% ) ELSE PAYDSP$ = 'OVRFLO' 13050 CALL FDV$PUT( FN.CENTS$( SBALANCE% ), 'SUMMARY', 1% ) 13055 CALL FDV$PUT( DEPDSP$, 'SUMMARY', 2% ) 13060 CALL FDV$PUT( PAYDSP$, 'SUMMARY', 3% ) 13065 CALL FDV$PUT( FN.CENTS$( BALANCE% ), 'SUMMARY', 4% ) 13072 !+ 13075 ! Get number of lines in scroll area from form named data (item 1). 13080 !- 13085 NSCROL$ = ' ' ! Pre-extend string variable before call (BASIC only). 13090 CALL FDV$RETDI( 1%, NSCROL$ ) \ C=FN.SRVCHK 13095 NSCROL% = VAL( NSCROL$ ) 13100 !+ 13105 ! Put lines from check register array into scrolled area. 13110 ! The window is initially from item 1 up to item 13115 ! min(NSCROL$,LASTREGNUM%), that is, up to the size of the scrolled 13120 ! area or the size of the register, whichever is less. Assume there 13125 ! is at least one line (the initial deposit). 13130 !- 13135 MINWINDOW% = 1 13140 CALL FDV$PUTSC( 'NUMBER', REGARRAY$(1) ) ! First line 13145 CURLINE% = 1 ! Reg item cursor is on 13150 WHILE ( CURLINE% < LASTREGNUM% AND CURLINE% < NSCROL% ) 13155 CURLINE% = CURLINE% + 1 13160 CALL FDV$PFT( FDV$K_FT_SFW, 'NUMBER' ) 13165 CALL FDV$PUTSC( 'NUMBER', REGARRAY$( CURLINE% ) ) 13170 NEXT 13171 MAXWINDOW% = CURLINE% 13175 !+ 13180 ! Get input from fake field of scrolled line and do what it says: 13185 ! kpd . or RETURN/ENTER => return to menu 13190 ! UPARROW or TAB => scroll forward 13192 ! DOWNARROW or BACKSPACE => scroll backward 13195 ! all others => ignore 13200 ! Note that there is no form function key UAR so this routine 13205 ! handles all terminators itself (by ignoring illegal ones). 13210 !- 13215 CALL FDV$GET( FAKE$, TERMINATOR%, 'FAKE' ) 13220 WHILE NOT ( TERMINATOR% = FDV$K_FT_NTR OR TERMINATOR% = FDV$K_KP_PER ) 13225 IF TERMINATOR% = FDV$K_FT_SFW OR TERMINATOR% = FDV$K_FT_SNX THEN C=FN.SCRFWD 13235 IF TERMINATOR% = FDV$K_FT_SBK OR TERMINATOR% = FDV$K_FT_SPR THEN C=FN.SCRBAK 13245 CALL FDV$GET( FAKE$, TERMINATOR%, 'FAKE' ) 13250 NEXT 13255 FNEND 13500 DEF FN.SCRFWD 13501 !+******************************************************************** 13505 ! Subroutine SCRFWD -- Scroll forward. 13510 ! CURLINE% is the line in the register that the cursor is on. 13512 ! MINWINDOW% and MAXWINDOW% delimit the part of the register 13513 ! currently displayed in the scrolled area 13515 !-******************************************************************** 13520 13525 !+ 13530 ! If cursor is at the end of the register, report, and return 13535 !- 13540 IF CURLINE% = LASTREGNUM% THEN CALL FDV$PUTL( 'Last line of register' ) FNEXIT 13545 !+ 13550 ! If cursor not at the last line of a window, just move down 13555 ! If cursor is at the last line of a window, 13560 ! move window forward one line, 13565 ! write the new last line to the last line of the scrolled area 13567 ! Move current line pointer forward 13570 !- 13580 IF CURLINE% <> MAXWINDOW% THEN CALL FDV$PFT( FDV$K_FT_SFW, 'NUMBER' ) ELSE MINWINDOW% = MINWINDOW% + 1 MAXWINDOW% = MAXWINDOW% + 1 CALL FDV$PFT( FDV$K_FT_SFW, 'NUMBER', REGARRAY$( MAXWINDOW% ) ) 13585 CURLINE% = CURLINE% + 1 13590 FNEND 13698 13700 DEF FN.SCRBAK 13701 !+******************************************************************** 13705 ! Subroutine SCRBAK -- Scroll backward 13710 ! CURLINE% is the line in the register that the cursor is on. 13712 ! MINWINDOW% and MAXWINDOW% delimit the part of the register 13713 ! currently displayed in the scrolled area 13715 !-******************************************************************** 13720 13725 !+ 13730 ! If the cursor is at the beginning of the register, report, and return 13735 !- 13740 IF CURLINE% = 1 THEN CALL FDV$PUTL( 'First line of register' ) FNEXIT 13745 !+ 13750 ! If cursor not at first line of the window, just move up 13755 ! If cursor is at first line of the window, 13760 ! move window back one line, 13765 ! write the new first line to the first line of the scrolled area 13767 ! Move current line pointer back 13770 !- 13780 IF CURLINE% <> MINWINDOW% THEN CALL FDV$PFT( FDV$K_FT_SBK, 'NUMBER' ) ELSE MINWINDOW% = MINWINDOW% - 1 MAXWINDOW% = MAXWINDOW% - 1 CALL FDV$PFT( FDV$K_FT_SBK, 'NUMBER', REGARRAY$( MINWINDOW% ) ) 13785 CURLINE% = CURLINE% - 1 13790 FNEND 14000 DEF FN.VUEACT 14001 !+******************************************************************** 14005 ! Subroutine VUEACT 14010 ! View the account data. 14015 ! If operator knows the secret word, let operator change 14017 ! the account data for this session. 14020 !-******************************************************************** 14025 14035 CALL FDV$CDISP( 'ACCOUNT_DATA' ) \ C=FN.SRVCHK 14045 CALL FDV$PUTAL( ACCOUNT$ ) 14046 CALL FDV$PUTD( 'SECRET') 14050 !+ 14051 ! This is not the best way to do protection, just a way of showing 14052 ! another FMS feature. At this point, supervisor mode is on, so the 14053 ! only input allowed is to the password field. 14055 ! If operator doesn't know password, return to menu. 14056 !- 14058 CALL FDV$GETAL( , TERMINATOR% ) !Don't care about value now 14059 IF TERMINATOR% = FDV$K_KP_PER THEN FNEXIT 14060 PASSWORD$ = SPACE$(12) !Pre-extend string variable (BASIC only) 14062 CALL FDV$RET( PASSWORD$, 'SECRET' ) 14065 IF OPW$ <> PASSWORD$ THEN FNEXIT 14070 !+ 14075 ! Allow input from other fields and read from them. 14080 ! If read is terminated by keypad period, don't change account. 14100 !- 14105 CALL FDV$SPOFF 14110 C=FN.SIMGTL ! Read all fields 14115 CALL FDV$SPON ! Not really needed, just showing off. 14120 IF TERMINATOR% <> FDV$K_KP_PER THEN CALL FDV$RETAL( ACCOUNT$ ) C=FN.FMTCHK ! Update the check workspace 14125 FNEND 14300 DEF FN.SIMGTL 14301 !+******************************************************************** 14305 ! Simulate action of FDV$GETAL, using FDV$GETAF and PFT. Could 14310 ! replace this whole routine with a call on FDV$GETAL, but this shows 14315 ! how mainline program can allow same operator freedom of filling in 14320 ! fields but still regain control after each or changed field. 14322 ! Technique is to read any field, looking only at terminator, then do 14323 ! a process field terminator call to do the operator's action. 14324 ! This technique can be used with calls on FDV$GET or FDV$GETAF. 14325 ! This example starts with a GET on field '*', first field on form. 14326 !-******************************************************************** 14330 CALL FDV$GET( JUNK$, TERMINATOR%, '*' ) 14335 CALL FDV$RETFN( FIELDNAME$, FIELDINDEX% ) !Get first field's name 14340 WHILE 1=1 14341 !+ 14342 ! Do any special processing for field FIELDNAME$ at this point. 14343 ! ... 14344 ! Go to next or previous field or leave form 14345 ! 14346 ! Note: PFT call will display error if FDV$DEBUG set thus the 14347 ! following check for special field terminators. This could be 14348 ! strengthened to (FDV$K_FT_NTR <= TERMINATOR <= FDV$K_FT_HLP) 14349 ! else PFT will return an error. 14350 !- 14351 IF TERMINATOR% = FDV$K_KP_PER THEN FNEXIT 14352 CALL FDV$PFT( TERMINATOR% ) 14357 !+ 14358 ! If status is error, then PFT failed because terminator was 14359 ! a keypad key, which means return to caller. 14360 !- 14361 IF FMSSTATUS% < 0 THEN FNEXIT 14365 IF TERMINATOR% = FDV$K_FT_NTR THEN IF FMSSTATUS% <> 2 THEN FNEXIT ELSE CALL FDV$PUTL( 'INPUT REQUIRED' ) CALL FDV$BELL 14380 !+ 14382 ! Go get any other field, returning its name 14384 !- 14390 CALL FDV$GETAF( JUNK$, TERMINATOR%, FIELDNAME$, FIELDINDEX% ) 14400 NEXT 14410 FNEND 15000 DEF FN.GETSTA 15001 !+******************************************************************** 15005 ! Subroutine GETSTA 15010 ! Check FMS status by calling FDV$STAT. 15012 ! If not success (>0), print and stop 15015 !-******************************************************************** 15021 15025 CALL FDV$STAT( FMSSTATUS%, RMSSTATUS% ) 15030 IF FMSSTATUS% > 0 THEN FNEXIT 15035 C=FN.ERROR ! and never come back 15040 FNEND 15300 DEF FN.SRVCHK 15301 !+******************************************************************** 15310 ! Subroutine SRVCHK 15315 ! Check FMS status by looking at the status recording variables. 15316 !-******************************************************************** 15325 IF FMSSTATUS% > 0 THEN FNEXIT 15330 C=FN.ERROR ! and never come back 15335 FNEND 15700 DEF FN.ERROR 15701 !+******************************************************************** 15702 ! Subroutine ERROR 15705 ! There is an error returned in the status variables. Detach the 15710 ! terminal to clean up, then print the errors, and stop. 15715 !-******************************************************************** 15730 CALL FDV$DTERM( TCA%() ) 15735 PRINT "FDV ERROR." 15740 PRINT "","FMS STATUS:",FMSSTATUS% 15745 PRINT "","RMS STATUS:",RMSSTATUS% 15747 STOP 15750 FNEND 15900 DEF FN.CENTS$( CENTS% ) 15905 !+******************************************************************** 15910 ! Function FN.CENTS 15915 ! Return the string value of CENTS% suitable for outputing in a six 15920 ! wide field with two decimal places. The important thing to note is 15925 ! that a number less than 100 should be output with leading zeros so 15930 ! that a string like "bbbbb9" doesn't display as "bbbb.b9" on the form. 15935 ! We actually convert all spaces to zero and then let the forms zero 15940 ! suppress the result. 15945 !-******************************************************************** 15950 CENTS$ = FORMAT$( CENTS%, "######" ) 15955 FN.CENTS$ = XLATE( CENTS$, STRING$(32%,0%)+'0'+STRING$(15%,0%)+'0123456789' ) 15960 FNEND 15999 END 16005 FUNCTION INTEGER VALID1 16010 !+******************************************************************** 16015 ! VALID1 16017 ! UAR for field validation of any one character field. The 16020 ! UAR associated data has in it the legal characters allowed, 16025 ! except that blank is not allowed unless it appears before 16030 ! the first trailing blank. For example an assoc. value string 16035 ! 'aqr' implies that only the letters a, q, and r are allowed. 16040 ! A string ' aqr' means that blank is acceptable in addition 16045 ! to a, q, and r. Note that this routine is case sensitive 16050 ! (that is, it checks for correct case). You can get around 16055 ! case sensitivity by using the force upper case field attribute 16060 ! and putting only capitals into the UAR associated value 16065 ! string. 16070 ! 16075 ! This routine can be used with any form and field since 16080 ! it determines the context for itself. 16085 !-******************************************************************** 16088 !+ 16089 DECLARE INTEGER CONSTANT & FDV$K_UVAL_SUC= 1000, !Field completion success & FDV$K_UVAL_FAIL=1001 !Field completion failure 16090 ! Pre-extend the strings into which FMS will return values 16095 !- 16096 FRMNAM$ = SPACE$(31) 16097 UARVAL$ = SPACE$(80) 16098 FLDNAME$ = SPACE$(31) 16099 FVALUE$ = SPACE$(1) 16105 16110 !+ 16120 ! Retrieve context: we will ignore TCA address, WKSP address, FRMNAM$, 16125 ! CURPOS, FLDTRM, INSOVR, and HELPNUM using only UARVAL$, and 16127 ! only the initial, non-blank characters of it. 16130 ! Retrieve field name and index. 16135 ! Retrieve field value. 16136 ! The parameters ATCA% and AWKSP are pointers to the TCA block. 16140 CALL FDV$RETCX( ATCA%, AWKSP%, FRMNAM$, UARVAL$, CURPOS%, FLDTRM%, INSOVR%, HELPNUM% ) 16142 UARVAL$ = TRM$( UARVAL$ ) 16145 CALL FDV$RETFN( FLDNAME$, FINDEX% ) 16150 CALL FDV$RET( FVALUE$, FLDNAME$, FINDEX% ) 16160 16165 !+ 16170 ! To be valid, FVALUE$ must occur in the string UARVAL$ 16175 !- 16185 IF POS( UARVAL$, FVALUE$, 1) > 0 THEN VALID1 = FDV$K_UVAL_SUC !Success ELSE CALL FDV$PUTL( 'Illegal value' ) VALID1 = FDV$K_UVAL_FAIL 16210 FUNCTIONEND 17000 FUNCTION INTEGER TAKE15 17010 !+******************************************************************** 17015 ! Function key User Action Routine for the MENU form of SAMP. 17020 ! Convert keypad 1-5 into field values 1-5. 17025 ! Convert keypad period into field value 1. 17030 ! Reject all other function keys with error message. 17035 !-******************************************************************** 17037 DECLARE INTEGER CONSTANT & FDV$K_KP_PER = 110, & FDV$K_KP_1 = 113, & FDV$K_KP_2 = 114, & FDV$K_KP_3 = 115, & FDV$K_KP_4 = 116, & FDV$K_KP_5 = 117, & FDV$K_UKEY_ERR= 3000, !Fn Key failure, FDV signals & FDV$K_UKEY_TRM= 3001, !FN Key success, normal f.k. & FDV$K_UKEY_NTR= 3003, !Fn Key succ, treat as ENTER & FDV$K_UKEY_SUC= 3004 !Fn Key succ, ignore 17040 17045 !+ 17050 ! Pre-extend the strings into which FMS will return values 17055 !- 17060 FRMNAM$ = SPACE$(4) 17062 UARVAL$ = SPACE$(1) !No UAR value expected 17065 17070 !+ 17075 ! Retrieve context: we will ignore TCA address, WKSP address, FRMNAM$, 17080 ! UARVAL$, CURPOS%, INSOVR% and HELPNUM%, using only FLDTRM% 17095 CALL FDV$RETCX( TCA%, WKSP%, FRMNAM$, UARVAL$, CURPOS%, FLDTRM%, INSOVR%, HELPNUM% ) 17100 17105 !+ 17110 ! Do the conversion, displaying the value converted if found. 17115 ! Reject if not one of the expected terminators. 17120 ! 17130 VALUE$ = '' 17135 IF FLDTRM% = FDV$K_KP_1 THEN VALUE$ = '1' 17140 IF FLDTRM% = FDV$K_KP_2 THEN VALUE$ = '2' 17145 IF FLDTRM% = FDV$K_KP_3 THEN VALUE$ = '3' 17150 IF FLDTRM% = FDV$K_KP_4 THEN VALUE$ = '4' 17155 IF FLDTRM% = FDV$K_KP_5 THEN VALUE$ = '5' 17160 IF FLDTRM% = FDV$K_KP_PER THEN VALUE$ = '1' 17165 IF VALUE$ <> '' THEN CALL FDV$PUT( VALUE$, 'OPTION' ) ! Treat as if it is RETURN TAKE15 = FDV$K_UKEY_NTR ELSE CALL FDV$PUTL( 'Illegal function key' ) CALL FDV$SIGOP ! Just ignore it now TAKE15 = FDV$K_UKEY_SUC 17175 FUNCTIONEND 18000 FUNCTION INTEGER PASSKY 18010 !+******************************************************************** 18015 ! General function key uar to pass only those from the (small) list 18020 ! in the uar associated value string and reject all others. 18021 ! The list is of the form: n n ... n 18023 ! For example the string '110 112' would accept keypad period and 18024 ! keypad zero but no other function keys. 18025 !-******************************************************************** 18030 DECLARE INTEGER CONSTANT & FDV$K_UKEY_ERR= 3000, !Fn Key failure, FDV signals & FDV$K_UKEY_TRM= 3001, !FN Key success, normal f.k. & FDV$K_UKEY_NTR= 3003, !Fn Key succ, treat as ENTER & FDV$K_UKEY_SUC= 3004 !Fn Key succ, ignore 18045 !+ 18050 ! Pre-extend the strings into which FMS will return values 18055 !- 18060 FRMNAM$ = SPACE$(4) 18062 UARVAL$ = SPACE$(82) !Two longer to ensure trailing blanks 18065 18070 !+ 18075 ! Retrieve context: we will ignore TCA address, WKSP address, FRMNAM$, 18080 ! INSOVR%, HELPNUM% and CURPOS%, using only FLDTRM% and UARVAL$. 18095 CALL FDV$RETCX( TCA%, WKSP%, FRMNAM$, UARVAL$, CURPOS%, FLDTRM%, INSOVR%, HELPNUM% ) 18100 18105 !+ 18110 ! Break up the list into numbers. Check each against the actual 18115 ! terminator. If terminator found in list, return success. 18120 !- 18122 UARVAL$ = UARVAL$ + ' ' 18125 NONBLANK% = 1 ! Beginning of string 18130 WHILE SEG$( UARVAL$, NONBLANK%, NONBLANK% ) <> ' ' 18135 NEXTBLANK% = POS( UARVAL$, ' ', NONBLANK% ) 18140 IF FLDTRM% = VAL( SEG$( UARVAL$, NONBLANK%, NEXTBLANK% - 1 ) ) THEN PASSKY = FDV$K_UKEY_TRM !Pass key to application FUNCTIONEXIT 18150 NONBLANK% = NEXTBLANK% + 1 18155 NEXT 18160 PASSKY = FDV$K_UKEY_ERR !Let FDV do the beeping 18165 FUNCTIONEND 19000 FUNCTION INTEGER CHKCHK 19010 !+******************************************************************** 19015 ! UAR for SAMP CHECK form. Makes sure that the check amount is 19020 ! less than or equal to the current balance. If not, complain and 19023 ! change video attributes on balance field so the potential bouncer 19024 ! can see what there is to work with. 19025 !-******************************************************************** 19027 DECLARE INTEGER CONSTANT & FDV$K_UVAL_SUC= 1000, !Field completion success & FDV$K_UVAL_FAIL=1001 !Field completion failure 19029 !+ 19030 ! Don't forget to pre-extend BASIC string variables before FMS calls. 19035 !- 19040 BALANCE$ = SPACE$( 6 ) 19045 AMTPAY$ = SPACE$( 6 ) 19050 CALL FDV$RET( BALANCE$, 'BALANCE' ) 19055 CALL FDV$RET( AMTPAY$, 'AMTPAY' ) 19060 IF VAL( BALANCE$ ) >= VAL( AMTPAY$ ) THEN CHKCHK = FDV$K_UVAL_SUC BLINKBOLD% = -1 !Restore to original CALL FDV$AFVA( BLINKBOLD%, 'BALANCE' ) ELSE CHKCHK = FDV$K_UVAL_FAIL BLINKBOLD% = 3 !Make it very visible CALL FDV$AFVA( BLINKBOLD%, 'BALANCE' ) CALL FDV$PUTL( "Your balance doesn't cover that much, reenter amount" ) 19080 FUNCTIONEND 20000 FUNCTION INTEGER RANGE 20010 !+******************************************************************** 20015 ! General purpose UAR to check the range of any numeric item. The 20020 ! associated UAR data must have one of the four forms: 20025 ! L,U{message} 20030 ! ,U{message} 20035 ! L,{message} 20040 ! ,{message} 20045 ! where L is lower bound, U is upper bound, and {message} is an 20050 ! optional error message in case the field value is out of bounds. 20055 ! If one of the bounds isn't given, it isn't checked for. If neither 20060 ! bound is given, nothing is checked, everything succeeds. If the 20065 ! UAR value doesn't have a comma, a FDV$_UAR error message is returned 20070 ! to the calling program by the FDV so the form designer has to go 20075 ! back and do it right. If no {message} is given, a simple 20080 ! "out of range U:L" message is given to the hapless operator. 20085 ! 20090 ! This UAR can work with any form and numeric field since it gets 20095 ! context itself. Care must be taken with fields using field marker 20097 ! periods since those periods are not returned to the program. 20098 !-******************************************************************** 20099 DECLARE INTEGER CONSTANT & FDV$K_UVAL_SUC= 1000, !Field completion success & FDV$K_UVAL_FAIL=1001 !Field completion failure 20100 !+ 20102 ! Pre-extend the strings into which FMS will return values. 20105 ! Get context which yields associated data value (ignore other stuff). 20110 ! Get current field name and index. 20115 ! Get field value. 20120 !- 20125 FRMNAM$ = SPACE$(31) 20130 UARVAL$ = SPACE$(80) 20132 NAME$ = SPACE$(31) 20133 NUMBER$ = SPACE$(132) 20135 CALL FDV$RETCX( TCA%, WKSP%, FRMNAM$, UARVAL$, CURPOS%, FLDTRM%, INSOVR%, HELPNUM% ) 20140 CALL FDV$RETFN( NAME$, INDEX% ) 20145 CALL FDV$RET( NUMBER$, NAME$, INDEX% ) 20150 NUMBER = VAL( NUMBER$ ) 20155 !+ 20157 ! Find comma and blank delimiters. 20160 ! Check for lower bound. 20165 !- 20170 COMMA% = POS( UARVAL$, ',' , 1 ) 20172 BLANK% = POS( UARVAL$, SPACE$(1), COMMA% + 1 ) 20175 IF COMMA% = 0 THEN RANGE = 0 ! Illegal UARVAL string, FDV returns error FUNCTIONEXIT 20180 IF COMMA% <> 1 THEN IF NUMBER < VAL( SEG$( UARVAL$, 1, COMMA% - 1 ) ) THEN 20300 20190 20195 !+ 20200 ! Check for upper bound 20205 !- 20215 IF BLANK% <> COMMA% + 1 THEN IF NUMBER > VAL( SEG$( UARVAL$, COMMA% + 1, BLANK% - 1 ) ) THEN 20300 20220 !+ 20225 ! Passed both tests successfully, return success for UAR value 20230 ! 20235 RANGE = FDV$K_UVAL_SUC 20240 FUNCTIONEXIT 20300 !+ 20305 ! Error in one of the bounds. 20310 ! Give error message: either from the UARVAL or make one up. 20315 !- 20320 IF SEG$( UARVAL$, BLANK% + 1, BLANK% + 1 ) <> SPACE$(1) THEN CALL FDV$PUTL( SEG$( UARVAL$, BLANK% + 1, 80 ) ) ELSE CALL FDV$PUTL( 'Field value out of bounds. Must be in range "' + & SEG$( UARVAL$, 1, BLANK% - 1 ) + '".' ) 20325 CALL FDV$SIGOP !Beep, too. 20330 RANGE = FDV$K_UVAL_FAIL 20335 FUNCTIONEND