Chapter 16: Mouse Clicks in Windows

In Chapter 14 and Chapter 15 we introduced the theory behind displaying icons in a window, but so far we’ve not considered how to let the user interact with them and provide feedback to our application. In the desktop environment, one obvious way is through the user clicking on icons and other parts of the window – and that’s what we’ll look at now.

Mouse Click events

In keeping with other parts of the Wimp, details of mouse activity inside our window are returned to our application using Mouse_Click events as a result of our calling Wimp_Poll – we briefly met these in Chapter 7, when we set up the code necessary to respond to clicks on our iconbar icon. When a Mouse_Click event is returned, the Wimp fills the Wimp_Poll block up with a wimp_pointer structure which holds details of the user’s action:

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

typedef struct wimp_pointer wimp_pointer;

The structure contains four pieces of information, the first of which is the position of the mouse pointer when the click occurred. This is held in wimp_pointer.pos, which is itself an os_coord structure:

struct os_coord {
        int                     x;
        int                     y;
};

typedef struct os_coord os_coord;

This means that the x and y coordinates of the pointer can be found in wimp_pointer.pos.x and wimp_pointer.pos.y respectively; they’re in OS Units, measured from the screen origin at the bottom left of the desktop. The combination of buttons which were clicked (or dragged) can be found in wimp_pointer.buttons, whilst the wimp_w and wimp_i handles of the window and icon under the pointer can be found in wimp_pointer.w and wimp_pointer.i respectively.

We’re going to update our application to listen out for any of these events which relate to our window, and display their details in the icon. To do that, we will need to know the wimp_i handle of the icon – so we start by adding a new global variable to the top of c.win:

static wimp_i win_icon_handle;

The line in the win_initialise() where win_create_icon() is called can then be updated: win_create_icon() returns the handle of the icon that it created, so this can be stored for future reference:

win_icon_handle = win_create_icon();

Back in Chapter 4, we started to use SFLib’s event library to process the events returned by Wimp_Poll and pass them on to the correct places. We now need to create a function to handle Mouse_Click events relating to our window, and then let the library know about it. Following a very similar process to the one that we used to handle clicks on the iconbar icon in Chapter 7, we can start by adding a new function to the end of c.win and a corresponding function prototype at the top of the file:

static void win_mouse_click(wimp_pointer *pointer)
{
        snprintf(win_icon_text, WIN_ICON_TEXT_LEN, "(%d,%d), buttons=%d, window=0x%p, icon=%d",
                        pointer->pos.x, pointer->pos.y,
                        pointer->buttons,
                        (void *) pointer->w, pointer->i);
        win_icon_text[WIN_ICON_TEXT_LEN - 1] = '\0';

        wimp_set_icon_state(win_handle, win_icon_handle, 0, 0);
}

We want the event library to pass Mouse_Click events on to this function whenever pointer->w contains the handle of our window. This is held in the win_handle variable, so we can register the function with the library by adding the following line to the end of the win_initialise() function:

event_add_window_mouse_event(win_handle, win_mouse_click);

We will need to remember to #include "sflib/event.h" to make the event_add_window_mouse_event() function available, and – since we’re using snprintf() – also #include <stdio.h>. A complete set of updates can be found in Download 16.1.

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

If the code is compiled and run, it will open a window which looks identical to the one seen in Section 15.7. If the mouse is clicked over the window and icon, however, it should be found that Menu clicks – and only Menu clicks – will be reported as seen in Figure 16.1.

Figure 16.1: Reporting menu clicks over our window

Updating the icon contents

Before looking at what is special about the Menu button, let’s start by considering what the win_mouse_click() is actually doing. Our application is now responding to our input in a practical way, and this is a very useful thing in itself!

Having registered it with the event library, win_mouse_click() will be called when a Mouse_Click event arrives from Wimp_Poll. Since the call to event_add_window_mouse_event() specified the wimp_w handle held in win_handle, we can be certain that pointer->w will always be equal to win_handle – if it isn’t, the event library will send the event somewhere else. At present, events where pointer->w is equal to wimp_ICON_BAR will be delivered to the ibar_mouse_click() in c.ibar whilst events containing any other wimp_w handles would be discarded – with the call to event_process_event() in main_poll() returning FALSE. However, since our application only has the one window and an iconbar icon, there should never be any other values in pointer->w.

Using snprintf(), the function writes a string describing the details held in the block into the buffer pointed to by win_icon_text and then makes sure that it’s safely terminated by writing '\0' to the end. This is the memory that we’ve used for our icon’s indirected data, so it causes the icon’s text to be updated. The Wimp doesn’t continuously monitor every icon for changes, however, so it’s necessary to request that the icon be redrawn.

The request can be made using the Wimp_SetIconState SWI, which requires both the wimp_w handle of the window containing the icon and the wimp_i handle of the icon itself. Having saved the icon’s handle in the win_icon_handle variable, these can simply be passed to the wimp_set_icon_state() function. The purpose of the call to this SWI should become obvious if it is commented out and the application is recompiled: the icon’s contents will still change when Menu is clicked over the window, but it will not update until another window or menu is dragged over it.

We’ll come back to look at Wimp_SetIconState in more detail in Section 20.2, but for now the final two parameters can be left as zero.

Event information

Along with the wimp_w handle of the window under the pointer when the click occurred, which in this case will always be the handle held in the win_handle variable, the wimp_pointer block contains other useful information about what happened.

The pointer->i element of the structure contains the wimp_i handle of the icon under the pointer, and experimentation with the Menu button should show that this is 0 when the pointer is over the icon and −1 when it isn’t. Being the first – and only – icon in the window, the Wimp has allocated the icon a handle of 0, and this is what has been stored in the win_icon_handle variable. The −1 indicates the window work area or “no icon” and if we need to test for this, OSLib helpfully provides a couple of useful definitions to make code clearer:

#define wimp_NO_ICON            ((wimp_w) 0xFFFFFFFFu)
#define wimp_ICON_WINDOW        ((wimp_i) 0xFFFFFFFFu)

The combination of buttons which were clicked (or dragged) can be found in wimp_pointer.buttons. It’s defined with a type of wimp_mouse_state, and – as we saw in Section 7.4 – OSLib defines a number of useful constants to help decipher the information. For now, the following three values will be useful:

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

Since the window is only responding to Menu at present, the value returned is always 2 – or wimp_CLICK_MENU.

Getting more reaction

The question is why the window only responds to Menu clicks? The answer is that when we set up the window’s work area flags in Section 12.6, we set them to wimp_BUTTON_NEVER; similarly, the icon’s button type was also set to wimp_BUTTON_NEVER (after a short flirtation with wimp_BUTTON_WRITABLE) in Section 15.3. This button type ignores mouse clicks, and never reports them to our application.

At least, that’s almost true. Due to the standard use of the Menu button for opening context menus on RISC OS, the Wimp will always pass Menu clicks through to the application, regardless of the object under the pointer. That’s why Menu clicks are being reported: there’s no way to switch them off.

If we wish to receive notifications of other clicks over our icon, then we need to change its button type as set in win_create_icon(). If we set it to wimp_BUTTON_CLICK, then the Wimp will return details of all clicks over the icon:

icon_definition.icon.flags = wimp_ICON_TEXT | wimp_ICON_INDIRECTED |
                wimp_ICON_BORDER | wimp_ICON_FILLED |
                wimp_ICON_HCENTRED | wimp_ICON_VCENTRED |
                (wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT) |
                (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) |
                (wimp_COLOUR_VERY_LIGHT_GREY << wimp_ICON_BG_COLOUR_SHIFT);

Compile this update and run it, and it should be clear that when the pointer is over the icon, Select, Menu and Adjust clicks are reported with codes 4, 2 and 1 respectively (which we saw above are wimp_CLICK_SELECT, wimp_CLICK_MENU and wimp_CLICK_ADJUST). Away from the icon, however, it’s still only Menu clicks which are returned.

The Wimp can be a bit more sophisticated than this, however. Move the pointer over the centre of the icon, then click and hold Select: the coordinates in the icon should update to show the point at which the click occurred. Still holding the button, move the mouse pointer around within the icon’s border: nothing changes, because wimp_BUTTON_CLICK returns information only once, at the start of the click.

Now change the button type to wimp_BUTTON_REPEAT, recompile the code and try the same experiment again. This time, the icon updates when the button is clicked, but continues to do so until either it is released again or the pointer moves outside the icon. This button type provides an ‘auto repeat’ functionality, keeping the application updated as long as the click is held over the icon.

Next, change the type to wimp_BUTTON_RELEASE and try again. Now when the button is clicked, the icon goes black as the button is pressed (as seen in Figure 16.2), and then clears when the pointer moves out of the icon’s area. This effect is the Wimp selecting the icon: something that we’ll look at in Section 18.4. The details in the icon are only updated when the button is released, so long as the pointer is still in the icon (and hence the icon is still selected) at the time. As its name suggests, wimp_BUTTON_RELEASE reports back to the application when the mouse button is released.

Figure 16.2: A release’ icon in use

The wimp_BUTTON_RELEASE is an example of one of the more complex interactions that the Wimp can implement automatically for an application. It’s really intended for things like action buttons, where the button presses at the user clicks the mouse but the action only happens when they release it – consider the operation of dialogue boxes on platforms such as Windows of Linux for an example. Sadly the RISC OS convention has always been to use wimp_BUTTON_CLICK for action buttons, so for consistency and ease of use wimp_BUTTON_RELEASE must remain unused – it can be seen as the close button on all windows, however!

The final button type that we’ll look at for now is wimp_BUTTON_ALWAYS. This is something like the opposite of wimp_BUTTON_NEVER, in that it reports Mouse_Click events back to the application all the time that the pointer is over the icon: trying it in our icon should show that the details are updated continuously for the whole time that the pointer is over the icon. This is actually not very useful, as there are invariably better ways to achieve any of the things that it can implement. It’s mentioned here only for completeness.

As Section 12.6 hinted, some of the button types can also be applied to the window’s work area. If we change window_definition.work_flags to wimp_BUTTON_CLICK in the win_initialise() function, then after recompiling the code Select and Adjust clicks will be reported outside of the icon as well.

window_definition.work_flags = wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT;

An icon’s button type always takes precedence over the window beneath it when the pointer is within the icon’s borders. Some of the button types also behave slightly differently when applied to a window, since there’s no concept of ‘selecting’ the work area or making it writable. Before moving on, it would be a good idea to experiment with the button types that we’ve seen so far, to ensure that their behaviour makes sense.

Mouse coordinates

When a Mouse_Click event is returned to our application, the coordinates of the click can be found in pointer->pos.x and pointer->pos.y. They are given as absolute values in OS Units, measured from the bottom-left corner of the desktop. Whilst this is often useful, we often wish to know where within our window the user clicked.

Fortunately, there’s a simple way to get to the window position – although we do need to know the window’s current position and scroll offsets. This information is readily available from the Wimp_GetWindowState which we met back in Chapter 13, however, so we can amend the win_mouse_click() as follows to make it report work area coordinates for each click:

static void win_mouse_click(wimp_pointer *pointer)
{
        int                     xpos, ypos;
        wimp_window_state       state;

        state.w = win_handle;
        wimp_get_window_state(&state);

        xpos = (pointer->pos.x - state.visible.x0) + state.xscroll;
        ypos = (pointer->pos.y - state.visible.y1) + state.yscroll;

        snprintf(win_icon_text, WIN_ICON_TEXT_LEN, "(%d,%d), buttons=%d, window=0x%p, icon=%d",
                        xpos, ypos,
                        pointer->buttons,
                        (void *) pointer->w, pointer->i);
        win_icon_text[WIN_ICON_TEXT_LEN - 1] = '\0';

        wimp_set_icon_state(win_handle, win_icon_handle, 0, 0);
}

As we saw in Chapter 12, the work area coordinates have their origin in the top-left of the window, increasing from left to right and decreasing from top to bottom. A complete set of code for this change can be dound in Download 16.2.

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