Chapter 21: Sprite Icons and Areas

Up to this point in our exploration, all of the sprite icons that we’ve used have taken their sprites from the Wimp Sprite Pool. In many cases this is a valid thing to do: where a sprite is one that is provided by the Wimp for all applications to use, or where we provide a sprite that other applications might need to use (for example our application sprite, and the icons for any filetypes that we might register). However, as we saw in Section 17.7, any sprites that we add to the pool need to have their names covered by a name allocation – and for many sprites that we might wish to use in our icons, there won’t be a valid reason for requesting one.

Fortunately, there’s another option: to create our own sprite area and store our sprites in this. There are several advantages to this approach, in addition to not polluting the namespace in the Wimp Sprite Pool. The memory allocated to our own area can be freed when our application exits, whereas sprites that we add to the Wimp Sprite Pool are stuck there consuming space until the machine is rebooted. There is no risk of name clashes, or of our affecting (not to mention being affected by) other applications.

Make some sprites

Before we can use a sprite area in our application, we’ll need some sprites to load in to it. Normally these would be driven by some sort of need in the application – such as toolbar icons – but since we don’t yet have such a need yet, we’ll go for some shapes instead. The files can be seen in Figure 21.1.

Figure 21.1: The sprites that we will use for our sprite area!

We have chosen to create three sprites, called “square”, “circle” and “triangle”. To keep things simple, the actual graphic in each sprite reflects its name as much as possible.

Just as with the application sprites that we created in Section 17.4, we will need a number of different sets of images to cater for different screen modes. There is a file called Sprites22 which contains three sprites sized at 150 × 150 pixels in a square pixel mode. Larger designs for high resolution modes are in Sprites11, and these are 300 × 300 pixels. Finally, for backwards compatibility, Sprites contains images at 150 × 75 pixels, created in an old rectangular pixel mode. The colour depth doesn’t really matter, but to balance having useful colours available to us with widest compatibility, we have used 256 colour sprites with no palette. The background in each image has been masked out. All of the sprites will occupy 300 × 300 OS units in their target modes.

The images here were all created using ArtWorks, with the sprites saved out through its bitmap export option; the rectangular pixel mode sprites in Sprites were generated from the square pixel ones in Sprites22 using ChangeFSI. The files will all be in the download below, or you can create your own to the same specification in your favourite bitmap editor.

The three files should be saved into the !ExamplApp directory, which should now have the contents shown in Figure 21.2 (there might also be a !RunImage file in here, unless MkClean has been used).

Figure 21.2: Once complete, the sprite files should be saved in the application directory

Updating the templates

Now that we have some sprites, we will need to update our window template to make use of them. Load the !ExamplApp.Templates file back into a template editor (we will again be using WinEd for the description here), and open the “Main” window for editing (Shift double-click on it in WinEd).

We’re going to get rid of the status display field, which is icon numbers 3 and 4 at the bottom of the window, just leaving the radio icons at the top. Select both icons (click Select on one and Adjust on the other, or drag Select around them both), then click Menu over them and choose Selection → Delete. This should leave the window looking a bit like Figure 21.3.

Figure 21.3: Removing the display field icons from the template

We will need an icon to hold a sprite from our file, and once again we can use one from the Icon picker as a starting point. The file icon is intended as the origin for drags from save dialogues, but it’s a simple sprite-only icon: drag one in to the window as shown in Figure 21.4.

Figure 21.4: Adding a sprite-only icon to the template

Double-click on the sprite icon to open its Edit icon dialogue as shown in Figure 21.5.

We need the icon to be an indirected sprite icon, so tick Indirected along with Sprite. Sprite names can be up to 12 characters, so allowing for a terminator we need to set Max text length to be 13. There is no validation string for indirected sprite icons, as we will see shortly.

For editing the template, it would be useful if we could see one of the sprites that we have designed in the icon, so replace “file_fff” in the Text/sprite field with square. Being square, this sprite will best fill the icon on screen.

We don’t want the icon responding to the mouse, so set Button type to “Never”. The flags should be updated so that only H centre and V centre are ticked, although ticking Border will make it clearer where the icon sits in the window template while we’re editing it. With the changes made, click on Don’t resize to set them.

Figure 21.5: Editing the details of our new sprite icon

Depending on how WinEd is configured, and where the Templates file was saved on disc, then the icon will either start to display the square sprite that we designed above, or will disappear completely. The sprite is not in the Wimp Sprite Pool, so we might not expect it to be available to our icon within WinEd. However, if the Templates file is being edited in-situ within the !ExamplApp folder and the sprite files described above have already been saved there as well, and if the Auto-load sprites option is set in WinEd’s choices, then WinEd will notice the Sprites files and load the appropriate one into an internal user sprite area for us when it loads the template file. If a suitable sprite file is not loaded, then dragging the correct one to any WinEd window will cause it to be loaded.

One thing to notice is that at present, dragging other windows or menus over the square icon will result in some strange redraw artefacts. This is because the sprite is 300 × 300 OS units, whilst the icon is only 68 × 68; the Wimp isn’t very good at clipping oversized sprites, and these redraw errors are a tell-tale sign that we need to size the icon correctly to match the sprite. We could do it by dragging the corner with Adjust, but since we want to make it the size of the sprite, we are better off asking WinEd to do it for us.

Click Menu over the icon and choose Icon 3 → Resize... to open the Resize icon window shown in Figure 21.6. Clicking on Both to minimise both width and height will set the icon to the correct size for the sprite.

Figure 21.6: Setting the size of the icon

The sprite icon will now be the correct size for the sprite, but it will still be positioned wherever it was originally dropped – most likely obscuring the radio icons and partially out of sight. We can drag it around using Select, but it would be good to accurately position it at the top of the window – which we can do using the Icon coordinates dialogue. Select the icon, click Menu over it and choose Icon 3 → Coordinates... to open it, as seen in Figure 21.7.

We will position the icon so that it is 20 OS units from the top-left of the window. Enter 20 in the Left field and −20 in the Top field (remember that the work area coordinates usually count down from zero at the top of the window). The Width and Height should both be 300 OS units as a result of resizing the icon to fit the sprite – if not, check the sprite design! With the numbers set, click on Move to adjust the icon.

Figure 21.7: Setting the display field coordinates

This change will result in the sprite icon overlaying the three radio icons as seen in Figure 21.8, so now we need to move the radio icons out of the way, towards the bottom of the window. To select them, it will be necessary to click to the right of the sprite icon, or drag a box around their right-hand ends as shown in Figure 21.8. If the sprite icon does get selected, an Adjust click over it will deselect it again.

Figure 21.8: Selecting the radio icons

To move the three radio icons down below the sprite, we can pick the selected group up and drag them around. If they are picked up over the top icon of the three (number 0), then the Monitor will show the position of that icon. Since the sprite icon is 300 OS units high, with a margin of 20 OS units above it, leaving a similar margin below will result in the top edge of the top radio icon needing to be −340 OS units from the top of the window. With the icons at the size they are in the supplied template file, they will be roughly centred in our design if the left-hand edge is at 36 OS units. This can be seen in Figure 21.9.

Figure 21.9: Moving the radio icons down below the sprite

The icons now fall outside of the window area, so we need to enlarge this. This can be done by simply dragging the window’s resize button in the usual way, but we can also calculate the size that we need and enter the numbers by hand for a more exact result. Click Menu over the window and choose Visible area... to open the Visible area dialogue seen in Figure 21.10.

We only want to change the width and height of the window, to fit symmetrically around the icons. The value in the Width field should be the width of the sprite icon (300 OS units), plus a margin of 20 OS units to the left and right: 340 OS units in total. The height is a little more tricky, but if we enlarge the window enough to see all three radio icons, then hover over the bottom one, we should (if the rearrangement above worked OK) see that its left hand edge is at 28 OS units and its bottom edge is at −488 OS units. To give the same space at the sides and below, subtract 28 from −488 to give −516 and enter the absolute value of this in the Height field.

Figure 21.10: Setting the visible area of the window

Clicking on Move to set the changes will result in the window looking similar to that shown in Figure 21.11.

Figure 21.11: The window after the visible area has been adjusted

Looking at the window now, the scroll bars are still showing that the work area is bigger than the visible area. We can tidy this up by shrinking the work area down, which we do using the work area dialogue; click Menu over the window and choose Work area... to open the dialogue shown in Figure 21.12.

We can shrink the work area down by clicking on Minimise in the Work area section. Click on Update to apply the changes.

Figure 21.12: Minimising the work area of the window

The final thing to do is to edit the labels on the three radio icons, so that number 0 reads “Circle”, number 1 reads “Square” and number 2 reads “Triangle”. This can be done by double-clicking on each in turn to open the Edit icon dialogue, changing the value in the Text/sprite field, using Minimise to reduce memory useage, and clicking on Update.

When complete, the window should look like the one in Figure 21.13. Close it, and save the template file.

Figure 21.13: The finished window

Loading a sprite file

Now that we have some sprites and a template to use them, we need a way to load the images into a user sprite area. A little surprisingly, there is no way to do this in a single operation, but it’s fairly straightforward to create a new, empty area and then load a sprite file into it. Since this is something that many applications will need to do, SFLib provides the resources_load_user_sprite_area() within its resources library to save duplicating effort. Since we will be using this, it’s worth looking at how it works before treating it as a black box.

The code is shown in Listing 21.1, and there’s a fair bit to unpick.

osspriteop_area *resources_load_user_sprite_area(char *file)
{
        int                     size;
        bits                    type;
        fileswitch_object_type  object;
        osspriteop_area         *area;
        char                    *suffix, full_file[RESOURCES_MAX_FILENAME];
        os_error                *error;

        if (file == NULL)
                return NULL;

        /* Identify the current mode sprite suffix. */

        suffix = wimpreadsysinfo_sprite_suffix();
        snprintf(full_file, RESOURCES_MAX_FILENAME, "%s%s", file, suffix);
        full_file[RESOURCES_MAX_FILENAME - 1] = '\0';

        /* Check for a suffixed sprite file. */

        object = osfile_read_stamped_no_path(full_file, NULL, NULL, &size, NULL, &type);

        /* If not found, check for an un-suffixed sprite file. */

        if (object != fileswitch_IS_FILE || type != osfile_TYPE_SPRITE) {
                strncpy(full_file, file, RESOURCES_MAX_FILENAME);
                full_file[RESOURCES_MAX_FILENAME - 1] = '\0';
                object = osfile_read_stamped_no_path(full_file, NULL, NULL, &size, NULL, &type);
        }

        /* If neither found, exit. */

        if (object != fileswitch_IS_FILE || type != osfile_TYPE_SPRITE)
                return NULL;

        /* Allocate the sprite area memory. */

        size += sizeof(int);
        area = malloc(size);
        if (area == NULL)
                return NULL;

        /* Initialise the sprite area. */

        area->size = size;
        area->first = 16;

        /* Load the sprite file into the area. */

        error = xosspriteop_load_sprite_file(osspriteop_USER_AREA, area, full_file);
        if (error != NULL) {
                free(area);
                return NULL;
        }

        return area;
}

Listing 21.1: SFLib's routine to load sprites into a sprite area

The first thing that we need to do is to identify which filename suffix, if any, is applicable to the current screen mode. This is something that the Wimp does for us when we use the *IconSprites command, as we saw in Section 17.4, but fortunately it also makes its internal code available to us through the Wimp_ReadSysInfo SWI. As its name suggests, this is a fairly generic SWI which returns many useful pieces of information depending on a reason code supplied in R0 on entry. Exactly what can be queried will depend on the version of the Wimp which is running, but the sprite suffix is available using reason code 2 from RISC OS 3.1 onwards.

As with other SWIs which use reason codes, OSLib generates a function for each code and includes them in a separate header file – in the case of Wimp_ReadSysInfo, we will need to include oslib/wimpreadsysinfo.h in addition to oslib/wimp.h (and a look at sflib/resources.c will show that does exactly this). The wimpreadsysinfo_sprite_suffix() function is defined as

extern char *wimpreadsysinfo_sprite_suffix(void);

and simply returns a pointer to a NULL terminated suffix string, such as “11” or “22”.

This suffix is then added to the supplied filename, writing it into the full_file buffer claimed from the stack. RESOURCES_MAX_FILENAME is chosen to be 1024 bytes, which limits filenames to 1023 characters plus a terminator. This isn’t ideal, but buffer overruns are caught and terminated cleanly so the worst that will happen is that a very long filename will be truncated and any file that it points to won’t be found. Remembering that the Wimp’s own API limits filenames to 212 bytes (something which we will discover later on when we look at drag and drop), the extra complexity of handling longer names on RISC OS isn’t justified. What is important is to set the length limits via constants, so that they can easily be extended with a simple re-compile should circumstances ever change.

Having built a candidate filename, we use OS_File with reason code 23 to read the catalogue information for a stamped object using “no path”. For those unfamiliar with it, OS_File is a fairly generic OS routine which can do many useful things with objects on disc. It takes a reason code in R0, and depending on what that code is, will save blocks of memory to file, delete or rename files, set filetypes, create directories and much else besides. Four reason codes, from 20 to 23, allow us to read the catalogue data for an object whose name is pointed to by R1, with some variations as listed in Table 21.1.

R0Filename
20Prepend the path in the <File$Path> system variable to the name pointed to by R1
21Prepend the path pointed to by R4 to the name pointed to by R1
22Prepend the path in the system variable whose name is pointed to by R4 to the name pointed to by R1
23Use the name pointed to by R1 unaltered

Table 21.1: Different options for locating objects with OS_File 20-23

In our case, we already have a full filename in our buffer, so we don’t want OS_File to mess around with it in any way – this means we use reason code 23. In OSLib, this becomes osfile_read_stamped_no_path(), which is defined in oslib/osfile.h as:

extern fileswitch_object_type osfile_read_stamped_no_path(
        char const *file_name,
        bits *load_addr,
        bits *exec_addr,
        int *size,
        fileswitch_attr *attr,
        bits *file_type
);

Since we’ve specified that we don’t want R4 to modify the filename, OSLib doesn’t give acess to the register: the call simply takes a pointer to the full name in *file_name, and returns information about the file in the locations pointed to by all of the other parameters. The value returned is the Fileswitch object type, defined by OSLib as:

typedef int fileswitch_object_type;

#define fileswitch_NOT_FOUND    ((fileswitch_object_type) 0x0u)
#define fileswitch_IS_FILE      ((fileswitch_object_type) 0x1u)
#define fileswitch_IS_DIR       ((fileswitch_object_type) 0x2u)
#define fileswitch_IS_IMAGE     ((fileswitch_object_type) 0x3u)

This indicates whether the supplied filename pointed to an object on disc and, if it did, what sort of thing was pointed to. This isn’t a RISC OS filetype, but something lower-level: fileswitch_IS_FILE indicates a file, while fileswitch_IS_DIR indicates a directory and fileswitch_IS_IMAGE indicates a file which is handled by an Image Filing System and therefore looks like a directory. If the object didn’t exist at all, fileswitch_NOT_FOUND is returned.

The RISC OS filetype is returned to us if we supply a pointer to a suitable variable in the *file_type parameter. In OSLib, bits is just an unsigned int, and we get back a conventional filetype or some additional magic numbers outside of the range 0x000u to 0xfffu to indicate “directory”, “application directory” or “untyped” (ie. a file with load and execution addresses).

We’re only interested in objects which are files and have a type of “Sprite”, or 0xff9u. OSLib conveniently defines a number of possible types in oslib/osfile.h, and alongside the magic numbers mentioned above we can find several common system types including

#define osfile_TYPE_SPRITE      0xFF9u

Using the constant makes it clear what we’re doing in the code. Armed with this information, we can test for the presence of a sprite file with the suffixed name, then fall back to the unsuffixed name if that fails. If neither file exists, we can give up and return NULL to the client.

If a sprite file was found, then we can move on to create a sprite area from it. RISC OS offers us no call to do this in one operation, but we can load a sprite file into an existing area using OS_SpriteOp with reason code 10. Since a sprite area is in fact just a sprite file with an extra four-byte word in front to give the size of the area, we first need to allocate enough memory to hold the file that we’ve found plus this extra word. We passed osfile_read_stamped_no_path() a pointer to the size variable so that it could return the size of the file; it only remains to add space for the extra word and then allocate the memory using malloc().

If the memory was allocated OK, then the area can be initialised as an empty sprite area by storing the size in area->size and initialising the offset to the first sprite in the area – which is offset 16 for an empty area – in area->first. The area is now ready to be given to OS_SpriteOp 10. Since this is another SWI which takes a reason code in R0, OSLib defines the necessary interface in oslib/osspriteop.h – including:

extern void osspriteop_load_sprite_file(
        osspriteop_flags flags,
        osspriteop_area *area,
        char const *file_name
);

extern os_error *xosspriteop_load_sprite_file(
        osspriteop_flags flags,
        osspriteop_area *area,
        char const *file_name
);

Unlike the other reason code SWIs that we’ve just met, OSLib’s interface to OS_SpriteOp differs a bit because the reason code also contains some flags to indicate what sprite area we intend to use – we have a choice of the System Sprite Area or a user sprite area. This means that we must still pass the flags in the flags parameter, and these are defined as

#define osspriteop_SYSTEM_AREA  ((osspriteop_flags) 0x0u)
#define osspriteop_USER_AREA    ((osspriteop_flags) 0x100u)
#define osspriteop_NAME         ((osspriteop_flags) 0x100u)
#define osspriteop_PTR          ((osspriteop_flags) 0x200u)

We need a user sprite area here (the System Sprite Area, which is not the same thing as the Wimp Sprite Pool, dates back to Arthur and is deprecated in normal use on RISC OS), and so we pass the pointer to it in to the *area parameter. Finally, the pointer to the filename is passed in the *file_name parameter. If an error occurs, we free the memory again and return NULL; otherwise we return a pointer to the sprite area containing the requested file.

For completeness, the other two OS_SpriteOp flags (osspriteop_NAME and osspriteop_PTR) allow us to choose how to address sprites within a user sprite area. Since osspriteop_load_sprite_file() is one of the OS_SpriteOp reason codes which operates on whole sprite areas and not individual sprites, the distinction isn’t important and it is therefore clearer to use the osspriteop_USER_AREA version of the flag. It’s not uncommon for OSLib to offer multiple definitions for the same constant when the meaning within RISC OS is potentially ambiguous.

Updating our code

So far, there has been a lot of theory in this chapter; let’s start to update our code, and see something in action! After remembering to

#include "oslib/osspriteop.h"
#include "sflib/resources.h"

at the top of h.main, we can update main_initialise() to the code seen in Listing 21.2.

static void main_initialise(void)
{
        os_error        *error;
        osspriteop_area *sprites;

        wimp_initialise(wimp_VERSION_RO3, main_application_name, NULL, NULL);

        error_initialise(main_application_name, main_application_sprite, NULL);

        event_add_message_handler(message_QUIT, EVENT_MESSAGE_INCOMING, main_message_quit);

        /* Load the application sprites. */

        sprites = resources_load_user_sprite_area("<ExamplApp$Dir>.Sprites");
        if (sprites == NULL)
                error_report_fatal("Failed to load application Sprites");

        /* Open the templates file. */

        error = xwimp_open_template("<ExamplApp$Dir>.Templates");
        if (error != NULL)
                error_report_program(error);

        /* Initialise the program modules. */

        ibar_initialise(main_application_sprite);
        win_initialise(sprites);

        /* Close the templates file. */

        wimp_close_template();
}

Listing 21.2: The changes to the main_initialise() function

The changes are fairly minimal: we load the sprite file into a new area and record its address in the *sprite pointer, then pass the value as a new parameter to the win_initialise() function.

If resources_load_user_sprite_area() returns NULL, we report it as a fatal error and exit. The error_report_fatal() from SFLib’s error library is similar to error_report_program() which we met in Chapter 19, in that it reports an error as a program error and exits the application. The only difference is that it takes a textual message and not a pointer to an os_error struct.

We need to make a number of changes to c.win, again starting with

#include "oslib/osspriteop.h"

The first thing we need to do is bring the icon handle constants up to date with the window template, as shown in Listing 21.3.

/* Constant Values */

#define WIN_ICON_OPT_CIRCLE 0
#define WIN_ICON_OPT_SQUARE 1
#define WIN_ICON_OPT_TRIANGLE 2
#define WIN_ICON_SHAPE 3

Listing 21.3: The new icon handle constants

Next, we will create some code to set the shape in the icon. An enum can be defined at the top of the file to identify the shape that we want, as seen in Listing 21.4.

/* Display Shapes */

enum win_shape {
        WIN_SHAPE_NONE,
        WIN_SHAPE_SQUARE,
        WIN_SHAPE_CIRCLE,
        WIN_SHAPE_TRIANGLE
};

Listing 21.4: The possible shapes listed in an enum

This can then be used as a parameter to a new function, shown in Listing 21.5 to update the icon.

static void win_set_shape(enum win_shape shape)
{
        char *sprite = NULL;

        switch (shape) {
        case WIN_SHAPE_SQUARE:
                sprite = "square";
                break;
        case WIN_SHAPE_CIRCLE:
                sprite = "circle";
                break;
        case WIN_SHAPE_TRIANGLE:
                sprite = "triangle";
                break;
        }

        if (sprite == NULL)
                return;

        icons_strncpy(WIN_HANDLE, win_icon_shape, sprite);
        wimp_set_icon_state(win_handle, WIN_ICON_SHAPE, 0, 0);
}

Listing 21.5: A function to select the current shape

Creating the win_set_shape() allows us to call the code from multiple places, whilst not having to duplicate the sprite names. If we ever changed these, or added more shapes to the file, there is only one place which needs to be updated. The win_mouse_click() event handler can now be updated as shown in Listing 21.6, so that it calls win_set_shape() instead of accessing the display icon directly.

static void win_mouse_click(wimp_pointer *pointer)
{
        enum win_shape shape = WIN_SHAPE_NONE;

        switch (pointer->i) {
        case WIN_ICON_OPT_CIRCLE:
                shape = WIN_SHAPE_CIRCLE;
                break;
        case WIN_ICON_OPT_SQUARE:
                shape = WIN_SHAPE_SQUARE;
                break;
        case WIN_ICON_OPT_TRIANGLE:
                shape = WIN_SHAPE_TRIANGLE;
                break;
        }

        if (shape != WIN_SHAPE_NONE)
                win_set_shape(shape);
}

Listing 21.6: The updated Mouse Click event handler

Indirected sprite icons

We have already looked at indirected text icons in Section 15.2 and indirected text and sprite icons in Chapter 18; the third type of indirected icon is the indirected sprite icon, which is what we created in our window template to hold the sprite of the shape.

For indirected sprite icons, the use of the 12 bytes of icon data changes a little and OSLib defines the indirected_sprite structure within union wimp_icon_data as follows:

struct {
        osspriteop_id   id;
        osspriteop_area *area;
        int             size;
} indirected_sprite;

Unlike the other two types of indirected icon, indirected sprite icons do not have a validation string pointer. Instead, that part of the block contains the *area pointer, which tells the Wimp where the sprite can be found. If it is set to 1, the sprite can be found in the Wimp Sprite Pool – and OSLib defines a wimpspriteop_AREA constant for us to use.

#define wimpspriteop_AREA       ((osspriteop_area *) 0x1u)

Alternatively, the pointer can be set to point to our own private sprite area – in which case, the Wimp will use our area to find the sprite.

The other two parts of the structure might seem a bit confusing on first sight. The size variable should be familiar, but instead of a pointer to a text buffer, we have an osspriteop_id variable called id. This is OSLib struggling to keep up with the multiple ways that the system can use the sprite data.

We mentioned in passing earlier that OS_SpriteOp had two options for addressing sprites: osspriteop_NAME and osspriteop_PTR. It can either take a pointer to a sprite name, in which case the sprites in an area will be searched until a match is found, or it can be given a pointer direct to the sprite in memory. The osspriteop_id type is how OSLib handles this pointer with a dual personality: it is a pointer to either a sprite name or a sprite. Which it is will depend on the flags passed to the OS_SpriteOp call.

In the case of indirected sprite data, the Wimp uses the value stored in size to identify the intended meaning of the value stored in id. If the size is zero, then the value in id is assumed to be a sprite pointer, and OS_SpriteOp will be called appropriately. If the value is greater than zero, then the value in id is taken to be a pointer to a buffer holding a sprite name, and size is the number of bytes allocated to the buffer – just like any other indirected icon, in fact.

In fact, if an icon is indirected, then so long as size is greater than zero, it is safe to treat the first entry in the indirected data structure (whether this is *text or id) as a pointer to a memory buffer of length size bytes. This is how win_set_shape(), which we defined above, can use icons_printf() for writing to the name buffer.

It only remains to update win_initialise() as seen in Listing 21.7, remembering that we will also need to amend its prototype in h.win to match.

void win_initialise(osspriteop_area *sprites)
{
        wimp_window     *window_definition;
        wimp_icon       *icons;

        /* Load and create the window. */

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

        icons = window_definition->icons;

        icons[WIN_ICON_SHAPE].data.indirected_sprite.area = sprites;

        win_handle = wimp_create_window(window_definition);
        free(window_definition);

        /* Register event handlers. */

        event_add_window_mouse_event(win_handle, win_mouse_click);

        event_add_window_icon_radio(win_handle, WIN_ICON_OPT_CIRCLE, FALSE);
        event_add_window_icon_radio(win_handle, WIN_ICON_OPT_SQUARE, FALSE);
        event_add_window_icon_radio(win_handle, WIN_ICON_OPT_TRIANGLE, FALSE);

        /* Initialise the radio icons. */

        wimp_set_icon_state(win_handle, WIN_ICON_OPT_CIRCLE, wimp_ICON_SELECTED, wimp_ICON_SELECTED);
        wimp_set_icon_state(win_handle, WIN_ICON_OPT_SQUARE, 0, wimp_ICON_SELECTED);
        wimp_set_icon_state(win_handle, WIN_ICON_OPT_TRIANGLE, 0, wimp_ICON_SELECTED);

        win_set_shape(WIN_SHAPE_CIRCLE);
}

Listing 21.7: The changes required to initialise our icon

We need to store the address of our sprite area in the definition for the WIN_ICON_SHAPE icon before the whole window definition is passed to wimp_create_window(), and we can find this through the window_definition->icons[] array – as we alluded to back in Chapter 19. It’s worth remembering that template editors should set this value to wimpspriteop_AREA when saving their files, but this might not always be guaranteed.

One point to note is that we’re assigning the address of window_definition->icons to the icons pointer, before using this to access the icon definition. This prevents the compiler from noticing that OSLib has defined window_definition->icons[] to have a length of 1, as discussed in Chapter 12 – if we did the more direct

window_definition->icons[WIN_ICON_SHAPE].data.indirected_sprite.area = sprites;

then Norcroft will trace back to the definition of wimp_window and issue a warning about a possible “out-of-bound offset 3 in address”. This would be reasonable – if unhelpful – since the compiler can’t know that the memory pointed to by window_definition is actually a definition containing four icons.

Finally, we set the state of the three radio icons, and pre-set the shape for a circle to match using win_set_shape().

Building our application

Compiling the code and running it should result in a window like the one in Figure 21.14, but attempting to use it will reveal a problem. Selecting the square is fine, but the other two shapes leave traces of their predecessor around the edges of the image. This is due to the masked background of the images: the Wimp cuts as many corners as it can when redrawing icons, and assumes that sprites will not contain masked areas.

Figure 21.14: The application in use

Depending on your template editor, there might also be a second problem: the square and circle display correctly, but the triangle does not. If the editor has minimised the size of the sprite name buffer to suit “square”, then “circle” will also fit but the longer “triangle” will not. This behaviour appears to show up in some versions of WinEd, so it is worth being aware of.

To resolve the first issue, we can tell the Wimp that the icon needs to be fully redrawn instead of simply plotting the new sprite over the top of the old, by setting the wimp_ICON_NEEDS_HELP flag.

#define wimp_ICON_NEEDS_HELP    ((wimp_icon_flags) 0x80u)

Load the Templates file back into your text editor, and open the Main window template. In WinEd, double-click on the square icon to open its Edit icon dialogue.

To deal with the possible buffer truncation problem first, change the contents of the Text/sprite field. We could enter “triangle” as the longest sprite name, or we could enter “square      ” with six spaces after the name to reserve the full 12 characters. The latter option gives us the best of both worlds, since the spaces at the end of the sprite name are ignored by OS_SpriteOp. We will be overwriting the name with a properly terminated one when our application initialises the icon, anyway.

To set the wimp_ICON_NEEDS_HELP flag, ensure that Needs help is ticked. With the dialogue as shown in Figure 21.15, click Update to store the changes, close the template and save the file.

Figure 21.15: The changes to the shape icon definition

The full set of code, sprites and templates can be found in Download 21.1.

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

Window sprite areas

Whilst we can now ask the Wimp to use our own sprite area for an indirected sprite-only icon, what about the other icons in a window? The answer is that, in just the same way that we can provide a pointer to a sprite area for an individual icon, we can also set one for the whole window. This will then apply to all icons, unless they set their own pointer.

When we met the wimp_window structure in Section 12.6, we briefly mentioned the window_definition.sprite_area pointer and set it to wimpspriteop_AREA – which, as we have now seen, made the Wimp use the Wimp Sprite Pool for all of the icons. Wimp_LoadTemplate also sets this location to wimpspriteop_AREA as it loads a window definition in to memory, regardless of what might be in the templates file: a dodgy pointer could cause the application to crash as the window is opened.

To try this out, we could add a couple of new sprites, named “radiooff” and “radioon“, to our three !ExamplApp.Sprites files, and then amend the code in win_initialise() to that shown in Listing 21.8 so that it sets window_definition->sprite_area to our sprite area.

/* Load and create the window. */

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

icons = window_definition->icons;

icons[WIN_ICON_SHAPE].data.indirected_sprite.area = sprites;

window_definition->sprite_area = sprites;

win_handle = wimp_create_window(window_definition);
free(window_definition);

Listing 21.8: Setting the window sprite area

Compiling this should result in the window looking as shown in Figure 21.16. The full code and sprites can be found in Download 21.2.

Figure 21.16

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

It should be possible to comment out the line which sets the window’s sprite area and recompile the code, after which the radio icons should go back to the standard RISC OS images.

One point worth highlighting is that if a window is using a private sprite area, the Wimp will still fall back to check the Wimp Sprite Pool if the required sprites are not found within the private area. This doesn’t appear to be documented anywhere, but has certainly been the case since RISC OS 4. Try leaving the amended code from Download 21.2 in place, but delete the “radiooff” and “radioon“ sprites from all three !ExamplApp.Sprites files – you should see the standard designs return.

We should be clear that this is not a recommendation to change standard parts of the RISC OS interface within an application! We’ll now be losing these changes from our application code, before moving on to look at another part of the Wimp completely.