9: More on Menus
We've now seen how to deal with the situation where we click the mouse on a menu item. The data block at b% contains a string of four-byte words, each holding the number of an item in a menu or submenu:
This works very well with our simple menu structure. We could use the technique if we had several menus, by using multiple CASE ... OF statements, rather like in PROCmouseclick and PROCwindow_click. If we had more than one menu, we could use topmenu% to remind ourselves of which one we had opened.
Things start to get rather complicated, however, if we have deep menu structures and, if we change the layout of the menus while we're experimenting, we could easily tie ourselves in knots.
Fortunately, a system call can help us. To see how it works, rewrite the middle part of PROCmenuclick:
1810 DEFPROCmenuclick 1820 REM handles mouse clicks on menu in response to Wimp_Poll reason code 9 1830 LOCAL c%,adj% 1840 c%=b%+900 1850 SYS "Wimp_GetPointerInfo",,c% 1860 adj%=(c%!8 AND 1) 1870 SYS "Wimp_DecodeMenu",,topmenu%,b%,c% 1880 CASE $c% OF 1890 WHEN "Quit":quit%=TRUE 1900 WHEN "Info":VDU 7 1910 ENDCASE 1920 IF adj% PROCshowmenu(topmenu%,topx%,topy%) 1930 ENDPROC 1940 :
We've called this version of the !RunImage file page_080 in the files.
We've kept the first part of the procedure, which identifies which mouse button we used, the same. Wimp_DecodeMenu uses the list of menu and submenu items in the data block from b% onwards. The call requires us to put the addresses of the menu data block in R1 and the list of menu items in R2. We also need a buffer in which to put the answer, whose address we put in R3. As we've finished with our data sub-block at c% at this point, we might as well use it again for this purpose.
Wimp_DecodeMenu works its way through the menu data block, using the list of selected items at b%, and extracts the name of the item. After the call, we will find a string, starting at c%, containing the name of the menu item, followed by any submenu items, separated by dots. As you can see from the listing, it's a simple matter to check which item we've clicked on, using a CASE ... OF ... ENDCASE structure. You can, of course, stop the program beeping if you click on Info, simply by deleting line 1900.
Speeding up Menu CreationIn the last section, we created a menu by typing in its entire data block. If we wanted several menus, we would have had to type in a block for each one, despite the fact that a lot of the data is identical to each block.
Clearly, we need a routine to do this for us, just by telling it what to do, and that is our next step.
We start by writing a new PROCmain_menu. If you are typing in the listing yourself, do not delete your original version just yet because you will be able to re-use some of its lines and save some typing.
1670 DEFPROCmain_menu 1680 REM creates main menu, calling FNmake_menu 1690 RESTORE +1 1700 DATA Test,Info,Quit,* 1710 mainmenu%=FNmake_menu 1720 ENDPROC 1730 :
You can see this is a much quicker way of creating a menu.
Line 1690 sets Basic's DATA pointer so that the next READ operation will take data from the following line. The DATA statements consist of the menu title, followed by the name of each menu item, then by a '*' to show that we've reached the end of the menu data. We shall be making special arrangements for ticks, dotted lines, writable items and items which generate Wimp messages.
By using a function to generate the menu block, we can make it return its start address, which we use when we call Wimp_CreateMenu.
Now for the function to make the block. This new listing is in the files as page_081.
1740 DEFFNmake_menu 1750 REM creates menu block from DATA statements 1760 LOCAL start%,title$,item$,ul%,tail$,writable%,buffer%,buflen% 1770 start%=menspc% 1780 READ title$ 1790 $(start%)=title$ 1800 start%?12=7:REM title foreground colour 1810 start%?13=2:REM title background colour 1820 start%?14=7:REM work area foreground colour 1830 start%?15=0:REM work area background colour 1840 start%!20=44:REM height of menu items 1850 start%!24=0:REM gap between items 1860 width%=LEN(title$)-3 1870 menspc%+=28 1880 REPEAT 1890 READ item$ 1900 IF item$<>"*" THEN 1910 !menspc%=0 1920 writable%=FALSE 1930 ul%=INSTR(item$,"_") 1940 IF ul% THEN 1950 tail$=RIGHT$(item$,LEN(item$)-ul%) 1960 IF INSTR(tail$,"T") !menspc%=!menspc% OR 1:REM tick 1970 IF INSTR(tail$,"D") !menspc%=!menspc% OR 2:REM dotted line 1980 IF INSTR(tail$,"W") !menspc%=!menspc% OR 4: writable%=TRUE:READ buffer%:READ buflen%:REM writable icon 1990 IF INSTR(tail$,"M") !menspc%=!menspc% OR 8:REM generate message 2000 item$=LEFT$(item$,ul%-1) 2010 ENDIF 2020 IF LENitem$>width% width%=LENitem$ 2030 menspc%!4=-1:REM submenu ptr 2040 IF writable% THEN 2050 menspc%!8=&0700F121:menspc%!12=buffer%:menspc%!16=-1 menspc%!20=buflen%:$buffer%=item$ 2060 ELSE 2070 IF LENitem$<12 THEN 2080 menspc%!8=&07000021:$(menspc%+12)=item$ 2090 ELSE 2100 menspc%!8=&07000121:menspc%!12=ws%:menspc%!16=-1:menspc%!20=LENitem$+1 2110 $ws%=item$:ws%+=LENitem$+1 2120 ENDIF 2130 ENDIF 2140 menspc%+=24 2150 ENDIF 2160 UNTIL item$="*" 2170 start%!16=width%*16+32 2180 !(menspc%-24)=!(menspc%-24) OR &80 2190 mptr%=menspc% 2200 =start% 2210 :
That was a great deal to assimilate all at once but we can use it to create as many menus as we like and it has given us great versatility. We shall be using this function when we create our Wimp shell application in the next section.
The function makes a menu block, starting at the current value of menspc%, and returns this value to the main program. It also increases menspc%, so that it's ready for the creation of the next menu block. This is why we originally DIMmed menspc% as 1,024 bytes - we could get quite a few menus into this space!
The beginning of the function is similar to our old menu procedure, except that the title string is read from the first of our DATA statements in line 1700. We start off by setting the menu width according to the length of the title, and increase it if we find a longer item name.
After increasing menspc% to the start of the block for the first item, most of the remainder of the function is a loop which is executed once for each item, reading in the item name until it comes to the '*' at the end of the list.
If there is something special about an item, we give its data statement in PROCmain_menu a suffix, beginning with an underline. For example, if we wanted Info to have a tick, its data statement would be:
The full list of suffixes that our function copes with is as follows:
You can combine two or more suffixes into one; for example, the data statement 'Info_TDWM' would produce a menu item called Info which was ticked, had a line under it, was writable and generated a Wimp message when you moved the pointer to the right of it!
Lines 1930-2010 deal with suffixes, setting the appropriate bits of the menu item flags as required. If the suffix includes a 'W', meaning that the item is writable, there should be two extra DATA statements giving the address and the length of the icon's indirected text buffer. These are read by the last part of line 1980.
If the item name is longer than previous names, line 2020 increases the width of the menu.
The icon data is set up by lines 2040 to 2130. If the text is more than 12 bytes long or the icon is writable, the data is automatically indirected. If the icon is not writable, the indirected data workspace at ws% is used.
After creating the item data block, menspc% is increased by 24 bytes in line 2140 and the loop run again if there are any more items to be dealt with.
After the last item, we know how wide the menu has to be, and this number is put into the word at byte 16 of the menu header block in line 2170.
Finally, the menu item flag word of the last item has its bit 7 set to 1 by line 2180, to tell the Wimp that there are no more items.
Where has the Info Box Gone?If you run the program now, you'll notice that we have lost our Info box. Line 2030 puts -1 into the submenu pointer word of every menu item block, so we need to replace it with the window handle of our Info box.
A procedure to do this would be very useful. All it would need to know is:
... the DATA statement 'Info_TDWM'
The procedure is very short:
2230 DEFPROCattach(menu%,item%,sub%) 2240 !(menu%+28+item%*24+4)=sub% 2250 ENDPROC 2260 :
We must also add another line to PROCinit:
390 DEFPROCinit 400 REM initialisation before polling loop starts 410 DIM b% 1023,ws% 1023,menspc% 1023 420 wsend%=ws%+1024 430 quit%=FALSE 440 PROCload_templates 450 PROCmain_menu 460 PROCattach(mainmenu%,0,info%) 470 ENDPROC 480 :
We've called this version page_085 in the files.
You will notice that this new line, line 460, comes after the calls to PROCload_templates and PROCmain_menu. This is because, of course, we can't attach a window to a menu until we know both the window handle and the address of the menu data block.
If you run the program again, you should find you have your Info box back.
Adding a SubmenuNow let's take advantage of the new-found freedom that these procedures have given us to make a change to our main menu. We will remove the beep facility from the Info item, and give it an item of its own. (The line numbering shown here reflects the situation after all the changes have been made.)
Begin by making a change to the DATA statements in PROCmain_menu:
1750 DATA Test,Info,Beep,Quit,*
We have added a third item to the main menu, so we've made it taller. It would be a good idea to change the '2' to a '3' in the line in PROCmouseclick which calls PROCshowmenu when Menu is clicked on the icon bar, in order to draw the menu a little higher up the screen, so that it will retain its correct position above the icon bar.
You could run the program like this and see the extra item, though it would not do anything yet. Although Quit is now the third item, not the second, it will still close down the application, as we are using Wimp_DecodeMenu in PROCmenuclick.
Now add another two lines to PROCinit, so that the procedure looks like this:
390 DEFPROCinit 400 REM initialisation before polling loop starts 410 DIM b% 1023,ws% 1023,menspc% 1023 420 wsend%=ws%+1024 430 quit%=FALSE 440 PROCload_templates 450 PROCmain_menu 460 PROCattach(mainmenu%,0,info%) 470 PROCbeep_menu 480 PROCattach(mainmenu%,1,beep%) 490 ENDPROC 500 :
Line 470 calls a new procedure, shown below, which will create the submenu, and line 480 attaches the submenu to the main menu.
2310 DEFPROCbeep_menu 2320 RESTORE +1 2330 DATA Beep,High,Low,* 2340 beep%=FNmake_menu 2350 ENDPROC 2360 :
Delete the line in PROCmenuclick which sounded a VDU 7 on clicking Info if you haven't already done so and add two lines in its place so that the procedure looks like this:
1570 DEFPROCmenuclick 1580 REM handles mouse clicks on menu in response to Wimp_Poll reason code 9 1590 LOCAL c%,adj% 1600 c%=b%+900 1610 SYS "Wimp_GetPointerInfo",,c% 1620 adj%=(c%!8 AND 1) 1630 SYS "Wimp_DecodeMenu",,topmenu%,b%,c% 1640 CASE $c% OF 1650 WHEN "Quit":quit%=TRUE 1660 WHEN "Beep.High":SOUND 1,-15,200,10 1670 WHEN "Beep.Low":SOUND 1,-15,20,10 1680 ENDCASE 1690 IF adj% PROCshowmenu(topmenu%,topx%,topy%) 1700 ENDPROC 1710 :
We've called this new version page_087 in the files.
You can see from lines 1660 and 1670 how Wimp_DecodeMenu adds the submenu item name to the parent item name with a dot between them.
We can create a submenu ...
Now you can produce two sorts of beep, by clicking on the appropriate menu item.
This concludes menus for now. If you wish, you should be able to create a separate menu that will appear if you click Menu over your window. It's up to you to experiment.