PULL DOWN MENUS ZX Computing, February 1987 Toni Baker shows how to give your programs that "sate of the art" look, with this routine for creating pull-down menus. This article is all about pull-down menus. Specifically, about how you can use them in BASIC (with the aid of a morsel of machine code of course). Before I start, let's begin with some definitions. A menu is something you normally get in restaurants, which lists the different choices of food available to you. Computers, however, can't eat anything except chips. For this reason computer menus are allowed to list not only food, but in fact anything that it's possible to list at all. When a menu appears on the screen, what you see is a list of choices. Usually this list is headed with a title. The computer will then wait until you're ready to order. You make your selection and then, once the computer has carried out whatever tasks it's been told to, the menu will disappear from the screen. Whatever was underneath the menu will reappear and the screen will be as it was before the menu appeared. Nests Sometimes you see menus happening in nests. When this happens lots of birds start chirping for worms and things. On the screen you may witness one menu or information panel appearing on top of a previous menu. Furthermore, you may then get even more menus layering themselves on top of these. When all tasks are complete you will see the menus disappearing, precisely in reverse order - each time leaving the screen exactly as it was before the particular menu appeared. When the final (ie. first) menu disappears the screen is as it was to begin with. This suggests some sort of STACK, because the first menu in becomes the last menu out. We are familiar with at least three stacks already - the machine stack, the calculator stack, and the GOSUB stack. If we wish to create yet another kind of stack still, we must first of all decide where to put it. What I propose to do in this article is to provide a comprehensive machine code program with access points to make most of its functions available to BASIC. If this is done then it means that nested menus and information panels may be created and recalled using only simple BASIC statements. Essentially the menu idea is a very simple one. PUSHing a menu consists of (1) storing the contents of the screen portion which lies underneath the proposed menu position; (2) printing a list, with a title and a pretty border, at the designated position; and (3) providing some means of allowing the user to choose one of the options. Alternatively we may wish to use step (1) only or steps (1) and (2) only, to allow for a variety of types of information panel, as well as just the standard kind of menu. The reverse process, that of POPping a menu, has only one step; the contents of the screen underneath the menu must be restored. This will cause the menu or information panel to disappear from the screen. Obscure RAM For this program, I have decided to use a little-known and seldom-used region of RAM in which to store the stack of panel information. It is possible to store information BETWEEN the BASIC program and the variables area. This region has the following advantages: (1) It is impossible for the stack to grow too big - error 4, Out of memory will occur if too much memory is asked for; (2) it will not overwrite any machine code (or in fact anything); and (3) the information will not be erased by the ROM between BASIC statements (as the workspace would be). My new stack grows upwards. Figure one [see PULLDOWN.GIF] shows a single stack entry diagrammatically. Note that each stack entry may be of variable length. As you can see from the diagram the print position and attribute colours are also stacked, so that when the menu disappears printing may continue as normal. The first two bytes of information store the address on the screen of the current print position. Note that this address is stored HIGH BYTE FIRST. This is unusual in Spectrum machine code, but is done for a good reason. Remember that to the left (in the diagram) of the panel information is the BASIC program itself. The RUN and LIST commands must know where to stop - it would not do if the panel information somehow got confused with program. The first byte beyond the BASIC program must be in the range 40 to FF, so as to distinguish it from a program line. Putting the high byte first ensures this. Notice also that the very last byte of panel information has bit 7 set. This too is for a reason. The machine code program must be able to tell whether or not the panel stack is empty. If, directly below the variables area, it finds a byte with bit 7 set, then the stack is non-empty. Note that if the stack WERE in fact empty then the byte directly below the variables area would be 0D (enter) which of course has bit seven reset. Once the principle of the panel information stack is understood, the rest of the algorithm is boringly straightforward. There's a lot of it, but this is because there is lots of work to do - not because of any untoward complexity. --- Figure 2. New commands for manipulating menus To clear menu stack entirely: RANDOMIZE USR MCLEAR To create information panel: RANDOMIZE USR PANEL: PRINT .[control;]. height, width To create menu panel (without choice): RANDOMIZE USR MPANEL: PRINT .[control;]. string.|, string.| To create menu panel and allow choice of options: LET variable=USR MENU: PRINT .[control;]. string.|, string.| To undo a menu or information panel: RANDOMIZE USR MENUOFF or: LET variable=USR MENUOFF (The latter case will assign the variable with 0 if the menu stack is now empty, or with 1 otherwise.) "control" may be either: AT y-coordinate,x-coordinate PAPER colour INK colour BRIGHT status FLASH status .[...]. means "optional, but may be repeated" .|...|. means "at least one must be included, more repeats are optional" --- Back to BASIC Let's look at the BASIC now. How does it operate? Figure two lists the five new statements which are allowed. Note that the middle three have rather complicated syntaxes. To begin with each of the three statements is in two parts, separated by a colon. The second part always begins "PRINT" - but this is not a PRINT statement - it just looks like one. You may follow the word PRINT by an optional number of controls, which may specify the colour of the menu (eg. PAPER 6); or the position of the menu (eg. AT 8,8;). In the case of USR PANEL you then have to specify the size of the panel - first the height, then the width, separated by a comma. In the remaining two cases (USR MPANEL and USR MENU) you have to supply a list of strings. The first one is the title, and the rest are the choices available on the menu. You don't have to specify the menu size once the strings are listed. The machine code will work that out for itself (ie. height = number of strings + 1; width = maximum length of string + 2). Although the syntax looks complicated at first glance, in fact it's remarkably easy once you're used to it. Figure Three is an example BASIC program. It doesn't do very much, apart from show off the menus. Each feature is demonstrated at least once. Try it - it's very interesting. You can use the BASIC statements in your own programs. As a guide - you can CREATE a menu with the LET X = USR MENU: statement, followed by "PRINT" and a list of strings. You don't have to use X of course - any variable will do. From then on, in the BASIC program, this menu will be on the screen, and X will be assigned with 1 if the first item on the list was chosen, 2 if the second item on the list was chosen, and so on. To remove this menu from the screen you should use RANDOMIZE USR MENUOFF. Alternatively, it may be the case that X has been assigned with zero - if this is so it means that the menu has been abandoned, and has already been removed from the screen. From a user's point of view, when you are confronted with a menu you must manipulate the bar cursor to the appropriate choice using the UP and DOWN cursor keys, and then press ENTER when the cursor is in the right place. Alternatively, to abandon the menu altogether, just press DELETE. Well, that's all from me for this article. All this talk about menus has made me hungry. Excuse me while I make myself a microfych supper. #8000 90909090 MENU_UDGS DEFB 90909090 #8004 90909090 DEFB 90909090 ;Definition for graphic-A #8008 09090909 DEFB 09090909 #800C 09090909 DEFB 09090909 ;Definition for graphic-B #8010 90909090 DEFB 90909090 #8014 9F8080FF DEFB 9F8080FF ;Definition for graphic-C #8018 09090909 DEFB 09090909 #801C F90101FF DEFB F90101FF ;Definition for graphic-D #8020 00000000 DEFB 00000000 #8024 FF0000FF DEFB FF0000FF ;Definition for graphic-E #8028 0000 PANEL_LEN DEFW 0000 #802A 0000 PANEL_ADDR DEFW 0000 #802C 00 PANEL_TYPE DEFB 00 #802D 00 STRING_COUNTDEFB 00 #802E 0000 MENU_AT DEFW 0000 ;System variables used by program #8030 7C ATTR_ADDR LD A,H ;A:= high part of screen address #8031 E638 AND #38 ;Identify screen third #8033 1F RRA #8034 1F RRA #8035 1F RRA ;A:= screen third number #8036 F658 OR #58 #8038 67 LD H,A ;HL:= corresponding attribute addr #8039 C9 RET #803A F5 DOWN_1 PUSH AF #803B 24 INC H ;Presume within character square #803C 7C LD A,H #803D E607 AND #07 #803F 200A JR NZ,DOWN_1_EXIT ;Jump if this is the case #8041 7D LD A,L #8042 C620 ADD A,#20 #8044 6F LD L,A ;Presume crossing screen thirds #8045 3804 JR C,DOWN_1_EXIT;Jump if this is the case #8047 7C LD A,H #8048 D608 SUB #08 #804A 67 LD H,A ;Must be within screen third #804B F1 DOWN_1_EXIT POP AF #804C C9 RET ;Return #804D 2A4B5C UNSTK_PANEL LD HL,(VARS);HL: points to variables area #8050 2B DEC HL ;HL: points to top of panel stack #8051 CB7E BIT 7,(HL) #8053 CBBE RES 7,(HL) #8055 C8 RET Z ;Return if panel stack empty #8056 46 LD B,(HL) #8057 2B DEC HL #8058 4E LD C,(HL) ;BC:= length of panel information #8059 C5 PUSH BC ;Stack length of panel information #805A 23 INC HL #805B 23 INC HL ;HL: points to variables area #805C A7 AND A #805D ED42 SBC HL,BC ;HL: points to panel information #805F E5 PUSH HL ;Stack this address #8060 56 LD D,(HL) #8061 23 INC HL #8062 5E LD E,(HL) ;DE:= previous print position #8063 23 INC HL #8064 ED53845C LD (DF_CC),DE ;Restore previous print position #8068 5E LD E,(HL) #8069 23 INC HL #806A 56 LD D,(HL) ;DE:= previous print coordinates #806B 23 INC HL #806C ED53885C LD (S_POSN),DE;Restore previous print coordinates #8070 5E LD E,(HL) ;E:= previous attribute byte #8071 23 INC HL #8072 56 LD D,(HL) ;D:= previous attribute mask #8073 23 INC HL #8074 ED538D5C LD (ATTR_P),DE;Restore previous attribute colours #8078 ED538F5C LD (ATTR_T),DE;and transparent mask #807C 7E LD A,(HL) ;A:= previous INVERSE/OVER status #807D 23 INC HL #807E 32915C LD (P_FLAG),A ;Restore INVERSE/OVER status #8081 5E LD E,(HL) #8082 23 INC HL #8083 56 LD D,(HL) ;DE:= addr within screen of panel #8084 23 INC HL #8085 4E LD C,(HL) ;C:= width of panel in squares #8086 23 INC HL #8087 0600 LD B,#00 ;BC:= width of panel in squares #8089 7E LD A,(HL) ;A:= height of panel in squares #808A 23 INC HL ;HL: points to prev data from scr #808B F5 PUSH AF ;Stack height of panel in squares #808C 87 ADD A,A #808D 87 ADD A,A #808E 87 ADD A,A ;A:= height of panel in pixels #808F D5 PUSH DE ;Stack panel address within screen #8090 D5 U_P_LOOP_1 PUSH DE ;Stack addr of pixel row to restore #8091 C5 PUSH BC ;Stack width of panel #8092 EDB0 LDIR ;Restore one pixel row of panel #8094 C1 POP BC ;BC:= width of panel #8095 D1 POP DE ;DE:= address of row just restored #8096 EB EX DE,HL #8097 CD3A80 CALL DOWN_1 #809A EB EX DE,HL ;DE:= addr of next row to restore #809B 3D DEC A #809C 20F2 JR NZ,U_P_LOOP_1;Restore all pixels to panel #809E D1 POP DE ;DE:= address of first row #809F EB EX DE,HL #80A0 CD3080 CALL ATTR_ADDR #80A3 EB EX DE,HL ;DE:= addr of first attribute row #80A4 F1 POP AF ;A:= height of panel in squares #80A5 C5 U_P_LOOP_2 PUSH BC ;Stack width of panel #80A6 D5 PUSH DE ;Stack address of attribute row #80A7 EDB0 LDIR ;Restore next attribute row #80A9 E3 EX (SP),HL ;HL:= addr of attr row restored #80AA 012000 LD BC,#0020 #80AD 09 ADD HL,BC ;HL:= address of next attribute row #80AE EB EX DE,HL ;DE:= address of next attribute row #80AF E1 POP HL ;HL: points into panel information #80B0 C1 POP BC ;BC:= width of panel #80B1 3D DEC A #80B2 20F1 JR NZ,U_P_LOOP_2;Restore all attributes to panel #80B4 E1 POP HL ;HL: points to start of panel info #80B5 C1 POP BC ;BC:= length of panel information #80B6 CDE819 CALL RECLAIM-2;Reclaim memory used by panel info #80B9 224B5C LD (VARS),HL;Restore variables-area pointer #80BC F6FF OR #FF ;Reset the zero flag #80BE C9 RET ;Return #80BF CD4D80 MENU_CLEAR CALL UNSTK_PANEL;Unstack topmost panel if exists #80C2 20FB JR NZ,MENU_CLEAR;Try again if there may be more #80C4 C36B0D JP CLS ;Clear the screen and return #80C7 F5 NEW_PANEL PUSH AF ;Stack required attribute colours #80C8 78 LD A,B ;A:= required height of panel #80C9 82 ADD A,D ;A:= maximum line number of panel #80CA 380C JR C,NP_ERROR ;Jump if greater than 255d #80CC FE19 CP #19 #80CE 3008 JR NC,NP_ERROR;Jump if height is off screen #80D0 79 LD A,C ;A:= required width of panel #80D1 83 ADD A,E ;A:= maximum column number of panel #80D2 3804 JR C,NP_ERROR ;Jump if greater than 255d #80D4 FE21 CP #21 #80D6 3802 JR C,#80DA ;Jump unless width is off screen #80D8 CF NP_ERROR RST #08 ;Report 5 - Out of screen #80D9 04 DEFB 04 #80DA C5 NEW_PANEL_2 PUSH BC ;Stack size of intended panel #80DB D5 PUSH DE ;Stack position of intended panel #80DC 110900 LD DE,#0009 ;DE:= bytes needed per chr scr #80DF 62 LD H,D #80E0 68 LD L,B ;HL:= height of intended panel #80E1 CDF82A CALL MULT-HL*DE ;HL:= HL * DE #80E4 59 LD E,C ;DE:= width of intended panel #80E5 CDF82A CALL MULT-HL*DE ;HL:= number of bytes needed for ;screen information for whole panel #80E8 1E0D LD E,#0D #80EA 19 ADD HL,DE ;HL:= length of required panel info #80EB 222880 LD (PANEL_LEN),HL ;Store this length #80EE 44 LD B,H #80EF 4D LD C,L ;BC:= length of required panel info #80F0 2A535C LD HL,(PROG);HL points to start of program area #80F3 E5 PUSH HL ;Stack this address #80F4 2A4B5C LD HL,(VARS);HL points to start of variables #80F7 222A80 LD (#802A),HL ;Store this as addr of panel info #80FA 2B DEC HL ;HL: points to top of panel stack #80FB C5 PUSH BC #80FC CD5516 CALL MAKE_ROOM;Make room for panel information #80FF C1 POP BC #8100 EB EX DE,HL ;HL: points to penultimate byte ;of new room #8101 71 LD (HL),C #8102 23 INC HL #8103 70 LD (HL),B ;Store length of panel info #8104 CBFE SET 7,(HL) ;Set bit 7 to "stack not empty" #8106 E1 POP HL ;HL:= address of program area #8107 22535C LD (PROG),HL;Restore system variable #810A 2A2A80 LD HL,(PANEL_ADDR) ;HL:= address of new room #810D ED5B845C LD DE,(DF_CC) ;DE:= current print position #8111 72 LD (HL),D #8112 23 INC HL #8113 73 LD (HL),E ;Store in panel information #8114 23 INC HL #8115 ED5B885C LD DE,(S_POSN);DE:= current print coordinates #8119 73 LD (HL),E #811A 23 INC HL #811B 72 LD (HL),D ;Store in panel information #811C 23 INC HL #811D ED5B8D5C LD DE,(ATTR_P);DE:= current attribute byte & mask #8121 73 LD (HL),E #8122 23 INC HL #8123 72 LD (HL),D ;Store in panel information #8124 23 INC HL #8125 3A915C LD A,(P_FLAG) ;A:= current INVERSE/OVER status #8128 77 LD (HL),A ;Store in panel information #8129 23 INC HL #812A 3E02 LD A,#02 #812C E5 PUSH HL #812D CD0116 CALL CHAN-OPEN;Select stream 2 (the screen) #8130 E1 POP HL #8131 C1 POP BC ;BC:= position of panel #8132 E5 PUSH HL ;Stack pointer into panel info #8133 CD9B0A CALL AT_B_C ;PRINT AT B,C; #8136 EB EX DE,HL ;DE:= addr within screen of panel #8137 E1 POP HL ;HL: points into panel info #8138 73 LD (HL),E #8139 23 INC HL #813A 72 LD (HL),D ;Store screen addr in panel info #813B 23 INC HL #813C C1 POP BC ;BC:= size of panel #813D 71 LD (HL),C #813E 23 INC HL #813F 70 LD (HL),B ;Store in panel information #8140 23 INC HL #8141 EB EX DE,HL ;DE:= addr at which to store data ;from screen; HL:= screen address #8142 F1 POP AF ;A:= required attribute byte #8143 E5 PUSH HL #8144 6F LD L,A #8145 2600 LD H,#00 #8147 228D5C LD (ATTR_P),HL #814A 228F5C LD (ATTR_T),HL;Set required colours #814D FD7457 LD (P_FLAG),H ;Also set OVER 0; INVERSE 0; #8150 E1 POP HL ;HL:= screen address of panel #8151 78 LD A,B ;A:= height of panel in squares #8152 0600 LD B,#00 ;BC:= width of panel in squares #8154 F5 PUSH AF ;Stack height of panel in squares #8155 87 ADD A,A #8156 87 ADD A,A #8157 87 ADD A,A ;A:= height of panel in pixels #8158 E5 PUSH HL ;Stack address of first row #8159 E5 N_P_LOOP_1 PUSH HL ;Stack address of row #815A C5 PUSH BC #815B EDB0 LDIR ;Store one row of panel #815D C1 POP BC #815E E1 POP HL ;HL:= address of row just stored #815F CD3A80 CALL DOWN_1 ;HL:= address of next row to store #8162 3D DEC A #8163 20F4 JR NZ,N_P_LOOP_1;Store all screen info for panel #8165 E1 POP HL ;HL:= address of first row #8166 CD3080 CALL ATTR_ADDR;HL:= address of first attr row #8169 F1 POP AF ;A:= height of panel in squares #816A C5 N_P_LOOP_2 PUSH BC ;Stack width of window #816B E5 PUSH HL ;Stack address of attribute row #816C EDB0 LDIR ;Store one attribute row #816E E1 POP HL ;HL:= addr of attr row just stored #816F 012000 LD BC,#0020 #8172 09 ADD HL,BC ;HL:= address of next attribute row #8173 C1 POP BC ;BC:= width of window #8174 3D DEC A #8175 20F3 JR NZ,N_P_LOOP_2;Store all attribute info for panel #8177 C9 RET ;Return #8178 C5 MENU_NEWLINEPUSH BC #8179 14 INC D ;Increment y coordinate #817A D5 PUSH DE #817B 42 LD B,D ;B:= required y coordinate #817C 4B LD C,E ;C:= required x coordinate #817D CD9B0A CALL AT_B_C ;PRINT AT B,C; #8180 D1 POP DE #8181 C1 POP BC #8182 C9 RET ;Return #8183 C5 MENU_STRING PUSH BC #8184 D5 PUSH DE #8185 2A5F5C LD HL,(X_PTR) ;HL: points to reqd string params #8188 23 INC HL #8189 5E LD E,(HL) #818A 23 INC HL #818B 56 LD D,(HL) ;DE:= original address of string #818C 23 INC HL #818D 46 LD B,(HL) ;B:= length of string #818E 23 INC HL #818F 23 INC HL #8190 225F5C LD (X_PTR),HL ;(X_PTR): pts to nxt string params #8193 2A655C LD HL,(STKEND);HL: pts to end of dynamic RAM #8196 ED52 SBC HL,DE #8198 380C JR C,M_STRING_2 ;Jump if address after dynamic RAM #819A 2A2A80 LD HL,(PANEL_ADDR) ;HL:= addr where info inserted #819D ED52 SBC HL,DE #819F 3005 JR NC,M_STRING_2;Jump if address below panel info #81A1 2A2880 LD HL,(PANEL_LEN) ;HL:= length of panel information #81A4 19 ADD HL,DE #81A5 EB EX DE,HL ;DE:= new address of string #81A6 04 M_STRING_2 INC B ;B:= length of string plus one #81A7 1804 JR MS_CHR_NEXT;Jump forward #81A9 1A MS_CHR_LOOP LD A,(DE) ;A:= next chr from string #81AA 13 INC DE ;DE: points to next chr #81AB D7 RST #10 ;Print the character #81AC 0D DEC C ;Decrement number of squares left #81AD 10FA MS_CHR_NEXT DJNZ MS_CHR_LOOP;Print whole of string #81AF 0C INC C ;C:= number of squares left + 1 #81B0 1803 JR MS_SPC_NEXT;Jump forward #81B2 3E20 MS_SPC_LOOP LD A,"space" #81B4 D7 RST #10 ;Print a space #81B5 0D MS_SPC_NEXT DEC C #81B6 20FA JR NZ,MS_SPC_LOOP ;Print all remaining spaces #81B8 D1 POP DE #81B9 C1 POP BC #81BA C9 RET #81BB F5 NEW_MPANEL PUSH AF ;Stack required attribute byte #81BC D5 PUSH DE ;Stack position of panel #81BD 78 LD A,B ;A:= number of strings supplied #81BE F5 PUSH AF ;Stack number of strings #81BF 2A655C LD HL,(STKEND);HL: points to end of calc stack #81C2 110000 LD DE,#0000 ;DE:= smallest number possible #81C5 2B NMP_LOOP DEC HL #81C6 46 LD B,(HL) #81C7 2B DEC HL #81C8 4E LD C,(HL) ;BC:= length of string #81C9 2B DEC HL #81CA 2B DEC HL #81CB 2B DEC HL #81CC EB EX DE,HL ;HL:= len of longest string so far #81CD A7 AND A #81CE ED42 SBC HL,BC #81D0 09 ADD HL,BC #81D1 3002 JR NC,NMP_STRLEN;Jump if HL greater than BC #81D3 60 LD H,B #81D4 69 LD L,C ;HL:= len of longest string so far #81D5 EB NMP_STRLEN EX DE,HL ;HL: points into calculator stack #81D6 3D DEC A #81D7 20EC JR NZ,NMP_LOOP;Scan all strings #81D9 A2 AND D #81DA 2005 JR NZ,NMP_ERROR ;Error if length gtr than 255d #81DC 4B LD C,E ;C:= maximum string length #81DD CB79 BIT 7,C #81DF 2802 JR Z,NMP_PANEL;Jump if length less than 128d #81E1 CF NMP_ERROR RST #08 ;Report 5, Out of screen #81E2 04 DEFB 04 #81E3 225F5C NMP_PANEL LD (X_PTR),HL ;Store pointer to string parameters #81E6 F1 POP AF #81E7 47 LD B,A ;B:= number of strings #81E8 D1 POP DE ;DE:= intended position for menu #81E9 F1 POP AF ;A:= required attribute byte #81EA C5 PUSH BC #81EB D5 PUSH DE #81EC 04 INC B ;Allow one extra line for border #81ED 0C INC C #81EE 0C INC C ;Allow two extra columns for border #81EF CDC780 CALL NEW_PANEL ;Assign panel information area #81F2 D1 POP DE #81F3 C1 POP BC #81F4 2A5F5C LD HL,(X_PTR) ;HL: points to first string param #81F7 22655C LD (STKEND),HL;Empty calc stack from here on #81FA 3E14 LD A,#14 #81FC D7 RST #10 ;PRINT INVERSE ... #81FD 3E01 LD A,#01 #81FF D7 RST #10 ;1; #8200 3E20 LD A,"space" #8202 D7 RST #10 ;Print a space #8203 CD8381 CALL MENU_STRING;Print the menu title #8206 3E20 LD A,"space" #8208 D7 RST #10 ;Print a space #8209 3E14 LD A,#14 #820B D7 RST #10 ;PRINT INVERSE ... #820C AF XOR A #820D D7 RST #10 ;0; #820E 2A7B5C LD HL,(UDG) #8211 E5 PUSH HL ;Stack UDGs address #8212 210080 LD HL,MENU_UDGS #8215 227B5C LD (UDG),HL ;Use specified UDGs #8218 05 DEC B ;B:= number of remaining strings #8219 C5 PUSH BC #821A CD7881 NMP_PRINT CALL MENU_NEWLINE ;Move print position to next line ;of menu panel #821D 3E90 LD A,"graphic-A" #821F D7 RST #10 ;Print left-hand border #8220 CD8381 CALL MENU_STRING;Print next string #8223 3E91 LD A,"graphic-B" #8225 D7 RST #10 ;Print right-hand border #8226 10F2 DJNZ NMP_PRINT ;Print all strings #8228 FD362600 LD (X_PTR+1),#00;Clear pointer #822C CD7881 CALL MENU_NEWLINE ;Move print position to last line ;of menu panel #822F 3E92 LD A,"graphic-C" #8231 D7 RST #10 ;Print bottom left-hand corner #8232 3E94 NMP_UNDER LD A,"graphic-E" #8234 D7 RST #10 ;Print underside border #8235 0D DEC C #8236 20FA JR NZ,NMP_UNDER ;Print whole of underside border #8238 3E93 LD A,"graphic-D" #823A D7 RST #10 ;Print bottom right-hand corner #823B C1 POP BC ;B:= number of strings, excluding ;title; C:= max length of string #823C E1 POP HL ;HL:= original address of UDGs #823D 227B5C LD (UDG),HL ;Restore system variable #8240 C9 RET ;Return #8241 D5 NEW_MENU PUSH DE #8242 CDBB81 CALL NEW_MPANEL ;Print menu on screen #8245 D1 POP DE #8246 0C INC C #8247 0C INC C ;C:= full width including borders #8248 3E01 LD A,#01 ;A:= initial menu choice #824A F5 N_M_LOOP PUSH AF ;Stack menu choice #824B 82 ADD A,D ;A:= line number of menu choice #824C 6F LD L,A #824D AF XOR A ;A:= 00 #824E 67 LD H,A ;HL:= line number of menu choice #824F 29 ADD HL,HL #8250 29 ADD HL,HL #8251 29 ADD HL,HL #8252 29 ADD HL,HL #8253 29 ADD HL,HL #8254 D5 PUSH DE #8255 57 LD D,A ;DE:= x coordinate of panel #8256 19 ADD HL,DE #8257 110058 LD DE,#5800 #825A 19 ADD HL,DE ;HL:= attr address of menu choice #825B D1 POP DE #825C C5 PUSH BC #825D 7E N_M_MARK LD A,(HL) ;A:= attribute byte for row #825E EE10 XOR #10 ;Change paper colour #8260 77 LD (HL),A ;Make change on screen #8261 23 INC HL ;HL: points to next attr byte in row #8262 0D DEC C #8263 20F8 JR NZ,N_M_MARK ;Mark entire row #8265 C1 POP BC #8266 FDCB01AE N_M_WAIT RES 5,(FLAGS) ;Signal "Ready for a new key" #826A FDCB016E N_M_WAIT2 BIT 5,(FLAGS) #826E 28FA JR Z,N_M_WAIT2 ;Wait until key pressed #8270 3A085C LD A,(LAST_K) ;A:= chr code of key pressed #8273 D60A SUB #0A #8275 2009 JR NZ,N_M_KEY_B;Jump unless key is "cursor down" #8277 F1 POP AF ;A:= current menu choice #8278 B8 CP B #8279 3C INC A ;Move one line down #827A 380C JR C,N_M_NEXT ;Jump unless below last entry #827C 3E01 LD A,#01 ;Go to first entry #827E 1808 JR N_M_NEXT ;Jump forward #8280 3D N_M_KEY_B DEC A #8281 2013 JR NZ,N_M_KEY_C;Jump unless key is "cursor up" #8283 F1 POP AF ;A:= current menu choice #8284 3D DEC A ;Move one line up #8285 2001 JR NZ,N_M_NEXT ;Jump unless above first entry #8287 78 LD A,B ;Go to last entry #8288 F5 N_M_NEXT PUSH AF ;Stack new menu choice #8289 C5 PUSH BC #828A 2B N_M_UNMARK DEC HL ;HL: points to next attr byte in row #828B 7E LD A,(HL) ;A:= attribute byte from row #828C EE10 XOR #10 ;Restore paper colour #828E 77 LD (HL),A ;Make change on screen #828F 0D DEC C #8290 20F8 JR NZ,N_M_UNMARK ;Remove marker entirely #8292 C1 POP BC #8293 F1 POP AF ;A:= menu choice #8294 18B4 JR N_M_LOOP ;Loop back for next try #8296 3D N_M_KEY_C DEC A #8297 2006 JR NZ,N_M_KEY_D;Jump unless key is "delete" #8299 F1 POP AF ;A:= menu choice #829A CD4D80 CALL UNSTK_PANEL ;Remove menu from screen #829D AF XOR A ;A:= 00 #829E C9 RET ;Return with A containing zero #829F 3D N_M_KEY_D DEC A #82A0 20C4 JR NZ,N_M_WAIT ;Jump back unless key is "enter" #82A2 F1 POP AF ;A:= menu choice #82A3 C9 RET ;Return with A containing choice #82A4 DF SEPARATOR? RST #18 ;A:= current character #82A5 FE27 CP "'" #82A7 C8 RET Z ;Set zero flag for "'" #82A8 FE2C CP "," #82AA C8 RET Z ;Set zero flag for "," #82AB FE3B CP ";" #82AD C9 RET ;Or set zero flag for ";" #82AE CDA482 SEPARATOR CALL SEPARATOR? ;Test current character #82B1 C8 RET Z ;Return with valid separator #82B2 CF ERROR_C RST #08 ;Report C, Nonsense in BASIC #82B3 0B DEFB 0B #82B4 AF B_PANEL XOR A ;A:= 00 #82B5 1806 JR B_INTERPRET #82B7 3E01 B_MPANEL LD A,#01 #82B9 1802 JR B_INTERPRET #82BB 3E03 B_MENU LD A,#03 #82BD 210000 B_INTERPRET LD HL,#0000 #82C0 222E80 LD (MENU_AT),HL;Initialise menu location #82C3 6F LD L,A #82C4 222C80 LD (PANEL_TYPE),HL ;Define panel type and reset ;string count #82C7 3E02 LD A,#02 #82C9 CD0116 CALL CHAN-OPEN ;Select stream 2 (the screen) #82CC CD4D0D CALL TEMPS ;Use permanent colours #82CF DF RST #18 ;A:= current char from BASIC line #82D0 FE3A CP ":" #82D2 20DE JR NZ,ERROR_C ;Error unless character is ":" #82D4 E7 RST #20 ;A:= next character from BASIC line #82D5 FEF5 CP "PRINT" #82D7 20D9 JR NZ,ERROR_C ;Error unless character is "PRINT" #82D9 FD340D INC (SUBPPC) ;Increment statement count #82DC E7 BI_LOOP RST #20 ;A:= next character from BASIC line #82DD FEAC CP "AT" #82DF 2010 JR NZ,BI_COLOURS ;Jump unless character is "AT" #82E1 CD791C CALL NEXT-2NUM ;Evaluate AT parameters #82E4 CD0723 CALL STK-TO-BC ;C,B:= AT parameters #82E7 61 LD H,C #82E8 68 LD L,B #82E9 222E80 LD (MENU_AT),HL;Store required AT coordinates #82EC CDAE82 CALL SEPARATOR ;Expect a separator #82EF 18EB JR BI_LOOP ;Jump back to continue interpreting #82F1 CDF221 BI_COLOURS CALL CO-TEMP-3 ;Evaluate colour commands #82F4 3805 JR C,BI_MENU ;Jump if no colour commands present #82F6 CDAE82 CALL SEPARATOR ;Expect a separator #82F9 18E1 JR BI_LOOP ;Jump back to continue interpreting #82FB 212C80 BI_MENU LD HL,PANEL_TYPE #82FE CB46 BIT 0,(HL) #8300 280F JR Z,BI_PANEL ;Jump if panel only requested #8302 CD8C1C BI_STR_LOOP CALL EXPT-EXP ;Expect a string expression #8305 212D80 LD HL,STRING_COUNT #8308 34 INC (HL) ;Increment number of strings counted #8309 CDA482 CALL SEPARATOR? ;Is there a separator? #830C 2016 JR NZ,BI_MENU_GO ;Jump if not #830E E7 RST #20 ;Skip over the separator #830F 18F1 JR BI_STR_LOOP ;Loop back to count remaining strs #8311 CD7A1C BI_PANEL CALL EXPT-2NUM ;Evaluate panel size #8314 CD0723 CALL STK-TO-BC ;C,B:= panel size #8317 78 LD A,B ;A:= panel width #8318 41 LD B,C ;B:= panel height #8319 4F LD C,A ;C:= panel width #831A ED5B2E80 LD DE,(MENU_AT);DE:= panel position #831E 3A8F5C LD A,(ATTR_T) ;A:= colours required #8321 C3C780 JP NEW_PANEL ;Jump to create panel #8324 ED4B2C80 BI_MENU_GO LD BC,(PANEL_TYPE) ;B:= number of strings #8328 ED5B2E80 LD DE,(MENU_AT);DE:= panel position #832C 3A8F5C LD A,(ATTR_T) ;A:= colours required #832F CB49 BIT 1,C #8331 CABB81 JP Z,NEW_MPANEL;Jump to create menu panel, if reqd #8334 CD4182 CALL NEW_MENU ;Else create menu panel and choose #8337 0600 LD B,#00 #8339 4F LD C,A ;BC:= menu choice #833A C9 RET ;Return #833B CD4D80 MENU_OFF CALL UNSTK_PANEL ;Unstack topmost panel, if it exists #833E 2A4B5C LD HL,(VARS) ;HL: points to variables area #8341 2B DEC HL ;HL: points to top of panel stack #8342 010000 LD BC,#0000 #8345 CB7E BIT 7,(HL) #8347 C8 RET Z ;Return if no panels left on stack #8348 03 INC BC ;BC:= one #8349 C9 RET ;Return