Chapter 11: Creating our First Window

With the initial legwork complete, we’re now ready to add the first window to our application. Windows are a fundamental part of the RISC OS desktop: so much so that most of us probably don’t give them much thought from day to day. A couple of examples, belonging to familiar applications Zap and Snapper, can be seen in Figure 11.1.

Figure 11.1: A couple of windows on the RISC OS desktop

All windows are views on to areas of the screen, which usually belong to an individual applications. Some, like the one belonging to Snapper to the front and left of the screenshot above, have a small fixed area which is always in view; others, like the Zap window to the back right, have a much larger area than is visible at any one time and which can be explored using their scroll bars. The area that’s visible is known as the visible area, while the whole ‘virtual’ area encompassed by moving the scroll bars from one extreme to the other is the work area. The job of the Wimp is to look after these areas and help the application to update them when required.

There’s a lot of theory to get through in order to define a window and get it on the screen, so instead of getting too bogged down in detail we’ll dive straight in at the deep end. Once we’ve put a window on the screen, we can then start to look at how it got there!

Window definitions

There are several stages to getting a window on to the screen with the RISC OS Wimp: first it must be defined in a way that the Wimp_CreateWindow SWI can understand. This SWI will create the window for us, giving us back a window handle which we can then use to open the window on screen with the help of the Wimp_OpenWindow SWI.

To make this happen, we’ll create a new file called c.win to hold the code which defines and opens the window. Since this is new, it’s shown in full in Listing 11.1.

 /**
 * Example 11.1
 *
 * (c) Stephen Fryatt, 2017
 *
 * File: win.c
 */

#include "oslib/wimp.h"
#include "oslib/wimpspriteop.h"

#include <string.h>

#include "win.h"

/* Global Variables */

static wimp_w win_handle;

/* Window Initialisation. */

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

/* Open the Window. */

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

Listing 11.1 (c.win): The code to define and open our first window

The file contains two functions. The first – win_initialise() – assembles the window definition in the wimp_window structure referred to by the window_definition variable, before passing it as a pointer to wimp_create_window(). The Wimp_CreateWindow SWI processes the definition and returns a window handle that is assigned to the win_handle variable, which is of the wimp_w type. From this we can deduce that the wimp_window type is used to hold window definitions, while wimp_w is used to hold window handles.

The second function – win_open() – does the job of taking the window handle stored in win_handle and opening it up on the screen. We’ll see how this works shortly.

To go with c.win, we also need a header file to make its functions available to the rest of the application: we’ll call this h.win, and it can be seen in full in Listing 11.2.

 /**
 * Example 11.2
 *
 * (c) Stephen Fryatt, 2017
 *
 * File: win.h
 */

#ifndef EXAMPLEAPP_WIN
#define EXAMPLEAPP_WIN

/* Window Initialisation. */

void win_initialise(void);

/* Open the Window. */

void win_open(void);

#endif

Listing 11.2 (h.win): The header file to define the win module’s interface

We now need to hook the new code into the rest of the application. To initialise the window when the application starts up, #include "win.h" in c.main and update the main_initialise() to call win_initialise():

static void main_initialise(void)
{
        wimp_initialise(wimp_VERSION_RO3, main_application_name, NULL, NULL);

        error_initialise(main_application_name, main_application_sprite, NULL);

        event_add_message_handler(message_QUIT, EVENT_MESSAGE_INCOMING, main_message_quit);

        ibar_initialise(main_application_sprite);
        win_initialise();
}

Since we’ll want to have the window open up when the user clicks on the iconbar icon, we can similarly #include "win.h" in c.ibar and update the main_initialise() function to call ibar_mouse_click():

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

Now, when the user clicks Select on the icon, the win_open() function will be called to open the window on screen. Note that we’ve taken the opportunity to remove the debugging output left over from the last few chapters.

The last thing to do is to update the Makefile so that the compiler knows to include c.win, and we should be ready to go.

OBJS = main ibar win

The updated source code should look something like that shown in Figure 11.2, and a full set of code can be found in Download 11.1.

Figure 11.2: The complete project, with the window code added

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

When compiled and run, the application should install on the iconbar as before. However this time, clicking Select over its iconbar icon will open a window – something like the one shown in Figure 11.3. Unfortunately, there’s a problem: the window doesn’t respond to the mouse. In fact, all attempts to move, scroll or even close it are simply ignored. Fortunately the application can still be quit with an Adjust click on its iconbar icon – at which point the window disappears.

Figure 11.3: Our first window – Hello World!

Being responsive

The reason for this apparent unresponsiveness is actually quite simple: we’re missing some code. Although it might seem a little surprising at first, the Wimp is unable to open or change the position of a window – by which we mean dragging, resizing or scrolling it – unless our application requests that it does so by calling Wimp_OpenWindow. Our window appears on screen because we called wimp_open_window() for it at the end of our win_open() function, but after that it’s stuck in the one place because we never ask the Wimp to move it again.

This behaviour on the part of the Wimp is deliberate, and gives the programmer a great deal of flexibility. Back in the days of RISC OS 3, applications like Draw – seen in Figure 11.4 below – could implement their toolboxes by manipulating whatever additional windows they wanted whenever the Wimp asked them to adjust their main window. With the advent of the Nested Wimp there are much easier ways to handle this requirement, but the facility still has some important uses: consider something like a spreadsheet, for example, where scrolling the window down or right adds rows or columns to the grid – requiring the window’s work area to be adjusted on the fly.

Figure 11.4: Applications like Draw use Open Window Request events to manage their tool panes

Fortunately, in the vast majority of cases – where we’re not trying to implement spreadsheets – things are nowhere near as difficult as they might at first appear. If we examine the wimp_block union returned by Wimp_Poll – which we met in Chapter 2 and Chapter 6 – we can see that it contains an entry called wimp_block.open which is a wimp_open structure. Looking back at how we implemented the win_open() function above, we should see that this is the same type as the one that we cast our pointer to when calling wimp_open_window().

This is no accident. When the Wimp needs an application to change the position of a window, it sends an Open_Window_Request event through Wimp_Poll and – in the data attached to the event – indicates where it thinks that the window should be on screen. Unless the application wishes to do something ‘clever’ with the request, all that it needs to do is pass the supplied data straight on to Wimp_OpenWindow and leave the Wimp to do the rest.

To make our window move, we could register an event handler for Open_Window_Request and use this to make the calls to Wimp_OpenWindow. Back in Chapter 7 we saw how to do this for Mouse_Click events on our iconbar icon, and the process is very similar. First, we need to define a function to handle the events:

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

As predicted, it’s very simple because we’re leaving the Wimp to tell us where the window needs to be opened. In a similar way to the Mouse_Click event handler, the function takes as a single parameter a pointer to the wimp_open structure which arrived with the event, and returns nothing. The data pointed to by *open is ready to be passed straight on to wimp_open_window(), which is all that we need to do.

We need to let the event library know about this handler, and this is also done in a very similar manner to that used in Chapter 7. The main difference is that back then, we regsitered the Mouse_Click handler with the special wimp_ICON_BAR window handle, but here we actually need to use the real handle that the Wimp has given to our window. This is the value returned by the wimp_create_window() function, which we have stored in the win_handle variable.

To register the handler, we can call the event_add_window_open_event() function at the end of win_initialise(), immediately after the call to wimp_create_window():

win_handle = wimp_create_window(&window_definition);
event_add_window_open_event(win_handle, win_open_window_request);

With a bit of tidying up to include the necessary function prototype and #include "sflib/event.h" in c.win, this addition can be found in Download 11.2. When run, clicking Select on the iconbar icon will open our window – which should now move around when dragged, and respond to resizing and scrolling.

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

However, clicking on the Close button still doesn’t work – and there’s another potential annoyance with this approach, too. We’ve said that every window needs to respond to Open_Window_Request events, and since our event handler is registered to this one specific window handle, we would need to register a handler for each window that our application uses. This isn’t impossible, but it will be a lot of duplication for something that’s so standard.

Generic event handlers

If we’re using SFLib’s event library to dispatch Wimp events, then there’s another way to handle these events. In c.main, the events returned by our application’s repeated calls to Wimp_Poll are sent out to the appropriate pieces of code by the library. Each time the SWI returns with details of a new event, it is passed on to the event_process_event() function and this tries to find a handler which is interested. At present, the code looks like this:

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

But what happens if event_process_event() can’t find an interested event handler? The answer is that the function returns a boolean osbool value, which is TRUE if a handler was found and FALSE if one wasn’t. We can use this to add default handlers for events and – given that the vast majority of windows simply need to call Wimp_OpenWindow unquestioningly for any Open_Window_Request events that they receive – this is an obvious use for that functionality.

Having undone the changes that we made to c.win when adding the Open_Window_Request event handler, we can now change the definition of the main_poll() function in c.main to re-introduce a switch statement very similar to the one that we removed back in Chapter 4.

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

The difference now is that, because this switch statement is only called when event_process_event() returns FALSE, it is only used when the event library can’t find a handler for an event. If our application had a main window with a requirement to do something clever when Open_Window_Request events were received, we could still register an event handler for it as we did in Download 11.2 and this would be used instead of this fall-back switch statement. If the same application also had dialogue boxes where a simple call to Wimp_OpenWindow were enough, then the default code here will handle it without us needing to give it a second thought.

Closing windows

You’ll notice that there are actually two case clauses in the switch that has been added above. This is because our window still wasn’t responding to clicks on its Close button, for a very similar reason.

Just as the Wimp sends applications an Open_Window_Request event when it wants them to open a window with Wimp_OpenWindow, it also delegates the closing of windows. When the user clicks on our window’s Close button, our application receives a Close_Window_Request event – and it should come as little surprise to know that in most cases we can simply pass this on to the Wimp_CloseWindow SWI for processing.

The wimp_close structure returned by Close_Window_Request event is defined by OSLib as follows:

struct wimp_close {
        wimp_w  w;
};

and simply contains a window handle held in a variable w of type wimp_w. The wimp_close_window() takes a window handle as its single parameter, and is defined like this:

extern void wimp_close_window(
        wimp_w  w
);

This makes handling Close_Window_Request events – at least in the default case – as simple as passing the window handle to Wimp_CloseWindow.

The changes to use default handlers for both Open_Window_Request and Close_Window_Request events can be found in Download 11.3. If the code is complied and run, the window should be seen to be fully responsive. Not only will it move around when dragged, scrolled and resized, but it will also close when asked to do so; it can then be re-opened with another Select click on the iconbar.

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

While our application now contains all of the code required to open a window and allow the user to move it around, we’ve glossed over a lot of detail in order to get here. Over the next couple of chapters we’ll go back over what we’ve done, examining how it works and why. As we go, feel free to experiment with the code in Download 11.3 to see how the ideas that are about to be covered work in practice.