Chapter 20: Radio Icons Revisited

At the end of the last chapter we had our application back on the iconbar, whilst loading its window design from a template file. There were a few loose ends left hanging, which we can now start to tidy up.

We will also move some of the items that we are currently implementing in our application over to use SFLib’s functionality, which will simplify the code further.

Pre-selecting radio icons

When our application first starts up, none of the three radio icons are selected. Whilst not actually harmful, it isn’t great from a user interface perspective – and it would be a much more significant issue were the window a dialogue box attempting to convey the current state of whatever the icons controlled!

We could, in fact, save the selected state of one of the icons in the window template, so that it is pre-selected when the window is loaded into our application. This isn’t a good way to go, however: aside from anything else, it wouldn’t help with the dialogue box, where we need to select whichever icon reflects the currently-configured state of the application. Instead, it’s better to select the required icon when the window opens.

After an icon has been created by Wimp_CreateWindow or Wimp_CreateIcon, we can change some – but not all – of the contents of the wimp_icon structure.

struct wimp_icon {
        os_box extent;
        wimp_icon_flags flags;
        wimp_icon_data data;
};

typedef struct wimp_icon wimp_icon;

Historically, all that could be changed were the icon flags, but with the advent of RISC OS 3.5, a new Wimp_ResizeIcon SWI made it possible to alter an icon’s extent so that it could be moved around within the window work area. It remains impossible to change the 12 bytes of icon data, but if an icon is indirected we can change the contents of the indirected buffer and validation string that the data points to.

Changing the flags is done with the Wimp_SetIconState SWI, which OSLib – a little unusually – defines in two different ways:

extern void wimp_set_icon_state(
        wimp_w w,
        wimp_i i,
        wimp_icon_flags eor_bits,
        wimp_icon_flags clear_bits
);

extern void wimp_set_icon_state_from_block(
        wimp_set_icon_state_block const *block
);

The second form takes a pointer to a wimp_set_icon_state_block structure, which is defined like this:

struct wimp_set_icon_state_block {
        wimp_w w;
        wimp_i i;
        wimp_icon_flags eor_bits;
        wimp_icon_flags clear_bits;
};

typedef struct wimp_set_icon_state_block wimp_set_icon_state_block;

The wimp_set_icon_state_from_block() version, which takes the structure pointer, is the one that mirrors how the real Wimp_SetIconState works. However, unlike most of the other memory blocks used by the Wimp, this one doesn’t turn up anywhere else – meaning that in many cases it is more convenient to pass the values as parameters to a normal function call. For this reason, OSLib offers us wimp_set_icon_state() as an alternative.

In addition to a standard pair of window and icon handles, the call takes two sets of wimp_icon_flags which it refers to as the eor_bits and clear_bits. It takes the existing flags from the icon and performs the following operation to create a new set:

new_flags = (old_flags & (~clear_bits)) ^ eor_bits;

Whilst this might not look intuitive, it allows us to perform some fairly complex operations on the icon flags in a single call. The full set of operations which can be carried out on each flag bit are shown in Table 20.1.

ClearEOREffect on flag
00Preserve flag state (no change)
01Toggle flag state
10Clear flag
11Set flag

Table 20.1: The operations which can be carried out by Wimp_SetIconState

In order to select the first radio icon, we can simply add the code in Listing 20.1 to the end of the win_initialise() function.

wimp_set_icon_state(win_handle, WIN_ICON_OPTION1, wimp_ICON_SELECTED, wimp_ICON_SELECTED);

Listing 20.1: Selecting the chosen radio icon

Having the wimp_ICON_SELECTED bit set in both eor_bits and clear_bits puts us into the “Set flag” row of the table. For a complete belt-and-braces approach, which is always a good idea, we can then deselect the other two in a similar way using the code in Listing 20.2.

wimp_set_icon_state(win_handle, WIN_ICON_OPTION2, 0, wimp_ICON_SELECTED);
wimp_set_icon_state(win_handle, WIN_ICON_OPTION3, 0, wimp_ICON_SELECTED);

Listing 20.2: Clearing the unwanted radio icons

In this case, wimp_ICON_SELECTED is clear in eor_bits and set in clear_bits, which puts us into the “Clear flag” row of the table.

The updated code can be found in Download 20.1.

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

Handling Adjust clicks

Unfortunately, the initial configuration of the options isn’t the only problem with our radio icon implementation: Acorn kindly left another trap for the unwary, which we need to resolve.

So long as we use Select to click on the radio icons, there’s no problem: click on an icon that isn’t selected and it becomes selected; click again and it stays selected. What if we use Adjust, though? In this case, clicking on an unselected icon still works as expected, but clicking on the selected one deselects it – leaving the set of icons in an ambiguous state.

There is, a little surprisingly, no way to fix this within the Wimp. Allowing all of the options to be turned off could be valid on some cases, but when it isn’t, the Wimp leaves it up to the developer to enforce the rule.

Fortunately there’s a fairly simple fix: in the win_mouse_click() event handler, we can use Wimp_SetIconState to force the radio icon to be selected if it was clicked on with Adjust as seen in Listing 20.3.

if ((pointer->buttons == wimp_CLICK_ADJUST) && (option > 0))
        wimp_set_icon_state(win_handle, pointer->i, wimp_ICON_SELECTED, wimp_ICON_SELECTED);

Listing 20.3: Catching Adjust clicks on the radio icons

We’re checking that the mouse button was indeed Adjust, and also that the click was on one of the radio icons (if option has been set to a value greater than zero), to make sure that we don’t inadvertantly try to set the state of another icon in the window at some stage in the future. The changes can be found in Download 20.2.

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

Whilst we’re on the subject of Adjust clicks on radio icons, there’s one more piece of Wimp functionality to mention. In amongst the icon flags is one that we haven’t yet covered:

#define wimp_ICON_ALLOW_ADJUST  ((wimp_icon_flags) 0x400u)

If this is set for an icon within an ESG, clicking with Adjust on it will not deselect any other icons within the group which are currently selected – allowing multiple options to be selected at once.

It’s easy to experiment with this by editing the window templates: in WinEd, the required option is called Allow adjust. For completeness, the change can be found in Download 20.3, although it is only the !ExamplApp.Templates file which has changed from Download 20.2.

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

Icon-level event handlers

One potential problem with the fix for Adjust clicks that we implemented in the previous section is that it requires us to have a Mouse_Click event handler in place. Whilst we do have one for our window, this might not always be the case and, unless a change in state should trigger another action (such as a display update, or shading part of a dialogue box), it could be better to leave the Wimp to handle the user interaction with the icons until we are ready to read the settings back. Additionally, whilst simple, the code would have to be duplicated every single time we use radio icons in a window.

To improve things, we can make use of a feature of SFLib’s event library which we have so far not mentioned. The win_mouse_click() event handler that we have created is in fact a window-level event handler, and before it is called, the event dispatcher will look for any icon-level event handlers which are registered for the target window and icon.

Unlike window-level handlers, these icon-level siblings report back whether or not they consider that they have completely handled an event. If they think that they have, then the dispatcher stops and reports success back to the caller of event_process_event(). If not, any other icon-level handlers that are registered for the icon in question will be tried, before the event is passed on to a window-level handler if one is registered. Figure 20.1 shows the process graphically.

Figure 20.1: The full process for handing Mouse Click events

Whilst they can be useful for general clicks in windows where there is only a single button, icon level handlers are intended for managing window components like bump icons and pop-up menus which we will meet later on. Another use is processing Adjust clicks on radio icons, and we can register the three icons using the event_add_window_icon_radio() function, which is defined as follows:

osbool event_add_window_icon_radio(
        wimp_w w,
        wimp_i i,
        osbool complete 
);

The window and icon handles are what we would expect, and identify the icon to be looked after. The complete parameter controls whether or not the radio icon handler will report the handling of the event to be complete, which as described above will determine whether the event gets passed on to any other event handlers.

To use this approach in our application, we can remove the code that we added to the win_mouse_click() event handler in the previous section, and replace it with the three lines from Listing 20.4 in the win_initialise() function:

event_add_window_icon_radio(win_handle, WIN_ICON_OPTION1, FALSE);
event_add_window_icon_radio(win_handle, WIN_ICON_OPTION2, FALSE);
event_add_window_icon_radio(win_handle, WIN_ICON_OPTION3, FALSE);

Listing 20.4: Registeing the radio icons with the event library

We have set complete to FALSE, since we do still wish to receive window-level Mouse_Click events for these icons in order to be able to update the status field. The changes to the code can be found in Download 20.4.

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

Writing to icons

When we updated the application to use templates in the previous chapter, we had to update win_mouse_click() to read the indirected buffer details from the Wimp before updating the status icon’s text:

static void win_mouse_click(wimp_pointer *pointer)
{
        wimp_icon_state         state;
        int                     option = 0;

        state.w = win_handle;
        state.i = WIN_ICON_INFO;
        wimp_get_icon_state(&state);

        switch (pointer->i) {
        case WIN_ICON_OPTION1:
                option = 1;
                break;
        case WIN_ICON_OPTION2:
                option = 2;
                break;
        case WIN_ICON_OPTION3:
                option = 3;
                break;
        }

        snprintf(state.icon.data.indirected_text.text, state.icon.data.indirected_text.size,
                        "Option %d is selected", option);
        state.icon.data.indirected_text.text[state.icon.data.indirected_text.size - 1] = '\0';

        wimp_set_icon_state(win_handle, WIN_ICON_INFO, 0, 0);
}

Changing the text within an icon is a fairly common thing to need to do, so SFLib contains a collection of functions in its icons library to support this. Aside from minimising code duplication, the functions also perform a number of sanity checks on the icon flags and data before proceeding, in order to be as sure as they can be that they aren’t going to write over something that they shouldn’t.

One useful function is icons_printf(), which is defined along the lines of the familiar printf() as follows:

int icons_printf(
        wimp_w w,
        wimp_i i,
        char *cntrl_string,
        ...
);

This function takes the usual printf() parameters, prefixed by the target window and icon handles. By using it, we can reduce the event handler down to the code in Listing 20.5.

static void win_mouse_click(wimp_pointer *pointer)
{
        int option = 0;

        switch (pointer->i) {
        case WIN_ICON_OPTION1:
                option = 1;
                break;
        case WIN_ICON_OPTION2:
                option = 2;
                break;
        case WIN_ICON_OPTION3:
                option = 3;
                break;
        }

        icons_printf(win_handle, WIN_ICON_INFO, "Option %d is selected", option);
        wimp_set_icon_state(win_handle, WIN_ICON_INFO, 0, 0);
}

Listing 20.5: Using SFLib's icons_printf() to update the icon

Not only is this simpler than the original, but there are also more sanity checks taking place before the data is written. Like most string copying functions in SFLib, the termination of over-length strings is handled as standard – meaning that we don’t need to write '\0' to the end of the buffer in order to be sure that over-length strings get teriminated properly.

A full version of the updated code can be found in Download 20.5.

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

Opening the window

One other area where we are duplicating code that SFLib can provide is in the win_open() function. The need to set up a window block for Wimp_OpenWindow by calling Wimp_GetWindowState is a common one when writing Wimp software: it arises every time an application opens a window up on screen for the first time. The window library in SFLib provides some useful functions for this, including windows_open_centred_on_screen().

void windows_open_centred_on_screen(
        wimp_w w
);

This contains the same code as we have written in win_open(), but with an added check to ensure that the window doesn’t obscure the iconbar. This means that we can safely replace our function with the following:

void win_open(void)
{
        windows_open_centred_on_screen(win_handle);
}

We can also lose the global win_width and win_height variables, since windows_open_centred_on_screen() handles this for us. The further simplified code can be found in Download 20.6.

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

This chapter has been fairly ‘bitty’, but – as promised – we’ve tidied up those loose ends. In the next chapter we will finish our look at icons with more details of the sprite-only variant.