Chapter 7: Clicks on the Iconbar

In the last chapter we got our application to place an icon on to the iconbar, but it didn’t do very much. The icon completely ignored user interaction, which prevented it from being much use.

Mouse clicks

Up to now, all of our communication with the Wimp has been through the results of calling Wimp_Poll – so it shouldn’t be too much of a surprise to learn that information about mouse clicks comes via this route as well. In fact, virtually everything that our application needs to know about what the user is up to arrives in this way.

Before we go on to implement our new code properly, we’ll go back and take a look at how it might have been implemented before we broke the application up into self-contained modules in Chapter 4 – this should make it clearer how things are working ‘beneath the surface’. Back in Listing 4.1, our call to Wimp_Poll looked like this:

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;
        }
}

As we saw in Chapter 2, Wimp_Poll returns many different reason codes. So far, we’ve only listened out for two – those called User_Message and User_Message_Recorded. Since we’re interested in mouse clicks, we now need to take a look at the one called Mouse_Click.

In the switch statement above, we’re testing the reason code returned from Wimp_Poll to see if it is equal to either wimp_USER_MESSAGE or wimp_USER_MESSAGE_RECORDED. We’ve already seen that OSLib defines a set of these constants:

#define wimp_NULL_REASON_CODE           ((wimp_event_no) 0x0u)  /* 0    */
#define wimp_REDRAW_WINDOW_REQUEST      ((wimp_event_no) 0x1u)  /* 1    */
#define wimp_OPEN_WINDOW_REQUEST        ((wimp_event_no) 0x2u)  /* 2    */
#define wimp_CLOSE_WINDOW_REQUEST       ((wimp_event_no) 0x3u)  /* 3    */
#define wimp_POINTER_LEAVING_WINDOW     ((wimp_event_no) 0x4u)  /* 4    */
#define wimp_POINTER_ENTERING_WINDOW    ((wimp_event_no) 0x5u)  /* 5    */
#define wimp_MOUSE_CLICK                ((wimp_event_no) 0x6u)  /* 6    */
#define wimp_USER_DRAG_BOX              ((wimp_event_no) 0x7u)  /* 7    */
#define wimp_KEY_PRESSED                ((wimp_event_no) 0x8u)  /* 8    */
#define wimp_MENU_SELECTION             ((wimp_event_no) 0x9u)  /* 9    */
#define wimp_SCROLL_REQUEST             ((wimp_event_no) 0xAu)  /* 10   */
#define wimp_LOSE_CARET                 ((wimp_event_no) 0xBu)  /* 11   */
#define wimp_GAIN_CARET                 ((wimp_event_no) 0xCu)  /* 12   */
#define wimp_POLLWORD_NON_ZERO          ((wimp_event_no) 0xDu)  /* 13   */
#define wimp_USER_MESSAGE               ((wimp_event_no) 0x11u) /* 17   */
#define wimp_USER_MESSAGE_RECORDED      ((wimp_event_no) 0x12u) /* 18   */
#define wimp_USER_MESSAGE_ACKNOWLEDGE   ((wimp_event_no) 0x13u) /* 19   */

and buried in there is reason code 6: wimp_MOUSE_CLICK. If the user clicks on part of our application which can accept clicks – such as the iconbar icon – then Wimp_Poll will return with reason set to wimp_MOUSE_CLICK to let us know all about it.

So how do we find out about the click itself? We’re passing Wimp_Poll a pointer to the block variable, which is declared to be a wimp_block. As we also saw in Chapter 2, OSLib defines wimp_block as follows:

union wimp_block {
        wimp_draw       redraw;
        wimp_open       open;
        wimp_close      close;
        wimp_leaving    leaving;
        wimp_entering   entering;
        wimp_pointer    pointer;
        wimp_dragged    dragged;
        wimp_key        key;
        wimp_selection  selection;
        wimp_scroll     scroll;
        wimp_caret      caret;
        wimp_pollword   pollword;
        wimp_message    message;
        byte            reserved[256];
};

typedef union wimp_block wimp_block;

The switch code looks inside the message part of the union when either User_Message or User_Message_Recorded is received; in a similar way, when a Mouse_Click event is received, it needs to look inside pointer instead. It’s worth re-iterating here that wimp_block is a union because all of these different pieces of data share the same 256 byte chunk of memory that is passed to Wimp_Poll: how the block is filled by the Wimp before the SWI returns will depend on the reason code being used. Each time it returns, what was in the block before is overwritten.

OSLib defines wimp_pointer as

struct wimp_pointer {
        os_coord                pos;
        wimp_mouse_state        buttons;
        wimp_w                  w;
        wimp_i                  i;
};

typedef struct wimp_pointer wimp_pointer;

meaning that we’re getting four pieces of information back with a Mouse_Click event. The screen coordinates of the click are found in wimp_pointer.pos, while the handles of the window and icon under the pointer at the time are in wimp_pointer.w and wimp_pointer.i respectively. Finally, the combination of mouse buttons which the user clicked is held in wimp_pointer.buttons.

The window handle is important, as it lets us quickly identify where in our application the click occurred. When we created the our icon in the last chapter, we saw that the Wimp treated the iconbar as a ‘special’ window with handles of wimp_ICON_BAR_LEFT or wimp_ICON_BAR_RIGHT – which one depended on which side of the bar we wished the icon to appear. The same approach is used for the information returned by Wimp_Poll, but because it’s no longer important to know which side of the iconbar our icon is on, the handle is simplified to be wimp_ICON_BAR – this covers both the left- and the right-hand side.

If we wished to have our application simply quit whenever any click was made on its icon, we could amend the switch statement as follows:

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

If the event returned by Wimp_Poll is Mouse_Click, and if the window over which it occurred is the iconbar, then main_quit_flag is set to TRUE. It’s worth highlighting that Wimp_Poll will only return clicks on the iconbar if they are over an icon owned by our application: there’s no danger of us quitting if there is a click on Edit’s icon, for example!

Registering events

Unfortunately there’s a problem with this code: the switch statement that we’ve just added it to was removed in Listing 4.2, when we replaced it with

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

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

Instead of handling the events directly, we’re now passing them on to event_process_event() from SFLib’s event library and letting this route them to interested parties.

Fortunately, just as wimp_pointer contains the wimp_pointer.w variable, many of the structures which form wimp_block have an associated window handle – as we’ll see, much of the user’s interaction with our application is based around windows. Using this, the event library can route these events by testing the window handle that is contained within them.

Since we’re dealing with clicks on the iconbar icon, the best place to put its Mouse_Click event handler is into the c.ibar file. Just as we did when we implemented an event handler for Message_Quit in Listing 4.2, the first thing to do is to package up the code from the switch statement above into a Mouse_Click event handler.

static void ibar_mouse_click(wimp_pointer *pointer)
{
        main_quit_flag = TRUE;
}

The handler is a function which takes a single parameter consisting of a pointer to a wimp_pointer structure, and returns nothing (ie. void) – the SFLib event library requires this prototype for all Mouse_Click event handlers. The wimp_pointer structure is the one filled in by Wimp_Poll, although at present we’re not interested in its contents because we’re going to quit as soon as any click is received.

As with the Message_Quit handler, a lot of the code from the switch is no longer required. The event library is testing for the Wimp_Poll reason code being wimp_MOUSE_CLICK before it even considers passing the event on to a Mouse_Click handler, so the case condition can be removed. Similarly, we’re going to register this function with the event library specifically for events affecting the iconbar ‘window’ – this means that the test to confirm that block.pointer.w is equal to wimp_ICON_BAR will also be done for us before our function is called. This only leaves us with the job of actually setting main_quit_flag to TRUE.

To let the event library know about this function, we need to add a line to ibar_initialise():

event_add_window_mouse_event(wimp_ICON_BAR, ibar_mouse_click);

This informs the event dispatcher that the ibar_mouse_click() function is interested in knowing about Mouse_Click events over the window whose handle is wimp_ICON_BAR – our iconbar icon.

There’s a little bit more work to do, however. The main_quit_flag from inside c.main needs to become visible outside that file in order that that the function in c.ibar can change its value, so we remove static from its declaration there

osbool main_quit_flag = FALSE;

and create a new h.main header file to declare it as an extern – as shown in Listing 7.1.

/**
 * Example 7.1
 *
 * (c) Stephen Fryatt, 2017
 *
 * File: main.h
 */

#ifndef EXAMPLEAPP_MAIN
#define EXAMPLEAPP_MAIN

/* Aplication Quit Request Flag. */

extern osbool main_quit_flag;

#endif

Listing 7.1 (h.main): The new main header file

The updated c.main is shown in Listing 7.2.

/**
 * Example 7.1
 *
 * (c) Stephen Fryatt, 2017
 *
 * File: main.c
 */

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

#include "main.h"

#include "ibar.h"

/* Global Variables */

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);

        ibar_initialise();
}

/* 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 7.2 (c.main): Updates to main itself

Finally, the changes to c.ibar give the result shown in Listing 7.3.

/**
 * Example 7.1
 *
 * (c) Stephen Fryatt, 2017
 *
 * File: ibar.c
 */

#include "oslib/wimp.h"
#include "sflib/event.h"
#include <string.h>

#include "ibar.h"

#include "main.h"

/* Function Prototypes. */

static void ibar_mouse_click(wimp_pointer *pointer);

/* Iconbar Initialisation. */

void ibar_initialise(void)
{
        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, "application", osspriteop_NAME_LIMIT);

        wimp_create_icon(&icon_bar);

        event_add_window_mouse_event(wimp_ICON_BAR, ibar_mouse_click);
}

/* Mouse Click event handler */

static void ibar_mouse_click(wimp_pointer *pointer)
{
        main_quit_flag = TRUE;
}

Listing 7.3 (c.ibar): Handling mouse clicks

There are no changes to h.ibar, which remains as shown in Listing 7.4.

/**
 * Example 7.1
 *
 * (c) Stephen Fryatt, 2017
 *
 * File: ibar.h
 */

#ifndef EXAMPLEAPP_IBAR
#define EXAMPLEAPP_IBAR

/* Iconbar Initialisation. */

void ibar_initialise(void);

#endif

Listing 7.4 (h.main): The new main header file

Running Mk should generate a new build of the application which installs on the iconbar as before. Crucially, however, clicking the mouse over its icon causes it to quit immediately: there’s no longer any need to go via the Task Manager.

A full set of files, with the changes for handling mouse clicks, can be found in Download 7.1.

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

Being more choosy

Normally the ‘quit’ option would be in an iconbar menu, but we don’t yet have one of those. None the less, it would be good if our application were only to quit if the click on its icon came from one of the buttons – let’s say Adjust. That way, we could use the other buttons for something else.

The *pointer parameter, passed to the ibar_mouse_click() function that we’ve just defined, is a pointer to the wimp_pointer structure returned from the call to Wimp_Poll. This means that it’s actually a pointer to the blk variable defined in the main_poll() function, although this doesn’t need to concern us in normal use. As far as the click handler function is concerned, the pointer supplied is simply a pointer to some data returned by the Wimp.

As we’ve already seen, the wimp_pointer structure contains an element called wimp_pointer.buttons within it, which uniquely identifies the button – or combination of buttons – used for the click. The codes used by the Wimp are more than a little confusing, because they can vary depending on the the type of click and what was under the pointer when it happened. For our iconbar icon, however, OSLib defines three useful values for us:

#define wimp_CLICK_SELECT       ((wimp_mouse_state) 0x4u)
#define wimp_CLICK_MENU         ((wimp_mouse_state) 0x2u)
#define wimp_CLICK_ADJUST       ((wimp_mouse_state) 0x1u)

Armed with this, we can update our mouse click event handler to test the button before it acts.

static void ibar_mouse_click(wimp_pointer *pointer)
{
        switch (pointer->buttons) {
        case wimp_CLICK_ADJUST:
                main_quit_flag = TRUE;
                break;
        }
}

A full set of the updated files can be found in Download 7.2. With this modification in place, it is now necessary to click Adjust on the iconbar icon in order to make the application exit: Select and Menu will be ignored.

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