Chapter 22: The Missing Menus

So far, we’ve looked at windows and icons in some detail, but have avoided the other main aspect of the RISC OS desktop. Menus are a central part of the way that a user interacts with the system, and an example can be seen in Figure 22.1.

Figure 22.1: The main menu in Zap is a good example of the genre

The implementation of menus in RISC OS has always seemed a little odd compared to the rest of the Wimp: there is almost a feeling of ‘afterthought’ about them, with many fairly fundamental aspects left up to the application developer. This may well account for the number of third-party libraries which have appeared over the years to help support the use of menus, not to mention Acorn’s Toolbox – and SFLib is no exception to this.

Whilst this tutorial covers the use of SFLib, at least part of its intention is to detail the underlying functionality of the Wimp. For this reason, we will start by putting together our own menu implementation using just the tools that Wimp gives us. Once we have understood things work, we will then be ready to go back and see what SFLib can do to help.

Menu definitions

Just as with windows and icons, which we started to cover in Chapter 12, menus are created from blocks of memory. It might be worth going back to refresh your memory of the wimp_window structure if necessary, because it shares a few similarities with wimp_menu. OSLib defines this new structure, and its child wimp_menu_entry, as follows:

struct wimp_menu {
        union {
                char            text[12];
                struct {
                        char    *text;
                        byte    reserved[8];
                } indirected_text;
        } title_data;
        wimp_colour             title_fg;
        wimp_colour             title_bg;
        wimp_colour             work_fg;
        wimp_colour             work_bg;
        int                     width;
        int                     height;
        int                     gap;
        wimp_menu_entry         entries[UNKNOWN];
};

struct wimp_menu_entry {
        wimp_menu_flags         menu_flags;
        wimp_menu               *sub_menu;
        wimp_icon_flags         icon_flags;
        wimp_icon_data          data;
};

In a similar way to the wimp_window structure, wimp_menu consists of a menu header followed by an array of wimp_menu_entry structures. Once again, OSLib defines the size of this array to be UNKNOWN, which actually turns out to be “1” – as we saw in Chapter 12, this was a way to make flexible arrays work within the confines of the C90 standard.

This causes us a problem, though. With windows, the ability to add icons using the Wimp_CreateIcon SWI enabled us to ignore the array of icon definitions at the end of the wimp_window structure until we were ready to load the contents in from a template file. This made it possible to define a variable of the wimp_window type and simply ignore the single icon definition that was included in the array at the end.

With menus, there’s no equivalent Wimp routine for adding entries to a definition – which means that we’re going to need to allocate enough memory to hold both the header and all of the entries that our menu will contain. There are a few ways to achieve this, but probably the easiest is to use malloc() for claiming the space that we need.

To do this, we will define a new menu_create() function shown in Listing 22.1. There is going to be a fair bit of new code related to menus, so we will add a new c.menu source file and the corresponding h.menu header to keep it all together.

wimp_menu *menu_create(char *title, int entries)
{
        wimp_menu       *menu = NULL;
        wimp_menu_entry *entry = NULL;
        char            *buffer = NULL;
        int             len, size;

        /* A menu must have one entry. */

        if (entries < 1)
                return NULL;

        /* Allocate the menu definition block. */

        size = wimp_SIZEOF_MENU(entries);

        menu = malloc(size);
        if (menu == NULL)
                return NULL;

        /* Set up the menu title. */

        len = strlen(title);
        if (len > 12)
                buffer = malloc(len + 1);

        if (buffer != NULL) {
                strncpy(buffer, title, len);
                buffer[len] = '\0';
                menu->title_data.indirected_text.text = buffer;
        } else {
                strncpy(menu->title_data.text, title, 12);
        }

        /* Set up the remainder of the menu header. */

        menu->title_fg = wimp_COLOUR_BLACK;
        menu->title_bg = wimp_COLOUR_LIGHT_GREY;
        menu->work_fg = wimp_COLOUR_BLACK;
        menu->work_bg = wimp_COLOUR_WHITE;
        menu->width = 16;
        menu->height = wimp_MENU_ITEM_HEIGHT;
        menu->gap = wimp_MENU_ITEM_GAP;

        /* Initialise the menu entries. */

        entry = menu->entries;

        while (entries-- > 0) {
                entry->menu_flags = (entries > 0) ? 0 : wimp_MENU_LAST;
                entry->sub_menu = NULL;
                entry->icon_flags = wimp_ICON_TEXT | wimp_ICON_FILLED |
                                (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                                (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT);
                strncpy(entry->data.text, "", 12);

                entry++;
        }

        /* Set the title indirection flag. */

        if (buffer != NULL)
                menu->entries[0].menu_flags |= wimp_MENU_TITLE_INDIRECTED;

        return menu;
}

Listing 22.1: The new menu_create() function

The function takes two parameters: a pointer to a string holding the required menu title, and the number of entries that the menu will contain. It will return either a pointer to a new wimp_menu structure, or NULL on failure. Menus must contain at least one entry, so the function simply returns NULL if this is not the case.

Next, we allocate the block for the menu, using malloc(). OSLib provides a wimp_SIZEOF_MENU() macro which we can use to calculate the number of bytes required, but we do need to include the standard stddef.h header for this to work. If the memory allocation fails, the function again returns NULL to indicate this.

There are 12 bytes of the menu header allocated to the menu title, and these behave very much like the 12 bytes of icon data in an icon definition. If the menu title is longer than 12 characters (remembering that the Wimp doesn’t require titles to be terminated if they are exactly 12 characters long), then a buffer is allocated, the name copied in and the buffer address inserted into the menu header. If the menu title will fit into the 12 bytes, or if the buffer allocation failed, then the text is copied in directly.

The remainder of the header consists of the four colours to be used for the menu title and work area, plus the width of the menu, the height of a menu item, and the gap between menu items (all in OS units). There is very little flexibility with these values, since the Style Guide reasonably requires that all menus should look the same. The colours should always be as shown, and the dimensions should always be the ones defined in the OSLib constants.

#define wimp_MENU_ITEM_HEIGHT           (44)
#define wimp_MENU_ITEM_GAP              (0)
#define wimp_MENU_ITEM_SEPARATION       (24)

The wimp_MENU_ITEM_SEPARATION constant included here for completeness is the height of a dashed separator between menu items; it can’t actually be changed, but the value can be useful when calculating menu heights. The width of the menu is set to 16 OS units for now; this will be updated as the menu entries are added, to take account of the length of their text.

With the header set up, we can move on to initialise the menu entries. Each entry consists of 24 bytes, as defined above: a set of menu item flags, a submenu pointer, then a set of icon flags and a standard wimp_icon_data block. The menu flags affect the behaviour of the menu entry, and are defined as follows:

#define wimp_MENU_TICKED                ((wimp_menu_flags) 0x1u)
#define wimp_MENU_SEPARATE              ((wimp_menu_flags) 0x2u)
#define wimp_MENU_WRITABLE              ((wimp_menu_flags) 0x4u)
#define wimp_MENU_GIVE_WARNING          ((wimp_menu_flags) 0x8u)
#define wimp_MENU_SUB_MENU_WHEN_SHADED  ((wimp_menu_flags) 0x10u)
#define wimp_MENU_LAST                  ((wimp_menu_flags) 0x80u)
#define wimp_MENU_TITLE_INDIRECTED      ((wimp_menu_flags) 0x100u)

Some of these will probably be obvious, some less so; we’ll come back to most of them over the following chapters, but for now there are two of interest. The last entry in the menu must have wimp_MENU_LAST set, otherwise the Wimp will not be able to find the end of the menu structure; for all other entries, the flags start unset. The submenu pointer (entry->sub_menu) should be initialised to NULL.

The remainder of the menu entry data is a subset of an icon definition, which is used by the Wimp to create the icon that forms the entry. The wimp_ICON_TEXT and wimp_ICON_FILLED flags should be set by default, and the colours should be a standard black and white. The icon data is initialised as an empty string, because the flags as supplied configure the entry to be non-indirected text icon.

There is one final menu flag to consider at this stage. If the menu title was made indirected above, then the wimp_MENU_TITLE_INDIRECTED flag must be set in the first entry of the menu. We do this at the end of the function, after initialising all of the flags, if the buffer pointer is not NULL.

With menu_create() defined, we can move on to define a second function called menu_entry() as shown in Listing 22.2, which will allow us to configure one of our blank menu entries.

void menu_entry(wimp_menu *menu, int entry, char *text, wimp_menu *sub_menu)
{
        wimp_menu_entry *definition = NULL;
        char            *buffer = NULL;
        int             len, width;

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

        /* Update the menu entry definition. */

        definition = menu->entries + entry;

        /* Set up the menu text. */

        len = strlen(text);
        if (len > 12)
                buffer = malloc(len + 1);

        if (buffer != NULL) {
                strncpy(buffer, text, len);
                buffer[len] = '\0';
                definition->data.indirected_text.text = text;
                definition->data.indirected_text.validation = "";
                definition->data.indirected_text.size = len + 1;
                definition->icon_flags |= wimp_ICON_INDIRECTED;
        } else {
                strncpy(definition->data.text, text, 12);
        }

        definition->sub_menu = sub_menu;

        /* Recalculate the menu width. */

        width = (16 * strlen(text)) + 16;
        if (width > menu->width)
                menu->width = width;
}

Listing 22.2: The new menu_entry() function

This takes four parameters: a pointer to the wimp_menu structure, the index of the menu entry to be configured, a pointer to the text to be used for the entry, and a pointer to a submenu (which for now we will set to NULL). If either of the other two pointers are NULL, the function will return immediately.

Using very similar logic to menu_create(), the title length is checked and a buffer allocated if it will need to be indirected. This time, we are dealing with standard icon data – so the process of updating the wimp_icon_data block and setting the wimp_ICON_INDIRECTED flag should be familiar from earlier chapters.

We then update the menu width, which was initialised in menu_create() to be 16 OS units. Since outline fonts arrived on the desktop in RISC OS 3.5, the Wimp has ignored the width field and automatically calculated menu widths based on the size of the entries in the current desktop font. This means that the width is being calculated for users of RISC OS 3.1 – it is in terms of the old system font, where characters were 16 OS units wide on the desktop.

An iconbar menu

Now that we have a means to create menus in our application, we can add something that has been missing since the start of this tutorial: an iconbar menu. Opening the c.ibar source file, we can #include menu.h, add a global variable and define some constants at the top of the file as seen in Listing 22.3.

/* Iconbar Menu Entries. */

#define IBAR_MENU_INFO 0
#define IBAR_MENU_HELP 1
#define IBAR_MENU_QUIT 2

/* Global Variables. */

static wimp_menu *ibar_menu;

Listing 22.3: The constants and global variables for the menu

You might already have noticed a difference from the way that we implemented our window in c.win. There, the wimp_window structure was only needed for long enough that it could be passed to Wimp_CreateWindow; at that point, we got a wimp_w window handle back, and the full wimp_window structure could be discarded. This meant that the global variable used to keep track of the window had the type wimp_w.

In contrast, menus have no such handle allocated to them by the Wimp. Unlike its window counterpart, the Wimp_CreateMenu SWI takes a wimp_menu block, creates a menu and displays it on screen in one operation. Whereas the wimp_w value returned by Wimp_CreateWindow is the window handle, if there is anything known as a “menu handle”, it is the pointer to the wimp_menu structure.

With the global variable in place, we can add a few lines to the ibar_initialise() function as shown in Listing 22.4.

/* 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", NULL);
menu_entry(ibar_menu, IBAR_MENU_HELP, "Help...", NULL);
menu_entry(ibar_menu, IBAR_MENU_QUIT, "Quit", NULL);

Listing 22.4: Defining the iconbar menu

The menu_create() function is called to allocate the memory required for the menu and set up the menu structure; if it fails, the initialisation ends. Next, menu_entry() is called three times to create the standard iconbar menu of a simple application.

After all this, getting the menu on to screen is quite simple, and involves a small change to the ibar_mouse_click() event handler as shown in Listing 22.5.

static void ibar_mouse_click(wimp_pointer *pointer)
{
        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                win_open();
                break;
        case wimp_CLICK_MENU:
                wimp_create_menu(ibar_menu, pointer->pos.x - 64, 228);
                break;
        case wimp_CLICK_ADJUST:
                main_quit_flag = TRUE;
                break;
        }
}

Listing 22.5: Updates to the Mouse Click event handler

If the click was with the Menu button, then Wimp_CreateMenu is called to display the menu on screen. OSLib defines the SWI as follows:

extern void wimp_create_menu(
        wimp_menu *menu,
        int x,
        int y
);

It takes three parameters: a pointer to the wimp_menu structure holding the menu to be shown, and the x and y coordinates of the top-left corner of the menu (excluding the title bar), in OS units relative to the screen origin in the bottom left corner of the display.

The coordinates are interesting, as we seem to have used some magic numbers. The x position is derived from the requirement that menus always open 64 OS units to the left of the point where the mouse click was recorded, which arrived in the click event data as pointer->pos.x. The y position comes from the requirement that all iconbar menus open with the bottom edge 96 OS units from the base of the screen. In our case, this is

y_pos = 96 + (3 * (wimp_MENU_ITEM_HEIGHT + wimp_MENU_ITEM_GAP)) + (0 * wimp_MENU_ITEM_SEPARATION);

where wimp_MENU_ITEM_HEIGHT is 44 OS units, wimp_MENU_ITEM_GAP is 0 OS units and wimp_MENU_ITEM_SEPARATION is 24 OS units. Hard-coding the value is not ideal, but it will do for now!

If we put all of the changes together, remembering to update the Makefile so that OBJS includes the new menu file, and compile them, the resulting application should respond to Menu clicks on the iconbar, as seen in Figure 22.2. It’s incomplete, and none of the entries work, but it’s a start!

Figure 22.2: Our application finally has an iconbar menu!

The code as it stands can be found in Download 22.1.

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

A program information window

Something that is obviously still missing from our new menu is the program information window, which would normally be expected to link from the Info entry at the top. Fortunately it is fairly easy to add one, so let’s resolve the problem before moving on.

Load !ExamplApp.Templates back into a template editor (we will be using WinEd again for the following description), and create a new window called “ProgInfo”. Double-click Select to open it up for editing.

The design guidelines for a program information window are fairly strict, so there’s limited room for creativity. We will need four information fields, so select the Comment and Display field from the Icon picker and drag them into our new window. We will also want to include a Website button to allow the user to visit our web page for more details, so drag the Cancel button over from the Icon picker.

All of the icons have the correct configurations, so it is just the text which needs to be changed in each case. With the title set and the visible area adjusted, the result should be similar to that shown in Figure 22.3. The icon numbers are not too important, except for the number of the Website button which we will be assuming is number 8 – this will be the case if it was the last icon to be added to the template. If it is not, then clicking Menu over the icon and sliding over Icon → Renumber → will lead to the Renumber icon dialogue box; enter 8 into the field and click on Renumber.

Figure 22.3: A template for the program information window

Before closing the window, we need to make some changes to its design details: as a dialogue box linked to a menu, there are some specific requirements that we must meet. Click Menu over the window and choose Edit window... to open the Edit window dialogue.

As we saw back in Section 12.3, we can turn off the bits of the window furniture that we don’t want on our dialogue. As part of a menu, the only tool that we want is the title bar, so untick all of the other switches in the Tools section. Also, as a dialogue box in a menu, the title bar should never change colour to indicate input focus (which we will meet in Chapter 27): we achieve this by changing the Input focus colour from 12 to 2 (which is the same as the default Title B. colour).

With the options as shown in Figure 22.4, click on Update to store the changes.

Figure 22.4: The program info window settings

Note that, at least in WinEd, not much will appear to change because windows retain all of their tools in edit mode. Click Menu over the window and choose Preview window to open it in preview mode, and it should appear as it will when we load it into our application – hopefully something like that seen in Figure 22.5. Menu over the preview will allow the window to be closed or returned to edit mode again.

Figure 22.5: Previewing the program info window in WinEd

With the new window design complete, remember to save the changes to the template file.

Making use of the new window template is fairly simple, and requires few additions to our code. First, we can define the important icon handles from the template file at the top of the file, as shown in Listing 22.6.

/* Program Info Window Icons. */

#define IBAR_PROGINFO_ICON_WEB 8

Listing 22.6: The program information window icon constants

This value is correct for the template file as included in Download 22.2, but if you have designed your own template then you will need to check that it matches the handle of the Website button.

We are already opening the window templates while the application initialises, so the file is open when ibar_initialise() is called. We can therefore add the following additional lines to that function, before the menu is set up as shown in Listing 22.7.

/* 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);

Listing 22.7: Creating the program information window

If this looks familiar, that’s because it is the same code that we used in Section 19.5 to create the main window template in win_initialise().

All that the Wimp requires to link a dialogue box into a menu structure is for us to store the wimp_w handle for the chosen window in the sub_menu field of the appropriate wimp_menu_entry. Now that we have the window handle for the program information window in prog_info, we can update the call to menu_entry() which creates the Info entry as shown in Listing 22.8.

It is necessary to cast the wimp_w to a pointer to a wimp_menu, but that is what the Wimp expects. The full code can be found in Download 22.2, and the result can be seen in Figure 22.6.

Figure 22.6: The program info window is finally included, too!

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

A website link

Before moving on to make our menu respond to the user, we should probably make the Website button do something useful and for this, we will need a Mouse_Click event handler to respond to clicks on the button. Since there is only one button in the window, we will define an icon-level handler for it instead of creating a window-level handler for the whole program information window. The main advantage is that the event dispatcher will check the icon handle for us, and only call our code if the click was on the icon that we specified.

We met the concept of window- and icon-level event handlers, which are part of SFLib’s event library, in Section 20.4. To define one to handle clicks on the Website button will require the code shown in Listing 22.9.

static osbool ibar_proginfo_web_click(wimp_pointer *pointer)
{
        url_launch("http://www.stevefryatt.org.uk/docs/wimp/");

        if (pointer->buttons == wimp_CLICK_SELECT)
                wimp_create_menu((wimp_menu *) -1, 0, 0);

        return TRUE;
}

Listing 22.9: An icon-level handler for clicks on the Website button

Launching URLs is too large a topic for this quick aside, but SFLib’s URL library, which we can use if we include sflib/url.h, contains a useful url_launch() function that does exactly what it says. If we pass it a URL, such as the one for this tutorial, it will try the various methods of launching URLs which exist on RISC OS until either a browser responds or it runs out of options. The URL code needs to register some message handlers with the event library, so we also need to call url_initialise() from within our main_initialise() function.

If the user clicks Select on our Website button, they might reasonably expect the dialogue box and menu to close, so we test for the button being wimp_CLICK_SELECT and call Wimp_CreateMenu with a pointer of −1 to request that the Wimp closes the currently open menu structure.

Having launched the URL and optionally closed the menu structure, there isn’t much else to do. As a result, the handler will return TRUE, to indicate that it considers the event handling to be complete – what this means was explained in more detail when we introduced icon-level handlers in Section 20.4.

All that remains now is to register the handler, which we do in ibar_initialise() using the line shown in Listing 22.10.

event_add_window_icon_click(prog_info, IBAR_PROGINFO_ICON_WEB, ibar_proginfo_web_click);

Listing 22.10: Registering the icon-level event handler

The event_add_window_icon_click() is defined as

osbool event_add_window_icon_click(
        wimp_w w,
        wimp_i i,
        osbool (*callback)(wimp_pointer *pointer)
);

The code for these changes can be found in Download 22.3.

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