Chapter 23: Menu Selections
In the last chapter we introduced the concept of menus, and added an iconbar menu to our application. On the way, we implemented a program information window and a button to take users to our website – but the menu itself remained unable to pass on details of selections made by the user.
It is now time to look at the Menu_Selection event, and investigate how we might allow our application to start to respond to it.
The menu selection event
Details of selections from menus, as with all other information from the Wimp, are returned from calls made to Wimp_Poll in our main_poll() function. They arrive with a reason code of wimp_MENU_SELECTION, and the wimp_block filled in by the SWI contains data in the selection part of the union.
union wimp_block { wimp_draw redraw; wimp_open open; wimp_close close; wimp_leaving leaving; wimp_entering entering; wimp_pointer pointer; wimp_dragged dragged; wimp_key key; wimp_selection selection; wimp_scroll scroll; wimp_caret caret; wimp_pollword pollword; wimp_message message; byte reserved[256]; }; typedef union wimp_block wimp_block;
OSLib defines wimp_selection as follows:
struct wimp_selection { int items[9]; }; typedef struct wimp_selection wimp_selection;
Compared to the other blocks returned by Wimp_Poll, this block is quite basic. The items[] array contains a series of menu entry indexes which track through the menu tree to the selection, before being terminated by −1. An example might help, so take a look at Figure 23.1.
If the highlighted item were returned as a menu selection to our application, the block would contain the values shown in Table 23.1.
Note that the event data from the Wimp contains no information about which menu the selection came from. If our application had more than one menu, such as one over its window and one on the iconbar, or used the same menu in several places, it would be up to us to remember which menu was open and what context it was opened in.
Menu event dispatch
There are many different ways that we could implement a scheme for tracking our application’s menus and working out where wimp_MENU_SELECTION events need to be delivered to. We’ll go for something relatively simple, and create the two global variables shown in Listing 23.1 within c.menu which can be used to remember menu activity:
The first is a simple pointer to a wimp_menu structure, which we will use to track the menu that is currently open on screen. The second, menu_current_callback, is a pointer to a function which takes pointers to a wimp_menu structure and a wimp_selection structure: we will use this to hold the details of a function to which the details of the next wimp_MENU_SELECTION event should be sent.
So that our code can set these values, we will create a new function in c.menu to open an iconbar menu on screen, as shown in Listing 23.2.
To use this function for creating a menu, we will need to pass it a pointer to the wimp_menu structure holding the menu, a pointer to a wimp_pointer structure holding details of where the mouse was clicked, and a pointer to a function which will be sent details of the wimp_selection structure when the user makes a selection.
The code sets the two global variables up, then calls the function shown in Listing 23.3 – which is defined in SFLib’s menus library – to create the menu. Instead of hard-coding the menu position as we did in the last chapter, menus_create_iconbar_menu() counts up the number of entries and separators in the menu and works out the correct vertical offset to apply such that the bottom of the menu is 96 OS units from the base of the screen. It’s more work to write, but won’t need to be changed whenever we add an item to our menu!
We can now change the code in c.ibar which responds to Menu clicks on the iconbar as shown in Listing 23.4. Instead of calling wimp_create_menu() directly, ibar_mouse_click() will now call our new menu_open_ibar() instead.
This passes the same ibar_menu and pointer variables that we used perviously to derive the parameters for wimp_create_menu(), but also passes a pointer to a new ibar_menu_selection(). We can define this as shown in Listing 23.5.
There is very little to this code, and it should be familiar from the other user input decoding that we have done. Since the menu is a single level, we can switch on the first menu entry, and for now we will only implement the Quit option.
We now need some code to direct incoming wimp_MENU_SELECTION events, and this can be achieved by adding the function shown in Listing 23.6 to c.menu:
If the menu_current_callback pointer is not NULL, and the event data pointer in selection is not NULL, the callback function is called. The pointer to the event data is passed through, along with the wimp_menu pointer that was supplied when the menu was opened.
When the user selects an item from a menu, the Wimp will close the menu structure, which means that the two pointers that we hold (menu_current_menu and menu_current_callback) no longer have any purpose. We reset them both to NULL, so that they don’t accidentally get used in the future. This leaves a question, though: what happens if the menu closes without the user making a selection?
The answer is that the Wimp will send our task a Message_MenusDeleted to inform us that the menu is no longer visible. We can register a message handler for this using the code in Listing 23.7.
So far, the only message that we have looked at is Message_Quit, which our task receives if it must quit immediately, and its somewhat abrupt nature meant that there was no additional information supplied within the wimp_message block. It’s more usual for messages to contain information, however, and Message_MenusDeleted is no exception.
To get at this data, we start by casting the generic pointer to a wimp_message structure which is passed in the message parameter into a more specific pointer to a wimp_full_message_menus_deleted structure. We saw back in Chapter 2 that wimp_message is defined by OSLib as follows:
struct wimp_message { int size; wimp_t sender; int my_ref; int your_ref; bits action; byte reserved[236]; }; typedef struct wimp_message wimp_message;
These are the bare minimum set of fields that a message will contain, but depending on what the message actually is, some or all of those 236 reserved bytes could contain additional data which is specific to that message type. In the case of Message_MenusDeleted, OSLib defines two structures:
struct wimp_message_menus_deleted { wimp_menu *menu; }; typedef struct wimp_message_menus_deleted wimp_message_menus_deleted; struct wimp_full_message_menus_deleted { int size; wimp_t sender; int my_ref; int your_ref; bits action; wimp_menu *menu; }; typedef struct wimp_full_message_menus_deleted wimp_full_message_menus_deleted;
The first, wimp_message_menus_deleted, contains only the fields specific to the Message_MenusDeleted message, while the second, wimp_full_message_menus_deleted, contains all of the fields which appear in the message. This is a pattern followed throughout OSLib’s Wimp message support; usually, when casting an incoming wimp_message pointer to another type, it will be the variant which begins with wimp_full_message_ that is required.
The *menu pointer in the message gives the menu block that the Wimp is closing, so we check that this matches the block that we have saved in menu_current_menu and, if it does, clear the stored values. As with the icon-level event handler that we met in Section 22.5, the message handler returns TRUE if the menu handles matched, to indicate that it was able to use the message details. If they did not match, it returns FALSE so that the message can be passed on to any other interested parties within our application which might be listening.
The full set of changes can be found in Download 23.1. When compiled and run, it should finally be possible to quit our application from its iconbar menu!
Adjust clicks in menus
While the working iconbar menu is a big step forward, there is one small oddity. Try clicking on the unimplemented Info or Help... entries, and the menu will close – even if Adjust is used. The Style Guide, and convention, require that menus should remain on screen if selections are made with Adjust, but it’s another part of the menu interface that Acorn left for applications to implement.
Fortunately, it is fairly simple to implement the use of Adjust. All that we need to do is to check the button used to make the selection, by calling Wimp_GetPointerInfo, then call Wimp_CreateMenu again with a pointer to the same wimp_menu block if Adjust was used. The Wimp will recognise that it is the same menu, and will re-open it in the same position.
To make this work, we can update the menu_process_event() as shown in Listing 23.8.
If an error occurs whilst reading the mouse state, we don’t bother failing – instead we just assume that Select was used. The full code can be found in Download 23.2.
The application help
Alongside the Info and Quit entries, our iconbar menu contains a Help... entry. This is standard practice, which is strongly encouraged by the Style Guide, and should allow the user to access our application’s documentation.
RISC OS has always expected an application to contain a file called !Help within its application folder, which in our case would be !ExamplApp.!Help: this is *Filer_Run by the Filer when a user selects App. '!ExamplApp' → Help from the Filer menu as seen in Figure 23.2.
The easiest way to implement our own Help... entry is for us to *Filer_Run the same file: this way, we only need to keep one file up to date, and can make whatever arrangements we like for it to launch a text file, a StrongHelp manual, an HTML document or whatever we choose. To do this, we can update ibar_menu_selection() as shown in Listing 23.9.
Obviously we now need to ensure that the !ExamplApp.!Help exists, and to show the principle we have included a very simple text file.
Before we wrap up the code into a final download, there is one small piece of tidying up remaining. Now that our application can be quit from its iconbar menu, there is no need for Adjust clicks on its iconbar icon to have the same effect. We can therefore remove the wimp_CLICK_ADJUST clause from the switch statement in ibar_mouse_click(), as shown in Listing 23.10.
With these changes, our ExamplApp is at last starting to look like a normal RISC OS application. The full code can be found in Download 23.3.