Chapter 13: Handling Windows

In the last chapter, we saw how to assemble a wimp_window structure in order to create a new window. Whilst this was used practically in Chapter 11, we now need to consider how the various SWIs provided by the Wimp for handling windows actually work.

Window definitions

As we’ve seen, creating a window on RISC  OS is simply a case of filling in a wimp_window structure and then passing a pointer to it to Wimp_CreateWindow so that it can be processed. The SWI uses the contents of the block as instructions for creating the new window, then returns us a wimp_w window handle for it. Whilst the process is simple, there are a couple of points which might not be immediately obvious about what’s happening behind the scenes.

In Listing 11.1, the code we used to create the window was as follows:

void win_initialise(void)
{
        wimp_window window_definition;

        window_definition.visible.x0 = 200;
        window_definition.visible.y0 = 200;
        window_definition.visible.x1 = 600;
        window_definition.visible.y1 = 600;
        window_definition.xscroll = 0;
        window_definition.yscroll = 0;
        window_definition.next = wimp_TOP;

        /* The values above this point aren't directly used by the Wimp */

        window_definition.flags = wimp_WINDOW_NEW_FORMAT |
                        wimp_WINDOW_MOVEABLE | wimp_WINDOW_AUTO_REDRAW |
                        wimp_WINDOW_BOUNDED_ONCE | wimp_WINDOW_BACK_ICON |
                        wimp_WINDOW_CLOSE_ICON | wimp_WINDOW_TITLE_ICON |
                        wimp_WINDOW_TOGGLE_ICON | wimp_WINDOW_VSCROLL |
                        wimp_WINDOW_SIZE_ICON | wimp_WINDOW_HSCROLL;
        window_definition.title_fg = wimp_COLOUR_BLACK;
        window_definition.title_bg = wimp_COLOUR_LIGHT_GREY;
        window_definition.work_fg = wimp_COLOUR_BLACK;
        window_definition.work_bg = wimp_COLOUR_VERY_LIGHT_GREY;
        window_definition.scroll_outer = wimp_COLOUR_MID_LIGHT_GREY;
        window_definition.scroll_inner = wimp_COLOUR_VERY_LIGHT_GREY;
        window_definition.highlight_bg = wimp_COLOUR_CREAM;
        window_definition.extra_flags = 0;
        window_definition.extent.x0 = 0;
        window_definition.extent.y0 = -1200;
        window_definition.extent.x1 = 1200;
        window_definition.extent.y1 = 0;
        window_definition.title_flags = wimp_ICON_TEXT |
                        wimp_ICON_BORDER | wimp_ICON_HCENTRED |
                        wimp_ICON_VCENTRED | wimp_ICON_FILLED;
        window_definition.work_flags = wimp_BUTTON_NEVER << wimp_ICON_BUTTON_TYPE_SHIFT;
        window_definition.sprite_area = wimpspriteop_AREA;
        window_definition.xmin = 0;
        window_definition.ymin = 0;
        strncpy(window_definition.title_data.text, "Hello World!", 12);
        window_definition.icon_count = 0;

        win_handle = wimp_create_window(&window_definition);
}

The first thing to realise is that, for all its complexity, the wimp_window structure is only needed up to the stage where a pointer to it is passed to wimp_create_window(). By the time the SWI has returned a new wimp_w handle for the window, it has copied the contents of the wimp_window structure away into its own workspace and the structure itself is no longer required.

We can actually see this in the code above, since window_definition is is being defined as a local variable within the win_initialise() function. The compiler will take the memory needed for local variables from the stack and so as a result, when the function exits, the whole wimp_window structure will disappear. The important thing is, this doesn’t matter: by the time the variable vanishes, the Wimp already has its own copy of everything that it requires!

One side effect of this is that the Wimp doesn’t care if we pass it a pointer to the same window definition more than once. The wimp_window structure is just a set of instructions for how to create the window, not the window itself: if we want two identical windows for some reason – and multi-document editors, to name just one example, often do – the Wimp doesn’t mind if we re-use the same instructions each time. Regardless of where the information came from, each window created will be a completely separate entity with its own wimp_w handle.

The second thing to realise is that the data in the structure falls into two distinct sections, which have been identified in the example above by a comment. The values set below the comment – from window_definition.flags to window_definition.icon_count – are taken away by the Wimp and used to define the look and feel of the new window. Once a window has been created, there’s no way to change its window flags, colours, sprite area or title bar definition. We can change the work area extent, but only by using a SWI dedicated to that purpose.

In contrast, the values set above the comment – from window_definition.visible to window_definition.next – aren’t actually used by the Wimp at all. It stores them and makes them available to us, but it never actually uses them itself. To see why, we need to consider what we need to do to get this new, but currently invisible, window open on the desktop.

Opening windows

As we’ve seen, Wimp_CreateWindow doesn’t put a window on the desktop: instead, it merely creates the window internally in the Wimp and allocates it a wimp_w window handle. At this point the window can be considered closed, and it will be necessary to open it in order to make it available to the user.

Opening a window is done by calling the Wimp_OpenWindow, which opens – or re-opens – a window on screen. In OSLib, the wimp_open_window() function is defined as follows:

extern void wimp_open_window(
        wimp_open *open
);

The SWI takes a single parameter, which is a pointer to a wimp_open structure. This structure, in turn, is defined as:

struct wimp_open {
        wimp_w  w;
        os_box  visible;
        int     xscroll;
        int     yscroll;
        wimp_w  next;
};

The information required by wimp_open_window() includes the visible area of the window (the screen coordinates of the bottom-left and top-right corners), the x and y scroll offsets, and the position of the window in the window stack. Between them, these pieces of information completely define the position of the window on screen: whether it is moved by dragging the title bar, resized with the resize button, has another window opened in front of it, or has its contents scrolled, the information contained in the wimp_open structure will completely describe its new state.

This is why the remit of the Wimp_OpenWindow is much wider than it might at first appear. It does include the obvious action of opening a previously closed window, but in fact the Wimp’s idea of “opening” includes any occasion when a parameter contained in the wimp_open structure changes. In fact, as we saw in Section 11.3, these attributes of a window can’t change unless the application calls Wimp_OpenWindow: our window was initially immovable until our code responded to the Open_Window_Request events being sent by the Wimp.

In the case that we’re responding to a Open_Window_Request event, working out what to put into the wimp_open structure is made easier because the Wimp supplies a structure of its own along with the event, containing what it thinks the values should be. The application can change the values, or – more usually – perform other actions based on what those values are, before passing them on to wimp_open_window(), but in many cases the code that we saw in Download 11.2 is all that’s required:

static void win_open_window_request(wimp_open *open)
{
        wimp_open_window(open);
}

In practice, an application built around SFLib would probably use the default method of responding to the event, as seen in Download 11.3, instead of a dedicated function like this – but the result is the same. Either way, the necessary code isn’t complicated, because the Wimp does the hard work for us.

Harder to work out is what to fill the wimp_open structure with when we wish to open a window which is currently closed. There’s no Open_Window_Request event to give us a hint, but in fact the Wimp can still help despite that. Comparing the contents of the wimp_open structure with the definition of wimp_window (which was given in the last chapter) should reveal that, aside from the wimp_w window handle wimp_open.w at the top of the wimp_open structure, it is identical to the first part of a window definition.

We said that the Wimp never used the values from above the comment in the wimp_window structure directly, but it does store them away for future reference when the structure is passed to Wimp_CreateWindow and then update them every time Wimp_OpenWindow is called for the window. At any time, whether the window is open or closed, the application can request the current set of data from the Wimp using the Wimp_GetWindowState SWI. In OSLib, this is defined as:

extern void wimp_get_window_state(
        wimp_window_state *state
);

It takes a single parameter, which is a pointer to a wimp_window_state structure:

struct wimp_window_state {
        wimp_w                  w;
        os_box                  visible;
        int                     xscroll;
        int                     yscroll;
        wimp_w                  next;
        wimp_window_flags       flags;
};

As with a lot of Wimp SWIs which take a pointer to a parameter structure, we need to fill some details into the structure before making the call. In the case of wimp_get_window_state(), the window handle state.w must be inserted, so that the Wimp knows which window to return the details for. In our application, we’re calling it as follows:

void win_open(void)
{
        wimp_window_state state;

        state.w = win_handle;
        wimp_get_window_state(&state);
        state.next = wimp_TOP;
        wimp_open_window((wimp_open *) &state);
}

The wimp_window_state structure is identical to the wimp_open structure, except for the additional state.flags at the end. This means that we’re safe to cast the state variable from wimp_window_state to wimp_open in order to pass it to wimp_open_window(), as the Wimp will simply ignore the additional data at the end of the structure.

Changing where our window opens

Every time a window is opened with Wimp_OpenWindow, the Wimp stores the new position away ready for it to be returned if Wimp_GetWindowState is called. In fact we can see this in action very easily: open our application’s window, drag it to a new location, then close it. If the window is re-opened with another Select click on the iconbar, it will appear in the last location that it was in before it closed. This happens because the call to wimp_get_window_state() retrieves the most recently saved values, which our application then passes on to wimp_open_window().

The values put into the wimp_window_state structure by the Wimp are only suggestions, of course: we’re free to change them as we wish, and in fact we’re already doing so to some extent. Between retrieveing the values and passing them on to Wimp_OpenWindow, there’s the following line:

state.next = wimp_TOP;

This resets the window’s position in the window stack, so that it always opens up at the top of the pile above every other window that’s open. It’s a common thing to do, as it usually makes most sense to put a newly-opened window on the top of the pile so that the user can see it. In fact, Wimp_OpenWindow accepts three special values for state.next, which OSLib defines as follows:

#define wimp_TOP        ((wimp_w) 0xFFFFFFFFu)
#define wimp_BOTTOM     ((wimp_w) 0xFFFFFFFEu)
#define wimp_HIDDEN     ((wimp_w) 0xFFFFFFFDu)

We’ve seen that wimp_TOP will cause the window to be opened above every other window that’s open on the desktop and, as one might expect, wimp_BOTTOM causes it to be opened at the bottom of the pile, just above the Pinboard. The third special location is wimp_HIDDEN, which requests that the window be opened but also be invisible to the user: this is a bit specialised, and unlikely to be of much use to us at present.

As well as these three special values, state.next can take any wimp_w window handle: if one is used, it will cause our window to open behind that window. This isn’t something that we often need to use directly, but the Wimp uses it whenever it sends an Open_Window_Request to our application: unless the window is at the top of the window stack, the wimp_open structure which arrives with the event will have the handle filled in for the window directly above ours in the stack. Perhaps surprisingly, this handle can be for a window belonging to any running application: it doesn’t have to belong to our application.

In the same way, it’s also possible to change the visible area of the window before opening it, in order to enable it to appear on screen in a sensible place. Exactly what “sensible” means will depend on the sort of window that we’re opening: the Style Guide requires new editor windows to open centred on the screen, while dialogue boxes might open centred on the pointer. In this case we’ll centre the window on the screen.

In order to centre a window on the screen, we need to know the screen dimensions. RISC OS makes this information available to us through the OS_ReadModeVariable SWI and, if we #include "sflib/general.h" then SFLib makes the general_mode_width() and general_mode_height() functions. These are defined within SFLib like this:

#include "oslib/os.h"

int general_mode_width(void)
{
        int width, shift;

        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT, &width);
        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR, &shift);

        return (width << shift);
}

int general_mode_height(void)
{
        int height, shift;

        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT, &height);
        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &shift);

        return (height << shift);
}

Each function returns one screen dimension in OS Units, and – armed with these – we can then change the win_open() function as follows:

void win_open(void)
{
        wimp_window_state       state;
        int                     width, height;

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

        width = state.visible.x1 - state.visible.x0;
        height = state.visible.y1 - state.visible.y0;

        state.visible.x0 = (general_mode_width() - width) / 2;
        state.visible.y0 = (general_mode_height() - height) / 2;

        state.visible.x1 = state.visible.x0 + width;
        state.visible.y1 = state.visible.y0 + height;

        state.next = wimp_TOP;

        wimp_open_window((wimp_open *) &state);
}

As before, the function starts by requesting the window state from the Wimp via Wimp_GetWindowState. However its next step is to work out the width and height of the window’s visible area by subtracting the x0 coordinate from x1 and the y0 coordinate from y1 respectively. With these values safely stored in the width and height variables, state.visible.x0 and state.visible.y0 can then be set such that the window’s visible area appears centred on the screen. Finally, state.visible.x1 and state.visible.y1 can be updated so that the overall width and height of the visible area remains unchanged.

A full set of modified code can be found in Download 13.1: when compiled and run, the application’s window should always open at the centre of the desktop. Slightly confusingly, it also jumps back to the centre if it is already open when Select is clicked on the iconbar icon; this isn’t really good behaviour, and we’ll sort it out shortly.

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

Getting information about a window

Along with the information required by wimp_open_window(), wimp_get_window_state() returns the window’s flags in the wimp_window_state structure. The reason for this isn’t to remind us of how we defined the window, but because in addition to the flags discussed in the last chapter, which we set before calling Wimp_CreateWindow, there are a group of flags which the Wimp keeps updated to tell us about the current state of the window. These can be read using Wimp_GetWindowState, and are defined in OSLib as follows:

#define wimp_WINDOW_OPEN                ((wimp_window_flags) 0x10000u)
#define wimp_WINDOW_NOT_COVERED         ((wimp_window_flags) 0x20000u)
#define wimp_WINDOW_FULL_SIZE           ((wimp_window_flags) 0x40000u)
#define wimp_WINDOW_TOGGLED             ((wimp_window_flags) 0x80000u)
#define wimp_WINDOW_HAS_FOCUS           ((wimp_window_flags) 0x100000u)
#define wimp_WINDOW_BOUNDED_ONCE        ((wimp_window_flags) 0x200000u)
#define wimp_WINDOW_PARTIAL_SIZE        ((wimp_window_flags) 0x400000u)

Some of the flags describe simple aspects of the window’s state. The wimp_WINDOW_OPEN flag is set if the window is open on screen, while wimp_WINDOW_HAS_FOCUS indicates that it has input focus – that keyboard input will be directed to it, as we will see in Chapter 27. If wimp_WINDOW_NOT_COVERED is set, we know that there are no other windows on top of ours.

Whilst all of these flags are useful in certain circumstances, the most common one for an application to read is probably wimp_WINDOW_OPEN. In the last section, when we updated the win_open() to open the window in the centre of the screen, we noticed that the changes had the effect of making the window jump back to its opening position if the icon was clicked while it was open. This is confusing, and in fact while suggesting that an application’s main window opens at the centre of the screen, the Style Guide also suggests that if the window is already open, a click on the iconbar icon should simply bring the window to the top of the window stack.

Fortunately, this is really easy to do in win_open(). The function already calls wimp_get_window_state() to retrieve the window’s position details, and the wimp_window_state structure contains the window flags. We can therefore make the code that resets the window’s position to the centre of the screen conditional on the window not being open:

void win_open(void)
{
        wimp_window_state       state;
        int                     width, height;

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

        if (!(state.flags & wimp_WINDOW_OPEN)) {
                width = state.visible.x1 - state.visible.x0;
                height = state.visible.y1 - state.visible.y0;

                state.visible.x0 = (general_mode_width() - width) / 2;
                state.visible.y0 = (general_mode_height() - height) / 2;

                state.visible.x1 = state.visible.x0 + width;
                state.visible.y1 = state.visible.y0 + height;
        }

        state.next = wimp_TOP;

        wimp_open_window((wimp_open *) &state);
}

The updated code can be found in Download 13.2, and if this is tried, Select clicks on the iconbar icon should seem a lot more ‘normal’.

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

One slight curiosity still remains: while the window returns to the centre of the screen whenever it is opened, the size of its visible area is remembered from when it was closed. To see this in action, open the window, change its size with the resize icon, close it, then finally open it again with another Select click on the iconbar icon. The effect happens because we calculate the width and height variables from the visible area returned by Wimp_GetWindowState – which, of course, remembers where the window was when it was last closed.

If we wished to reset the window’s visible area each time it is opened up, we could always replace the two lines

width = state.visible.x1 - state.visible.x0;
height = state.visible.y1 - state.visible.y0;

in the win_open() function with the simpler

width = 400;
height = 400;

This isn’t ideal, however. It means that we’re now setting the visible area of the window in the win_initialise() as part of the original definition passed to Wimp_CreateWindow

window_definition.visible.x0 = 200;
window_definition.visible.y0 = 200;
window_definition.visible.x1 = 600;
window_definition.visible.y1 = 600;

before ignoring the information and replacing it completely by the values 400 in the win_open(). Duplicating values like this is never good, as it can lead to confusion when the code is revisited later on. In addition, deriving as much information as possible from the wimp_window structure will be an advantage once we meet template files later on. For these reasons, a better solution would be to create two new global variables at the top of the file to hold the initial width and height of the window:

static int win_width, win_height;

These can then be set in the win_initialise() function, after the window_definition structure has been filled in, using the same calculation that was in the win_open() function:

win_width = window_definition.visible.x1 - window_definition.visible.x0;
win_height = window_definition.visible.y1 - window_definition.visible.y0;

Finally, the conditional code in win_open() can change to use the new global win_width and win_height variables:

if (!(state.flags & wimp_WINDOW_OPEN)) {
        state.visible.x0 = (general_mode_width() - win_width) / 2;
        state.visible.y0 = (general_mode_height() - win_height) / 2;

        state.visible.x1 = state.visible.x0 + win_width;
        state.visible.y1 = state.visible.y0 + win_height;
}

Now the window will always return to its initial dimensions when it is closed and then re-opened. The full set of modifications to the code can be found in Download 13.3.

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

Closing and deleting windows

Closing a window is the exact opposite of opening it with Wimp_OpenWindow: it is removed from view on the desktop, whilst retaining both its wimp_w window handle and the associated record in the Wimp’s list of known windows. In effect, closing a window puts it back into a similar state to that which it was in just after Wimp_CreateWindow was called – except that any changes made since then will be stored ready for retrieval by Wimp_GetWindowState.

Windows are closed using the Wimp_CloseWindow SWI, but unlike its counterparts for opening windows, it’s a much simpler affair. OSLib defines it as:

extern void wimp_close_window(
        wimp_w  w
);

The only parameter required is a wimp_w window handle: the one belonging to the window that is to be closed. If the window is being closed in response to a Close_Window_Request event from Wimp_Poll, the Wimp supplies us the handle inside the wimp_close structure that comes with the event; at other times, it’s assumed that we will know which window – and hence window handle – we wish to close.

It’s important to remember that closing a window does not delete it from the Wimp’s list of windows. In fact, to completely remove a window from the Wimp’s ‘memory’ it is necessary to use the Wimp_DeleteWindow SWI – in the same way that Wimp_OpenWindow and Wimp_CloseWindow form a pair, Wimp_DeleteWindow is the opposite number to Wimp_CreateWindow.

As with Wimp_CloseWindow, Wimp_DeleteWindow takes a single parameter consisting of the window handle for the window to be deleted:

extern void wimp_delete_window(
        wimp_w  w
};

For now, the main reason for knowing about Wimp_DeleteWindow is to clarify the status of windows closed with Wimp_CloseWindow. It’s rare for simple applications to need to delete windows, as there’s no problem closing and then re-opening a window as many times as required. In fact, when Wimp_CloseDown is called as an application exits (as our example does in the main_terminate() function), any remaining windows will be closed and then deleted without specific action needing to be taken to do it.