Chapter 26: Menus Revisited

Over the past four chapters, we have seen how the Wimp handles menus and have built up a library of code in c.menu which can be used to display them and process selections made by the user. Whilst this has enabled us to see how the facilities offered by the Wimp work, it is likely to come as little surprise to learn that similar functionality is provided by SFLib’s event library. In this chapter, we will look at what is required to make use of it.

Menu events

One of the biggest probems with the Wimp’s native support for menus is the somewhat piecemeal approach used. It is left up to the application to open create and open menus using Wimp_CreateMenu, and to re-open them if selections are made with Adjust. In addition, as we have already seen, it is also the responsibility of the application to ensure that the menu is correctly set up before Wimp_CreateMenu is called.

Selections by the user are reported through the Menu_Selection event, but closure of a menu is harder to pin down. If a selection is made, the application can assume that the menu has closed if it does not take action to reopen it; if a menu closes for any other reason, then it is instead reported by the Message_MenusDeleted message.

In an attempt to improve this situation, SFLib’s event library provides a range of support for menus. Menus can be attached to windows or individual icons, and they are opened automatically when requested by the user. To allow an application to interact with its menus, the library creates the four window-level ‘pseudo-events’ listed in Table 26.1, which are reported to the window where the menu is attached.

EventPurpose
PrepareRaised when a menu is about to be opened or reopened using Wimp_CreateMenu
WarningRaised when a Message_SubmenuWarning is received for a menu
SelectionRaised when a selection is received from a Menu_Selection event
CloseRaised when a menu is closed, following a Menu_Selection event or on receipt of a Message_MenusDeleted

Table 26.1: The menu 'pseudo-events' implemented by SFLib's event library

Using these ‘pseudo-events’, an application can streamline its menu support. It is guaranteed to receive a Menu Prepare event before a menu opens – which allows it to allocate any resources needed and update the menu state. It will receive a Menu_Selection event whenever the user makes a selection, which might be followed by another Menu Prepare event if Adjust was used. Finally, whether or not a selection was made, it will receive a single Menu Close event when the menu is eventually closed, so that any resources claimed during the Menu Prepare event can be released again. The Menu Warning events are raised whenever a Message_SubmenuWarning is received for a menu; we will be meeting these later on.

In addition to the window-level events listed above, the library also provides support for pop-up menus. Simple menus can be left to the library to look after, or, if more interactivity is required, an icon-level event can be generated whenever a new selection is made. For truly custom menu support, it is possible for an application to manage pop-up menus itself via the standard window-level events.

The SFLib event library implements these ‘pseudo-events’ in a similar way to the code in our own c.menu file. The approach taken by our application up to now is perfectly valid, but if you’re using SFLib’s event handling, it makes sense to use the menu support as well!

Updating the iconbar

The easiest menu to update for use with SFLib’s menu events will be the one on the iconbar, since this is self-contained. At present, we open the menu ourselves with a call to menu_open_ibar(), passing it a pointer to the ibar_menu_selection() event hander for returning any Menu_Selection events which are raised.

In keeping with the Style Guide, the event library allows us to add a single menu to any window in our application – this will then be displayed whenever the Menu button is clicked anywhere in that window. A menu is added by calling the event_add_window_menu(), which is defined as follows:

osbool event_add_window_menu(
        wimp_w w,
        wimp_menu *menu
);

It takes two parameters: the window handle in w, and a pointer to the menu’s wimp_menu block in *menu. The Wimp treats the iconbar as a special window with a handle of wimp_ICON_BAR, so adding an iconbar menu to our application is simply a case of calling

event_add_window_menu(wimp_ICON_BAR, ibar_menu);

to associate our iconbar menu (whose handle is held in the ibar_menu variable) with the wimp_ICON_BAR window.

If the event library has opened a menu over our window as a result of this association, then it will start to return the appropriate ‘pseudo-events’ to any event handlers that we choose to register. We are interested in Menu_Selection events, so we can register a handler using the event_add_window_menu_selection() function.

osbool event_add_window_menu_selection(
        wimp_w w,
        void (*callback)(wimp_w w, wimp_menu *m, wimp_selection *selection)
);

This again takes two parameters: the window handle in w, which will again be wimp_ICON_BAR for the iconbar, and a pointer to an an event handler function in *callback. We can call it with

event_add_window_menu_selection(wimp_ICON_BAR, ibar_menu_selection);

to associate our existing Menu_Selection event handler with the library’s event.

Putting all of the changes together, we end up with a new ibar_initialise() function as shown in Listing 26.1.

void ibar_initialise(char *sprite)
{
        os_error                *error;
        wimp_window             *window_definition;
        wimp_w                  prog_info;
        wimp_icon_create        icon_bar;

        /* Program Info Window. */

        window_definition = windows_load_template("ProgInfo");
        if (window_definition == NULL) {
                error_report_error("Failed to load ProgInfo template");
                return;
        }

        prog_info = wimp_create_window(window_definition);
        free(window_definition);

        event_add_window_icon_click(prog_info, IBAR_PROGINFO_ICON_WEB, ibar_proginfo_web_click);

        /* Iconbar Menu. */

        ibar_menu = menu_create("Example", 3);
        if (ibar_menu == NULL) {
                error_report_error("Failed to create Iconbar Menu");
                return;
        }

        menu_entry(ibar_menu, IBAR_MENU_INFO, "Info", (wimp_menu *) prog_info);
        menu_entry(ibar_menu, IBAR_MENU_HELP, "Help...", NULL);
        menu_entry(ibar_menu, IBAR_MENU_QUIT, "Quit", NULL);

        /* Iconbar Icon. */

        icon_bar.w = wimp_ICON_BAR_RIGHT;
        icon_bar.icon.extent.x0 = 0;
        icon_bar.icon.extent.y0 = 0;
        icon_bar.icon.extent.x1 = 68;
        icon_bar.icon.extent.y1 = 68;
        icon_bar.icon.flags = wimp_ICON_SPRITE | (wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT);
        strncpy(icon_bar.icon.data.sprite, sprite, osspriteop_NAME_LIMIT);

        error = xwimp_create_icon(&icon_bar, NULL);
        if (error != NULL) {
                error_report_error(error->errmess);
                return;
        }

        /* Register event handlers. */

        event_add_window_mouse_event(wimp_ICON_BAR, ibar_mouse_click);
        event_add_window_menu(wimp_ICON_BAR, ibar_menu);
        event_add_window_menu_selection(wimp_ICON_BAR, ibar_menu_selection);
}

Listing 26.1: The updated ibar_initialise() function

A couple of small changes are required to ibar_menu_selection(), since the event library expects a slightly different set of parameters for its event handler. In addition to the wimp_menu pointer and wimp_selection block, the library will also pass the handle of the window to which the menu was attached; the update can be seen in Listing 26.2.

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

        if (menu != ibar_menu)
                return;

        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 26.2: The updated Menu Selection event handler

We have also added a check to make sure that the menu block is the one stored in ibar_menu before proceeding to act on the event. This isn’t essential here, since there is only one menu attached to the iconbar, but it is good practice for when things become more complicated.

The final change is to the ibar_mouse_click() event handler, as there is no longer any need to open the menu ourselves since the event library will do this for us. We can therefore remove the case statement to handle the wimp_CLICK_MENU condition, as shown in Listing 26.3.

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

Listing 26.3: We only need to handle Select clicks on the icon now

The full code can be found in Download 26.1. When compiled and run, the application should behave the same, but will now be leaving the iconbar menu to SFLib’s event library to look after.

Download 26.1
The source code and files in this example are licenced under the EUPL v1.1.

A window menu

We saw in the last section that SFLib’s event library allowed a menu to be associated with our application’s iconbar icon, so that clicking Menu over the icon opened the menu without our code having to do anything. This support extends to windows, so we can associate our main menu – the one with the decimal place options – with the main window of our application in a similar way.

There is a difference, however, because unlike the iconbar menu, our main menu requires setting up using the win_set_menu() before it is opened on screen. Fortunately, the library provides a Menu Prepare ‘pseudo-event’ to enable us to do this. In a similar way to the Menu_Selection event that we have alsready seen, an event handler can be registered with the Menu Prepare event using the event_add_window_menu_prepare() function. This is defined as

osbool event_add_window_menu_prepare(
        wimp_w w,
        void (*callback)(wimp_w w, wimp_menu *m, wimp_pointer *pointer)
);

To implement this, we will need to add some lines to register the necessary event handlers in our win_initialise() function, as seen in Listing 26.4.

/* Register event handlers. */

event_add_window_mouse_event(win_handle, win_mouse_click);
event_add_window_menu(win_handle, win_menu);
event_add_window_menu_prepare(win_handle, win_set_menu);
event_add_window_menu_selection(win_handle, win_menu_selection);

Listing 26.4: The updated event handler resitrations in win_initialise()

As with the iconbar menu in Listing 26.1, we associate the menu handle with the window, and then register Menu Prepare and Menu_Selection event handlers. Our existing win_set_menu() and win_menu_selection() functions can be used, with some modifications – those required to win_set_menu() can be seen in Listing 26.5.

static void win_set_menu(wimp_w w, wimp_menu *menu, wimp_pointer *pointer)
{
        int places;

        if (menu != win_menu)
                return;

        places = calc_get_places();

        menus_tick_entry(win_menu, WIN_MENU_DECIMAL1, places == 1);
        menus_tick_entry(win_menu, WIN_MENU_DECIMAL2, places == 2);
        menus_tick_entry(win_menu, WIN_MENU_DECIMAL3, places == 3);
}

Listing 26.5: Preparing the main menu for display

The main changes are to the parameters taken by the function: as with the Menu_Selection handler that we saw above, the handler for Menu Prepare events takes the handle of the window over which the menu was opened in w and a pointer to the wimp_menu block for the menu in *menu.

The third parameter, *pointer, is a pointer to a wimp_pointer block. If the handler is being called when a menu first opens due to a Menu click, then this points to the block returned by the Wimp in the original Mouse_Click event. When the handler is subsequently called before the menu is re-opened due to a selection being made with Adjust, the pointer will be NULL. Our application can use this to determine whether the menu is newly opening, or simply remaining on screen. This distinction doesn’t matter to us, so we simply check the handle of the menu to make sure that it is the main menu, and then update the three ticks as we previously did.

The next piece of code to be updated is the Menu_Selection event handler, which will follow the same format as the one that we updated for the iconbar menu. The changes can be seen in Listing 26.6: the w parameter has been added, and we check that the correct menu pointer is in *menu. There is also no need to call win_set_menu() before returning, as the event library will do this for us automatically.

static void win_menu_selection(wimp_w window, wimp_menu *menu, wimp_selection *selection)
{
        if (menu != win_menu)
                return;

        switch (selection->items[0]) {
        case WIN_MENU_DECIMAL1:
                calc_set_format(1);
                break;
        case WIN_MENU_DECIMAL2:
                calc_set_format(2);
                break;
        case WIN_MENU_DECIMAL3:
                calc_set_format(3);
                break;
        }

        win_update_all_calculations();
}

Listing 26.6: Handling selections from the main menu

With our event handlers hooked up to the ‘pseudo-events’, we no longer need to open the menu when the user clicks Menu over the window. This means that we can update the win_mouse_click() function as shown in Listing 26.7, so that it returns to just opening the pop-up menu.

static void win_mouse_click(wimp_pointer *pointer)
{
        if (pointer->i == WIN_ICON_SHAPE_POPUP && pointer->buttons == wimp_CLICK_SELECT)
                menu_open_popup(win_shape_menu, pointer, win_shape_menu_selection);
}

Listing 26.7: We only need to open the pop-up menu on Mouse Click events

With these changes in place, the code continue to function the same when compiled and run. The full set of updates can be found in Download 26.2.

Download 26.2
The source code and files in this example are licenced under the EUPL v1.1.

Automating pop-up menus

Now that we have given the problems of managing our main and iconbar menus over to the SFLib event library, it would be useful if we could delegate the pop-up menu as well. Fortunately we can, since the library allows menus to be attached to icons within a window as part of its icon-level event handling that we met back in Section 20.4.

Various levels of support are available, starting from a menu which is attached to an icon but which requires us to look after it through all of the usual ‘pseudo-events’ that we have already seen. Whilst this is useful for doing custom things, it can be overklill for a simple pop-up menu and so, at the other end of the scale is a fully automatic option where the library looks after the menu completely and provides a function through which an application can read the current selection. We will need something somewhere in between: our menu is standard, but we do need to know when a new shape has been selected so that we can update the display.

To register a pop-up menu with an icon, the event library provides the event_add_window_icon_popup() function.

osbool event_add_window_icon_popup(
        wimp_w w,
        wimp_i i,
        wimp_menu *menu,
        wimp_i field,
        char *token
);

This takes five parameters, of which three are always required – these are shown graphically in Figure 26.1. The w parameter should be the handle of the window containing the icon, while i should be the handle of the icon itself. A pointer to the wimp_menu block of the menu to be attached should be passed in the *menu parameter.

What value is passed to the field parameter will depend on how much control we want over the menu; passing wimp_ICON_WINDOW (indicating no icon) would leave us to look after everything, but since we want the library to look after the menu for us we also need to supply the handle of the icon to be used as the pop-up menu field. The *token parameter is a pointer to an optional MessageTrans token; since we haven’t met MessageTrans yet, we can safely pass NULL for this.

Figure 26.1: The parts of a pup-up menu and its field

For our menu, we will need to make the call as follows:

event_add_window_icon_popup(win_handle, WIN_ICON_SHAPE_POPUP,
                win_shape_menu, WIN_ICON_SHAPE_FIELD, NULL);

So far, this has associated the pop-up menu with the icon, but our application will still receive events via the usual window-level event handlers. If we had passed wimp_ICON_WINDOW in the field parameter, then the menu would produce the four ‘pseudo-events’; since we have specified a field icon, our application will only receive Menu_Selection events.

As we have provided a field icon, we can simplify things further by using the event_set_window_icon_popup_action() to change the behaviour of the menu; this is defined as follows:

osbool event_set_window_icon_popup_action(
        wimp_w w,
        wimp_i i,
        osbool complete,
        osbool (*callback)(wimp_w, wimp_menu *, unsigned)
);

By supplying the details of the pop-up icon from the previous call to event_add_window_icon_popup(), we can control how the event library handles the user interacting with the menu in more detail. The complete parameter allows us to choose whether the library considers its handling of the event ‘complete’ according to the explanation in Section 20.4. The *callback parameter allows an icon-level event handler to be registered for the menu.

A full list of the combinations can be seen in Table 26.2. Remember that event_set_window_icon_popup_action() can only be called if the field parameter to the event_add_window_icon_popup() function was not wimp_ICON_WINDOW – if it was, then all four menu ‘pseudo-events’ will be returned.

complete*callbackResult
TRUE NULL The library will handle the update of the pop-up menu and field, and the application will not receive any events.
FALSE NULL The library will handle the update of the pop-up menu and field, but selections will be reported to the application through the window-level Menu_Selection event.
FALSE Function Pointer The library will handle the update of the pop-up menu and field, but selections will initially be reported to the application through the icon-level pop-up menu selection event. If its handler returns FALSE to indicate “not complete” a window-level Menu_Selection event will then be raised.

Table 26.2: The different pop-up menh handling options

In the case of our menu, although we want to leave the event library to sort out its operation, we still need to know when a selection has been made so that we can update the shape that is on display. We could do this using the window-level Menu_Selection events, but since we already have a separate function to process selections from the pop-up menu, we will register this as an icon-level handler as follows:

event_set_window_icon_popup_action(win_handle, WIN_ICON_SHAPE_POPUP,
                FALSE, win_shape_menu_selection);

The only thing that remains for us to do now is to set the default selection, which we can do using the event_set_window_icon_popup_selection() function provided by the library. This takes the wimp_w and wimp_i handles for the pop-up menu icon, and the required index for the selection; it will set the menu ticks and update the pop-up menu field icon accordingly.

osbool event_set_window_icon_popup_selection(
        wimp_w w,
        wimp_i i,
        unsigned selection
);

A corresponding event_get_window_icon_popup_selection() function can be used to read the index of the current selection showing in the field, which can be useful in a situation where receiving and tracking events would be over complicated.

unsigned event_get_window_icon_popup_selection(
        wimp_w w,
        wimp_i i
);

Putting these changes together results in the amendments to the win_initialise() function shown in Listing 26.8. In addition to registering the pop-up menu and its handler, it initialises the menu to show the sqaure. Note that the Mouse_Click handler is no longer being registered; since both menus are now displayed automatically, we no longer require the win_mouse_click() function and have therefore deleted it from the code.

/* Register event handlers. */

event_add_window_menu(win_handle, win_menu);
event_add_window_menu_prepare(win_handle, win_set_menu);
event_add_window_menu_selection(win_handle, win_menu_selection);
event_add_window_icon_popup(win_handle, WIN_ICON_SHAPE_POPUP, win_shape_menu, WIN_ICON_SHAPE_FIELD, NULL);
event_set_window_icon_popup_action(win_handle, WIN_ICON_SHAPE_POPUP, FALSE, win_shape_menu_selection);

/* Initialise the window contents. */

win_set_shape(CALC_SHAPE_SQUARE);
event_set_window_icon_popup_selection(win_handle, WIN_ICON_SHAPE_POPUP, WIN_MENU_SHAPE_SQUARE);

Listing 26.8: Registering the pop-up menu in win_initialise()

The updated win_shape_menu_selection() event handler has been updated to suit the prototype required by the icon-level pop-up menu event raised by SFLib’s event library, as seen in Listing 26.9. It receives the wimp_w and wimp_i window and icon handles for the menu in window and icon; the currently selected index is provided in selection.

As with other icon-level event handlers, the function returns an osbool value, indicating whether the handling of the event should be considered complete. We do not wish to receive details of the event to the window-level win_menu_selection() function, so win_shape_menu_selection() returns TRUE to indicate that it has completely handled it.

static osbool win_shape_menu_selection(wimp_w window, wimp_menu *menu, unsigned selection)
{
        switch (selection) {
        case WIN_MENU_SHAPE_CIRCLE:
                win_set_shape(CALC_SHAPE_CIRCLE);
                break;
        case WIN_MENU_SHAPE_TRIANGLE:
                win_set_shape(CALC_SHAPE_TRIANGLE);
                break;
        case WIN_MENU_SHAPE_SQUARE:
                win_set_shape(CALC_SHAPE_SQUARE);
                break;
        }

        return TRUE;
}

Listing 26.9: The pop-up menu selection event handler

Once again, the updated code can be found in Download 26.3; from a user’s perspective, it should function in the same way as the other versions in this chapter.

Download 26.3
The source code and files in this example are licenced under the EUPL v1.1.

Tidying up

With the changes above in place, all of the menus in our application are being handled by SFLib’s event library – which means that the support for Menu_Selection events and Message_MenusDeleted in c.menus is no longer required.

To tidy up, we can remove the calls to the menu_initialise() and menu_process_event() functions from c.main, and then delete them from c.menus. The menu_message_menus_deleted() function is no longer required, either. Finally, the menu_open(), menu_open_ibar(), menu_open_popup() and menu_set_popup_menu() functions are not required, since the library does these actions for us.

With these removed, the c.menu file only contains the menu_create() and menu_entry() functions for setting up menus blocks. We will keep these for now, since the system doesn’s provide any standard way of building menus. The newly-streamlined code can be found in Download 26.4.

Download 26.4
The source code and files in this example are licenced under the EUPL v1.1.

Now that we have tidied up our menu implementation, we can move on in the next chapter to look at another new area of the Wimp.