Beginner's Guide to WIMP
Programming
Martyn Fox

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:

b%   Item in top menu
b%+4Item in first submenu
b%+8Item in second submenu
... and so on
 
-1 at end of list

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 Creation

In 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:

    Info_T

The full list of suffixes that our function copes with is as follows:

Suffix   Purpose
_Ttick
_Ddividing (dashed/3D separator) line
_Wwritable item
_Mgenerates Wimp message

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 address of the parent menu data block.
  • The item number in the parent menu, starting with zero.
  • The address of the submenu data block, or the window handle if we are attaching a window.

... the DATA statement 'Info_TDWM'

... 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 Submenu

Now 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 ...

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.

previousmain indexnext

 
© Martyn & Christine Fox 2004