Microsoft KB Archive/45520

= Microsoft Knowledge Base =

Complete QuickBasic 4.50 QCARDS.BAS Tutorial Program
Last reviewed: May 30, 1996

Article ID: Q45520

SUMMARY
The final working program that results from successful completion of the QuickBasic version 4.50 QCARDS tutorial is shown below.

You can also obtain this program (DONECARD.BAS) by downloading QCARDS.EXE from the Microsoft Software Library.

You can find QCARDS.EXE, a self-extracting file, on these services:

  Microsoft's World Wide Web site on the Internet On the www.microsoft.com home page, click the Support icon Click Knowledge Base, and select the product Enter kbfile QCARDS.EXE, and click GO! Open the article, and click the button to download the file   Internet (anonymous FTP) ftp ftp.microsoft.com Change to the Softlib/Mslfiles folder Get QCARDS.EXE   The Microsoft Network On the Edit menu, click Go To, and then click Other Location Type &quot;mssupport&quot; (without the quotation marks) Double-click the MS Software Library icon Find the appropriate product area Locate and Download QCARDS.EXE   Microsoft Download Service (MSDL) Dial (206) 936-6735 to connect to MSDL Download QCARDS.EXE 

For additional information about downloading, please see the following article in the Microsoft Knowledge Base:

ARTICLE-ID: Q119591 TITLE    : How to Obtain Microsoft Support Files from Online Services

MORE INFORMATION
QCARDS.BAS is a sample program included on disk with QuickBasic version 4.50 to be used in conjunction with the hands-on tutorial in the &quot;Microsoft QuickBasic: Learning to Use&quot; manual. QCARDS was intended to be used as a teaching device -- a piece of incomplete code that would be finished by following the step-by-step instructions in the tutorial. Completing these steps results in the program DONECARD.BAS, shown below.

QCARDS (or DONECARD) is a simple electronic cardfile program that stores a database of names, addresses, and phone numbers. It includes features to add, edit, copy, delete, find, sort, and print records.

QCARDS.BAS and DONECARD.BAS are copyrighted (C) by Microsoft Corporation, 1988.

The DATA statements in the program below define the card-screen template using standard ASCII characters. The template will look nicer if you replace these DATA statements with the DATA statements after the Cardscreen label in the original QCARDS.BAS program (on the QuickBasic version 4.50 disk). The original QCARDS uses extended-ASCII line-drawing characters, which cannot be included in this on-line knowledge base.

DONECARD.BAS
'* QCards - A simple database using a cardfile user interface. '* Each record in the database is represented by a card. The user '* can scroll through the cards using normal scrolling keys. '* Other commands allow the user to edit, add, sort, find, or '* delete cards. '* '* Input: Keyboard - user commands and entries '*        File - database records '* '* Output: Screen - card display and help '*        File - database records '*

' The module-level code begins here.

'*************** Declarations and definitions begin here ************ DEFINT A-Z 'Resets default data type from single precision to integer

' Define names similar to keyboard names with equivalent key codes. CONST SPACE = 32, ESC = 27, ENTER = 13, TABKEY = 9 CONST DOWN = 80, UP = 72, LEFT = 75, RIGHT = 77 CONST HOME = 71, ENDK = 79, PGDN = 81, PGUP = 73 CONST INS = 82, DEL = 83, NULL = 0 CONST CTRLD = 4, CTRLG = 7, CTRLH = 8, CTRLS = 19, CTRLV = 22

' Define English names for color-specification numbers. Add BRIGHT to ' any color to get bright version. CONST BLACK = 0, BLUE = 1, GREEN = 2, CYAN = 3, RED = 4, MAGENTA = 5 CONST YELLOW = 6, WHITE = 7, BRIGHT = 8

' Assign colors to different kinds of text. By changing the color ' assigned, you can change the color of the QCARDS display. The ' initial colors are chosen because they work for color or ' black-and-white displays. CONST BACKGROUND = BLACK, NORMAL = WHITE, HILITE = WHITE + BRIGHT

' Codes for normal and highlight (used in data statements) CONST CNORMAL = 0, CHILITE = 1

' Screen positions - Initialized for 25 rows. Screen positions can be ' modified for 43-row mode if you have an EGA or VGA adapter. CONST HELPTOP = 15, HELPBOT = 23, HELPLEFT = 60, HELPWID = 20 CONST CARDSPERSCREEN = 7, LASTROW = 25

' Miscellaneous symbolic constants CONST FALSE = 0, TRUE = NOT FALSE CONST CURSORON = 1, CURSOROFF = 0

' Filenames CONST TMPFILE$ = &quot;$$$87y$.$5$&quot;      ' Unlikely filename CONST DISKFILE$ = &quot;QCARDS.DAT&quot;

' Field names CONST NPERSON = 0, NNOTE = 1, NMONTH = 2, NDAY = 3 CONST NYEAR = 4, NPHONE = 5, NSTREET = 6, NCITY = 7 CONST NSTATE = 8, NZIP = 9, NFIELDS = NZIP + 1

' Declare user-defined type (a data structure) for random-access ' file records. TYPE PERSON

CardNum    AS INTEGER          'First element is card number Names      AS STRING * 37      'Names (in order for alpha. sort) Note       AS STRING * 31      'Note about person Month      AS INTEGER          'Birth month Day        AS INTEGER          'Birth day Year       AS INTEGER          'Birth year Phone      AS STRING * 12      'Phone number Street     AS STRING * 29      'Street address City       AS STRING * 13      'City State      AS STRING * 2       'State Zip        AS STRING * 5       'Zip code END TYPE

' SUB procedure declarations begin here. DECLARE SUB Alarm

DECLARE SUB DirectionKey (Choice$, TopCard%, LastCard%) DECLARE SUB AsciiKey (Choice$, TopCard%, LastCard%) DECLARE SUB CleanUp (LastCard%) DECLARE SUB ClearHelp DECLARE SUB DrawCards  DECLARE SUB EditCard (Card AS PERSON) DECLARE SUB InitIndex (LastCard%) DECLARE SUB PrintLabel (Card AS PERSON) DECLARE SUB SortIndex (SortField%, LastCard%) DECLARE SUB ShowViewHelp  DECLARE SUB ShowTopCard (WorkCard AS PERSON) DECLARE SUB ShowEditHelp  DECLARE SUB ShowCmdLine  DECLARE SUB ShowCards (TopCard%, LastCard%)

' FUNCTION procedure declarations begin here. DECLARE FUNCTION EditString$ (InString$, Length%, NextField%) DECLARE FUNCTION FindCard% (TopCard%, LastCard%) DECLARE FUNCTION Prompt$ (Msg$, Row%, Column%, Length%) DECLARE FUNCTION SelectField%

' Procedure declarations end here.

' Define a dummy record as a work card. DIM Card AS PERSON

'*************** Declarations and definitions end here **************

' The execution-sequence logic of QCARDS begins here.

' Open data file QCARDS.DAT for random access using file #1 OPEN DISKFILE$ FOR RANDOM AS #1 LEN = LEN(Card)

' To count records in file, divide the length of the file by the ' length of a single record; use integer division (\) instead of ' normal division (/). Assign the resulting value to LastCard. LastCard = LOF(1) \ LEN(Card)

' Redefine the Index array to hold the records in the file plus ' 20 extra (the extra records allow the user to add cards). ' This array is dynamic - this means the number of elements ' in Index varies depending on the size of the file. ' Also, Index is a shared procedure, so it is available to ' all SUB and FUNCTION procedures in the program. ' ' Note that an error trap lets QCARDS terminate with an error ' message if the memory available is not sufficient. If no ' error is detected, the error trap is turned off following the ' REDIM statement. ON ERROR GOTO MemoryErr REDIM SHARED Index(1 TO LastCard + 20) AS PERSON ON ERROR GOTO 0

' Use the block IF...THEN...ELSE statement to decide whether ' to load the records from the disk file QCARDS.DAT into the ' array of records called Index declared earlier. In the IF ' part, you will check to see if there are actually records ' in the file. If there are, LastCard will be greater than 0, ' and you can call the InitIndex procedure to load the records ' into Index. LastCard is 0 if there are no records in the ' file yet. If there are no records in the file, the ELSE ' clause is executed. The code between ELSE and END IF starts ' the Index array at card 1. IF LastCard <> 0 THEN

CALL InitIndex(LastCard) ELSE

Card.CardNum = 1 Index(1) = Card PUT #1, 1, Card END IF

' Use the DrawCards procedure to initialize the screen ' and draw the cards. Then, set the first card as the top ' card. Finally, pass the variables TopCard and LastCard ' as arguments to the ShowCards procedure. The call to ' ShowCards places all the data for TopCard on the front ' card on the screen, then it places the top-line ' information (the person's name) on the remaining cards. CALL DrawCards TopCard = 1 CALL ShowCards(TopCard, LastCard)

' Keep the picture on the screen forever with an unconditional ' DO...LOOP statement. The DO part of the statement goes on ' the next code line. The LOOP part goes just before the END ' statement. This loop encloses the central logic that lets ' a user interact with QCARDS. DO

' Get user keystroke with a conditional DO...LOOP statement. ' Within the loop, use the INKEY$ function to capture a user ' keystroke, which is then assigned to a string variable. The ' WHILE part of the LOOP line keeps testing the string ' variable. Until a key is pressed, INKEY$ keeps returning a ' null (that is a zero-length) string, represented by &quot;&quot;. ' When a key is pressed, INKEY$ returns a string with a ' length greater than zero, and the loop terminates.

' DO...LOOP with test at the bottom of the loop DO      Choice$ = INKEY$ LOOP WHILE Choice$ = &quot;&quot;

' Use the LEN function to find out whether Choice$ is greater ' than a single character (i.e. a single byte). If Choice$ is ' a single character (that is, it is less than 2 bytes long), ' the key pressed was an ordinary &quot;typewriter keyboard&quot; ' character (these are usually called ASCII keys because they ' are part of the ASCII character set). When the user enters ' an ASCII character, it indicates a choice of one of the QCARDS ' commands from the command line at the bottom of the screen. ' If the user did press an ASCII key, use the LCASE$ function ' to convert it to lower case (in the event the capital letter ' was entered). ' ' The ELSE clause is only executed if Choice$ is longer than a ' single character (and therefore not a command-line key). ' If Choice$ is not an ASCII key, it represents an &quot;extended&quot; ' key. (The extended keys include the DIRECTION keys on the ' numeric keypad, which is why QCARDS looks for them.) The ' RIGHT$ function is then used trim away the extra byte, ' leaving a value that may correspond to one of the DIRECTION ' keys. Use a SELECT CASE construction to respond to those key- ' presses that represent numeric-keypad DIRECTION keys.

IF LEN(Choice$) = 1 THEN ' Handle ASCII keys. CALL AsciiKey(Choice$, TopCard, LastCard)

ELSE ' Convert 2-byte extended code to 1-byte ASCII code and handle. Choice$ = RIGHT$(Choice$, 1) CALL DirectionKey(Choice$, TopCard, LastCard)

END IF

' Adjust the cards according to the key pressed by the user, ' then call the ShowCards procedure to show adjusted stack. IF TopCard < 1 THEN TopCard = LastCard + TopCard IF TopCard > LastCard THEN TopCard = TopCard - LastCard IF TopCard <= 0 THEN TopCard = 1 CALL ShowCards(TopCard, LastCard)

' This is the bottom of the unconditional DO loop. LOOP

END

' The execution sequence of the module-level code ends here. ' The program may terminate elsewhere for legitimate reasons, ' but the normal execution sequence ends here. Statements ' beyond the END statement are executed only in response to ' other statements.

' This first label, MemoryErr, is an error handler. MemoryErr:

PRINT &quot;Not enough memory. Can't read file.&quot; END

' Data statements for screen output - initialized for 25 rows. Can ' be modified for 43-row mode if you have an EGA or VGA adapter.

'   Modified 6/6/89: Standard ASCII line characters substituted for '   extended ASCII to support uploading to on-line KnowledgeBase. '   If you wish smoother lines (using extended ASCII line-drawing '    characters), replace the following CardScreen DATA statements '   with the original DATA statements from QCARDS.BAS. CardScreen:

DATA &quot;                 -&quot; DATA &quot;                 |                                       |&quot; DATA &quot;              -==|&quot; DATA &quot;              |                                       |  |&quot; DATA &quot;           -==|  |&quot; DATA &quot;           |                                       |  |  |&quot; DATA &quot;        -==|  |  |&quot; DATA &quot;        |                                       |  |  |  |&quot; DATA &quot;     -==|  |  |  |&quot; DATA &quot;     |                                       |  |  |  |  |&quot; DATA &quot;  -==|  |  |  |--|&quot; DATA &quot;  |                                       |  |  |  |  |&quot; DATA &quot;-==| | | |--|&quot; DATA &quot;| _____________________________________ | | | | |&quot; DATA &quot;========================================= | | |--|&quot; DATA &quot;| Note: _______________________________ | | | |&quot;

DATA &quot;|                                      |  |  |--|&quot; DATA &quot;| Birth: __/__/__  Phone: ___-___-____ |  |  |&quot; DATA &quot;|                                      |  |--|&quot; DATA &quot;| Street: _____________________________ | |&quot;

DATA &quot;|                                      |--|&quot; DATA &quot;| City: ____________ ST: __ Zip: _____ |&quot; DATA &quot;|---|&quot;

' Color codes and strings for view-mode help ViewHelp: DATA 0, &quot;Select card with:&quot;

DATA 1, &quot;     UP&quot; DATA 1, &quot;     DOWN&quot; DATA 1, &quot;     PGUP&quot; DATA 1, &quot;     PGDN&quot; DATA 1, &quot;     HOME&quot; DATA 1, &quot;     END&quot; DATA 1, &quot;&quot; DATA 1, &quot;&quot;

' Color codes and strings for edit-mode help EditHelp: DATA 0, &quot;Next field:&quot;

DATA 1, &quot;     TAB&quot; DATA 0, &quot;Accept card:&quot;

DATA 1, &quot;     ENTER&quot; DATA 0, &quot;Edit field:&quot;

DATA 1, &quot;     DEL     BKSP&quot; DATA 1, &quot;     RIGHT   LEFT&quot; DATA 1, &quot;     HOME    END&quot; DATA 1, &quot;     INS     ESC&quot;

' Row, column, and length of each field FieldPositions:

DATA 14, 6, 37                     : ' Names DATA 16, 12, 31                    : ' Note DATA 18, 13, 2                     : ' Month DATA 18, 16, 2                     : ' Day DATA 18, 19, 2                     : ' Year DATA 18, 31, 12                    : ' Phone DATA 20, 14, 29                    : ' Street DATA 22, 12, 13                    : ' City DATA 22, 29, 2                     : ' State DATA 22, 38, 5                     : ' Zip DATA 0, 0, 0

SUB Alarm

' The Alarm procedure uses the SOUND statement to send ' signals to the computer's speaker and sound an alarm ' ' ' Parameters: None ' ' Output: Sends an alarm to the user

' Change the numbers to vary the sound FOR Tone = 600 TO 2000 STEP 40

SOUND Tone, Tone / 7000 NEXT Tone

END SUB

'* '* AsciiKey - Handles ASCII keys. You can add new commands by '* assigning keys and actions here and adding them to the command '* line displayed by the ShowCmdLine SUB. For example, you could add '* L (for Load new file) to prompt the user for a new database file. '* '* Params: UserChoice$ - key pressed by the user '*        TopCard - the number of the current record '*        LastCard - the number of records '* SUB AsciiKey (UserChoice$, TopCard%, LastCard%) DIM WorkCard AS PERSON

SELECT CASE LCASE$(UserChoice$) ' Edit the current card. CASE &quot;e&quot; CALL ShowEditHelp Tmp$ = Prompt$(&quot;Editing Card...&quot;, LASTROW, 1, 0) CALL EditCard(Index(TopCard)) PUT #1, Index(TopCard).CardNum, Index(TopCard) LOCATE, , CURSOROFF CALL ShowViewHelp

' Add and edit a blank or duplicate card. CASE &quot;a&quot;, &quot;c&quot; IF UserChoice$ = &quot;c&quot; THEN WorkCard = Index(TopCard)  ' Duplicate of top card ELSE WorkCard.CardNum = 0       ' Initialize new card. WorkCard.Names = &quot;&quot; WorkCard.Note = &quot;&quot; WorkCard.Month = 0 WorkCard.Day = 0 WorkCard.Year = 0 WorkCard.Phone = &quot;&quot; WorkCard.Street = &quot;&quot; WorkCard.City = &quot;&quot; WorkCard.State = &quot;&quot; WorkCard.Zip = &quot;&quot; END IF           TopCard = LastCard + 1 LastCard = TopCard Index(TopCard) = WorkCard Index(TopCard).CardNum = TopCard CALL ShowCards(TopCard, LastCard) CALL ShowEditHelp Tmp$ = Prompt$(&quot;Editing Card...&quot;, LASTROW, 1, 0) CALL EditCard(Index(TopCard)) PUT #1, Index(TopCard).CardNum, Index(TopCard) LOCATE, , CURSOROFF CALL ShowViewHelp

' Move deleted card to end and adjust last card. CASE &quot;d&quot; FOR Card = TopCard TO LastCard - 1 SWAP Index(Card + 1), Index(Card) NEXT Card LastCard = LastCard - 1

' Find a specified card. CASE &quot;f&quot; CALL ShowEditHelp Tmp$=&quot;Enter fields for search (blank fields are ignored)&quot; Tmp$ = Prompt$(Tmp$, LASTROW, 1, 0) Card = FindCard(TopCard, LastCard) IF Card THEN TopCard = Card ELSE BEEP CALL ClearHelp Tmp$ = &quot;Can't find card. Press any key...&quot; Tmp$ = Prompt$(Tmp$, LASTROW, 1, 1) END IF           LOCATE, , CURSOROFF CALL ShowViewHelp

' Sorts cards by a specified field. CASE &quot;s&quot; CALL ClearHelp Tmp$ = &quot;TAB to desired sort field, then press ENTER&quot; Tmp$ = Prompt$(Tmp$, LASTROW, 1, 0) CALL SortIndex(SelectField, LastCard) TopCard = 1 CALL ShowViewHelp

' Prints address of top card on printer. CASE &quot;p&quot; CALL PrintLabel(Index(TopCard))

' Terminates the program. CASE &quot;q&quot;, CHR$(ESC) CALL CleanUp(LastCard) LOCATE, , CURSORON CLS END CASE ELSE BEEP END SELECT END SUB

'* '* CleanUp - Writes all records from memory to a file. Deleted '* records (past the last card) will not be written. The valid records '* are written to a temporary file. The old file is deleted, and the '* new file is given the old name. '* '* Params: LastCard - the number of valid records '* '* Output: Valid records to DISKFILE$ through TMPFILE$ '* SUB CleanUp (LastCard)

' Write records to temporary file in their current sort order. OPEN TMPFILE$ FOR RANDOM AS #2 LEN = LEN(Index(1)) FOR Card = 1 TO LastCard PUT #2, Card, Index(Card) NEXT

' Delete old file and replace it with new version. CLOSE KILL DISKFILE$ NAME TMPFILE$ AS DISKFILE$ END SUB

'* '* ClearHelp - Writes spaces to the help area of the screen. '* '* Params: None '* '* Output: Blanks to the screen '* SUB ClearHelp

' Clear key help COLOR NORMAL, BACKGROUND FOR Row = HELPTOP TO HELPBOT LOCATE Row, HELPLEFT PRINT SPACE$(HELPWID) NEXT

' Clear command line LOCATE LASTROW, 1 PRINT SPACE$(80); END SUB

SUB DirectionKey (Choice$, TopCard%, LastCard%)

SELECT CASE Choice$ CASE CHR$(DOWN) TopCard = TopCard - 1 CASE CHR$(UP) TopCard = TopCard + 1 CASE CHR$(PGDN) TopCard = TopCard - CARDSPERSCREEN CASE CHR$(PGUP) TopCard = TopCard + CARDSPERSCREEN CASE CHR$(HOME) TopCard = LastCard CASE CHR$(ENDK) TopCard = 1 CASE ELSE CALL Alarm END SELECT END SUB

'* '* DrawCards - Initializes screen by setting the color, setting the '* width and height, clearing the screen, and hiding the cursor. Then '* writes card text and view-mode help to the screen. '* '* Params: None '* '* Output: Text to the screen '* SUB DrawCards

' Clear screen to current color. WIDTH 80, LASTROW COLOR NORMAL, BACKGROUND CLS LOCATE, , CURSOROFF, 0, 7

' Display line characters that form cards. RESTORE CardScreen FOR Row = 1 TO 23 LOCATE Row, 4 READ Tmp$ PRINT Tmp$; NEXT

' Display help. CALL ShowViewHelp END SUB

'* '* EditCard - Edits each field of a specified record. '* '* Params: Card - the record to be edited '* '* Return: Since Card is passed by reference, the edited version is '*        effectively returned. '* SUB EditCard (Card AS PERSON)

' Set NextFlag and continue editing each field. ' NextFlag is cleared when the user presses ENTER.

NextFlag = TRUE DO

RESTORE FieldPositions

' Start with first field. READ Row, Column, Length LOCATE Row, Column ' Edit string fields directly. Card.Names = EditString(RTRIM$(Card.Names), Length, NextFlag) ' Result of edit determines whether to continue. IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Card.Note = EditString(RTRIM$(Card.Note), Length, NextFlag) IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column ' Convert numeric fields to strings for editing. Tmp$ = LTRIM$(STR$(Card.Month)) Tmp$ = EditString(Tmp$, Length, NextFlag) ' Convert result back to number. Card.Month = VAL(Tmp$) LOCATE Row, Column PRINT USING &quot;##_/&quot;; Card.Month; IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Tmp$ = LTRIM$(STR$(Card.Day)) Tmp$ = EditString(Tmp$, Length, NextFlag) Card.Day = VAL(Tmp$) LOCATE Row, Column PRINT USING &quot;##_/&quot;; Card.Day; IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Tmp$ = LTRIM$(STR$(Card.Year)) Tmp$ = EditString(Tmp$, Length, NextFlag) Card.Year = VAL(Tmp$) LOCATE Row, Column PRINT USING &quot;##&quot;; Card.Year; IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Card.Phone = EditString(RTRIM$(Card.Phone), Length, NextFlag) RSET Card.Phone = Card.Phone IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Card.Street=EditString(RTRIM$(Card.Street), Length, NextFlag) IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Card.City = EditString(RTRIM$(Card.City), Length, NextFlag) IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Card.State = EditString(RTRIM$(Card.State), Length, NextFlag) IF NextFlag = FALSE THEN EXIT SUB

READ Row, Column, Length LOCATE Row, Column Card.Zip = EditString(RTRIM$(Card.Zip), Length, NextFlag) IF NextFlag = FALSE THEN EXIT SUB

LOOP END SUB

'* '* EditString$ - Edits a specified string. This function '* implements a subset of editing functions used in the QuickBasic '* environment and in Windows. Common editing keys are recognized, '* including direction keys, DEL, BKSP, INS (for insert and overwrite '* modes), ESC, and ENTER. TAB is recognized only if the NextField '* flag is set. CTRL-key equivalents are recognized for most keys. '* A null string can be specified if no initial value is desired. '* You could modify this function to handle additional QB edit '* commands, such as CTRL+A (word back) and CTRL+F (word forward). '* '* Params: InString$ - The input string (can be null) '*        Length - Maximum length of string (the function beeps and '*           refuses additional keys if the user tries to enter more) '*        NextField - Flag indicating on entry whether to accept TAB '*          key; on exit, indicates whether the user pressed '*          TAB (TRUE) or ENTER (FALSE) '* '* Input: Keyboard '* Ouput: Screen - Noncontrol keys are echoed. '*        Speaker - beep if key is invalid or string is too long '* '* Return: The edited string '* FUNCTION EditString$ (InString$, Length, NextField) STATIC Insert

' Initialize variables and clear field to its maximum length. Work$ = InString$ Row = CSRLIN: Column = POS(0) FirstTime = TRUE P = LEN(Work$): MaxP = P   PRINT SPACE$(Length);

' Since Insert is STATIC, its value is maintained from one ' call to the next. Insert is 0 (FALSE) the first time the ' function is called. IF Insert THEN LOCATE Row, Column, CURSORON, 6, 7 ELSE LOCATE Row, Column, CURSORON, 0, 7 END IF

' Reverse video on entry. COLOR BACKGROUND, NORMAL PRINT Work$;

' Process keys until either TAB or ENTER is pressed. DO

' Get a key -- either a one-byte ASCII code or a two-byte ' extended code. DO           Choice$ = INKEY$ LOOP WHILE Choice$ = &quot;&quot;

'Translate two-byte extended codes to the one meaningful byte. IF LEN(Choice$) = 2 THEN Choice$ = RIGHT$(Choice$, 1) SELECT CASE Choice$

' Translate extended codes to ASCII control codes. CASE CHR$(LEFT) Choice$ = CHR$(CTRLS) CASE CHR$(RIGHT) Choice$ = CHR$(CTRLD) CASE CHR$(INS) Choice$ = CHR$(CTRLV) CASE CHR$(DEL) Choice$ = CHR$(CTRLG)

' Handle HOME and END keys, since they don't have ' control codes. Send NULL as a signal to ignore. CASE CHR$(HOME) P = 0 Choice$ = CHR$(NULL) CASE CHR$(ENDK) P = MaxP Choice$ = CHR$(NULL)

' Make other key choices invalid. CASE ELSE Choice$ = CHR$(1) END SELECT END IF

' Handle one-byte ASCII codes. SELECT CASE ASC(Choice$)

' If it is null, ignore it. CASE NULL

' Accept field (and card if NextField is used). CASE ENTER NextField = FALSE EXIT DO

' Accept the field unless NextField is used. If NextField ' is cleared, TAB is invalid. CASE TABKEY IF NextField THEN EXIT DO               ELSE BEEP END IF

' Restore the original string. CASE ESC Work$ = InString$ LOCATE Row, Column, CURSOROFF PRINT SPACE$(MaxP) EXIT DO

' CTRL+S or LEFT ARROW moves cursor to left. CASE CTRLS IF P > 0 THEN P = P - 1 LOCATE, P + Column ELSE BEEP END IF

' CTRL+D or RIGHT ARROW moves cursor to right. CASE CTRLD IF P < MaxP THEN P = P + 1 LOCATE, P + Column ELSE BEEP END IF

' CTRL+G or DEL deletes character under cursor. CASE CTRLG IF P < MaxP THEN Work$=LEFT$(Work$,P) + RIGHT$(Work$,MaxP-P-1) MaxP = MaxP - 1 ELSE BEEP END IF

' CTRL+H or BKSP deletes character to left of cursor. CASE CTRLH, 127 IF P > 0 THEN Work$=LEFT$(Work$, P-1) + RIGHT$(Work$, MaxP-P) P = P - 1 MaxP = MaxP - 1 END IF

' CTRL+V or INS toggles between insert & overwrite modes. CASE CTRLV Insert = NOT Insert IF Insert THEN LOCATE, , , 6, 7 ELSE LOCATE, , , 0, 7 END IF

' Echo ASCII characters to screen. CASE IS >= SPACE

' Clear the field if this is first keystroke, then ' start from the beginning. IF FirstTime THEN LOCATE, Column COLOR NORMAL, BACKGROUND PRINT SPACE$(MaxP); LOCATE, Column P = 0: MaxP = P                   Work$ = &quot;&quot; END IF

' If insert mode and cursor not beyond end, insert ' character. IF Insert THEN IF MaxP < Length THEN Work$=LEFT$(Work$,P)+Choice$+RIGHT$(Work$,MaxP-P) MaxP = MaxP + 1 P = P + 1 ELSE BEEP END IF

ELSE ' If overwrite mode and cursor at end (but                   ' not beyond), insert character. IF P = MaxP THEN IF MaxP < Length THEN Work$ = Work$ + Choice$ MaxP = MaxP + 1 P = P + 1 ELSE BEEP END IF

' If overwrite mode and before end, overwrite ' character. ELSE MID$(Work$, P + 1, 1) = Choice$ P = P + 1 END IF               END IF

' Consider other key choices invalid. CASE ELSE BEEP END SELECT

' Print the modified string. COLOR NORMAL, BACKGROUND LOCATE, Column, CURSOROFF PRINT Work$ + &quot; &quot;; LOCATE, Column + P, CURSORON FirstTime = FALSE

LOOP

' Print the final string and assign it to function name. COLOR NORMAL, BACKGROUND LOCATE Row, Column, CURSOROFF PRINT Work$; EditString$ = Work$ LOCATE Row, Column END FUNCTION

'* '* FindCard - Finds a specified record. The user specifies as many '* fields to search for as desired. The search begins at the card '* after the current card and proceeds until the specified record or '* the current card is reached. Specified records are retained between '* calls to make repeat searching easier. This SUB could be enhanced '* to find partial matches of string fields. '* '* Params: TopCard - number of top card '*        LastCard - number of last card '* '* Params: None '* '* Return: Number (zero-based) of the selected field '* FUNCTION FindCard% (TopCard%, LastCard%)

STATIC TmpCard AS PERSON, NotFirst

' Initialize string fields to null on the first call. (Note that   ' the variables TmpCard and NotFirst, declared STATIC above,    ' retain their values between subsequent calls.) IF NotFirst = FALSE THEN TmpCard.Names = &quot;&quot; TmpCard.Note = &quot;&quot; TmpCard.Phone = &quot;&quot; TmpCard.Street = &quot;&quot; TmpCard.City = &quot;&quot; TmpCard.State = &quot;&quot; TmpCard.Zip = &quot;&quot; NotFirst = TRUE END IF

' Show top card, then use EditCardFunction to specify fields ' for search. CALL ShowTopCard(TmpCard) CALL EditCard(TmpCard)

' Search until a match is found or all cards have been checked. Card = TopCard DO       Card = Card + 1 IF Card > LastCard THEN Card = 1 Found = 0

' Test name to see if it's a match. SELECT CASE RTRIM$(UCASE$(TmpCard.Names)) CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).Names)) Found = Found + 1 CASE ELSE END SELECT

' Test note text. SELECT CASE RTRIM$(UCASE$(TmpCard.Note)) CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).Note)) Found = Found + 1 CASE ELSE END SELECT

' Test month. SELECT CASE TmpCard.Month CASE 0, Index(Card).Month Found = Found + 1 CASE ELSE END SELECT

' Test day. SELECT CASE TmpCard.Day CASE 0, Index(Card).Day Found = Found + 1 CASE ELSE END SELECT

' Test year. SELECT CASE TmpCard.Year CASE 0, Index(Card).Year Found = Found + 1 CASE ELSE END SELECT

' Test phone number. SELECT CASE RTRIM$(UCASE$(TmpCard.Phone)) CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).Phone)) Found = Found + 1 CASE ELSE END SELECT

' Test street address. SELECT CASE RTRIM$(UCASE$(TmpCard.Street)) CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).Street)) Found = Found + 1 CASE ELSE END SELECT

' Test city. SELECT CASE RTRIM$(UCASE$(TmpCard.City)) CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).City)) Found = Found + 1 CASE ELSE END SELECT

' Test state. SELECT CASE RTRIM$(UCASE$(TmpCard.State)) CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).State)) Found = Found + 1 CASE ELSE END SELECT

' Test zip code. SELECT CASE TmpCard.Zip CASE &quot;&quot;, RTRIM$(UCASE$(Index(Card).Zip)) Found = Found + 1 CASE ELSE END SELECT

' If match is found, set function value and quit, else ' next card. IF Found = NFIELDS - 1 THEN FindCard% = Card EXIT FUNCTION END IF

LOOP UNTIL Card = TopCard

' Return FALSE when no match is found. FindCard% = FALSE END FUNCTION

'* '* InitIndex - Reads records from file and assigns each value to '* array records. Index values are set to the actual order of the '* records in the file. The order of records in the array may change '* because of sorting or additions, but the CardNum field always '* has the position in which the record actually occurs in the file. '* '* Params: LastCard - number of records in array '* '* Input: File DISKFILE$ '* SUB InitIndex (LastCard) STATIC DIM Card AS PERSON

FOR Record = 1 TO LastCard

' Read a record from the file and put each field in the array. GET #1, Record, Card Index(Record).CardNum = Record Index(Record).Names = Card.Names Index(Record).Note = Card.Note Index(Record).Month = Card.Month Index(Record).Day = Card.Day Index(Record).Year = Card.Year Index(Record).Phone = Card.Phone Index(Record).Street = Card.Street Index(Record).City = Card.City Index(Record).State = Card.State Index(Record).Zip = Card.Zip

NEXT Record END SUB

'* '* PrintLabel - Prints the name, address, city, state, and zip code '* from a card. This SUB could easily be modified to print a return '* address or center the address on an envelope. '* '* Params: Card - all the data about a person '* '* Output: Printer '* SUB PrintLabel (Card AS PERSON)

LPRINT Card.Names LPRINT Card.Street LPRINT Card.City; &quot;, &quot;; Card.State; Card.Zip LPRINT END SUB

'* '* Prompt$ - Prints a prompt at a specified location on the screen '* and (optionally) gets a user response. This function can take one '* of three different actions depending on the length parameter. '* '* Params: Msg$ - message or prompt (can be &quot;&quot; for no message) '*        Row '*        Column '*        Length - one of the following: '*          <1 - Don't wait for response '*           1 - Get character response '*          >1 - Get string response up to length '* '* Output: Keyboard '* Output: Screen - noncontrol characters echoed '* '* Return: String entered by user '* FUNCTION Prompt$ (Msg$, Row, Column, Length)

LOCATE Row, Column PRINT Msg$;

SELECT CASE Length CASE IS <= 0   ' No return Prompt$ = &quot;&quot; CASE 1         ' Character return LOCATE, , CURSORON Prompt$ = INPUT$(1) CASE ELSE      ' String return Prompt$ = EditString(&quot;&quot;, Length, FALSE) END SELECT END FUNCTION

'* '* SelectField - Enables a user to select a field using TAB key. '* TAB moves to the next field. ENTER selects the current field. '* '* Params: None '* '* Return: Number (zero-based) of the selected field '* FUNCTION SelectField%

' Get first cursor position and set first FieldNum. RESTORE FieldPositions READ Row, Column, Length FieldNum = 0

' Rotate cursor through fields. DO

' Set cursor on current field. LOCATE Row, Column, CURSORON

' Get a TAB or ENTER. DO           Ky = ASC(INPUT$(1)) LOOP UNTIL (Ky = ENTER) OR (Ky = TABKEY)

' If ENTER pressed, turn off cursor and return field. IF Ky = ENTER THEN

LOCATE, , CURSOROFF SelectField% = FieldNum EXIT FUNCTION

' Otherwise, it was TAB, so advance to next field. ELSE

FieldNum = FieldNum + 1 READ Row, Column, Length IF Row = 0 THEN RESTORE FieldPositions READ Row, Column, Length FieldNum = 0 END IF

END IF

LOOP END FUNCTION

'* '* ShowCards - Shows all the fields of the top card and the top '* field of the other visible cards. '* '* Params: TopCard - number of top card '*        LastCard - number of last card '* '* Output: Screen '* SUB ShowCards (TopCard, LastCard)

' Show each field of top card. CALL ShowTopCard(Index(TopCard))

' Show the Names field for other visible cards. Card = TopCard RESTORE FieldPositions READ Row, Column, Length FOR Count = 2 TO CARDSPERSCREEN

' Show location and card number for next highest card. Row = Row - 2: Column = Column + 3 Card = Card + 1 IF Card > LastCard THEN Card = 1

LOCATE Row, Column PRINT SPACE$(Length)

LOCATE Row, Column PRINT Index(Card).Names

NEXT Count END SUB

'* '* ShowCmdLine - Puts command line on screen with highlighted key '* characters. Modify this SUB if you add additional commands. '* '* Params: None '* '* Output: Screen '* SUB ShowCmdLine

LOCATE LASTROW, 1 COLOR HILITE, BACKGROUND: PRINT &quot; E&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;dit Top  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;A&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;dd New  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;C&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;opy to New  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;D&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;elete  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;F&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;ind  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;S&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;ort  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;P&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;rint  &quot;; COLOR HILITE, BACKGROUND: PRINT &quot;Q&quot;; COLOR NORMAL, BACKGROUND: PRINT &quot;uit &quot;; END SUB

'* '* ShowEditHelp - Reads colors and strings for edit-mode help and '* puts them on screen. '* '* Params: None '* '* Output: Screen '* SUB ShowEditHelp

' Clear old help and display new. ClearHelp RESTORE EditHelp FOR Row = HELPTOP TO HELPBOT READ Clr IF Clr = CNORMAL THEN COLOR NORMAL, BACKGROUND ELSE COLOR HILITE, BACKGROUND END IF       LOCATE Row, HELPLEFT READ Tmp$ PRINT Tmp$; NEXT

' Restore normal color. COLOR NORMAL, BACKGROUND END SUB

'* '* ShowTopCard - Shows all the fields of the top card. '* '* Params: WorkCard - record to be displayed as top card '* '* Output: Screen '* SUB ShowTopCard (WorkCard AS PERSON)

' Display each field of current card. RESTORE FieldPositions READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT WorkCard.Names;

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT WorkCard.Note;

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT USING &quot;##_/&quot;; WorkCard.Month; WorkCard.Day; PRINT USING &quot;##&quot;; WorkCard.Year; READ Row, Column, Length, Row, Column, Length

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT WorkCard.Phone;

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT WorkCard.Street;

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT WorkCard.City;

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length); LOCATE Row, Column PRINT WorkCard.State;

READ Row, Column, Length LOCATE Row, Column PRINT SPACE$(Length) LOCATE Row, Column PRINT WorkCard.Zip; END SUB

'* '* ShowViewHelp - Reads colors and strings for view-mode help and '* puts them on screen. '* '* Params: None '* '* Output: Screen '* SUB ShowViewHelp

' Clear old help and display new. ClearHelp RESTORE ViewHelp FOR Row = HELPTOP TO HELPBOT READ Clr IF Clr = CNORMAL THEN COLOR NORMAL, BACKGROUND ELSE COLOR HILITE, BACKGROUND END IF       LOCATE Row, HELPLEFT READ Tmp$ PRINT Tmp$; NEXT

' Restore color and show command line. COLOR NORMAL, BACKGROUND ShowCmdLine END SUB

'* '* SortIndex - Sorts all records in memory according to a specified '* field. After the sort, the first record in memory becomes the top '* card. Note that although the order is changed in memory, the order '* remains the same in the file. The true file order is shown by the '* CardNum field of each record. This SUB uses the Shell sort '* algorithm. '* '* Params: SortField - 0-based number of the field to sort on '*        LastCard - number of last card '* SUB SortIndex (SortField, LastCard)

' Set comparison offset to half the number of records. Offset = LastCard \ 2

' Loop until offset gets to zero. DO WHILE Offset > 0

Limit = LastCard - Offset

DO

' Assume no switches at this offset. Switch = FALSE

' Compare elements for the specified field and switch ' any that are out of order. FOR i = 1 TO Limit SELECT CASE SortField CASE NPERSON IF Index(i).Names > Index(i+Offset).Names THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NNOTE IF Index(i).Note > Index(i + Offset).Note THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NMONTH IF Index(i).Month > Index(i+Offset).Month THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NDAY IF Index(i).Day > Index(i + Offset).Day THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NYEAR IF Index(i).Year > Index(i + Offset).Year THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NPHONE IF Index(i).Phone > Index(i+Offset).Phone THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NSTREET IF Index(i).Street>Index(i+Offset).Street THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NCITY IF Index(i).City > Index(i + Offset).City THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NSTATE IF Index(i).State > Index(i+Offset).State THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                    CASE NZIP IF Index(i).Zip > Index(i + Offset).Zip THEN SWAP Index(i), Index(i + Offset) Switch = i                       END IF                END SELECT

NEXT i

' Sort on next pass only to location where last switch ' was made. Limit = Switch

LOOP WHILE Switch

' No switches at last offset. Try an offset half as big. Offset = Offset \ 2 LOOP END SUB