Chapter 18: Sprite Icons and Choosing Options

Back in Section 14.6 and Section 14.7 we looked as using sprites in icons, but glossed over a lot of the details. Now that we’ve cleared up some more essential background information, we can return to the subject in a more useful way.

Indirected text and sprite icons

In Download 14.3, we saw that we could create an icon containing both a sprite and some text, but were limited by the fact that both the text and the name of the sprite had to be the same. Fortunately there is a way around this, and it involves the validation strings that we met in Chapter 15. The code that we used to create our original text and sprite icon in Section 14.7 looked like this:

static wimp_i win_create_icon(void)
{
        wimp_icon_create icon_definition;

        icon_definition.w = win_handle;
        icon_definition.icon.extent.x0 = 100;
        icon_definition.icon.extent.y0 = -300;
        icon_definition.icon.extent.x1 = 300;
        icon_definition.icon.extent.y1 = -100;
        icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_SPRITE |
                        wimp_ICON_BORDER | wimp_ICON_FILLED |
                        wimp_ICON_HCENTRED | wimp_ICON_VCENTRED |
                        (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                        (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT);
        strncpy(icon_definition.icon.data.text_and_sprite, "directory", 12);

        return wimp_create_icon(&icon_definition);
}

The problem that we faced was that in its non-indirected form, the icon only has the 12 bytes available to us at icon_definition.icon.data.text_and_sprite for storing both the icon text and the name of the sprite. Using the same name for both is a little limiting.

If we change to an indirected icon, then the use of those 12 bytes changes. We saw how the space is used for indirected text icons in Section 15.2; for text and sprite icons, OSLib defines the indirected_text_and_sprite structure within union wimp_icon_data as follows:

struct {
        char            *text;
        char            *validation;
        int             size;
} indirected_text_and_sprite;

Once again, we now have three fields: two pointers to external buffers (*text and *validation), and a field holding the length of the buffer pointed to by *text. To make use of this, we can update the definition of win_create_icon() as in Listing 18.1.

static wimp_i win_create_icon(void)
{
        wimp_icon_create icon_definition;

        icon_definition.w = win_handle;
        icon_definition.icon.extent.x0 = 100;
        icon_definition.icon.extent.y0 = -144;
        icon_definition.icon.extent.x1 = 500;
        icon_definition.icon.extent.y1 = -100;
        icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_SPRITE |
                        wimp_ICON_INDIRECTED | wimp_ICON_VCENTRED |
                        (wimp_BUTTON_NEVER << wimp_ICON_BUTTON_TYPE_SHIFT) |
                        (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                        (wimp_COLOUR_VERY_LIGHT_GREY << wimp_ICON_BG_COLOUR_SHIFT);

        icon_definition.icon.data.indirected_text_and_sprite.text = win_icon_text;
        icon_definition.icon.data.indirected_text_and_sprite.size = WIN_ICON_TEXT_LEN;
        icon_definition.icon.data.indirected_text_and_sprite.validation = "Soptoff";

        strncpy(win_icon_text, "This is a useless option", WIN_ICON_TEXT_LEN);
        win_icon_text[WIN_ICON_TEXT_LEN - 1] = '\0';

        return wimp_create_icon(&icon_definition);
}

Listing 18.1: Creating a text+sprite icon

As with previous indirected icons, we’ve set icon_definition.icon.data.indirected_text.text to point to the win_icon_text buffer, and used icon_definition.icon.data.indirected_text.size to indicate the buffer size. The buffer itself is filled with the text “This is a useless option”, which will be the text that is shown in the icon.

Instead of relying on this to also give the Wimp the name of the sprite to be shown in the icon, we can instead make use of the S validation command, which is followed by the name of a sprite. We set icon_definition.icon.data.indirected_text.validation to the validation string Soptoff, which the Wimp recognises as a request to use the “optoff” sprite in the icon. This is another sprite that we can assume will be in the Wimp Sprite Pool; its intended purpose should become clear shortly.

There are a couple of other changes to make to the code. We no longer wish to respond to mouse clicks on the window or icon, so we can delete the win_mouse_click() function and the corresponding

event_add_window_mouse_event(win_handle, win_mouse_click);

from within the win_initialise() function and change the window button type to wimp_BUTTON_NEVER. The modified code can be found in Download 18.1.

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

When compiled and run, the resulting window should look familiar, as can be seen in Figure 18.1. The icon won’t be that responsive yet, but we can deal with that later!

Figure 18.1: Indirection is useful for icons containing both text and a sprite

An option button

The icon that we’ve just created looks very much like an option button: a component that applications can include in dialogue boxes to allow the user to turn something on and off. The Wimp provides a fair bit of support for these, including the dedicated sprites “optoff” and “opton” which are always in the Wimp Sprite Pool.

At present, the icon ignores our clicks because its button type is set to wimp_BUTTON_NEVER as discussed in Section 15.3. Helpfully, the Wimp provides a dedicated button type for option buttons: wimp_BUTTON_RADIO. If we use this, then the Wimp will handle the toggling of the icon’s state from unticked to ticked and back again when the user clicks on it.

There’s one other thing to consider, though: when the option is on, a different sprite should be shown so that a tick appears, and in fact the “opton” sprite is also in the Wimp Sprite Pool for this purpose. Once again, this is easy to implement, because the S validation command can actually take two comma separated sprite names. If we use Soptoff,opton then the sprite will change between “optoff” when the icon is not ticked, and “opton” when it is.

Bringing this together results in the changes to win_create_icon() as seen in Listing 18.2.

static wimp_i win_create_icon(void)
{
        wimp_icon_create icon_definition;

        icon_definition.w = win_handle;
        icon_definition.icon.extent.x0 = 100;
        icon_definition.icon.extent.y0 = -144;
        icon_definition.icon.extent.x1 = 500;
        icon_definition.icon.extent.y1 = -100;
        icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_SPRITE |
                        wimp_ICON_INDIRECTED | wimp_ICON_VCENTRED |
                        (wimp_BUTTON_RADIO << wimp_ICON_BUTTON_TYPE_SHIFT) |
                        (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                        (wimp_COLOUR_VERY_LIGHT_GREY << wimp_ICON_BG_COLOUR_SHIFT);

        icon_definition.icon.data.indirected_text_and_sprite.text = win_icon_text;
        icon_definition.icon.data.indirected_text_and_sprite.size = WIN_ICON_TEXT_LEN;
        icon_definition.icon.data.indirected_text_and_sprite.validation = "Soptoff,opton";

        strncpy(win_icon_text, "This is a useless option", WIN_ICON_TEXT_LEN);
        win_icon_text[WIN_ICON_TEXT_LEN - 1] = '\0';

        return wimp_create_icon(&icon_definition);
}

Listing 18.2: Creating an option button

Compiling this and running it should allow the box to be ticked and unticked in the expected way. The full code can be found in Download 18.2.

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

Reading an icon’s state

If the Wimp keeps track of clicks on our option icon, then how does the application know whether or not it is ticked? In fact the wimp_BUTTON_RADIO button type does return clicks to the task, so we could register an event handler for Mouse_Click events as we saw in Chapter 16 and track the changes. This isn’t ideal, though: it’s a lot of effort, especially if we have many such icons in our application.

In fact, the Wimp will once again do the work for us. As hinted at above, the icon is being toggled between two states: selected and deselected. The two parameters given to the S validation command actually apply to the deselected and selected states respectively, and we can read the current state back from the Wimp when required. Exactly when we do this depends on the application: we might do it each time there is a click on the icon if an immediate response is required, whereas in a dialogue box, we would probably only check when the user clicks on the OK button.

Maybe unsurprisingly, the Wimp holds the selected state of the icon in the icon flags. In a similar way to window flags, icon flags contain details which can both be set by the application and read back again to identify the current situation.

#define wimp_ICON_SELECTED      ((wimp_icon_flags) 0x200000u)
#define wimp_ICON_SHADED        ((wimp_icon_flags) 0x400000u)
#define wimp_ICON_DELETED       ((wimp_icon_flags) 0x800000u)

Along with a wimp_ICON_SELECTED flag, there are two further flags which allow an icon to be shaded (“greyed out” in more common RISC OS parlance) and deleted. The latter can be used to hide icons and reveal them only when required, althrough care needs to be taken since deleted icons will be overwritten if Wimp_CreateIcon is used.

We can read the state of an icon back using the Wimp_GetIconState SWI, but before we do, we probably need to tidy our code up a bit so that we can add a few more icons to the window. First, we will rename the win_icon_handle and win_icon_text variables to win_radio_icon_1_handle and win_radio_icon_1_text respectively. We will also rename the wimp_create_icon() as wimp_create_radio_icon(), and amend it so that we pass a y coordinate, along with pointers to the text buffer and the desired text buffer contents, as parameters.

We can then create a similar function, win_create_info_icon(), which will create an icon capable of displaying information for the user. This needs its own memory buffer, which again we can pass in as a parameter. Finally, we will re-instate the win_mouse_click() event handler which was deleted earlier in the chapter.

Taken together, these result in the following changes to the global variables and function prototypes as seen in Listing 18.3

/* Constant Values */

#define WIN_ICON_TEXT_LEN 100

/* Global Variables */

static wimp_w win_handle;

static wimp_i win_radio_icon_1_handle;

static wimp_i win_info_icon_handle;

static int win_width, win_height;

static char win_radio_icon_1_text[WIN_ICON_TEXT_LEN];

static char win_info_icon_text[WIN_ICON_TEXT_LEN];

/* Function Prototypes */

static wimp_i win_create_radio_icon(int ypos, char *buffer, char *text);
static wimp_i win_create_info_icon(int ypos, char *buffer, char *text);
static void win_mouse_click(wimp_pointer *pointer);

Listing 18.3: Radio icon file header

The last few lines of the win_initialise() become those shown in Listing 18.4.

/* Create the icons. */

win_radio_icon_1_handle = win_create_radio_icon(-100, win_radio_icon_1_text, "This is a useless option");
win_info_icon_handle = win_create_info_icon(-200, win_info_icon_text, "");

/* Register event handlers. */

event_add_window_mouse_event(win_handle, win_mouse_click);

Listing 18.4: Initialising the radio icons

Finally, for now, the two icon definition functions become the ones seen in Listing 18.5.

/* Create a radio icon in a window. */

static wimp_i win_create_radio_icon(int ypos, char *buffer, char *text)
{
        wimp_icon_create icon_definition;

        icon_definition.w = win_handle;
        icon_definition.icon.extent.x0 = 100;
        icon_definition.icon.extent.y0 = ypos - 44;
        icon_definition.icon.extent.x1 = 500;
        icon_definition.icon.extent.y1 = ypos;
        icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_SPRITE |
                        wimp_ICON_INDIRECTED | wimp_ICON_VCENTRED |
                        (wimp_BUTTON_RADIO << wimp_ICON_BUTTON_TYPE_SHIFT) |
                        (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                        (wimp_COLOUR_VERY_LIGHT_GREY << wimp_ICON_BG_COLOUR_SHIFT);

        icon_definition.icon.data.indirected_text_and_sprite.text = buffer;
        icon_definition.icon.data.indirected_text_and_sprite.size = WIN_ICON_TEXT_LEN;
        icon_definition.icon.data.indirected_text_and_sprite.validation = "Soptoff,opton";

        strncpy(buffer, text, WIN_ICON_TEXT_LEN);
        buffer[WIN_ICON_TEXT_LEN - 1] = '\0';

        return wimp_create_icon(&icon_definition);
}

/* Create an information icon in a window. */

static wimp_i win_create_info_icon(int ypos, char *buffer, char *text)
{
        wimp_icon_create icon_definition;

        icon_definition.w = win_handle;
        icon_definition.icon.extent.x0 = 100;
        icon_definition.icon.extent.y0 = ypos - 44;
        icon_definition.icon.extent.x1 = 500;
        icon_definition.icon.extent.y1 = ypos;
        icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_INDIRECTED |
                        wimp_ICON_HCENTRED | wimp_ICON_VCENTRED |
                        wimp_ICON_BORDER | wimp_ICON_FILLED |
                        (wimp_BUTTON_NEVER << wimp_ICON_BUTTON_TYPE_SHIFT) |
                        (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                        (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT);

        icon_definition.icon.data.indirected_text.text = buffer;
        icon_definition.icon.data.indirected_text.size = WIN_ICON_TEXT_LEN;
        icon_definition.icon.data.indirected_text.validation = "";

        strncpy(buffer, text, WIN_ICON_TEXT_LEN);
        buffer[WIN_ICON_TEXT_LEN - 1] = '\0';

        return wimp_create_icon(&con_definition);
}

Listing 18.5: Creating radio icons and display fields

Next, we need a way to read the state of the option icon when the user clicks on it. Reading the state of an icon is done using the Wimp_GetIconState, which OSLib defines as

extern void wimp_get_icon_state (
        wimp_icon_state *icon_state
);

It takes a pointer to a block of memory, which is defined as

struct wimp_icon_state {
        wimp_w w;
        wimp_i i;
        wimp_icon icon;
};

typedef struct wimp_icon_state wimp_icon_state;

and we can see that this contains the wimp_icon structure that we are already using to define icons. This means that having got an icon’s state, we can easily read the icon data back and examine or process it as required.

Putting this into practice, we can reinstate our win_mouse_click() Mouse_Click event handler as in Listing 18.6

static void win_mouse_click(wimp_pointer *pointer)
{
        wimp_icon_state         state;

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

        if (state.icon.flags & wimp_ICON_SELECTED)
                snprintf(win_info_icon_text, WIN_ICON_TEXT_LEN, "Option button is ticked");
        else
                snprintf(win_info_icon_text, WIN_ICON_TEXT_LEN, "Option button is not ticked");

        win_info_icon_text[WIN_ICON_TEXT_LEN - 1] = '\0';

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

Listing 18.6: A Mouse Cick event handler for radio icons

First we initialise the state structure to contain the window handle and the handle of the option icon, before calling wimp_get_icon_state() to get the information that we need from the Wimp. Finally, we can update the contents of the information icon depending on whether or not the option icon was selected and refresh it as discussed in Section 16.3.

Putting the code together results in the application found in Download 18.3.

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

When compiled and run, the window should now contain an additional information field as seen in Figure 18.2. The option button will toggle as before, but now it will also report the current state in the information field below.

Figure 18.2: We can check the current state of an option icon with the Wimp

Multiple options

In addition to simple on/off options, there is a variant of the option icon which uses multiple switches: one for each possible option. Known as radio icons because of their similarity to the tuning selection buttons on an old radio, they are also a common sight in dialogue boxes.

As the name might suggest, the wimp_BUTTON_RADIO button type that we’re already using will also implement radio icons. First, we need to define some global variables to hold the extra icons and their text labels as shown in Listing 18.7.

/* Global Variables */

static wimp_w win_handle;

static wimp_i win_radio_icon_1_handle;
static wimp_i win_radio_icon_2_handle;
static wimp_i win_radio_icon_3_handle;

static wimp_i win_info_icon_handle;

static int win_width, win_height;

static char win_radio_icon_1_text[WIN_ICON_TEXT_LEN];
static char win_radio_icon_2_text[WIN_ICON_TEXT_LEN];
static char win_radio_icon_3_text[WIN_ICON_TEXT_LEN];

static char win_info_icon_text[WIN_ICON_TEXT_LEN];

Listing 18.7: The file heading changes for radio icons

before creating the extra icons in win_initialise() as seen in Listing 18.8.

/* Create the icons. */

win_radio_icon_1_handle = win_create_radio_icon(-100, win_radio_icon_1_text, "Option 1");
win_radio_icon_2_handle = win_create_radio_icon(-152, win_radio_icon_2_text, "Option 2");
win_radio_icon_3_handle = win_create_radio_icon(-204, win_radio_icon_3_text, "Option 3");
win_info_icon_handle = win_create_info_icon(-256, win_info_icon_text, "");

Listing 18.8: Creating the radio icons

We will also need to make some changes to our icon definition, since the Wimp needs to know that we want our three icons to operate together as an exclusive selection group (or ESG). As with an icon’s button type and colours, this is defined within the icon flags.

#define wimp_ICON_ESG_SHIFT     (16)
#define wimp_ICON_ESG           ((wimp_icon_flags) 0x1F0000u)

It used to be the case that five bits of the flags were set aside for the ESG, occupying bits 16 to 20, and this is still what OSLib has defined. However, in recent versions of the Wimp (where ‘recent’ means ‘this century’), bit 20 has been reallocated and only the four bits from 16 to 19 are available for the purpose. This still allows for 15 distinct groups within a window, which is probably more than enough; a zero ESG, which is what all of our previous icons have defaulted to, means that the icon is not part of any group.

We can amend the icon flag definition within the win_create_radio_icon() function to include a non-zero ESG as in Listing 18.9.

icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_SPRITE |
                wimp_ICON_INDIRECTED | wimp_ICON_VCENTRED |
                (wimp_BUTTON_RADIO << wimp_ICON_BUTTON_TYPE_SHIFT) |
                (1 << wimp_ICON_ESG_SHIFT) |
                (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                (wimp_COLOUR_VERY_LIGHT_GREY << wimp_ICON_BG_COLOUR_SHIFT);

Listing 18.9: The icon flags for a radio icon

In addition, radio icons should use the “radiooff” and “radioon” sprites instead of “optoff” and “opton”, so we amend the validation string accordingly, as seen in Listing 18.10.

icon_definition.icon.data.indirected_text_and_sprite.validation = "Sradiooff,radioon";

Listing 18.10: The S validation command for radio icons

In addition to these changes, we will also need to update the Mouse_Click event handler, so that it can recognise the three distinct options now available. We could read the state of each icon individually using Wimp_GetIconState as we did with the single option icon, and this might still be the best option in a dialogue box when we’re reading the state some time after the user has set it up. However, in this case, we want to react immediately and can therefore use the fact that with radio icons in an ESG, the last icon to be clicked by the user is almost guaranteed to be selected.

This makes the following alternative approach seen in Listing 18.11 possible.

static void win_mouse_click(wimp_pointer *pointer)
{
        if (pointer->i == win_radio_icon_1_handle)
                snprintf(win_info_icon_text, WIN_ICON_TEXT_LEN, "Option 1 is selected");
        else if (pointer->i == win_radio_icon_2_handle)
                snprintf(win_info_icon_text, WIN_ICON_TEXT_LEN, "Option 2 is selected");
        else if (pointer->i == win_radio_icon_3_handle)
                snprintf(win_info_icon_text, WIN_ICON_TEXT_LEN, "Option 3 is selected");

        win_info_icon_text[WIN_ICON_TEXT_LEN - 1] = '\0';

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

Listing 18.11: Updating the Mouse Click event handler

In fact this isn’t perfect, as there are a few holes in the way that the Wimp handles ESGs, but it’s OK for now. We will return to look at the niggles in Chapter 20 (and ensure that the selection guarantee promised above becomes true), but the full code as it stands can be found in Download 18.4.

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

The result when compiled should be as seen in Figure 18.3.

Figure 18.3: Radio buttons can operate in groups

However, even in a simple window with just four icons, keeping track of all of the handles and memory is quickly becoming complicated – so it shouldn’t come as a surprise to learn that very few applications will do things this way. In the next chapter, before dealing with those niggles, we will look at a better way to design windows and icons.