A couple of chapters ago we left a few loose ends regarding the Wimp’s report boxes. Before moving on to create our first window, it would be a good idea to get them tidied up.

User choices

The first loose end is actually fairly straight forward to resolve: we had no way to find out which of the buttons in our report the user had selected. However, if we take a look at the definitions for wimp_report_error() and wimp_report_error_by_category(), we can see that they both return a value of the type wimp_error_box_selection.

extern wimp_error_box_selection wimp_report_error_by_category(
        os_error const          *error,
        wimp_error_box_flags    flags,
        char const              *name,
        char const              *sprite_name,
        osspriteop_area const   *area,
        char const              *buttons
);

The clue is largely in the name: when a report closes, the Wimp returns a number indicating which button (if any) the user selected. wimp_error_box_selection is defined from an int, and OSLib also defines three constants using it.

typedef int wimp_error_box_selection;

#define wimp_ERROR_BOX_SELECTED_NOTHING ((wimp_error_box_selection) 0x0u)
#define wimp_ERROR_BOX_SELECTED_OK      ((wimp_error_box_selection) 0x1u)
#define wimp_ERROR_BOX_SELECTED_CANCEL  ((wimp_error_box_selection) 0x2u)

The easiest way to see how this works is probably by example. First, let’s go to c.ibar and amend the call to repbox_message() so that we don’t pass any custom button names.

repbox_message("Hello World!", NULL);

Then in c.repbox, #include "sflib/debug.h" and amend the definition of repbox_message() as follows:

void repbox_message(char *message, char *buttons)
{
        os_error                        error;
        wimp_error_box_selection        selection = wimp_ERROR_BOX_SELECTED_NOTHING;

        error.errnum = 255;
        strncpy(error.errmess, message, os_ERROR_LIMIT);
        error.errmess[os_ERROR_LIMIT - 1] = '\0';

        selection = wimp_report_error_by_category(&error, wimp_ERROR_BOX_GIVEN_CATEGORY  |
                        (wimp_ERROR_BOX_CATEGORY_INFO << wimp_ERROR_BOX_CATEGORY_SHIFT) |
                        wimp_ERROR_BOX_OK_ICON | wimp_ERROR_BOX_CANCEL_ICON,
                        "Example App", "application", wimpspriteop_AREA, buttons);

        switch (selection) {
        case wimp_ERROR_BOX_SELECTED_OK:
                debug_printf("User selected Continue button (code %d)", selection);
                break;
        case wimp_ERROR_BOX_SELECTED_CANCEL:
                debug_printf("User selected Cancel button (code %d)", selection);
                break;
        default:
                debug_printf("User selected custom button %d", selection);
                break;
        }
}

If this code, available in Download 10.1, is compiled and run then – assuming that Reporter is loaded – clicks on the iconbar should result in messages being displayed in Reporter’s window as seen in Figure 10.1.

Trying different combinations of the wimp_ERROR_BOX_OK_ICON, wimp_ERROR_BOX_CANCEL_ICON and wimp_ERROR_BOX_HIGHLIGHT_CANCEL flags in wimp_report_error_by_category() should reveal that clicks on Continue and Cancel always return the values wimp_ERROR_BOX_SELECTED_OK and wimp_ERROR_BOX_SELECTED_CANCEL respectively – regardless of the order in which they are displayed in the report box.

Download 10.1: Report on Continue and Cancel Clicks
7 KBytes | 11th June, 2017

Once the values returned by Continue and Cancel make sense, we can consider what values custom buttons – added using the string passed in *buttons – might return. The answer is that they return values starting with 3 for the right-most custom button and counting upwards for each button towards the left. This remains true even if one or both of the Continue and Cancel buttons are missing.

Go back to c.ibar and return the call to repbox_message() to the way it was before the previous example:

repbox_message("Hello World!", "Howdy!,Go Away");

Back in c.repbox, trying different combinations of the wimp_ERROR_BOX_OK_ICON, wimp_ERROR_BOX_CANCEL_ICON and wimp_ERROR_BOX_HIGHLIGHT_CANCEL flags in wimp_report_error_by_category() again should demonstrate that Go Away! always returns the value 3 while Howdy! always returns 4. This can be seen in Figure 10.2.

A complete copy of the example code is available in Download 10.2.

Download 10.2: Report on Custom Button Clicks
8 KBytes | 11th June, 2017

A reporting library

Over the course of the past couple of chapters, we’ve seen how to use Wimp_ReportError to get messages to the user. However, this kind of functionality underpins all applications, since being able to communicate with the user when things go wrong is essential – particularly if one wants to get any constructive bug reports back!

As useful as our repbox_message() has been in explaining the facilities on offer from the Wimp, it really belongs in a library to avoid having to duplicate it into every application that we write. Perhaps unsurprisingly, SFLib contains an errors library which will do what we need. Before updating our application to use it, we’ll work through the code that the library contains – but remember that the error_ functions which follow are all contained inside SFLib and therefore don’t actually need to be included in our code.

A small annoyance with our implementation of the error box in c.repbox was that it contained duplicates of the application’s name and sprite details. When it called wimp_report_error_by_category(), there were hard-coded references to "Example App" and "application":

wimp_report_error_by_category(&error, wimp_ERROR_BOX_GIVEN_CATEGORY  |
        (wimp_ERROR_BOX_CATEGORY_INFO << wimp_ERROR_BOX_CATEGORY_SHIFT) |
        wimp_ERROR_BOX_OK_ICON | wimp_ERROR_BOX_CANCEL_ICON,
        "Example App", "application", wimpspriteop_AREA, buttons);

These values are already used elsewhere in the application – when registering with Wimp_Initialise and creating the iconbar icon – and duplication isn’t ideal. Aisde from anything else, it makes the application a lot less maintainable as it grows: if the names change – as they almost certainly will do before release – they’re much harder to change if they are specified in lots of different places. In addition, if the code is in a shared library, then the required values will be different in every application which uses it.

To overcome this, SFLib’s errors library provides an initialisation function which must be called when the application starts up. This function takes as parameters a pair of pointers to the application and sprite names, and stores them in static global variables *error_app_name and *error_app_sprite for future reference. It also takes a pointer to a function called *closedown, which we’re not going to use and can safely be set to NULL.

static char     *error_app_name = NULL;
static char     *error_app_sprite = NULL;
static void     (*error_close_down_function)(void);

void error_initialise(char *name, char *sprite, void (*closedown)(void))
{
        error_app_name = name;
        error_app_sprite = sprite;
        error_close_down_function = closedown;
}

In fact, this is a bit of a fib: a bit like one of those ‘facts’ told to students for passing exams. The actual c.errors in SFLib’s source code contains some very different code, including several things that we haven’t yet encountered in this guide. We’ll come clean on these later, but for now the code above will behave in exactly the same way as that in the library whilst only using concepts that we’ve met so far.

The library then goes on to define a static error_wimp_os_report() function for its own internal use:

static wimp_error_box_selection error_wimp_os_report(os_error *error, wimp_error_box_flags type,
                wimp_error_box_flags buttons, char *custom_buttons)
{
        wimp_error_box_selection        click;
        wimp_error_box_flags            flags;
        char                            *name, *sprite;

        name = (error_app_name != NULL) ? error_app_name : "Application";
        sprite = (error_app_sprite != NULL) ? error_app_sprite : "application";

        if (custom_buttons != NULL && *custom_buttons != '\0') {
                flags = wimp_ERROR_BOX_GIVEN_CATEGORY |
                                (type << wimp_ERROR_BOX_CATEGORY_SHIFT);
                click = wimp_report_error_by_category(error, flags, name, sprite,
                                wimpspriteop_AREA, custom_buttons);
        } else {
                flags = wimp_ERROR_BOX_GIVEN_CATEGORY | buttons |
                                (type << wimp_ERROR_BOX_CATEGORY_SHIFT);
                click = wimp_report_error_by_category(error, flags, name, sprite,
                                wimpspriteop_AREA, NULL);
        }

        return click;
}

The code here should be fairly familiar: it takes a pointer to an OS Error block, flags – split into two parameters for clarity – and a pointer to a string of button names. If either of the application or sprite name pointers passed to error_initialise() were NULL, they will be substituted by "Application" and "application" respectively. If *custom_buttons is either NULL or pointing to an empty string, then the default Continue and Cancel buttons are used as specified in the buttons flags; otherwise the custom buttons are used in isolation. Either way, the function returns the user’s selection.

Next, another static function – error_complete_block() – is defined to allow error blocks to be completed easily:

#define ERROR_NUMBER 255

static void error_complete_block(os_error *error, char *message)
{
        if (error == NULL || message == NULL)
                return;

        error->errnum = ERROR_NUMBER;
        strncpy(error->errmess, message, os_ERROR_LIMIT);
        error->errmess[os_ERROR_LIMIT - 1] = '\0';
}

Being static, neither error_wimp_os_report() nor error_complete_block() are available to our application; instead, the library builds on them to define some more functions which are for its use. The first, error_report_error(), reports a message – pointed to by *message – with an error status and a Continue button:

wimp_error_box_selection error_report_error(char *message)
{
        os_error        error;

        error_complete_block(&error, message);
        return error_wimp_os_report(&error, wimp_ERROR_BOX_CATEGORY_ERROR, wimp_ERROR_BOX_OK_ICON, NULL);
}

When it’s necessary to pass on information which isn’t an error, the error_report_info() function can be used to present the user with a information status message – again pointed to by *message – and a Continue button:

wimp_error_box_selection error_report_info(char *message)
{
        os_error        error;

        error_complete_block(&error, message);
        return error_wimp_os_report(&error, wimp_ERROR_BOX_CATEGORY_INFO, wimp_ERROR_BOX_OK_ICON, NULL);
}

Finally, when we need to ask the user a question, there’s the error_report_question() function. Once again, the message is pointed to by *message; if *buttons points to a string containing comma-separated button names then these will be used for the buttons, otherwise the default Continue and Cancel will be offered instead.

wimp_error_box_selection error_report_question(char *message, char *buttons)
{
        os_error        error;

        error_complete_block(&error, message);
        return error_wimp_os_report(&error, wimp_ERROR_BOX_CATEGORY_QUESTION,
                        wimp_ERROR_BOX_OK_ICON | wimp_ERROR_BOX_CANCEL_ICON, buttons);
}

The value returned from all three functions is a standard wimp_error_box_selection: this means that it’s either wimp_ERROR_BOX_SELECTED_OK, wimp_ERROR_BOX_SELECTED_CANCEL or a value counting from 3 upwards for custom buttons.

Throwing it all away

The first step towards making use of this library is – a little drastically – to delete c.repbox and h.repbox from our project and remove the reference to it in Makefile: they’re going to be replaced completely. This leaves the OBJS variable to be set in Makefile as follows:

OBJS = main ibar

The application and sprite names that we’re using are "Example App" and "application"; for now, we’ve just been inserting them into the code as string constants whenever we need them. To tidy things up, we can go to c.main and at the top (near where we define main_quit_flag) add a couple of variable definitions:

static char *main_application_name = "Example App";

static char *main_application_sprite = "application";

We can then modify main_initialise() to use these new variables:

static void main_initialise(void)
{
        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);

        ibar_initialise(main_application_sprite);
}

In addition to updating the call to wimp_initialise() to take its pointer to the application name from the main_application_name variable, we’re using both variables in the new call to error_initialise() – which initialises SFLib’s errors library. Note that we have to #include "sflib/errors.h" at the top of the file to be able to add this call.

The final change is to pass main_application_sprite to ibar_initialise(). We’re going to need to update the definition of this function before it will compile, but it means that the code creating the iconbar icon will also have access to the same sprite name as the errors library. This will make things very much easier when we come to add a proper application sprite!

Moving on to c.ibar, we need to swap the #include "repbox.h" for #include "sflib/errors.h". We then need to amend the definition of ibar_initialise() to take the sprite name parameter that we’ve just added to c.main:

void ibar_initialise(char *sprite)
{
        wimp_icon_create icon_bar;

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

        wimp_create_icon(&icon_bar);

        event_add_window_mouse_event(wimp_ICON_BAR, ibar_mouse_click);
}

Remember to change the function prototype in h.ibar to match. Finally, the call to repbox_message() can be replaced with a call to error_report_question() as follows:

error_report_question("Hello World!", "Howdy!,Go Away!");

The complete project as it now stands without c.repbox and h.repbox, along with the files and folders generated by compiling it, can be seen in Figure 10.3. A full set of code can be found in Download 10.3.

Download 10.3: Replace Report Code with SFLib’s Errors Library
6 KBytes | 11th June, 2017

Running the application should once again place an icon on the iconbar, which responds to clicks with the mouse in the same way as Download 8.3. As an experiment, it would be possible to amend ibar_mouse_click() to take note of and report on the selected button – in the same way that repbox_message() used to do:

static void ibar_mouse_click(wimp_pointer *pointer)
{
        wimp_error_box_selection selection = wimp_ERROR_BOX_SELECTED_NOTHING;

        switch (pointer->buttons) {
        case wimp_CLICK_SELECT:
                selection = error_report_question("Hello World!", "Howdy!,Go Away!");

                switch (selection) {
                case wimp_ERROR_BOX_SELECTED_OK:
                        debug_printf("User selected Continue button (code %d)", selection);
                        break;
                case wimp_ERROR_BOX_SELECTED_CANCEL:
                        debug_printf("User selected Cancel button (code %d)", selection);
                        break;
                default:
                        debug_printf("User selected custom button %d", selection);
                        break;
                }
                break;
        case wimp_CLICK_ADJUST:
                debug_printf("This was an Adjust click - Goodbye!");
                main_quit_flag = TRUE;
                break;
        }
}

Remember to #include "sflib/debug.h" at the top of the file in order to use debug_printf().

And with that, we’ve probably done enough playing with Wimp_ReportError for now. Armed with this knowledge and the code from Download 10.3, however, we’re ready to move on to more recognisable aspects of the Wimp – and create our first window!