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 (X1 − X0) 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 (Y1 − Y0) 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%
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.
bar_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 = main%!16 - bar_height% : REM Visible Area Y0 toolbar%!16 = main%!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 = main%!4 : REM Visible Area X0 toolbar%!12 = main%!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.
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.
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 in front of the main window – inserting it in front of the main window, exactly as we did before.
toolbox%!28 = main%!28 SYS "Wimp_OpenWindow",,toolbox%
With the toolbox open in the correct place, we can open the toolbar behind it, which will mean that it will end up inserted directly in front of the main window in a similar way.
toolbar%!28 = ToolBoxWindow% SYS "Wimp_OpenWindow",,toolbar%
Putting all of the new code together with the old, PROChandle_pane_windows() now looks as shown in Listing 3.3.
DEF PROChandle_pane_windows(main%) LOCAL toolbox%, toolbar%, box_width%, box_height%, bar_height% 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 Find the height of the toolbar pane's visible area. bar_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 main window is to go. toolbar%!4 = main%!4 : REM Visible Area X0 toolbar%!8 = main%!16 - bar_height% : REM Visible Area Y0 toolbar%!12 = main%!12 : REM Visible Area X1 toolbar%!16 = main%!16 : REM Visible Area Y1 REM Find the width and height of the toolbox pane's visible area. box_width% = toolbox%!12 - toolbox%!4 : REM Visible Area X1 - X0 box_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 main window is to go. CASE TRUE OF WHEN main%!4 > box_width% toolbox%!4 = main%!4 - box_width% : REM Visible Area X0 toolbox%!12 = main%!4 : REM Visible Area X1 WHEN main%!4 > 0 toolbox%!4 = 0 : REM Visible Area X0 toolbox%!12 = box_width% : REM Visible Area X1 OTHERWISE toolbox%!4 = main%!4 : REM Visible Area X0 toolbox%!12 = main%!4 + box_width% : REM Visible Area X1 ENDCASE toolbox%!8 = main%!16 - box_height% : REM Visible Area Y0 toolbox%!16 = main%!16 : REM Visible Area Y1 REM Open the toolbox pane behind the same window that the main window REM was opened behind. This will place it directly in front of the REM main window. toolbox%!28 = main%!28 SYS "Wimp_OpenWindow",,toolbox% REM Set the toolbar window's Open Behind so that it opens behind the toolbox. toolbar%!28 = ToolBoxWindow% SYS "Wimp_OpenWindow",,toolbar% ENDPROC
Listing 3.3: 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.
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.
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 the height of the toolbar to the calculation in PROChandle_pane_windows() which sets the vertical position of the toolbox – the changes required can be seen in Listing 3.4.
toolbox%!8 = main%!16 - (bar_height% + box_height%) : REM Visible Area Y0 toolbox%!16 = main%!16 - bar_height% : REM Visible Area Y1
Listing 3.4: Offsetting the toolbox so that it clears the toolbar
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.
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 writable 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.5 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.5: 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.6 – 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.6: 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 (X1 − X0) 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.
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 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.11 so that it always fills the available toolbar.
Figure 3.11: 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.
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.