Chapter 28: Keyboard Input

In the last chapter, we added some writable icons to our window so that the user could enter values, but had no way to identify new numbers and use them in the shape calculations. It would be better if the user could press Return after entering a new number, and have the other values update.

Key pressed events

To be able to detect when Return is pressed, our application will need to listen out for Key_Pressed events being returned from Wimp_Poll. The Wimp sends these with a reason code of wimp_KEY_PRESSED when a key is pressed whilst the caret is in one of our windows, and supplies data in a wimp_key structure which OSLib defines for us as follows.

struct wimp_key {
        wimp_w          w;
        wimp_i          i;
        os_coord        pos;
        int             height;
        int             index;
        wimp_key_no     c;
};

typedef struct wimp_key wimp_key;

Having met the wimp_caret structure returned by Wimp_GetCaretPosition in Section 27.5, the contents of the wimp_key structure should be familiar. It contains the wimp_w and wimp_i handles of the window and icon containing the caret; as before, the icon could be −1 (or wimp_ICON_WINDOW) if the caret is not in a writable icon.

The position of the caret in OS units, relative to the work area origin of the window whose handle is supplied, is in pos, while the caret height and flags are in height. If i is not −1, then index contains the index of the caret into the icon text.

At the end of the structure is an entry named c, which has the type wimp_key_no. This is simply an int, and is defined by OSLib as

typedef int wimp_key_no;

In most cases, the value returned will be a one-byte character code from the current character set, and will represent a printable character; ‘top-bit’ characters with values of 128 and above can be entered using the Alt key and should be expected. The keys from Ctrl-A to Ctrl-Z, along with other special keys such as Return, Escape, Home, Backspace and Delete also appear within this range, as shown in Table 28.1.

Note that without taking additional measures, it isn’t possible to differentiate Ctrl-H from Backspace, or Ctrl-M from Return. It is also difficult to detect combinations like Shift-Ctrl-A, because Shift is ignored by the system and they return the same codes as the un-shifted keys.

KeyDecimalHex
Ctrl-ACtrl-Z1 – 260x01 – 0x1a
Backspace80x08
Return130x0d
Escape270x1b
Home300x1e
Delete1270x7f

Table 28.1: Single-byte key codes which don't represent characters

OSLib defines constants for some of these keys, as listed here.

#define wimp_KEY_BACKSPACE      ((wimp_key_no) 0x8u)
#define wimp_KEY_RETURN         ((wimp_key_no) 0xDu)
#define wimp_KEY_ESCAPE         ((wimp_key_no) 0x1Bu)
#define wimp_KEY_HOME           ((wimp_key_no) 0x1Eu)
#define wimp_KEY_DELETE         ((wimp_key_no) 0x7Fu)

To allow access to other keys on the keyboard, the Wimp supports a range of additional two-byte values as shown in Table 28.2. The values arranged into a pattern, such that the Shift and Ctrl keys are always represented by bits 4 and 5 respectively. This makes it easy to create the Shift, Ctrl and Shift-Ctrl variants of the codes from the unmodified versions.

Note that the codes used for the Page Up and Page Down keys are in the same range as the codes returned by the Up and Down Arrows. This enforces the requirements of the Style Guide (for example Shift-Down has the same action as Page Down), but makes it impossible to identify exactly which key was pressed.

KeyAloneShiftCtrlShift-Ctrl
Print0x1800x1900x1a00x1b0
F1F90x181 – 0x1890x191 – 0x1990x1a1 – 0x1a90x1b1 – 0x1b9
Tab0x18a0x19a0x1aa0x1ba
Copy0x18b0x19b0x1ab0x1bb
Left Arrow0x18c0x19c0x1ac0x1bc
Right Arrow0x18d0x19d0x1ad0x1bd
Down Arrow0x18e0x19e0x1ae0x1be
Up Arrow0x18f0x19f0x1af0x1bf
Page Down0x19e0x18e0x1be0x1ae
Page Up0x19f0x18f0x1bf0x1af
F10F120x1ca – 0x1cc0x1da – 0c1dc0x1ea – 0x1ec0x1fa – 0x1fc
Insert0x1cd0x1dd0x1ed0x1fd

Table 28.2: Two-byte key codes supported by the Wimp

To help with these two-byte key codes, OSLib defines a number of ‘base’ constants for us. All of these can be used alone, or combined with Shift and Control.

/* Base Key Codes */

#define wimp_KEY_PRINT          ((wimp_key_no) 0x180u)
#define wimp_KEY_F1             ((wimp_key_no) 0x181u)
#define wimp_KEY_F2             ((wimp_key_no) 0x182u)
#define wimp_KEY_F3             ((wimp_key_no) 0x183u)
#define wimp_KEY_F4             ((wimp_key_no) 0x184u)
#define wimp_KEY_F5             ((wimp_key_no) 0x185u)
#define wimp_KEY_F6             ((wimp_key_no) 0x186u)
#define wimp_KEY_F7             ((wimp_key_no) 0x187u)
#define wimp_KEY_F8             ((wimp_key_no) 0x188u)
#define wimp_KEY_F9             ((wimp_key_no) 0x189u)
#define wimp_KEY_TAB            ((wimp_key_no) 0x18Au)
#define wimp_KEY_COPY           ((wimp_key_no) 0x18Bu)
#define wimp_KEY_LEFT           ((wimp_key_no) 0x18Cu)
#define wimp_KEY_RIGHT          ((wimp_key_no) 0x18Du)
#define wimp_KEY_DOWN           ((wimp_key_no) 0x18Eu)
#define wimp_KEY_UP             ((wimp_key_no) 0x18Fu)
#define wimp_KEY_PAGE_DOWN      ((wimp_key_no) 0x19Eu)
#define wimp_KEY_PAGE_UP        ((wimp_key_no) 0x19Fu)
#define wimp_KEY_LOGO           ((wimp_key_no) 0x1C0u)
#define wimp_KEY_MENU           ((wimp_key_no) 0x1C1u)
#define wimp_KEY_F10            ((wimp_key_no) 0x1CAu)
#define wimp_KEY_F11            ((wimp_key_no) 0x1CBu)
#define wimp_KEY_F12            ((wimp_key_no) 0x1CCu)
#define wimp_KEY_INSERT         ((wimp_key_no) 0x1CDu)

/* The Shift and Ctrl Key Bits */

#define wimp_KEY_SHIFT          ((wimp_key_no) 0x10u)
#define wimp_KEY_CONTROL        ((wimp_key_no) 0x20u)

Due to the pattern in the numbers, creating combinations is simply a case of combining bits, so Shift-Control-F12 would be

wimp_KEY_SHIFT | wimp_KEY_CONTROL | wimp_KEY_F12

A keypress handler

To make use of the Key_Pressed events, we will need a handler for them. One can be seen in Listing 28.1; the #define will need to go at the top of the file.

#define WIN_NUM_BUF_SIZE 12

static osbool win_keypress(wimp_key *key)
{
        char    buffer[WIN_NUM_BUF_SIZE];
        double  value = 0.0;

        if (key->c != wimp_KEY_RETURN)
                return FALSE;

        if (key->i == wimp_ICON_WINDOW)
                return TRUE;

        icons_copy_text(win_handle, key->i, buffer, WIN_NUM_BUF_SIZE);

        value = atof(buffer);
        if (value == 0.0)
                return TRUE;

        switch(key->i) {
        case WIN_ICON_LENGTH_FIELD:
                calc_set_dimension(CALC_DIMENSION_LENGTH, value);
                break;
        case WIN_ICON_PERIMETER_FIELD:
                calc_set_dimension(CALC_DIMENSION_PERIMETER, value);
                break;
        case WIN_ICON_AREA_FIELD:
                calc_set_dimension(CALC_DIMENSION_AREA, value);
                break;
        }

        win_update_all_calculations();

        return TRUE;
}

Listing 28.1: The Key Pressed event handler

The format of the function is very similar to the other window-level event handlers that we have defined for use with SFLib’s event library, with one exception: it returns an osbool value to indicate whether or not the keypress has been used. We will look at why this is shortly.

The function declares a buffer of WIN_NUM_BUF_SIZE bytes on the stack, and then checks the key reported by the event (in c within the wimp_key structure) to see if it is the Return key (indicated by the wimp_KEY_RETURN value). We will make our interface respond when the user presses Return in one of the writable fields, so if the key was anything else the function exits immediately – returning FALSE to indicate that they keypress wasn’t of interest.

If the key was Return, the next check is to make sure that the caret was in an icon: although we don’t give the user a way to place the input focus anywhere other than into the three icons (because we leave placing input focus up to the Wimp), a third-party utility could do it and so it is sensible to make sure. If the caret wasn’t in an icon, we again return from the function – but this time returning TRUE to show that we have accepted the Return keypress.

Assuming we get past the tests, we can be confident that Return was pressed and the caret was in one of our writable icons. We use the icons_copy_text() function from SFLib to copy the text from the target icon into our temporary buffer, which is defined as follows.

char *icons_copy_text(
        wimp_w w,
        wimp_i i,
        char *buffer,
        size_t length
);

As we mentioned in the last chapter when looking at placing the caret, string terminators in icons can be a little vague. The icons_copy_text() function will accept any control character as a terminator, but leave our buffer terminated with a '\0' character. The size of the buffer pointed to by buffer should be passed in the length parameter.

Once the value entered by the user is in the buffer, the standard atof() function from the stdlib.h is used to convert it to a floating point value. This can then be passed to calc_set_dimension() with the appropriate dimension type, before calling win_update_all_calculations() to recalculate all of the results and update the window.

With the handler defined, we must register it with the event library as seen in Listing 28.2.

/* Register event handlers. */

event_add_window_key_event(win_handle, win_keypress);
event_add_window_menu(win_handle, win_menu);
event_add_window_menu_prepare(win_handle, win_set_menu);
event_add_window_menu_selection(win_handle, win_menu_selection);
event_add_window_icon_popup(win_handle, WIN_ICON_SHAPE_POPUP, win_shape_menu, WIN_ICON_SHAPE_FIELD, NULL);
event_set_window_icon_popup_action(win_handle, WIN_ICON_SHAPE_POPUP, FALSE, win_shape_menu_selection);

Listing 28.2: Registering the Key Pressed event hander in win_initialise()

With all of the updates compiled, our application should look similar to Figure 28.1. Entering a value and pressing Return in one of the fields will finally update the others to reflect the corresponding values!

Figure 28.1: Finally we can calculate the parameters of a triangle!

The full set of code can be found in Download 28.1.

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

Passing keys on

So far, we have suggested that the Wimp will simply pass the details of keypresses to the task owning the window with input focus, but this isn’t quite the whole story. There are some keypresses – such as F12 to access the command line, or Ctrl-Shift-F12 to shut down the system – which we expect to work regardless of which window has input focus. To achieve this, the Wimp uses the concept of hot keys.

When a key is pressed, the Wimp will initially give the details to the task which owns window with input focus using the Key_Pressed event. If that task fails to process the keypress, however, then the Wimp will pass the keypress on to any other tasks which have expressed an interest in hot keys, starting from the top of the window stack and working down. To express interest, a task should have a window open with its its wimp_WINDOW_HOT_KEYS flag set – we introduced this briefly in Section 12.4.

In order to make hot keys work, the Wimp requires applications which claim the Key_Pressed event to pass on any keypresses which they do not use. This is done using the Wimp_ProcessKey SWI, which simply takes the code of the unwanted keypress:

extern void wimp_process_key(
        wimp_key_no c
);

extern os_error *xwimp_process_key(
        wimp_key_no c
);

This is something that we’re not currently doing, and the effect the omission can be clearly seen. Placing the caret in one of our writable icons and pressing F12 will result in nothing happening, when we would expect the a command line to appear at the foot of the screen. Move the caret away from our application (click in a text editor window, for example) and pressing F12 again should now accesses the command line in the usual way (press Return at the * prompt to return to the desktop afterwards). This isn’t desirable, because applications should never prevent these system-wide hot keys from working.

To resolve the problem, we can make use of the fact that the event_process_event() function in SFLib’s event library, which we are using in c.main to dispatch events from Wimp_Poll, returns TRUE if an event is handled and FALSE otherwise. We are already using this to implement the generic event handlers for Open_Window_Request and Close_Window_Request events, as we saw in Section 11.4, and we if we change the code as shown in Listing 28.3 then we can also use it to pass on unwanted keypresses from Key_Pressed events.

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

                if (!event_process_event(reason, &blk, pollword, NULL)) {
                        switch (reason) {
                        case wimp_OPEN_WINDOW_REQUEST:
                                wimp_open_window(&(blk.open));
                                break;

                        case wimp_CLOSE_WINDOW_REQUEST:
                                wimp_close_window(blk.close.w);
                                break;

                        case wimp_KEY_PRESSED:
                                wimp_process_key(blk.key.c);
                                break;
                        }
                }
        }
}

Listing 28.3: Passing on unwanted keypresses to other applications

In the case of Key_Pressed event handlers registered with SFLib using the event_add_window_key_event() function, the return value is passed on by event_process_event(). Since event_process_event() will also return FALSE if no handler was found, we can treat this as an instruction to pass the keypress on via Wimp_ProcessKey.

The changes can be found in Download 28.2.

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

Deciding which keys to pass on might seem difficult, but in fact our keypress handler here is quite unusual in that we’re only interested in one specific keypress. Most keypress handlers will follow the form of a switch() statement checking off the possible keys, before passing the keypress on to Wimp_ProcessKey if none of them match.

static osbool win_keypress(wimp_key *key)
{
        switch(key->c) {
        case wimp_KEY_RETURN:
                process_dialogue_contents();
                close_dialogue();
                break;
        case wimp_KEY_ESCAPE:
                close_dialogue();
                break;
        default:
                return FALSE;
        }

        return TRUE;
}

There is something else to remember if setting the wimp_WINDOW_HOT_KEYS flag in a window. The wimp_w handle in the wimp_key structure will be the handle of the window with input focus when the key was pressed, not the window with the interest in hot keys. This means that for a hot key press, the window will almost certainly belong to another task and so will need to be processed in the generic handler that we have just added.

In general, this will not be too much of a problem since the vast majority of keyboard shortcuts used in applications will not be handled as hot keys. Consider pressing F3 in the window of a text editor to save the file: the editor will need to know which file to save, and so will take the keypress in the window with input focus. The wimp_WINDOW_HOT_KEYS flag is only really useful for system-wide actions like F12 which don’t relate to a specific window, and we won’t be looking at it in more detail for now.

An immediate reaction

One possible improvement that we could make to our application is to have the calculations update as values are typed into the writable icons. For a calculator application this would give the user more immediate feedback, at the expense of some slightly non-standard behaviour.

It should be remembered that in general, RISC OS applications do not act immediately on character input in this way. In a dialogue box, effects like this are likely to cause more confusion than actual advantages – and they should be avoided unless there is a good reason for them. We will therefore make the behaviour a configurable option, and have it default to off.

It’s also worth considering that while we can implement this feature fairly simply here in order to demonstrate a concept, implementing it well will in many cases be very difficult to do. Effects like this are best used sparingly, when there is a real need for them!

We will start, as usual, by updating the “Main” window template so that our application can see all of the keypresses within the writable icons, even if the Wimp handles them for us. There is very little to change: the validation string for each of the three writable icons should be updated so that the K command contains the N flag. This will make the full string “Pptr_write;Ktan;A0-9.”. Save the changed templates file.

In the c.win file, we will start by adding another menu entry to the menu entry index constants, as seen in Listing 28.4.

/* Window Menu Entries. */

#define WIN_MENU_DECIMAL1 0
#define WIN_MENU_DECIMAL2 1
#define WIN_MENU_DECIMAL3 2
#define WIN_MENU_IMMEDIATE 3

Listing 28.4: Updated menu entry index constants

Armed with this, we can then define a new entry in the menu for toggling Immediate updates, as seen in Listing 28.5. It will be operating separately from the three entries above it, which behave as a mutually-exclusive group, so it would be a good idea to place a separator between them using a new menu_separator() function.

If you’re copying the changes into the code by hand, note that the number of entries specified in the call to menu_create() has increased to four. Our routines for creating menus perform no bounds checks on the blocks that they are updating!

/* Window Menu. */

win_menu = menu_create("Example", 4);
if (win_menu == NULL) {
        error_report_error("Failed to create Main Menu");
        return;
}

menu_entry(win_menu, WIN_MENU_DECIMAL1, "1 Decimal place", NULL);
menu_entry(win_menu, WIN_MENU_DECIMAL2, "2 Decimal places", NULL);
menu_entry(win_menu, WIN_MENU_DECIMAL3, "3 Decimal places", NULL);
menu_separator(win_menu, WIN_MENU_DECIMAL3);
menu_entry(win_menu, WIN_MENU_IMMEDIATE, "Immediate", NULL);

Listing 28.5: Defining the additional menu entry

The new menu_separator() function will need to be defined in c.menu as shown in Listing 28.6, with a suitable prototype added to h.menu. All that it has to do is set the wimp_MENU_SEPARATE flag in the entry’s menu_flags, which tells the Wimp to place a dotted line below the entry.

void menu_separator(wimp_menu *menu, int entry)
{
        wimp_menu_entry *definition = NULL;

        if (menu == NULL)
                return;

        /* Update the menu entry definition. */

        definition = menu->entries + entry;

        definition->menu_flags |= wimp_MENU_SEPARATE;
}

Listing 28.6: Adding a separator to a menu entry

Changing the behaviour

Now that we have a menu entry to allow the user to choose between ‘normal’ and ‘immediate’ responses, we will need to keep track of their wishes and act accordingly. To do this, we will create a new win_immediate_update global variable in c.win as seen in Listing 28.7. We will also add three new double variables (win_value_length, win_value_perimeter and win_value_area), which we will need later on.

/* Global Variables */

static wimp_w win_handle;
static wimp_menu *win_menu;
static wimp_menu *win_shape_menu;
static osbool win_immediate_update = FALSE;
static double win_value_length = 0.0;
static double win_value_perimeter = 0.0;
static double win_value_area = 0.0;

Listing 28.7: Adding global variables to track the behaviour and values

The win_immediate_update variable will be FALSE by default, but can be toggled to TRUE and back again using the new menu entry. To show the user the current state, we will update win_set_menu() to tick the menu entry as appropriate before the menu opens, as seen in Listing 28.8.

static void win_set_menu(wimp_w w, wimp_menu *menu, wimp_pointer *pointer)
{
        int places;

        if (menu != win_menu)
                return;

        places = calc_get_places();

        menus_tick_entry(win_menu, WIN_MENU_DECIMAL1, places == 1);
        menus_tick_entry(win_menu, WIN_MENU_DECIMAL2, places == 2);
        menus_tick_entry(win_menu, WIN_MENU_DECIMAL3, places == 3);
        menus_tick_entry(win_menu, WIN_MENU_IMMEDIATE, win_immediate_update == TRUE);
}

Listing 28.8: We also need to set the Immediate tick when the menu opens

To change the state of the new option, we will also update win_menu_selection() as seen in Listing 28.9. Selecting the Immediate menu entry will toggle the state of the win_immediate_update variable. The tick in the menu will be updated by win_set_menu() before the menu is redisplayed, even if Adjust is used.

static void win_menu_selection(wimp_w window, wimp_menu *menu, wimp_selection *selection)
{
        if (menu != win_menu)
                return;

        switch (selection->items[0]) {
        case WIN_MENU_DECIMAL1:
                calc_set_format(1);
                break;
        case WIN_MENU_DECIMAL2:
                calc_set_format(2);
                break;
        case WIN_MENU_DECIMAL3:
                calc_set_format(3);
                break;
        case WIN_MENU_IMMEDIATE:
                win_immediate_update = !win_immediate_update;
                break;
        }

        win_update_all_calculations(CALC_DIMENSION_NONE);
}

Listing 28.9: Detecting clicks on the Immediate menu entry

We mentioned above that implementing immediate updates was potentially difficult, and now that we must update our win_keypress() handler, this is likely to become more apparent. So far, we’ve ignored any keypress which isn’t Return, but here we need to detect when the values in the fields are changing whilst also continuing to pass on keypresses – such as F12 – that we don’t make use of. Previously, the Wimp was only passing on keys that it couldn’t use in the writable icons; with the N flag set in the K command, it is now passing on everything.

There are many ways to approach this problem, and one that immediately comes to mind is to pass on those keys that we know the icon won’t use. We have set the A command to “A0-9.”, so in theory we could pass on anything which isn’t 0 through to 9, . or Return. However, if we think a bit harder, we can see that the Delete and Backspace keys could also change the value in the field, as could Ctrl-V if a number is pasted in from the clipboard, and so on. This quickly becomes complicated!

The best way to do this might be to keep a copy of the value from each field, and compare this copy to the contents of the icon each time a key is pressed. If the values are different then we know that the contents of the icon must have changed, so we can take a new copy and recalculate the other values. To this end, we defined the win_value_length, win_value_perimeter and win_value_area global variables earlier, which we will now use to update the keypress handler as seen in Listing 28.10.

The initial test for the Return key has been extended to allow any key through if win_immediate_update is TRUE. This will also allow keys like F12 through, so we need to be a lot more careful about the remaining checks to ensure that we don’t swallow any hot keys. As a result, any keypress which occurs outside an icon is now passed on to other tasks, before the content of the contents of the icon in question is converted to a floating point value as before.

To detect changes, the resulting number is compared to one of the three new global variables, depending on which field it represents. If the values differ, or if the key was Return, we store the new value and set the target variable to report the change. If target remains set to CALC_DIMENSION_NONE at the end of these checks, no change has occurred as a result of the keypress and the key is again passed on to other tasks.

static osbool win_keypress(wimp_key *key)
{
        char                    buffer[WIN_NUM_BUF_SIZE];
        enum calc_dimension     target = CALC_DIMENSION_NONE;
        double                  value = 0.0;

        /* Only allow Return, unless immediate update is on. */

        if (key->c != wimp_KEY_RETURN && win_immediate_update == FALSE)
                return FALSE;

        /* We pass the key on if the caret isn't in an icon. */

        if (key->i == wimp_ICON_WINDOW)
                return FALSE;

        /* Copy the icon text, and convert it into a value. */

        icons_copy_text(win_handle, key->i, buffer, WIN_NUM_BUF_SIZE);

        value = atof(buffer);
        if (value == 0.0)
                return FALSE;

        /* Check to see if the value has changed. */

        switch (key->i) {
        case WIN_ICON_LENGTH_FIELD:
                if (value != win_value_length || key->c == wimp_KEY_RETURN) {
                        win_value_length = value;
                        target = CALC_DIMENSION_LENGTH;
                }
                break;
        case WIN_ICON_PERIMETER_FIELD:
                if (value != win_value_perimeter || key->c == wimp_KEY_RETURN) {
                        win_value_perimeter = value;
                        target = CALC_DIMENSION_PERIMETER;
                }
                break;
        case WIN_ICON_AREA_FIELD:
                if (value != win_value_area || key->c == wimp_KEY_RETURN) {
                        win_value_area = value;
                        target = CALC_DIMENSION_AREA;
                }
                break;
        }

        if (target == CALC_DIMENSION_NONE)
                return FALSE;

        /* Update the calculations. */

        calc_set_dimension(target, value);

        win_update_all_calculations((key->c == wimp_KEY_RETURN) ? CALC_DIMENSION_NONE : target);

        return TRUE;
}

Listing 28.10: Use keypresses to trigger immediate calculation updates

There’s another subtlety to be aware of with immediate updates, too. Up to now, win_update_all_calculations() has updated all of the fields in the window, but if we’re updating the values as the user types, we don’t want to update the value in the icon where they’re typing – if we do, decimal places will be appearing and the caret moving as they go.

To avoid this, we will update the win_update_all_calculations() function so that it takes a parameter which indicates a dimension type to be left unchanged, as seen in Listing 28.11. This can then be used to skip the field where the user is typing.

static void win_update_all_calculations(enum calc_dimension avoid)
{
        char            *text = NULL;
        int             index;
        wimp_caret      caret;
        wimp_icon_state state;

        /* Update the window data. */

        text = calc_get_sides();
        if (text != NULL) {
                icons_strncpy(win_handle, WIN_ICON_SIDES_FIELD, text);
                wimp_set_icon_state(win_handle, WIN_ICON_SIDES_FIELD, 0, 0);
        }

        text = calc_get_internal_angle();
        if (text != NULL) {
                icons_strncpy(win_handle, WIN_ICON_INT_ANGLE_FIELD, text);
                wimp_set_icon_state(win_handle, WIN_ICON_INT_ANGLE_FIELD, 0, 0);
        }

        text = calc_get_length();
        if (text != NULL && avoid != CALC_DIMENSION_LENGTH) {
                icons_strncpy(win_handle, WIN_ICON_LENGTH_FIELD, text);
                wimp_set_icon_state(win_handle, WIN_ICON_LENGTH_FIELD, 0, 0);
        }

        text = calc_get_perimeter();
        if (text != NULL && avoid != CALC_DIMENSION_PERIMETER) {
                icons_strncpy(win_handle, WIN_ICON_PERIMETER_FIELD, text);
                wimp_set_icon_state(win_handle, WIN_ICON_PERIMETER_FIELD, 0, 0);
        }

        text = calc_get_area();
        if (text != NULL && avoid != CALC_DIMENSION_AREA) {
                icons_strncpy(win_handle, WIN_ICON_AREA_FIELD, text);
                wimp_set_icon_state(win_handle, WIN_ICON_AREA_FIELD, 0, 0);
        }

        /* Correct the caret position if required. */

        if (xwimp_get_caret_position(&caret) != NULL)
                return;

        if (caret.w != win_handle)
                return;

        state.w = caret.w;
        state.i = caret.i;
        if (xwimp_get_icon_state(&state) != NULL)
                return;

        index = string_ctrl_strlen(state.icon.data.indirected_text.text);

        if (caret.index < index)
                index = caret.index;

        xwimp_set_caret_position(caret.w, caret.i, 0, 0, -1, index);
}

Listing 28.11: Allow a value to be omitted from the calculation update

We will also need to update the call to win_update_all_calculations() from win_set_shape(), as seen in Listing 28.12.

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

        /* Update the graphic. */

        switch (shape) {
        case CALC_SHAPE_SQUARE:
                sprite = "square";
                break;
        case CALC_SHAPE_CIRCLE:
                sprite = "circle";
                break;
        case CALC_SHAPE_TRIANGLE:
                sprite = "triangle";
                break;
        }

        if (sprite != NULL) {
                icons_strncpy(win_handle, WIN_ICON_SHAPE, sprite);
                wimp_set_icon_state(win_handle, WIN_ICON_SHAPE, 0, 0);
        }

        /* Change the shape. */

        calc_set_shape(shape);

        /* Perform the shape calculations. */

        win_update_all_calculations(CALC_DIMENSION_NONE);
}

Listing 28.12: The call to win_update_all_calculations() must be updated everywhere

With all of these changes in place, our application’s menu will have gained an Immediate entry as seen in Figure 28.2. It will be off by default, but if ticked, the fields will update in real time as values are typed into the icons in the window.

Figure 28.2: We have an option to toggle immediate key response

The full set of code can be found in Download 28.3.

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

As already noted, immediate update has been a lot of work, which also has potential downsides in terms of the user interface. We’ve used it here to demonstrate some features of the Wimp, but in general the K validation command’s N flag is a feature of the Wimp that is best used very sparingly indeed.