Beginner's Guide to WIMP Programming
Everything you need to know to start writing your own applications.
8: Of Mice and Menus
We now have an application that uses windows. If you've been experimenting, you may have tried using the template editor to produce a template with more windows, and tried loading them into your program.
If you have more than one window, remember that you have to load them one at a time, and call Wimp_CreateWindow before loading the next one. This allows you to use the same data block for all the windows. Don't forget the call to Wimp_CloseTemplate when you have loaded the last one.
Now to menus. At the moment, we're quitting our application by clicking Adjust on its icon. Although this works well, it's not exactly standard RISC OS practice! Obviously it's time we got to grips with menus.
There are three steps to using menus:
A menu is a bit like a window, in that it's created using a data block. There are several important differences, though.
The chief one is that the Wimp does not create the menu somewhere else in the machine's memory, like it does when we create a window. When we use a menu, its data block has to be present in our application's memory, ready to be used.
For this reason, we can't use our normal data block, which starts at b%. Instead, we must define a new block for our menu data. If we have more than one menu, each will need its own block.
If we want to create several menus, we would do well to devise a routine to do this for us. For simplicity, though, we will start by creating just one.
There is a technique for creating menus and submenus only when they're needed and discarding them afterwards. We'll discuss this briefly towards the end of this guide (see the end of Section 20). It can save memory but it's rather complicated so, for now, we'll create all our menus when we initialise the program.
Unfortunately, there is no menu equivalent of the window template. We have to type in our menu blocks rather like the first window we created. Fortunately, the block is not nearly so long, and there is a short cut, which we will look at later.
Let's create a simple menu, to be opened when we click Menu on our application's icon. It will have a title, and contain two items, Info and Quit.
This latest version of the !RunImage file is in the files as page_068.
... and contain two items, Info and Quit
Start by adding a further definition to the DIM statements in PROCinit, then add a line to call our next procedure, so that PROCinit now reads as follows:
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 ENDPROC 470 :
We have defined our menu workspace, menspc%. We will not need anything like 1,024 bytes for our simple menu, but the space will be useful later on, as we shall see.
Now for the procedure to create the menu. If you're typing it in, you can, of course, leave out the REMs, as they are only there to explain what everything is for.
1460 DEFPROCmain_menu 1470 REM creates main menu data block 1480 start%=menspc% 1490 $(start%)="Test" 1500 start%?12=7:REM title foreground colour 1510 start%?13=2:REM title background colour 1520 start%?14=7:REM work area foreground colour 1530 start%?15=0:REM work area background colour 1540 start%!16=96:REM width of menu 1550 start%!20=44:REM height of menu items 1560 start%!24=0:REM gap between items 1570 menspc%+=28 1580 REM data for "Info" item 1590 !menspc%=0:REM menu flags 1600 menspc%!4=-1:REM submenu pointer 1610 menspc%!8=&07000021:REM menu icon flags 1620 $(menspc%+12)="Info":REM menu icon data 1630 menspc%+=24 1640 REM data for "Quit" item 1650 !menspc%=&80:REM menu flags - bit 7 set to indicate last item 1660 menspc%!4=-1:REM submenu pointer 1670 menspc%!8=&07000021:REM menu icon flags 1680 $(menspc%+12)="Quit":REM menu icon data 1690 menspc%+=24 1700 mainmenu%=start% 1710 ENDPROC 1720 :
Our menu data block consists of 28 bytes, plus 24 for each menu item. We're going to use a special technique to simplify the process of counting the bytes.
Each time we finish typing in a section of the block, we will increase the value of menspc% so that it contains the address of the first byte following the section that we've just typed in. This means that we will start counting the bytes from zero again as we type in the next section. As a result of this, the lines for each of the 24-byte menu item sections will be almost identical, and you can save yourself some work by copying the lines for the second item from those of the first.
The first 12 bytes (in line 1490) contain the menu title string. RISC OS 3 onwards makes special arrangements for dealing with indirected text where a menu has more than 12 characters in its title, but we will stick to non-indirected titles for the moment.
The next four bytes define the colours, using Wimp palette numbers. Bytes 14 and 15 don't really serve any useful purpose, as the 'work area' of a menu is not normally seen. A menu is rather like a window that is completely covered with icons. Only if the gap between items in byte 24 is not zero will the work area background be seen (and this value should never be anything other than zero; applications should take care to present users with menus that have a standard appearance). Still, it keeps the Wimp happy!
Byte 16 sets the width of the whole menu and byte 20 sets the height of each item to 44, which is the normal height for menu items (and again, you should not change this value unless there is a very good reason to do so, as it helps to make menus appear consistent between applications). Note that if an outline font is being used for text in desktop icons and menus, the Wimp calculates menu widths by itself, and also right-justifies any keyboard shortcuts that may be listed. However, we should still supply a suitable width value in byte 16.
Now that we've completed our 28-byte header, we increase menspc% by 28 in line 1570, and start counting from zero again. We set variable start% at the beginning of the procedure, to remember where the menu block started.
Menu Item DetailsThe 24 bytes for each menu item are rather like an icon block, which we dealt with when we covered windows. The first word, though, contains special menu flags, with their meanings as follows:
In our simple menu, the only flag that we use will be bit 7 in the final item. You can, of course, experiment with adding ticks and separators to your menu.
The next word enables us to add a submenu, leading off this menu item. To do this, we put the address of the submenu block here. We must, of course, have already generated this submenu, so that we know where it is. If, instead of a submenu, you want to use a window, such as a dialogue box, you simply put its window handle here instead. The arrow to indicate a submenu will appear automatically. If you are not using a submenu or dialogue box, this word contains -1, as in lines 1600 and 1660.
The menu icon flags and menu icon data are the same as for an ordinary icon. Certain flags aren't used, for example, the 'icon is deleted' flag. These are explained more fully in the Programmer's Reference Manual, in the description of Wimp_CreateMenu.
After generating the 24 bytes for the first item, we again increase the value of menspc%, this time by 24 bytes in line 1630, ready for the next item.
Note that the last item has its menu flags set to &80: bit 7 is set, telling the Wimp that this is the last item.
At the end, we again add 24 to menspc%, so that it ends up with the address of the unused portion of the menu workspace. This is so we can easily produce another menu block, if we wish.
Finally, we set mainmenu% to the start of the block.
Line 450 in PROCinit ensures that this procedure is called when the program starts up.
Opening the MenuYou may have noticed that we haven't used any operating system calls in this procedure; all we've done is create the block. This is because we only call Wimp_CreateMenu when we actually open the menu - creating a menu is the equivalent of opening a window.
Now let's set about doing just that. We add one line to PROCmouseclick, so that the full listing looks like this:
550 DEFPROCmouseclick 560 REM handles mouse clicks in response to Wimp_Poll reason code 6 570 REM b%!0=mousex,b%!4=mousey:b%!8=buttons:b%!12=window handle (-2 for icon bar):b%!16=icon handle 580 CASE b%!12 OF 590 WHEN -2:CASE b%!8 OF 600 WHEN 1:quit%=TRUE 610 WHEN 2:PROCshowmenu(mainmenu%,!b%-64,96+2*44):REM replace '2' with number of menu items 620 WHEN 4:!b%=main%:SYS "Wimp_GetWindowState",,b%:SYS "Wimp_OpenWindow",,b% 630 ENDCASE 640 WHEN main%:PROCwindow_click 650 ENDCASE 660 ENDPROC 670 :
We will leave the quit instruction, now in line 600, in place for the moment until we've written the bit where the program actually carries out the instruction that we give it through the menu.
Positioning the MenuWhen we create the menu, we have to tell the Wimp where the menu data block is, and also where on the screen to put the menu, specified by the coordinates of its top left-hand corner.
PROCshowmenu will do this for us, if we feed it the address of the menu block and the x and y coordinates of where we want the menu to appear. Normally, we would expect a menu to appear at the place where we clicked the mouse, but in line 610, we want it to appear just above the icon bar, wherever on the bar our icon may be. We get the horizontal position from the x coordinate of the mouse, which is in the word at b% when we are in PROCmouseclick. The y coordinate is calculated from the number of menu items, allowing 44 OS units for the height of each one, plus a further 96 units to get it clear of the icon bar and place it at the standard vertical position at which the bottom edge of an icon bar menu should appear. Note that this figure does not allow for any extra height in the menu due to spacing lines between items. You need to add a further 24 OS units for each separator that you add.
Now to the procedure for opening the menu:
1730 DEFPROCshowmenu(menu%,x%,y%) 1740 REM opens menu at given coordinates 1750 topmenu%=menu%:topx%=x%:topy%=y% 1760 SYS "Wimp_CreateMenu",,menu%,x%,y% 1770 ENDPROC 1780 :
The three parameters passed to the procedure are stored in variables topmenu%, topx% and topy%. These will be used later to keep the menu open if we click on it with Adjust. We will also use topmenu% to decode the menu.
You should now find that clicking Menu on your icon produces a menu with two items, Info and Quit, which you can drag around the screen, and which will vanish when you click any of the mouse buttons. This is not a lot of use at the moment, so let's consider how we make it do something.
... which you can drag around the screen ...
Decoding the MenuWhen we click a mouse button on a menu item, we get a return from Wimp_Poll with a reason code not of 6, as we do when the mouse is clicked on our window or icon, but of 9; a special code for menu selection.
When this happens, the first word in the data block at b% contains the number of the item in the top menu - how far down the menu it is, beginning with zero for the top item. The next word contains the number for the item in the first submenu, the following for the second, and so on, finishing with -1 when there are no submenus left.
This means that if we select Info on our small menu the word at b% will contain zero, and if we select Quit it will contain 1. In both cases, the word at b%+4 will contain -1, as there are no submenus.
We will look at the procedure to interpret this in a moment. First, though, consider what has to be done if we were to click Adjust on our menu. If we want our application to behave in the correct RISC OS manner, the menu should remain on the screen. Unfortunately, the Wimp does not make special provision for this, so we have to do it ourselves.
Now let's look at the procedure for doing all this. First, add a further line to PROCpoll to handle reason code 9. The procedure should now look like this:
260 DEFPROCpoll 270 REM main program Wimp polling loop 280 SYS "Wimp_Poll",,b% TO r% 290 CASE r% OF 300 WHEN 2:SYS "Wimp_OpenWindow",,b% 310 WHEN 3:SYS "Wimp_CloseWindow",,b% 320 WHEN 6:PROCmouseclick 330 WHEN 8:PROCkeypress 340 WHEN 9:PROCmenuclick 350 WHEN 17,18:PROCreceive 360 ENDCASE 370 ENDPROC 380 :
Our polling loop is getting close to completion. Now for the new procedure:
1790 DEFPROCmenuclick 1800 REM handles mouse clicks on menu in response to Wimp_Poll reason code 9 1810 LOCAL c%,adj% 1820 c%=b%+900 1830 SYS "Wimp_GetPointerInfo",,c% 1840 adj%=(c%!8 AND 1) 1850 CASE !b% OF 1860 WHEN 0:VDU 7 1870 WHEN 1:quit%=TRUE 1880 ENDCASE 1890 IF adj% PROCshowmenu(topmenu%,topx%,topy%) 1900 ENDPROC 1910 :
Our first priority is to establish whether or not the Adjust button was clicked, which we do with a call to Wimp_GetPointerInfo. We do not want to disturb the information in our main data block, as we shall want it again shortly, so we will use a data sub-block, at c%.
The contents of this sub-block after the call are the same as those of the block on a return from Wimp_Poll with a reason code of 6, following a mouse click. The word at c%+8 contains the state of the buttons, with bit zero set if the Adjust button is pressed. Line 1840 sets up variable adj%, to be TRUE if this is the case and FALSE if it isn't.
Having sorted that out, we can now proceed to find out which menu item we selected. As we saw earlier, the first word of the block at b% will be zero if we selected Info, and 1 if we selected Quit. It is a simple matter of using a CASE ... OF ... ENDCASE structure to take the necessary action.
Taking ActionFor now, to keep things simple, let's just have a beep if we choose Info. It will at least show that something has happened. Closing down the application when we choose Quit is simply a matter of setting quit% to TRUE and letting matters take their course.
Obviously, if we choose to quit, there is not much scope for redrawing the menu! We will, however, make provision for redrawing it if we choose Info with Adjust. Line 1890 takes care of this, making another call to PROCshowmenu to open up the menu again if adj% is TRUE. The Wimp, incidentally, remembers if we had already opened this menu, and reopens any submenus.
Now you should find that choosing Quit will actually close down the application. Choosing Info will cause a beep, through the VDU 7 command in line 1860, and, if you do it with Adjust, the menu will stay open. Our application is beginning to look more and more professional all the time!
'Why a menu item called Info that just beeps?', you are no doubt asking. 'Shouldn't there be an Info box with it?' Of course there should. Let's make one.
One of the joys of a template editor is that you can examine the template files of other applications and learn their secrets. It's also easy to 'borrow' such things as Info and Save boxes. These are virtually identical in most applications, so there is little point in making your own from scratch.
Load your own application's template file into WinEd, then open the application directory of another application which has a standard Info box. Double-click on its Templates file and a second template window will open, showing all the windows in the application's file. The one you want will probably be called 'progInfo' (you can check this if you like, by double-clicking on it and opening it). You can copy it into your own Templates file simply by dragging it between windows, like copying a file from one directory to another. If it doesn't have the identifier "progInfo", rename it, so that it will work with our program.
Now you need to make whatever changes are necessary to the text to put your own details in. Each time you edit the text in one of the icons, click on Minimise to set the icon's indirected text workspace to a minimum. You can, of course, put in your own name, version number and today's date.
When everything is correct, resave the template file. You now have a template with an extra window, as a result of which we need two extra lines in PROCload_templates. The procedure should now look like this:
1210 DEFPROCload_templates 1220 REM opens window template file, loads and creates window 1230 SYS "Wimp_OpenTemplate",,"<Test$Dir>.Templates" 1240 SYS "Wimp_LoadTemplate",,b%,ws%,wsend%,-1,"Main",0 TO ,,ws% 1250 ind%=!(b%+88+32*1+20) 1260 SYS "Wimp_CreateWindow",,b% TO main% 1270 SYS "Wimp_LoadTemplate",,b%,ws%,wsend%,-1,"progInfo",0 TO ,,ws% 1280 SYS "Wimp_CreateWindow",,b% TO info% 1290 SYS "Wimp_CloseTemplate" 1300 ENDPROC 1310 :
This new version is in the files with the name page_077.
We've loaded a window template with the identifier name 'progInfo', and created a window with the handle info%. The Templates1 file already contains the Info box; it just hasn't been used until now.
You can now see how we alternately load templates and create windows, so that we can keep reusing the same data block.
Now change one line in PROCmain_menu. Note that this listing does not show the entire procedure:
1600 REM data for "Info" item 1610 !menspc%=0:REM menu flags 1620 menspc%!4=info%:REM submenu pointer 1630 menspc%!8=&07000021:REM menu icon flags 1640 $(menspc%+12)="Info":REM menu icon data 1650 menspc%+=24
This puts the window handle of our Info box into the second word of the Info menu item's data block, in place of the -1.
You will now find that the Info item has an arrow leading you to the Info box. It still beeps if you click on it, but that is of little consequence.