ษออออออออออออออออออออออออออออออออออออป บ OPTION.BAS - LINE MENUS FOR QBASIC บ ศออออออออออออออออออออออออออออออออออออผ Freeware by John Moll M & B Software 404-962-9588 ********************************************************************** If you find this routine useful, don't send me money. Send $5.00 to the Sysop of the BBS you downloaded this from. It's not that I don't need the money, but I use their boards (obviously, you do too!), and I'd rather see them encouraged. My company writes commercial CAD software for the Engineer/Surveyor, and that's where we make our money. If you have questions, or would like enhancements, you may call me at the number listed above. I make no claims as to the suitability or integrity of this software, except that it works like a charm for me. Feel free to copy it, share it, or change it, but if you change it, attach your own text file, and delete this one. This is written for QBasic, but can be easily modified for GWBasic by adding line numbers, changing the loop controls, and the subroutine call. If you need to do that, and are lost, call me. ********************************************************************** WHAT DOES THIS THING DO? ********************************************************************** The program included, OPTION.BAS, is a simple example of how the subroutine, GETOPT , is used. When you run it, a menu line comes up at your present cursor position, with user options contained within brackets. By using the left and right arrow keys, the user can highlight the various options. If the user hits the ENTER key, he will select the highlighted option. If he hits the ESC key, he will abort the selection process. If he tries to move outside of the brackets, he will hear a beep. Here is what you will see if you run the example program: First, you will see the line menu prompt: Choose: [YES] [NO] [MAYBE] The YES option will be highlighted in blue (unless you have modified my default color selections). If you press ENTER with this choice highlighted, the program will print, "YES" and exit. If you use the right or left arrow keys, you will be able to highlight the other choices. When you pick one, it will be printed, and the program will exit. When the choices are highlighted, if you press the ESC key, "ABORT" will be printed. Attempting to move left from the YES selection (that would be "off" the menu) will result in a beep. Ditto for moving right from the MAYBE selection. Before we look at how the sample program logic works, and at the full specifications for the GETOPT call, let's look at how you might use this utility. Page 1 ********************************************************************** USING GETOPT ********************************************************************** Getting user decisions in BASIC is often a chore, and often we resort to having the user type one letter or number, to indicate some choice. This type of choice might look like this: (E)xit to DOS (A)dd a record (M)odify a record (D)elete a record (S)ort records (L)ast menu Enter your choice (E,A,M,D,S, or L): You would scan the keyboard input, test the entry, etc., and then branch off to the proper place in the program, based on the user choice. GETOPT is much easier. All you do is call the subroutine, and pass it a string containing choices in brackets. The routine returns the number of the choice picked, and you can immediately branch off to the proper logic. Here is what the user would see: Record options: [EXIT] [ADD] [MODIFY] [DELETE] [SORT] [LAST MENU] The EXIT option would be highlighted, and the user would use the arrow keys to move to the selection he wanted. Once it was highlighted, he would hit the ENTER key to select. Here's how your code might look: A$ = "Record options: [EXIT] [ADD] [MODIFY] [DELETE] [SORT] [LAST MENU]" IDEC = 0 CALL GETOPT(A$,IDEC,IABORT) IF (IABORT = -1) GOTO ABORTED ON IDEC GOTO 1000, 200, 300, 400, 500, 600, 700 You simply pass a string with choices in brackets, and the subroutine returns the users choice as an integer in IDEC. If the user pressed the ESC key to abort, you can test for that first. I use the abort option to quickly "back up" through submenus. If the user were three menus "deep" into a process, and realized he was making a mistake, he could just tap the ESC key three times, to return to the "top" menu. Now that we have covered how you would use this thing, we will go over the logic of the subroutine, in detail, to let you see how it works, and how to make changes you may need. Page 2 ********************************************************************** SUBROUTINE GETOPT(DISPLAY$, IDEC, IABORT) ********************************************************************** We're going to assume you know a bit about QBasic programming, but we will try to include enough information so that a novice will be able to use this routine. First of all, you will want this statement at the top of your main routine, if you plan to use GETOPT in one of your programs: DECLARE SUB GETOPT (DECIDE$, IDEC%, IABORT%) Notice that it is the first statement in the OPTION.BAS program we provided as an example. Now, on to the subroutine itself. Here are the first few lines: SUB GETOPT (DECIDE$, IDEC, IABORT) ' DIM IDStart(25), IDEnd(25) DIM DSTRNG$(25) ' RightB$ = "]" LeftB$ = "[" DEFforcol = 7 HIforcol = 7 DEFbackcol = 0 HIbackcol = 1 The first line is the statement that identifies this as a subroutine. Below that, we dimension three variables as having 25 members. This allows you to have up to 25 choices on any menu, if you have the room across your screen. Something like this: Font: [1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20] As you can see, with a 80 column screen, 25 choices is a bunch, but if you need more, change the 25 in each of these dimension statements. The three variables dimensioned here are: IDStart(i) - The start position for the text within bracket number "i" IDEnd(i) - The end position for the text within bracket number "i" DSTRNG$(i) - The actual text between the brackets for choice "i" The start and end positions are used to "build" the text string. The text itself is stored so that we will know what to print when we want to "highlight" or "un-highlight" one of the choices. The next few lines define some variables. They are: RightB$ - This is what we look for to end a choice. LeftB$ - This is what we look for to start a choice. DEFforcol - This is the default foreground color for the menu. We are using white (7), and you can change this here if you want. DEFbackcol - The default background color, black (0). Changable. HIforcol - Highlighted foreground color, white (7). Changable. HIbackcol - Highlighted background color, blue (1). Changable. Page 3 Now, we need to save the current cursor position, so that we will know where to "offset" from, when locating our highlighted text. This is done with: ' 'Save cursor position ' ICcol = POS(0) ICrow = CSRLIN LOCATE ICrow, ICcol The current column is saved to ICcol, and the row to ICrow. If you want to do some checking here, you can, to be sure the cursor in positioned so that your whole menu string will fit on the screen. I like to take care of that in the main program, before I call this routine. The LOCATE call here is not needed, but is included, so that you can remember to add the other options, if you want to. Basically, you can add ",0" to the end of this call to turn the cursor off (it is normally off anyway), or a ",1" to turn it on. There are more options that can follow, if you want to change the shape of the cursor. Also, screens can be changed here, if you are using that technique, and the LOCATE call will be sure the cursor in where you want it. Next comes the fun part, where we parse the choice string, to get all the choice text, and the positions where they start and stop. Here is the code: ' ' Parse decision string ' IDECNO = 1 ILEN = LEN(DECIDE$) ISTART = 1 DO WHILE ISTART < ILEN IDStart(IDECNO) = INSTR(ISTART, DECIDE$, LeftB$) + 1 IF (IDStart(IDECNO)) = 0 GOTO ABORT ISTART = IDStart(IDECNO) - 1 IDEnd(IDECNO) = INSTR(ISTART, DECIDE$, RightB$) - 1 IF (IDEnd(IDECNO)) = 0 GOTO ABORT ISTART = IDEnd(IDECNO) + 1 IDLEN = IDEnd(IDECNO) - IDStart(IDECNO) + 1 DSTRNG$(IDECNO) = MID$(DECIDE$, IDStart(IDECNO), IDLEN) IDECNO = IDECNO + 1 LOOP IDECNO = IDECNO - 1 IF (IDECNO < 2) GOTO ABORT First, we set IDECNO (decision or choice number) to 1, so that we are working on the first choice position and text. Now, we set ILEN equal to the length of the string that contains all of the choices (DECIDE$). This is the string that was passed to the subroutine. Next, we set the start position for our search (ISTART) to 1. This sets us up to loop through DECIDE$, looking for brackets and text. We use a DO WHILE statement, to stay in the loop, as long as ISTART is less than the length of the entire string. Obviously, when our search position (ISTART) is past the end of the string, we are done. Page 4 This is what is happening in the loop. We use INSTR to find the first occurance of a left bracket, assigning it to IDStart(IDECNO). Actually, we add one to the left bracket position, since that is where the text itself starts. Since this is the first time through the loop, IDECNO is 1. The next line tests for a value of 0 in the first left bracket position, meaning that no left bracket was found anywhere in the string. If this happens, we return an abort. Since you, the programmer will be passing the menu strings, this should never happen, but this test will stop runtime errors if you somehow err. Now that we know where the first bracket is found, we set our search position, ISTART, back to where we found the left bracket, and we start looking for a right bracket. Again, we use INSTR, and set the text end position, IDEnd(IDECNO), equal to the bracket position, minus one space, which is where the text actually ends. We test for no bracket found for the first time through, and then set ISTART, our current search position, to where we actually found the bracket. During this first time through the loop, we have found a set of brackets, and set the first set of text pointers, IDStart(1) and IDEnd(1) to the beginning and end of the text between those brackets. We have left ISTART where we found the closing right bracket. Now, to finish up, we get the length of the text between the brackets (IDLEN), by sub- tracting the start position from the end position, and adding one to the total. Finally, we extract the text for this choice from the menu string (DECIDE$), by using the MID$ function. We start at IDStart(1), and read IDLEN characters. This substring is assigned to DSTRNG$(IDECNO), with IDECNO being 1 this first time through the loop. Before returning to the top of the loop, we increment IDECNO by +1. This process will continue, until we find the final right bracket in the string. One important thing to remember is to make the last character a right bracket. Anything else as a last character will keep us in the loop forever, since ISTART will never be equal to, or greater than the length of the DECIDE$. When the last character is a right bracket, we find it, and set ISTART equal to that position, plus one. At this point, the WHILE clause (WHILE ISTART < ILEN) is satisfied, and we fall through the bottom of the loop. Since we incremented IDENCO inside the loop, it is now one bigger than it should be, so we immediately subtract one from it. At this point, we test for at least two choices, since a single choice would have no meaning, and abort if only one choice was found. Perhaps you could change this, if your users are the type that require a "menu" with only one choice, in order to maintain some semblence of order. WHEW! The string is parsed, and we know where all the individual text choices start and end. This will be important, since we want to be able to highlight and un-highlight each choice, as the user uses the arrow keys to select a choice, and eventually presses the [ENTER] key to indicate his selection. The next section in the main part of the routine. Here, we will be polling the keyboard, looking for certain keys, and managing the display, highlighting and un-highlighting choices. One thing we will be doing, is keeping up with where the user currently is in the menu. We also need to know where he was. We will highlight the text where he is, and just as importantly, un-highlight the text where he just left from. Page 5 The first section of code, sets his starting position to choice 1, and sets his last position to 0, which is a special case for the first time through. It also sets the color to the default foreground and default background colors, and prints the menu string on the screen. Here is that code: ' ' String is parsed, with pointers to each bracket group ' Now highlight first selection, and poll keyboard ' ILASTPOS = 0 ISELPOS = 1 COLOR DEFforcol, DEFbackcol PRINT DECIDE$; This is pretty self-explanatory, with the current position, ISELPOS, set to 1, and the last position, ILASTPOS, set to 0. Now we go into the highlight/lowlight section, which alters the colors of part of the menu string. Here is that code: HILIT: COLOR HIforcol, HIbackcol LOCATE ICrow, IDStart(ISELPOS) PRINT DSTRNG$(ISELPOS); IF (ILASTPOS = 0) GOTO LOOKEY LOLIT: COLOR DEFforcol, DEFbackcol LOCATE ICrow, IDStart(ILASTPOS) PRINT DSTRNG$(ILASTPOS) Note that we will be jumping back to the HILITE: label after every "arrow" keypress. First, we set the color to the highlight fore- ground color, and the highlight background color, and move the cursor. The cursor is placed on the current row (remember that we saved it early on), and at the start of the text (IDStart(ISELPOS)) for the current menu position. Now, we simply print the contents of the text for this position (DSTRNG$(ISELPOS)), in the "HI" colors, remembering to add a semi-colon to the end of the print statement, to supress the automatic carriage return that BASIC likes to add to PRINT statements. Now, we do a test. If this is the first time through, ILASTPOS will be 0. Any other time, it will have a value. So, the first time through, we don't have to "lowlight" anything, since only the current position has been highlighted. If this were a later pass through, we would first highlight the current position, and then would set the color to the default (Lowlight) colors, and print the formerly highlighted text at the last position, to avoid having two positions highlighted at once. This cycle of highlighting the current, and lowlighting the last, will continue until the user hits [ENTER], or aborts the process. Page 6 Now, we check the keyboard for a legal keypress with this code: LOOKEY: KEYB$ = INKEY$ IF (KEYB$ = "") GOTO LOOKEY IF (KEYB$ = CHR$(27)) GOTO ABORT IF (KEYB$ = CHR$(13)) GOTO DONE IF (KEYB$ = CHR$(0) + CHR$(75)) GOTO BACKAR IF (KEYB$ = CHR$(0) + CHR$(77)) GOTO FORAR GOTO LOOKEY First we set KEYB$ to the last keypress, using the INKEY$ function. If no key was pressed, the next line returns us immediately to the label, LOOKEY:, and we wait for a keypress. Once a key is pressed, we pass the first test, and then check for ESC, ENTER, Left Arrow, and Right Arrow, respectively. CHR$(27) is the escape key. If you are not familiar with key codes and extended key codes, you need to get familiar with them. For effective programming, you must be able to use the entire keyboard, and be prepared for anything the user may decide to press. If they press ESC, we jump to label ABORT:. ENTER (CHR$(13)), will take us to DONE:. Left Arrow is an extended code, which returns a CHR$(0) and a CHR$(75), which will take us to BACKAR:. Finally, Right Arrow, (CHR$(0) and CHR$(77)), will take us to FORAR:. We'll deal with the arrow presses first. Left Arrow takes us to the BACKAR: label. Here is that code: BACKAR: IF (ISELPOS = 1) THEN BEEP: GOTO LOOKEY ILASTPOS = ISELPOS ISELPOS = ISELPOS - 1 GOTO HILIT The first thing we do, is to check if we are already at position 1. If we are, we can go no further left, so we beep at the user, and go back to get another keypress. If we are anywhere else, we can move to the left, so we set ILASTPOS to where we are now (ISELPOS), and decrement our current position in the menu by one. This set us up for a jump to HILIT, where we highlight the new postion, followed by LOLIT, where we "lowlight" the last postion, and then down to LOOKEY, where we wait for another keypress. Right Arrow works the same way, except that we check to see if we are already at the last (rightmost) position, and that we increment the current position by one. Here is the code without further comment: FORAR: IF (ISELPOS = IDECNO) THEN BEEP: GOTO LOOKEY ILASTPOS = ISELPOS ISELPOS = ISELPOS + 1 GOTO HILIT As long as arrow keys are pressed, this senerio will repeat. The user is trapped inside the menu, with only two ways out. One is to pick a position, by pressing ENTER while it is highlighted, and the other is to give up, and press the ESC key to abort. Either one will end the subroutine. Page 7 If the user presses the ENTER key, he will jump to the DONE: label. Here is the code: DONE: IDEC = ISELPOS IABORT = 0 COLOR DEFforcol, DEFbackcol LOCATE ICrow, ICcol PRINT DECIDE$; LOCATE ICrow + 1, ICcol GOTO BYE First, we set IDEC, which is one of the values returned by this subroutine, to ISELPOS, which the current highlighted menu position. Next, we set IABORT, the other value returned, to 0, indicating a successful return with a valid result in IDEC. Now, we return the color values to the default foreground and background colors, put the cursor back where we started, and print the menu string in default colors at its original position. This avoids leaving a highlighted string on the screen, which appears active. Finally, we put the cursor on the next row, and jump to BYE: If the user doesn't like anything on the menu (indicating a LIM), he press ESC, which takes him to the ABORT: label. For those of you who don't know, a LIM is a (L)iveware (I)nterface (M)alfunction, or, as more commonly expressed, a human error. The ABORT: label is also used for a IPM (Idiotic Programmer Malfunction), as you may recall from some of the code above. No matter how you get here, this is the code: ABORT: IDEC = 0 IABORT = -1 COLOR DEFforcol, DEFbackcol LOCATE ICrow, ICcol PRINT DECIDE$; LOCATE ICrow + 1, ICcol GOTO BYE First, we return a menu selection of 0, indicating nothing was picked. Next, we set IABORT to -1, indicating a problem. This is the logical value for "TRUE" in basic. Next, we return colors to defaults, and print the menu again, just as in the normal exit. The cursor is set to the next column, and we jump to BYE:. Note that this code would fall through to BYE: anyway, but you will probably want to add other keys, or error trapping down here in production code, so we defined an exit from this abort procedure code. Well, that's it. This routine will make program control a snap for you, and make things look much better, too! Just pass a menu string, and jump to your destination with ON IDEC. Hope you enjoy it. John Moll M & B Software