Chapter 4: Event Driven Programming

Our first foray into programming the Wimp in C (see Listing 2.1) followed the very BASIC-like approach of repeatedly polling the Wimp inside a while loop and processing the resulting reason codes in a switch statement. While it worked, it’s not very well structured: apart from anything else, the entire application is contained inside the single main() function!

Modular code

The first thing to do is to take the code from Listing 2.1 and re-write it in a slightly more modular form. This isn’t essential at this stage, but it will help to keep things clear as we go on and add more features. It results in the code seen in Listing 4.1.

/**
 * Example 4.1
 *
 * (c) Stephen Fryatt, 2015.
 *
 * File: main.c
 */

#include "oslib/wimp.h"

/* Global Variables */

static osbool main_quit_flag = FALSE;

/* Function Prototypes */

static void main_initialise(void);
static void main_poll(void);
static void main_terminate(void);

/* Main code entry point. */

int main(int argc, char *argv[])
{
        main_initialise();
        main_poll();
        main_terminate();

        return 0;
}

/* Global application initialisation. */

static void main_initialise(void)
{
        wimp_initialise(wimp_VERSION_RO3, "Example App", NULL, NULL);
}

/* Wimp_Poll loop. */

static void main_poll(void)
{
        wimp_block      block;
        wimp_event_no   reason;

        while (!main_quit_flag) {
                reason = wimp_poll(wimp_MASK_NULL |
                         wimp_MASK_ENTERING | wimp_MASK_LEAVING |
                         wimp_MASK_GAIN | wimp_MASK_LOSE |
                         wimp_MASK_POLLWORD, &block, NULL);

                switch (reason) {
                case wimp_USER_MESSAGE:
                case wimp_USER_MESSAGE_RECORDED:
                        if (block.message.action == message_QUIT)
                                main_quit_flag = TRUE;
                        break;
                }
        }
}

/* Global application termination. */

static void main_terminate(void)
{
        wimp_close_down(0);
}

Listing 4.1 (c.main): Restructuring the code to use functions

While very similar to its predecessor, there’s now an initialisation, poll and termination routine, which breaks the code up into logical chunks. Each has a name starting with main_ – this isn’t essential, but will help us to keep track of where things are when the code grows. The main() itself simply calls these routines.

A copy of the modified code can be found in Download 4.1. It should compile in exactly the same way as Download 3.1.

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

Routing events

There was another problem with Listing 2.1, however, which is still present in Listing 4.1 and will become more obvious as the application develops: the switch statement following wimp_poll() will need to know about every single piece of the application in order to pass control on to the correct parts. For now that’s not much of an issue, but as more features are added to our application it will soon become one.

This is a common problem in BASIC, and many applications will be centred around a CASE statement similar to the one in below (don’t worry about what the different WHEN clauses actually do).

DEF PROCpoll
LOCAL reason%

SYS "Wimp_Poll", &3831, b%, TO reason%

CASE reason% OF
        WHEN 1          : PROCredraw_handler(b%)
        WHEN 2          : SYS "Wimp_OpenWindow",,b%
        WHEN 3          : SYS "Wimp_CloseWindow",,b%
        WHEN 6          : PROCmouse_click_handler(b%)
        WHEN 8          : PROCkeypress_handler(b%)
        WHEN 9          : PROCmenu_selection_handler(b%)
        WHEN 17, 18     : PROCwimp_message_handler(b%)
        WHEN 19         : PROCbounced_message_handler(b%)
ENDCASE
ENDPROC

Each of those procedures will very likely contain another CASE statement, which in turn will have calls to procedures in every part of the program. While this is fine for the un-modular BBC BASIC, it makes it difficult to break the code up into self-contained chunks – one of the advantages of moving to C.

Anyone who has ever written software for other windowing systems – especially those that use so-called ‘visual’ development environments – will be familiar with the concept of event-driven programming. An action button can have a function associated with it, which gets called whenever the user clicks on the button; windows can have functions which are called whenever they are moved or require redrawing.

While it’s relatively simple for beginners – not to mention being open to abuse – the approach does have advantages. These ‘event handlers’ can be self-contained, and often don’t need to be known about outside of the code that they relate to; this makes it possible to write extremely modular code, which can be much easier to update and maintain.

The RISC OS Wimp is also event-driven, although to a casual observer – especially one working in BASIC – it’s quite well hidden. With the exception, perhaps, of Null_Reason, the information returned by Wimp_Poll is a series of events: a Mouse_Click could be an event destined for an action button in a window somewhere, while Redraw_Window_Request is an indication that a window wishes to be redrawn.

The while loop contained in main_poll() in Listing 4.1 does in fact contain an event handler – in the switch statement here.

switch (reason) {
case wimp_USER_MESSAGE:
case wimp_USER_MESSAGE_RECORDED:
        if (block.message.action == message_QUIT)
                main_quit_flag = TRUE;
        break;
}

The handler processes events coming in with reason codes of User_Message and User_Message_Recorded, when the action is Message_Quit. If these conditions are all met, the code main_quit_flag = TRUE is executed – making this an event handler for Message_Quit when it arrives as one of the two forms of user message.

Event dispatch

It’s not a great leap to go from here to full event handlers, but it will require assistance from a third-party library. Over the years, several libraries have been written which include a Wimp event dispatcher – which to use is a matter of personal preference and this guide will be sticking to SFLib (and in turn OSLib) throughout.

Once all of the tests for reason and message action codes have been removed, the routine above can be re-packaged as an event handler function.

static osbool main_message_quit(wimp_message *message)
{
        main_quit_flag = TRUE;

        return TRUE;
}

This shows the standard form of a message event handler for use with SFLib: it takes a single parameter wimp_message *message, which is a pointer to the message block that arrived from Wimp_Poll, and will be called whenever a Message_Quit is received by the application. The function returns TRUE to let SFLib know that the message has been handled: we’ll see why when we look at messages in more detail later on.

To let SFLib’s event library know that this hander exists, we can add a line to main_initialise() to register it.

event_add_message_handler(message_QUIT, EVENT_MESSAGE_INCOMING, main_message_quit);

This call to event_add_message_handler() lets the event dispatcher know that main_message_quit() is interested in receiving details of Message_Quit – it’s only possible to specify one message, but the same handler can be registered multiple times (with different messages in each) if required. EVENT_MESSAGE_INCOMING indicates that the handler is interested in messages arriving by both the User_Message and User_Message_Recorded events – again, we’ll explain this properly later on.

It’s this call to event_add_message_handler() which replaces the tests in the original switch statement: the event dispatcher won’t pass the event to main_message_quit() unless the exact requirements are met.

The other thing that we need to do is to make the event dispatcher aware of events arriving from Wimp_Poll, so that it can pass them on. We do this by changing the main polling loop to pass details of the incoming events on to the dispatcher.

static void main_poll(void)
{
        wimp_block      blk;
        wimp_event_no   reason;
        int             pollword;

        while (!main_quit_flag) {
                reason = wimp_poll(wimp_MASK_NULL, &blk, &pollword);

                event_process_event(reason, &blk, pollword, NULL);
        }
}

Now, instead of passing incoming reason codes to a switch statement, they’re passed directly to the event library. Internally this will use a similar approach to determine where events should end up – but it does this without us having to worry about it. The code here isn’t quite the full story – we’ll add the additional bits when they’re required in a later example.

Finally, we need to add a reference to SFLib’s event library: we do this by adding a suitable #include to the top of the file to make the functions available to us.

#include "oslib/wimp.h"
#include "sflib/event.h"

Putting this all together results in the code found in Listing 4.2, which should perform exactly the same as Listing 4.1 when run.

/**
 * Example 4.2
 *
 * (c) Stephen Fryatt, 2015.
 *
 * File: main.c
 */

#include "oslib/wimp.h"
#include "sflib/event.h"

/* Global Variables */

static osbool main_quit_flag = FALSE;

/* Function Prototypes */

static void main_initialise(void);
static void main_poll(void);
static void main_terminate(void);
static osbool main_message_quit(wimp_message *message);

/* Main code entry point. */

int main(int argc, char *argv[])
{
        main_initialise();
        main_poll();
        main_terminate();

        return 0;
}

/* Global application initialisation. */

static void main_initialise(void)
{
        wimp_initialise(wimp_VERSION_RO3, "Example App", NULL, NULL);

        event_add_message_handler(message_QUIT, EVENT_MESSAGE_INCOMING, main_message_quit);
}

/* Wimp_Poll loop. */

static void main_poll(void)
{
        wimp_block      blk;
        wimp_event_no   reason;
        int             pollword;

        while (!main_quit_flag) {
                reason = wimp_poll(wimp_MASK_NULL, &blk, &pollword);

                event_process_event(reason, &blk, pollword, NULL);
        }
}

/* Global application termination. */

static void main_terminate(void)
{
        wimp_close_down(0);
}

/* Message_Quit event handler. */

static osbool main_message_quit(wimp_message *message)
{
        main_quit_flag = TRUE;

        return TRUE;
}

Listing 4.2 (c.main): Using the event dispatcher

We’ve now got the basic structure of a multitasking application: while it still doesn’t do much as yet, we can now start to build some more familiar features around it. However, before doing that, we need to tidy up and simplify the build process.