Chapter 3: A Top Toolbar

An alternative to a toolbox at the side of a window, as we created in the last chapter, is a toolbar across the top of a window. Which to go for is more of a stylistic question than a technical one, and it’s even possible to have both at the same time. Ovation Pro does just this, as can be seen in Figure 3.1.

Figure 3.1: Ovation Pro has both a side toolbox and a toolbar at the same time

In this chapter, we’ll add such a toolbar to our own window, to complement the existing toolbox.

Adding a toolbar

Just as with the toolbox in the previous chapter, the first thing that we will need to do in order to create a toolbar is to design a suitable window template. This time, we will create a toolbar containing a couple of action buttons and a writeable icon, which will span the top of our main window – a bit like the URL bar in a browser. The template has been called “Toolbar”, and can be seen in Figure 3.2 being edited in WinEd.

Figure 3.2: The template for the new toolbar, in WinEd

Much of the window design, including the window flags, is the same as for the toolbox. The toolbar has no window furniture, has its ‘window is a pane’ flag set and its ‘window is moveable’ flag clear.

Compared to the toolbox, we need to take a little more care over the window dimensions. We want the toolbar to span the width of the main window, so the work area width (X1X0) must be at least as large as that of the main window: 1040 OS Units in this case. In our demo, we can simply set this in the template definition, as seen in Figure 3.3. However, it’s important to remember that if our application was to ever call Wimp_SetExtent in order to increase the width of the main window’s work area, it would also have to make the same adjustment to the toolbar – if it does not, then the toolbar could stop short of the right-hand side of the window.

Figure 3.3: The work area width must be the same size as that of the main window

The height of the work area (Y1Y0) is set to be 60 OS Units: this allows for a standard writeable icon with a height of 52 OS Units, plus a 4 OS Unit space above and below it.

We have also adjusted the minimum sizes of the bar, although this is as much to keep WinEd happy as anything else. With the minimum y dimension set to the default value of zero, the Wimp will limit the size to that required for the vertical scroll bar which WinEd applies in editing mode, making it hard to set the dimensions that we require.

The window contains two action buttons and a writeable icon, so that we can easily see how the work area moves around in relation to its parent window. All three icons are 52 OS Units high, to match the standard height for a writeable icon, and there is a 4 OS Unit gap around the edges of the work area on all four sides.

Once again, we can add the lines shown in Listing 3.1 to PROCinitialise in order to load the new template and create a window from it.

PROCtemplate_load("Toolbar", b%, buffer_size%, -1)
SYS "Wimp_CreateWindow",,b% TO ToolBarWindow%

Listing 3.1: Loading the toolbar template

The handle of the toolbar is stored in ToolBarWindow%, so that it will be available to the rest of the program.

Handling the new pane

In terms of infrastructure to handle our new toolbar, there’s not much to add since most of the hard work was done in the last chapter. All that we need to do is to make sure that the toolbar opens in the correct place relative to both the main window and the existing toolbox, which will be managed in PROChandle_pane_windows, and that it closes when the main window closes. For the latter, we can update PROCclose_window_request as shown in Listing 3.2, so that both panes are closed if the main window closes.

DEF PROCclose_window_request(b%)
IF !b% = MainWindow% THEN
  !q% = ToolBoxWindow%
  SYS "Wimp_CloseWindow",,q%
  !q% = ToolBarWindow%
  SYS "Wimp_CloseWindow",,q%
ENDIF

SYS "Wimp_CloseWindow",,b%
ENDPROC

Listing 3.2: Dealing with Close Window Request events

The code that we need to add to PROChandle_pane_windows should already be fairly familiar. First, we need to create a window state block for the toolbar pane, just as we do for the toolbox. To keep things simple, we’ll borrow another 32 bytes from the Open_Window_Request event block, this time placing the toolbar pane’s data at an offset of 128 bytes into the block.

toolbar% = main% + 128

!toolbar% = ToolBarWindow%
SYS "Wimp_GetWindowState",,toolbar%

We can then call a new procedure, PROCposition_toolbar, to do the work of adjusting the toolbar’s visible area to suit the position of the main window.

PROCposition_toolbar(main%, toolbar%)

This will work in a similar way to the PROCposition_side_toolbox procedure that we defined in the last chapter and takes two parameters: a pointer to the main window state block as parent%, and a pointer to the toolbar pane’s window state block as toolbox%. We will pass main% and toolbar% to these respectively.

The calculations for the position of the toolbox will remain as they are, before which we will insert in a similar set of calculations for the new toolbar. To be able to do this, we’ll need to know the height of the bar – which we can get from its visible area as before.

height% = toolbar%!16 - toolbar%!8        : REM Visible Area Y1 - Y0

We don’t need the width of the bar, because that will be tied to the width of the main window’s visible area as shown in Figure 3.4.

Figure 3.4: The relationship between the main window and its toolbar

The tops of the two windows (Y1 for the two visible areas) are once again level, whilst the bottom of the pane (its Y0) is the height of the pane below the top (Y1) of the main window. This means that the vertical dimensions for the toolbar’s visible area are calculated in exactly the same way as for the toolbox.

toolbar%!8 = parent%!16 - height%         : REM Visible Area Y0
toolbar%!16 = parent%!16                  : REM Visible Area Y1

The horizontal positioning of the toolbar is, if anything, easier than for the toolbox: it aligns completely with the visible area of the main window, so X0 and X1 can be copied directly from the main window’s data block.

toolbar%!4 = parent%!4                    : REM Visible Area X0
toolbar%!12 = parent%!12                  : REM Visible Area X1

This is why the horizontal extent of the pane has to match that of the main window: if the main window is fully extended, the pane has to be able to follow suit.

Putting the code together results in Listing 3.3.

DEF PROCposition_toolbar(parent%, toolbar%)
LOCAL height%

REM Find the height of the toolbar pane's visible area.

height% = toolbar%!16 - toolbar%!8        : REM Visible Area Y1 - Y0

REM Move the toolbar pane so that it's in the correct X and Y position
REM relative to where the parent window is to go.

toolbar%!4 = parent%!4                    : REM Visible Area X0
toolbar%!8 = parent%!16 - height%         : REM Visible Area Y0
toolbar%!12 = parent%!12                  : REM Visible Area X1
toolbar%!16 = parent%!16                  : REM Visible Area Y1
ENDPROC

Listing 3.3: The code to position the toolbar horizontally and vertically

Stacking order

Just as with our previous example, the two panes must appear directly in front of the main window in the window stack. The exact order doesn’t matter to the Wimp, so we will choose to put the toolbar in front of the main window, and the toolbox in front of the toolbar. When deciding, it pays to look at positions where the different panes might overlap, and consider which will look best to the user.

Returning to PROChandle_pane_windows, we will start at the top of the pile of panes and work down towards the main window – so the first thing to open is the toolbox directly behind the window that’s supposed to be in front of the main window, exactly as we did before.

IF (main%!28 <> ToolBarWindow%) OR (toolbar%!28 <> ToolBoxWindow%) THEN toolbox%!28 = main%!28

SYS "Wimp_OpenWindow",,toolbox%

We still need to check to see if the windows are already in their correct places in the stack, but with two panes to consider we must now check both whether the main window is behind the toolbar, and whether the toolbar is behind the toolbox. If either isn’t in the correct place, then we will need to re-position the toolbox – as the top window of the pile – and work down from that.

With the toolbox open, we can open the toolbar behind it...

toolbar%!28 = ToolBoxWindow%

SYS "Wimp_OpenWindow",,toolbar%

...and then, at the bottom of our little pile of windows, comes the main window itself.

main%!28 = ToolBarWindow%

left% = main%!4
right% = main%!12
top% = main%!16

SYS "Wimp_OpenWindow",,main%

After opening the main window, we will need to check whether it was placed in the requested location and, if not, re-open the toolbar pane in addition to the toolbox. The toolbar is anchored to the top, left and right edges of the main window’s visible area, so the code will look as follows:

IF (main%!4 <> left%) OR (main%!12 <> right%) OR (main%!16 <> top%) THEN
  PROCposition_toolbar(main%, toolbar%)
  SYS "Wimp_OpenWindow",,toolbar%
ENDIF

Putting all of the new code together with the old, PROChandle_pane_windows now looks as shown in Listing 3.4.

DEF PROChandle_pane_windows(main%)
LOCAL toolbox%, toolbar%, top%, left%, right%

REM Get the Window State block for the toolbox pane, using some of the
REM spare space above the data for the state of the main window.
REM
REM Note: ON RISC OS 5, we could more clearly use DIM toolbox% LOCAL 64
REM here to allocate the required memory from the stack.

toolbox% = main% + 64

!toolbox% = ToolBoxWindow%
SYS "Wimp_GetWindowState",,toolbox%

REM Get the Window State block for the toolbar pane, using more of the
REM spare space above the data for the state of the main window.

toolbar% = main% + 128

!toolbar% = ToolBarWindow%
SYS "Wimp_GetWindowState",,toolbar%

REM Move the toolbar pane so that it's in the correct X and Y position
REM relative to where the main window is to go.

PROCposition_toolbar(main%, toolbar%)

REM Move the toolbox pane so that it's in the correct X and Y position
REM relative to where the main window is to go.

PROCposition_side_toolbox(main%, toolbox%)

REM Unless the main window is to be opened behind the toolbar pane, and the
REM toolbar pane is already behind the toolbox pane, meaning that all three
REM windows must already be in the correct place in the stack, set the toolbox's
REM Open Behind so that it appears in the stack where the main window is
REM required to go. Then (re-)open the toolbox.

IF (main%!28 <> ToolBarWindow%) OR (toolbar%!28 <> ToolBoxWindow%) THEN toolbox%!28 = main%!28

SYS "Wimp_OpenWindow",,toolbox%

REM Set the toolbar window's Open Behind so that it opens behind the toolbox.
REM The (re-)open the toolbar

toolbar%!28 = ToolBoxWindow%

SYS "Wimp_OpenWindow",,toolbar%

REM Set the main window's Open Behind so that it opens behind the toolbar.

main%!28 = ToolBarWindow%

REM (Re-)open the main window.

left% = main%!4
right% = main%!12
top% = main%!16

SYS "Wimp_OpenWindow",,main%

REM If the main window was moved on opening (perhaps if it opened off-screen),
REM re-position the toolbox and toolbar against the new position and re-open them.

IF (main%!4 <> left%) OR (main%!12 <> right%) OR (main%!16 <> top%) THEN
  PROCposition_toolbar(main%, toolbar%)
  SYS "Wimp_OpenWindow",,toolbar%
ENDIF

IF (main%!4 <> left%) OR (main%!16 <> top%) THEN
  PROCposition_side_toolbox(main%, toolbox%)
  SYS "Wimp_OpenWindow",,toolbox%
ENDIF
ENDPROC

Listing 3.4: The code to position both of the panes and the main window

Running the updated application should reveal a new toolbar attached to the top of the main window, as shown in Figure 3.5. If resized or moved around the screen, both the toolbar and the original toolbox should move and adjust their sizes together. Note that if the caret is placed in the toolbar’s writeable icon, the main window is shown as having input focus: this is a result of the panes having their ‘window is a pane’ flags set.

Figure 3.5: The window with both toolbar and toolbox

The full code can be found in Download 3.1.

Download 3.1
The source code and files in this example are made available under the MIT No Attribution License.

Missing work area

Although it works, there are a few rough edges with our new pane’s implementation as it stands. The first should be obvious as soon as the main window opens: the toolbar slices the top of the work area off, and obscures part of the first row of squares.

This shouldn’t be too surprising, as the toolbar pane is overlaying part of the main window’s work area and the Wimp knows nothing about it. It’s up to us to us to make an allowance for the effect, and the easiest way is simply to extend the work area by the height of the toolbar pane. One solution can be seen in Figure 3.6; the new Y1 extent of 64 OS Units allows for both the pane’s work area height of 60 OS Units and the lower window border of 4 OS Units.

Figure 3.6: Extending the main window work area to allow for the toolbar

An alternative approach would be to simply add 64 OS Units to the −1040 OS Units of the Y0 extent, to give −1104 OS Units. The Wimp doesn’t mind either way, and it’s up to the developer to take the presence of the toolbar into account when calculating the position of mouse clicks in the window or deciding where to plot items in a redraw loop. If these calculations are done correctly, with reference to the appropriate minimum and maximum visible area coordinates and the scroll offsets, then the required values should drop out at the end.

With the amended templates in place, the full set of squares can be seen in the main window, as shown in Figure 3.7.

Figure 3.7: An extended work area makes the window contents fully visible

A copy of the application with an updated main window template can be found in Download 3.2.

Download 3.2
The source code and files in this example are made available under the MIT No Attribution License.

Overlapping panes

Another minor niggle can be seen if we bring the window to the edge of the screen: as the toolbox retracts over the main window, it obscures the left-hand end of the toolbar. It’s worth noting that if we had put the two panes in the other order, with the toolbar in front of the main window and the toolbox at the top, then the toolbox would be retracting under the toolbar – which would look even stranger.

Figure 3.8: At the side of the screen, the toolbox begins to obscure the toolbar

As it is, this isn’t an enormous problem, and a simple solution might just be to have the toolbox fixed so that it doesn’t push back over the main window.

An alternative approach would be to push the toolbox down the side of the main window, so that it sits below the base of the toolbar. This is simple enough to do, and can be achieved by adding in a vertical offset to the calculation in PROCposition_side_toolbox, which sets the vertical position of the toolbox relative to the top of the parent window’s visible area – the changes required can be seen in Listing 3.5.

DEF PROCposition_side_toolbox(parent%, toolbox%, offset%)
LOCAL width%, height%

REM Find the width and height of the toolbox pane's visible area.

width% = toolbox%!12 - toolbox%!4             : REM Visible Area X1 - X0
height% = toolbox%!16 - toolbox%!8            : REM Visible Area Y1 - Y0

REM Move the toolbox pane so that it's in the correct X and Y position
REM relative to where the parent window is to go.

CASE TRUE OF
WHEN parent%!4 > width%
  toolbox%!4 = parent%!4 - width%             : REM Visible Area X0
  toolbox%!12 = parent%!4                     : REM Visible Area X1
WHEN parent%!4 > 0
  toolbox%!4 = 0                              : REM Visible Area X0
  toolbox%!12 = width%                        : REM Visible Area X1
OTHERWISE
  toolbox%!4 = parent%!4                      : REM Visible Area X0
  toolbox%!12 = parent%!4 + width%            : REM Visible Area X1
ENDCASE

toolbox%!8 = parent%!16 - (offset% + height%) : REM Visible Area Y0
toolbox%!16 = parent%!16 - offset%            : REM Visible Area Y1
ENDPROC

Listing 3.5: Offsetting the toolbox so that it can clear the toolbar

To be able to supply this information, we will also need to know the height of the toolbar: to this end, we will convert the PROCposition_toolbar procedure into the FNposition_toolbar function, which returns this information. The change can be seen in Listing 3.6.

DEF FNposition_toolbar(parent%, toolbar%)
LOCAL height%

REM Find the height of the toolbar pane's visible area.

height% = toolbar%!16 - toolbar%!8        : REM Visible Area Y1 - Y0

REM Move the toolbar pane so that it's in the correct X and Y position
REM relative to where the parent window is to go.

toolbar%!4 = parent%!4                    : REM Visible Area X0
toolbar%!8 = parent%!16 - height%         : REM Visible Area Y0
toolbar%!12 = parent%!12                  : REM Visible Area X1
toolbar%!16 = parent%!16                  : REM Visible Area Y1

=height%

Listing 3.6: Calculating the required size of the offset

Tying these two changes together is simply a case of updating PROChandle_pane_windows so that it stores the value returned by FNposition_toolbar and passes it on to PROCposition_side_toolbox as seen in Listing 3.7.

toolbar_height% = FNposition_toolbar(main%, toolbar%)
PROCposition_side_toolbox(main%, toolbox%, toolbar_height%)

Listing 3.7: Passing the offset between panes

With this small modification in place, the toolbar is now anchored lower down the side of the main window and slides in below the work area of the toolbar as seen in Figure 3.9.

Figure 3.9: Moving the toolbox down to miss the toolbar

A full copy of the application with the modification can be found in Download 3.3.

Download 3.3
The source code and files in this example are made available under the MIT No Attribution License.

Cropping the writeable field

There’s one final niggle to look at before we move on, which can be seen when the width of the main window is reduced. The writeable icon in the bar appears to be truncated, with the right-hand end disappearing off out of view as seen in Figure 3.10. Again, this isn’t the end of the world – we could simply left-align the text and ignore it. In fact, if we wish to support RISC OS 3.1 or earlier, then that’s the only option we have unless we wish to investigate drawing our own icons.

Figure 3.10: When the window is shrunk down, the writeable icon is truncated

However, if we’re happy to only support machines from the RiscPC onwards – which isn’t an unreasonable idea in modern software – then we can improve things more easily. RISC OS 3.5 introduced a new Wimp_ResizeIcon SWI, which we can use to adjust the size of the writeable icon as we re-open the panes.

To make the application as general as possible, we’ll start by adding the code shown in Listing 3.8 to PROCinitialise. This will check to see if the Wimp_ResizeIcon functionality is available; if it isn’t, we can just skip the resizing operation. This way, the application will still work all the way back to RISC OS 3.1, albeit with less visual finesse.

SYS "XOS_SWINumberFromString",,"Wimp_ResizeIcon" TO ;flags%
ResizeIconAvailable% = ((flags% AND 1) = 0)

Listing 3.8: Testing for the Wimp_ResizeIcon SWI

The code attempts to read the number for the Wimp_ResizeIcon SWI; if the SWI doesn’t exist, an error will be raised and the call will return with the V flag set. The value of the flag is tested, and the ResizeIconAvailable% variable set to TRUE or FALSE depending on the outcome. It’s good practice to test for specific functionality when possible, instead of testing Wimp or OS version numbers – aside from anything else, it’s much easier to keep on top of in terms of code complexity!

The resizing operation itself can be performed in PROChandle_pane_windows, immediately after the position of the toolbar window has been set. The code required can be seen in Listing 3.9 – it has been bracketed in an IF ... ENDIF statement, so that it will only be called if the Wimp_ResizeIcon SWI is available.

IF ResizeIconAvailable% THEN
  icon% = main% + 192

  icon%!0 = ToolBarWindow%
  icon%!4 = 0 : REM Writeable Icon Handle
  SYS "Wimp_GetIconState",,icon%

  bar_rhs% = (toolbar%!12 - toolbar%!4) + toolbar%!20 : REM Visible Area (X1 - X0) + X Scroll Offset

  SYS "Wimp_ResizeIcon", icon%!0, icon%!4, icon%!8, icon%!12, bar_rhs% - 4, icon%!20
  SYS "Wimp_ForceRedraw", ToolBarWindow%, icon%!8, icon%!12, bar_rhs%, icon%!20

  SYS "Wimp_GetCaretPosition",,icon%
  IF icon%!0 = ToolBarWindow% AND icon%!4 = 0 THEN SYS "Wimp_SetCaretPosition", icon%!0, icon%!4,,,icon%!16, icon%!20
ENDIF

Listing 3.9: Resizing the writeable icon

Attempting to change the size of an icon – not least a writeable icon – is taking us a little way into “here be dragons” territory. We start by reading the current state of the writeable icon into another spare part of the Open_Window_Request block, using Wimp_GetIconState. This gives us the icon’s current coordinates; the top, bottom and left-hand side (Y1, Y0 and X0) will not be changing, and can therefore be used directly in the call to resize the icon.

The right-hand side of the icon (X1) will be related to the right-hand side of the toolbar’s visible work area, so we calculate this in work area coordinates by taking the width of the visible area (X1X0) and adding on any scroll offset in the X direction. This offset should be zero, but it’s usually a good idea to do window coordinate calculations in full – that way, they continue to work even if the assumptions change!

Now that we have the work area coordinate for the right-hand edge of the visible toolbar, we can use Wimp_ResizeIcon to set the right-hand coordinate of the icon to be 4 OS Units in to the left from this. The Wimp won't refresh the display for us, however, so we also need to call Wimp_ForceRedraw for the whole area of the icon and the space between it and the edge of the toolbar. Figure 3.11 shows the area affected with a little exageration; any area falling outside of the window will be redrawn by the Wimp.

Figure 3.11: Redrawing the writeable icon after a resize

Since this is a writeable icon, there’s one final gotcha that we must be aware of. If the caret was in the icon, the Wimp won’t move it to reflect any alteration to the position of the text. Instead we must use Wimp_GetCaretPosition to find out where the caret is currently located and, if it is in our icon, call Wimp_SetCaretPosition to replace it in exactly the same place within the text.

If the size of the main window is reduced now, the writeable icon will also reduce in size as seen in Figure 3.12 so that it always fills the available toolbar.

Figure 3.12: Adjusting the writeable icon to fit the toolbar

A copy of the application with the icon resizing code can be found in Download 3.4.

Download 3.4
The source code and files in this example are made available under the MIT No Attribution License.

With this modification, support for the toolbar is now largely complete: we’ve attached it to the window, configured the work area and even made the fields within it update as the shape changes. In the next chapter, we’ll consider a slightly different use-case for the bar.