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.

Figure 23.1: A three level menu selection

If the highlighted item were returned as a menu selection to our application, the block would contain the values shown in Table 23.1.

Block LocationValueMenu Entry
items[0]1Help
items[1]0Mode
items[2]3Email
items[3]−1

Table 23.1: The values returned in the Wimp_Poll block

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:

/* Global Variables. */

static wimp_menu *menu_current_menu = NULL;

static void (*menu_current_callback)(wimp_menu *menu, wimp_selection *selection) = NULL;

Listing 23.1: Global variables to track the current menu

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.

void menu_open_ibar(wimp_menu *menu, wimp_pointer *pointer,
                void (*callback)(wimp_menu *menu, wimp_selection *selection))
{
        menu_current_menu = menu;
        menu_current_callback = callback;

        menus_create_iconbar_menu(menu, pointer);
}

Listing 23.2: A function to open an iconbar menu

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!

wimp_menu *menus_create_iconbar_menu(wimp_menu *menu, wimp_pointer *pointer)
{
        int entry = 0, entries = 0, lines = 0;

        if (menu == NULL || pointer == NULL)
                return NULL;

        do {
                entries++;
                if ((menu->entries[entry].menu_flags & wimp_MENU_SEPARATE) != 0)
                        lines++;
        } while (!(menu->entries[entry++].menu_flags & wimp_MENU_LAST));

        if (xwimp_create_menu(menu, pointer->pos.x - 64,
                        96 + (entries * (menu->height + menu->gap)) + (lines * wimp_MENU_ITEM_SEPARATION)) != NULL)
                return NULL;

        return menu;
}

Listing 23.3: The code used by SFLib to position an iconbar 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.

static void ibar_mouse_click(wimp_pointer *pointer)
{
        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                win_open();
                break;
        case wimp_CLICK_MENU:
                menu_open_ibar(ibar_menu, pointer, ibar_menu_selection);
                break;
        case wimp_CLICK_ADJUST:
                main_quit_flag = TRUE;
                break;
        }
}

Listing 23.4: Updating the iconbar Mouse Click event handler

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.

static void ibar_menu_selection(wimp_menu *menu, wimp_selection *selection)
{
        switch (selection->items[0]) {
        case IBAR_MENU_QUIT:
                main_quit_flag = TRUE;
                break;
        }
}

Listing 23.5: The new iconbar Menu Selection event handler

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:

void menu_process_event(wimp_selection *selection)
{
        if (menu_current_callback != NULL && selection != NULL)
                menu_current_callback(menu_current_menu, selection);

        /* Clear our saved menu details. */

        menu_current_menu = NULL;
        menu_current_callback = NULL;
}

Listing 23.6: Handling the incoming Menu Selection events from the Wimp

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.

void menu_initialise(void)
{
        event_add_message_handler(message_MENUS_DELETED, EVENT_MESSAGE_INCOMING, menu_message_menus_deleted);
}

static osbool menu_message_menus_deleted(wimp_message *message)
{
        wimp_full_message_menus_deleted *deleted = (wimp_full_message_menus_deleted *) message;

        /* Check that the deleted menu is ours. */

        if (deleted == NULL || deleted->menu != menu_current_menu)
                return FALSE;

        /* Clear our saved menu details. */

        menu_current_menu = NULL;
        menu_current_callback = NULL;

        return TRUE;
}

Listing 23.7: Handling Message_MenusDeleted messages

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!

Download 23.1
The source code and files in this example are made available under Version 1.2 of the European Union Public Licence.

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.

void menu_process_event(wimp_selection *selection)
{
        wimp_pointer    pointer;
        os_error        *error;

        /* Read the mouse button used to make the selection. */

        error = xwimp_get_pointer_info(&pointer);
        if (error != NULL)
                pointer.buttons = wimp_CLICK_SELECT;

        /* Call the client back with the selection info. */

        if (menu_current_callback != NULL && selection != NULL)
                menu_current_callback(menu_current_menu, selection);

        /* Either re-open the menu on Adjust, or clear the saved details. */

        if (menu_current_menu != NULL && pointer.buttons == wimp_CLICK_ADJUST) {
                xwimp_create_menu(menu_current_menu, 0, 0);
        } else {
                menu_current_menu = NULL;
                menu_current_callback = NULL;
        }
}

Listing 23.8: Handling Adjust clicks on menu entries

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.

Download 23.2
The source code and files in this example are made available under Version 1.2 of the European Union Public Licence.

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.

Figure 23.2: The Filer has always offered a way to access application help

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.

static void ibar_menu_selection(wimp_menu *menu, wimp_selection *selection)
{
        os_error *error;

        switch (selection->items[0]) {
        case IBAR_MENU_HELP:
                error = xos_cli("%Filer_Run <ExamplApp$Dir>.!Help");
                if (error != NULL)
                        error_report_os_error(error, wimp_ERROR_BOX_OK_ICON);
                break;
        case IBAR_MENU_QUIT:
                main_quit_flag = TRUE;
                break;
        }
}

Listing 23.9: Adding the Help... entry to the Menu Selection event handler

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.

static void ibar_mouse_click(wimp_pointer *pointer)
{
        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                win_open();
                break;
        case wimp_CLICK_MENU:
                menu_open_ibar(ibar_menu, pointer, ibar_menu_selection);
                break;
        }
}

Listing 23.10: The iconbar Mouse Click event handler with Adjust removed

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.

Download 23.3
The source code and files in this example are made available under Version 1.2 of the European Union Public Licence.