Beginner's Guide to WIMP
Programming
Martyn Fox

20: Further Refinements

Although our application is multitasking, with windows and menus, and can save and load by dragging icons in the correct RISC OS manner, there are still a few aspects of its behaviour that fall short of what we would expect from a top-notch commercial package. This section puts the finishing touches to our application and deals with a number of aspects of its programming.

Adding a New Dimension

If you are using a machine running RISC OS 3 or later, you will have noticed that the Info and Save windows which we purloined from elsewhere contained '3D' or 'slabbed' icons. Yet the simple OK icon that we created in our Options window had a flat 2D icon border in the style of old RISC OS 2 button icons.

The secret to producing the slabbed effect is in the indirected data validation string. So far, we've made limited use of this feature. We put sprite names in radio icons which also contained text, using the 'S' command in Section 12; we made the pointer change shape into an I-beam by using the 'P' command in Section 16; and we also used the 'A' command when we introduced our save box in Section 16 to make it impossible to put spaces in the filename string.

There are several more validation string commands listed in the Programmer's Reference Manual - you can use several at once, separating them with semi-colons (;). The one which produces slabbed icons is the 'R' (for boRder) command. This is entered as either a capital 'R' or lowercase 'r' followed by a number from 1 to 7; zero and 8 or higher will give a normal pixel border:

(Icon 3D border types)

The icon must have its border flag set for the command to have any effect.

Border type 6 is intended for the 'default action' buttons in a dialogue boxes; buttons like 'OK' which close the box when you click on them, and may also activate if you press Return. Types 5 and 6 both 'slab in' when they are selected (usually when you click on them) and their backgrounds change colour. This defaults to Wimp colour 3 (mid-grey), though you can choose an alternative colour by specifying it in the command after a comma. The command:

    r5,14

will, for example, produce an action button that slabs in and turns Wimp colour 14 (orange) when selected.

Changes to the Dialogue Box

Let's smarten up our options box with 3D icons. Load the Templates file, open the Options window and modify each of the colour icons in turn. Each one must be set to be indirected, and you will have to make them into either text or sprite icons as well, otherwise you'll find that the Validation string will not become editable. It doesn't matter that there will be no text or sprite in your so-called Text or Sprite icon, but since there won't be, set the buffer length to 1. There's no point in wasting indirected data space, after all! Enter 'r5' in the validation string for each colour icon. After doing so, each one will take on a slabbed-out appearance, and it will slab in and change colour when you click on it. If you like, you could make each icon change to a complementary colour (black to white, grey-1 to grey-6, dark blue to light blue and so on) by specifying a command such as r5,15 (15 being the light blue colour in the sixteen-colour Wimp palette). More sensibly, you could alternatively make the slabbed-in colour the same as the icon colour, so that the 3D border changes but each icon keeps its inner colour when it's clicked. Of course, in general it isn't a good idea to change these colours because the default colour of slabbed-in icons is a RISC OS standard, and you should stick to it so that your application has a consistent appearance with other programs that run on the desktop.

Give icon 4, the colour indicator icon, a type 2 border to make it slab inwards, and give icon 7, the text-entry icon, a type 7 border to show that it's writable. Also give it a 'P' command so that the pointer changes shape to the I-beam when over it; the validation string should read something like "r7;Pptr_write" for this icon. In fact, type 7 borders are only used occasionally for writable icons; it's very common for writable icons just to have a white background and a 2D single-pixel border (and to use the ptr_write pointer). However, the type 7 border is there if you want to use it, and it looks good in some situations. We will use it here for the sake of having it.

Give icon 5, the 'OK' icon, a type 6 border to show that it is the 'default action' button. Reduce the height of this icon to make room for another one above it and create a new icon with border type 5 and a background colour the same as the surrounding window (colour 1). Give it the text 'Cancel' and renumber it as icon 8 (be careful not to alter the icon numbers of the other important icons: the radio buttons, the OK button, the colour indicator and the text-entry icon). We will use the new Cancel icon later on in this section. Finally, put a Back tool in window's title bar.

Apart from the addition of the extra icon and window tool, everything we've done in this section so far has been entirely cosmetic. Your dialogue box should now look something like this:

(3D-enhanced Options window)

You would probably find that your program will crash if you were to try to run it now. Although the window data block is only 32 bytes longer due to the 'Cancel' icon, the indirected data and validation strings also form part of the template which has to be loaded initially into the 1,024 bytes at stack% and it may be too big to fit. You could increase the size of the block to 2,048 bytes if you wished, but most of it would be wasted after you'd loaded the templates.

An alternative technique would be to load the templates into the 2,048 bytes at list% as this space isn't being used at the time. If you do this, you will have to move the line in PROCinit which puts -1 at list% and fontlist% so that it comes after the templates are loaded and windows created. Alternatively, if you increased the menu data block to 2,048 bytes to accommodate the font name menu, you could use that memory instead.

Persistent Dialogue Boxes

There are two types of dialogue box: transient and persistent. The Save box is a typical transient dialogue box. You reach it through the menu tree, where it appears as a submenu. If you move the mouse back onto the parent menu, or click it anywhere else on the screen, the box disappears.

A persistent dialogue box is reached by clicking the mouse on the menu item referring to it. Such an item name should be followed by an ellipsis (...) to indicate that it will open a window. We are going to change our Options window into a persistent dialogue, so we should change the DATA statements in PROCwindow_menu to read:

 2860 DATA Shapes,Options...,Clear,Save,Font_M,Font size,Print,*

We no longer want the arrow leading to the options box, so delete the line from PROCmenus which attaches the box to the menu:

    PROCattach(wmenu%,0,options%)

Because we will now get to our Options box by clicking the mouse, we need an extra line in the CASE ... OF structure in PROCmenuclick:

 2180   WHEN "Options...":!c%=options%:
        SYS "Wimp_GetWindowState",,c%:SYS "Wimp_OpenWindow",,c%

Remember to include the ellipsis so that the string matches the menu item.

You should now be able to open the options box by clicking on Options...; the application is now actually easier to use because you can keep the box on the screen while you draw lines etc. Changing the radio icon selection takes effect immediately because the application reads the icon states directly, but colour changes only happen after you've clicked on OK.

What you can't do at the moment is close the dialogue box again. Clicking on the Cancel button does nothing yet, other than to change the selected colour to a light grey. Clicking on the OK button also doesn't close the window, because the program only issues a command to close any menu which is open and our box no longer forms part of the menu structure.

We can correct this with a few alterations to PROCopt_box:

 3490 DEFPROCopt_box(button%,icon%)
 3500 CASE icon% OF
 3510   WHEN 0,1,2,3,6:
 3520   WHEN 5:
 3530     !b%=options%:b%!4=4
 3540     SYS "Wimp_GetIconState",,b%
 3550     colsel%=(b%!24)>>28
 3560     IF button%=4 SYS "Wimp_CloseWindow",,b%
 3570   WHEN 8:
 3580     !b%=options%:b%!4=4:b%!8=colsel%<<28:b%!12=&F<<28
 3590     SYS "Wimp_SetIconState",,b%
 3600     IF button%=4 SYS "Wimp_CloseWindow",,b%
 3610   OTHERWISE
        etc.

We've changed the last line of the 'WHEN 5:' part of the structure to close the window if Select is clicked, instead of expecting the Wimp to do it. The window handle, options%, is already in the word at b% from the earlier part of the operation.

Clicking on icon 8 has the same effect as on icon 5 except that the currently selected colour, colsel% is not updated. Instead, the background colour of icon 4 is reset to colsel% ready for the next time the box is opened.

If you want to be really smart, you could devise some way of over-riding the feature that keeps the menu open when you choose Options... with Adjust, so that it always closes when the box opens.

The listing at this stage is supplied as page_222.

Button Presses and Hot Keys

We haven't made much use of the keyboard so far in this guide. Apart from typing strings into writable icons, we've only used Return to save a file.

The 'default action button' border on the OK button in our Options dialogue box suggests that we ought to be able to simulate its action by pressing Return. It would also look good if the icon were to 'slab in' as we did so.

Professional applications also use 'hot keys' to open dialogue boxes from the keyboard. To do all this will require a complete rewrite of PROCkeypress:

 4900 DEFPROCkeypress
 4910 REM processes keypresses in response to Wimp_Poll reason code 8
 4920 LOCAL key%
 4930 key%=b%!24
 4940 CASE key% OF
 4950   WHEN 13:
 4960     CASE TRUE OF
 4970       WHEN FNwind_open(saveas%):PROCpush(saveas%,0)
 4980       WHEN FNwind_open(options%):PROCpush(options%,5)
 4990     ENDCASE
 5000   WHEN 27:
 5010     CASE TRUE OF
 5020       WHEN FNwind_open(options%):!b%=options%:SYS "Wimp_CloseWindow",,b%
 5030     ENDCASE
 5040   WHEN &180:PROCprint
 5050   WHEN &182:!b%=options%:SYS "Wimp_GetWindowState",,b%:SYS "Wimp_OpenWindow",,b%
 5060   WHEN &183:!b%=saveas%:SYS "Wimp_GetWindowState",,b%:PROCshowmenu(saveas%,b%!4,b%!16)
 5070   OTHERWISE
 5080     SYS "Wimp_ProcessKey",key%
 5090 ENDCASE
 5100 ENDPROC
 5110 :

This procedure introduces a new procedure and function:

 6880 DEFFNwind_open(h%)
 6890 LOCAL c%
 6900 c%=FNstack(36)
 6910 !c%=h%
 6920 SYS "Wimp_GetWindowState",,c%
 6930 PROCunstack(c%)
 6940 =(c%!32 AND 1<<16)<>0
 6950 :
 6960 DEFPROCpush(w%,i%)
 6970 LOCAL c%
 6980 PROCget_origin(w%,xorig%,yorig%)
 6990 c%=FNstack(56)
 7000 !c%=w%:c%!4=i%:SYS "Wimp_GetIconState",,c%
 7010 x%=xorig%+c%!8:y%=yorig%+c%!12
 7020 SYS "OS_ReadMonotonicTime" TO t%
 7030 SYS "OS_Byte",138,9,(x%+20) MOD 256
 7040 SYS "OS_Byte",138,9,(x%+20) DIV 256
 7050 SYS "OS_Byte",138,9,(y%+20) MOD 256
 7060 SYS "OS_Byte",138,9,(y%+20) DIV 256
 7070 SYS "OS_Byte",138,9,4
 7080 SYS "OS_Byte",138,9,t% MOD 256
 7090 SYS "OS_Byte",138,9,(t% DIV &100) MOD 256
 7100 SYS "OS_Byte",138,9,(t% DIV &10000) MOD 256
 7110 SYS "OS_Byte",138,9,(t% DIV &1000000) MOD 256
 7120 PROCunstack(c%)
 7130 ENDPROC
 7140 :

We introduce a local variable, key%, which contains the ASCII code for the key pressed. Note that this is a word, not a byte.

The CASE TRUE OF ... ENDCASE structure in lines 4960 to 4990 deals with the situation when Return is pressed. The new function FNwind_open returns TRUE if the window whose handle is passed to it is open; in this situation, we call our other new routine, PROCpush, which simulates a click with Select over the icon whose handle we pass in the second parameter. In this way, we can close either the options dialogue or the save box. The advantage of doing it this way, rather than simply closing the window and doing whatever else has to to be done, is that the icon will be seen to slab inward before the window closes.

The following CASE TRUE OF ... ENDCASE structure does the same thing if the Escape key (ASCII code 27) is pressed. We don't need a line for the save box this time because it will close automatically if we press Esc, being a transient dialogue box, but the CASE ... OF structure allows us to add extra persistent dialogue boxes, if we wish. If you've created a print dialogue box, you may wish to make it persistent, too.

The new function FNwind_open simply loads part of the window data into stack memory at c% and checks its flags, which are at c%+32. If the window is open, bit 16 will be set, in which case the function returns TRUE.

Mouse Simulation

The procedure for simulating a mouse click works by putting nine bytes into the mouse buffer. The practice is discouraged, because it's possible that a real mouse click may occur while you're doing it, with the result that its data would get mixed up with yours; however, it appears to be the only way of achieving this result. On fast machines, though, it will be quite hard to see the slabbing-in effect, so you may prefer not to include PROCpush in your own software.

If you try programming in assembly language, you should disable the interrupts before you start poking numbers into the buffer and then re-enable them immediately afterwards. In the meantime, don't click the mouse and press a key at the same time!

The first three lines of the procedure work out the x and y coordinates of the bottom left-hand corner of the icon so that we can simulate clicking the mouse in the right place. The following line reads monotonic time. This is the timer which starts counting from zero when you first switch on your machine. It can be read with the Basic variable TIME, and should not be confused with time of day, which is read with TIME$.

The routine uses OS_Byte to put numbers into the mouse buffer, one byte at a time. This is the SWI called by the *FX command, so each of these calls is the equivalent of *FX138,9,x. The first number, 138, is an instruction to insert a number into a buffer; 9 is the number of the mouse buffer and x is the number being put into it. The first four calls insert the x and y coordinates, the fifth is the button state (4 indicates that Select has been pressed) and the rest are the monotonic time.

Responding to Function Keys

The remainder of PROCkeypress checks for function keys. These normally return a code with bit 7 set (&80 plus the key number), which could cause confusion if you were to enter such a code with the Alt key and the keypad, so the Wimp adds &100 to the code for F0 to F9. The 'Print' key is the equivalent of F0 so line 4270 calls the printing procedure (open your print dialogue box instead, if you created one), line 4280 opens the Options box if F2 is pressed and the following line opens the save box on pressing F3, which is standard RISC OS practice. (A table giving more details about function key codes appears later in this section.)

Because we want the save box to be a transient dialogue box, we open it through PROCshowmenu which treats it as a menu. The number passed to Wimp_CreateMenu may be either the address of a menu data block or a window handle. In this case, we are using the latter so that the menu 'tree' consists entirely of the save box.

The first part of line 5060 obtains some coordinates to position the window. In this case, we're using its position when it was last closed; you may prefer to use the current mouse position instead.

There is just one snag with this procedure: it probably won't work! You'll find you can close the save box, but nothing else happens unless you have the Options box open and the caret in its writable icon.

The reason is simply that it is only in this situation that one of our windows has the input focus. If this is not the case, the Wimp has no reason to notify our application of keypresses.

Getting the Input Focus

There are two ways around this problem; you may wish to adopt either or both of them. The first is to give our main window the input focus every time we click Select or Adjust in it. To do this, add this line to the beginning of both PROCadd_item and PROCdelete_item:

 2990 DEFPROCadd_item
 3000 SYS "Wimp_SetCaretPosition",main%,-1,0,0,1<<25,-1
      etc.

 3380 DEFPROCdelete_item
 3390 SYS "Wimp_SetCaretPosition",main%,-1,0,0,1<<25,-1
      etc.

It doesn't matter where in the procedures you put this line (provided it is not in a loop), as it doesn't affect the procedure. Its purpose is to position the caret in the main window and, in so doing, give it the input focus. Register R0 contains the window handle, main%, and R1 the icon handle (-1 as there isn't one). R2 and R3 hold the position of the caret, relative to the window origin. As we will be making the caret invisible, it doesn't matter where we put it. R4 has the height of the caret and some flags. The one which concerns us is bit 25 which, if set, makes the caret invisible.

If we were setting the caret to a particular position in a string, for example in a writable icon, we would put its position into R5, in which case the position in R2 and R3 would be modified. As we're not doing this, we put -1 in R5.

You can now use hot keys, provided you click on your main window first to give it the input focus. As any click on the window adds or removes an entry in the Plot Instruction List, however, you may not wish to do this.

The other alternative is to modify the window template. If you load the Templates file into WinEd, open the main window and double-click on it to edit its attributes, you will see that one of the Behaviour flags is labelled Hot keys. Turn this on to set the flag, then resave the Templates file.

If the Wimp cannot handle a keypress (for example, by putting a character in a writable icon), it first notifies the task which owns the window with the input focus, if there is one. If there isn't, or the task passes it on, it is then offered to the owner of any window with the 'hot keys' flag set, beginning at the top of the window stack. For this technique to work, it is important that all applications which handle keypresses pass on unwanted ones by calling Wimp_ProcessKey.

If you use both of these techniques, your application should be able to use hot keys unless another task has the input focus (and uses the same keys) or no task has it but another window with the 'hot keys' flag set is ahead of yours.

It is easier to learn the workings of a program if the hot keys are listed on the menus, so amend the DATA statements in PROCwindow_menu to read:

 2860 DATA Shapes,Options...  F2,Clear,Save        F3,Font_M,Font size,
      Print    PRINT,*

The Options..., Save and Print items are padded out with spaces so that each string is 14 bytes long. Naturally, you need to make similar changes to PROCmenuclick.

The listing at this stage is supplied as page_227.

Auto Loading Files

When you use a RISC OS application such as a wordprocessor, you expect to be able to start it up and load a file into it in one operation, just by double-clicking on the file icon.

A piece of software of this type is classified as an editor because it can load, edit and resave data in a file. The term doesn't only cover packages that work with text; it also applies to Draw, Paint, Maestro and our own Shapes application. So how does the machine know what to do with a file when we double-click on its icon? It looks at its filetype. You will recall that we decided in Section 14 that files saved by our application would have filetype &012. This is as good a number as any within the range &000 to &0FF which has been set aside for user applications.

If you double-click on one of your files at the moment, though, you will get an error message saying 'No run action specified for this file type' (or some more meaningfully-worded message, depending on the version of RISC OS you happen to be using). The machine doesn't yet know what to do with the file.

If you press F12 to get the command line and type:

    *Show

you will see all the system variables listed. Note in particular several beginning with 'Alias$@RunType_'. These are instructions telling the machine what to do with various filetypes when you try to run them. Each variable is set to the full pathname of the !Run file in its application directory, with a rather strange-looking set of characters on the end. If, for example, your computer has 'seen' the Maestro application, your list will contain a line something like:

    Alias$@RunType_AF1 : Run ADFS::4.$.Apps.!Maestro.!Run %*0

Parameter Passing

The expression '%*0' is a way of passing parameters to an Obey file, via the command line interpreter. A parameter may be a filename or some suffix telling the file what to do. If you have more than one parameter, you can refer to them as %0, %1 and so on. The star in the expression means that it is to be replaced by the appropriate parameter, plus any others that come after it.

Maestro's filetype is &AF1. When you double-click on a file of this type, the string in the system variable Alias$@RunType_AF1 is passed to the Command Line Interpreter. The first part of the string tells the machine to run Maestro's !Run file and exactly where to find it; the bit on the end is replaced by the CLI with a parameter; in this case the pathname of the file you clicked on.

The instruction to set up this system variable came from Maestro's !Boot file. All you had to do to run this file was open the parent directory containing Maestro and the !Boot file was run automatically. Whenever you open a directory window containing an application directory, the desktop first checks to see if the Wimp sprite pool contains a sprite with the same name as the application. If it doesn't, it runs the !Boot file, if there is one.

We obviously need a !Boot file for our application so start up your favourite text editor, create a new Obey file and call it !Boot, then enter this:

    |Boot file for Test
    Set File$Type_012 Shapes
    Set Alias$@RunType_012 Run <Obey$Dir>.!Run %%*0
    IconSprites <Obey$Dir>.!Sprites
    

The first line after the opening comment sets a name for the filetype, to be used in Filer Info boxes and 'Full info' windows in place of the filetype number. The following line sets up the system variable. The Command Line Interpreter replaces '<Obey$Dir>' with the pathname of the directory where the !Boot file is being run. Notice especially that the expression on the end of the string now has two percentage (%) signs. This is to prevent the CLI from trying to replace it with a parameter; in this case, it simply strips off one of the % signs and leaves the rest alone.

The last line is necessary because the Filer will not automatically look for a !Sprites file if it finds a !Boot file.

If your application is located in the root directory of your hard disc and you open the root directory, you will see a line similar to the following if you subsequently list the system variables:

    Set Alias$@RunType_012 Run ADFS::4.$.!Shapes.!Run %*0

When you double-click on the icon of a 'Shapes' file, the CLI will use its filename as a parameter to replace '%*0' when it calls the !Run file. The latter file must pass on the parameter to the !RunImage file, so amend the last line of the !Run file to read:

    Run <Shapes$Dir>.!RunImage %*0

Take note that there is only one percentage sign this time.

You can see all this happening when you add the next procedure, as we'll temporarily include an error line to show the complete instruction that calls the !RunImage file.

First ensure that you have a file saved by the application, then add a command to the program's first section:

   10 REM >!RunImage
   20 REM (C) Martyn Fox
   30 REM shape drawing program
   40 REM based on Wimp shell program v0.01
   50 version$="0.01 (date)"
   60 ON ERROR SYS "Wimp_CloseDown",task%,&4B534154:REPORT:PRINT" at line ";ERL:END
   70 SYS "Wimp_Initialise",200,&4B534154,"Shapes" TO ,task%
   80 PROCinit
   90 PROCcreateicon
  100 PROCcommand
  110 ON ERROR IF FNerror THEN PROCclose:END
  120 REPEAT
  130   PROCpoll
  140 UNTIL quit%
  150 PROCclose
  160 END
  170 :

We only want to call the new procedure once, after the program has been initialised but before it enters the polling loop. We've also positioned the instruction before the error handler line; if it came after it, any error would result in the procedure being called again.

Now enter the procedure for checking the instruction:

 7160 DEFPROCcommand
 7170 LOCAL ptr%
 7180 SYS "OS_GetEnv" TO com$
 7190 ERROR 1,com$
 7200 ptr%=INSTR(com$,"!RunImage")
 7210 WHILE ASC(MID$(com$,ptr%,1))>32:ptr%+=1:ENDWHILE
 7220 WHILE ASC(MID$(com$,ptr%,1))=32:ptr%+=1:ENDWHILE
 7230 IF ASC(MID$(com$,ptr%,1))>31 THEN
 7240   com$=MID$(com$,ptr%)
 7250   SYS "OS_CLI","Load "+com$+" "+STR$~list%
 7260   $savestr%=com$
 7270   PROCupdate_fonts
 7280   !b%=main%
 7290   SYS "Wimp_GetWindowState",,b%
 7300   IF ((b%!32) AND 1<<16)=0 THEN
 7310     SYS "Wimp_OpenWindow",,b%
 7320   ELSE
 7330     PROCforce_redraw(main%)
 7340   ENDIF
 7350 ENDIF
 7360 ENDPROC
 7370 :

Line 7190 will be deleted after we've conducted a little experiment.

The call OS_GetEnv returns various details of the program's environment. After the call, register R1 contains the address of the highest RAM address in the program's Wimp slot and R2 has the address of five bytes containing the real time when the program was started. We are concerned with what is in R0, however, which is the command line that called the program. The error message produced by line 7190 will show us what the line contains.

The listing which includes these changes is supplied as page_230. We will also include a copy of the '*Set Alias$@RunType_012' line in the !Run file; this will ensure that it is redefined each time the application is run. This could be useful if you have more than one copy of the application on your disc.

Getting the Right Run Action

Now double-click on your saved Shapes file to cause it to run the Shapes application and thus produce the error message. Suppose your application was in the root directory of your hard disc, and the file you double-clicked was called 'ShapeFile' in a subdirectory called 'Files' off the root directory. The application would start and the error message would read:

    BASIC -quit "ADFS::4.$.!Shapes.!RunImage" ADFS::4.$.Files.ShapeFile at line 7190

What happened was that RISC OS received a command to run your file called ShapeFile in your Files directory. The instruction included the full pathname of the file. The system checked the filetype, found that it was &012 and executed the string in the system variable called Alias@RunType_012. This consisted of an instruction to run the application's !Run file, plus a parameter '%*0' which it replaced with the pathname of the file. The !Run file included a similar instruction, this time to run the !RunImage file, passing on the parameter in the same way.

RISC OS now had to check the filetype of the !RunImage file and found it was &FFB, meaning a Basic file. It followed the same procedure again, this time with system variable Alias@RunType_FFB, which contains:

    Basic -quit |"%0|" %*1

There are now two parameters; the pathname of the !RunImage file and that of the file we clicked on.

The Command Line Interpreter first starts up Basic. The suffix '-quit' tells it that it should close down when it has finished executing the program. The remainder of the line will consist of the pathname of the !RunImage file in quotes (created by the ' |"%0|" ' part), followed by any other parameters which, in this case means our filename.

You can see this line, fully expanded, in the error message. When you understand it, delete line 7190 so that the program can function properly.

The purpose of this exercise is to find the pathname of the file we clicked on, if any. A good starting point is the word '!RunImage', which must be in the string somewhere because it's the name of the file we're running. Line 7200 finds it with the INSTR command and increments a pointer, ptr%, to the point where it starts. We now know that ptr% points to somewhere in the pathname of the !RunImage file. Line 7210 steps ptr% on to the end of the pathname, then the following line steps it over any spaces.

If we had started the application without clicking on a file, ptr% will now be pointing to a control code, in which case we skip the rest of the procedure. If the character isn't a control code, ptr% now points to the start of the pathname of our file, and we need to load it.

The remainder of the procedure is a copy of PROCload, minus the lines concerned with message passing.

Double Trouble

Double-clicking on a Shape file will now run the application and load the file into it. Unfortunately, if you do the same with another file, you will end up with two copies of the application running, each with its own icon on the icon bar. This isn't really what we want; the second file should be loaded into the application that is already running, just like in a word processor.

... you will end up with two copies of the application running ...

... you will end up with two copies of the application running ...

Every time we click on a file of this type, the machine follows the instruction to start up the application, which then loads the file into itself. The fact that the application is already running makes no difference! Clearly, we need a way to prevent this from happening.

The thing that comes to our rescue is another Wimp message called Message_DataOpen. This message is broadcast - sent to all tasks that are running - every time we double-click on a file icon. If we reply to it, it will prevent the Wimp from executing whatever is in the Alias$@RunType_ system variable. At the same time, it tells our application all about the file we double-clicked on, so that we can load it.

We saw in Section 15 that the first four words of a message data block contain the following:

b%+0   Length of message block
b%+4Task handle of sender
b%+8my_ref - sent by sender
b%+12your_ref or zero if the message is not a reply.

To reply to a message, we take the my_ref number from b%+8, put it in b%+12 and send the reply with the sender's task handle, which we get from b%+4, in R2. The reply in this particular case consists of a DataLoadAck message.

The DataOpen message has an action code of 5 so add a line to PROCreceive:

 1720 DEFPROCreceive
 1730 REM handles messages received from the Wimp with reason codes 17 or 18
 1740 CASE b%!16 OF
 1750   WHEN 0:quit%=TRUE
 1760   WHEN 2:PROCsave
 1770   WHEN 3:PROCload
 1780   WHEN 5:PROCdata_open
 1790   WHEN &400C0:PROCmenu_message
 1800 ENDCASE
 1810 ENDPROC
 1820 :

The procedure to handle the message is very short:

 7380 DEFPROCdata_open
 7390 IF b%!40=&012 PROCload
 7400 ENDPROC
 7410 :

Although the message data block is not identical to that of the DataLoad message, it is sufficiently similar for it to be handled by PROCload. The word at b%+40 contains the filetype and the pathname of the file starts at b%+44. The other important word is the one at b%+4, which contains the Filer's task handle.

Line 7380 checks to see if the file double-clicked on was one of ours. This procedure is called every time we double-click on any file icon while our application is running, unless another task responds to it first, so if it isn't the correct filetype, we must do nothing (and do it quickly!).

The remainder of the operation is handled by PROCload, including the all-important reply to the DataOpen message. The beginning of PROCload looks like this:

 3900 DEFPROCload
 3910 IF b%!40<>&012 ERROR 1<<30,"Filetype not recognised"
 3920 PROCterm(b%+44)
 3930 PROClose_fonts
 3940 SYS "XOS_CLI","LOAD "+$(b%+44)+" "+STR$~list% TO err%;flags%
 3950 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err%
 3960 b%!12=b%!8
 3970 b%!16=4:REM Message_DataLoadAck
 3980 SYS "Wimp_SendMessage",17,b%,b%!4
      etc.

Line 3910 is superfluous in this instance as we checked the filetype before calling the procedure.

After loading the file, we send the DataLoadAck message. Line 3960 transfers the 'my_ref' number in b%+8 to the 'your_ref' field in b%+12. The 'myref' number was obtained on this occasion from the DataOpen message. This is important as it tells the filing system that this DataLoadAck message is a reply to the DataOpen message, even though it doesn't have reason code 19. This is sufficient to stop the filing system from running the file and opening a second copy of the application.

The listing up to this point is supplied as page_234.

Selective Redrawing

Up to now, we've redrawn everything in the Plot Instruction List on each call to PROCdraw. We've done this for two reasons: to avoid extra complexity in the programming and because it is probably just as quick to redraw lines, circles and rectangles as it would be to calculate whether or not we need to do so.

You may have noticed, though, that if you have several text strings in your window, redrawing the window can be rather slow (depending on the speed of your machine). Try dragging a menu or another window across it. The redrawing is just as slow even if the menu doesn't go across any text because we always go through the motions of painting all the text strings. This is clearly a situation in which we which would benefit from only redrawing text strings where necessary.

We won't aim to redraw just part of a string; that would be very complicated and not produce any noticeable improvement. If any part of the string needs redrawing, it is easier to draw all of it.

To work out whether or not we need to redraw a string at all, though, we need to know two things:

  • The coordinates of the area to be redrawn
  • The coordinates of the bounding box of the string

If the two areas overlap, we must redraw the string.

The first piece of information is very easy to obtain as it is passed to us by Wimp_RedrawWindow or Wimp_GetRectangle. For the second, we can call Font_StringBBox.

This SWI is very easy to use. We just tell it the address of the string in R1 and it returns in R1 to R4 the coordinates of the box which would just enclose the string if it was painted using the current font.

To try it out, enter Basic and type:

    SYS "Font_FindFont",,"Trinity.Medium",24*16,24*16

This will select 24pt Trinity Medium. You can, of course, select whatever typeface and size you like. Now type:

    SYS "Font_StringBBox",,"Hello" TO ,r1%,r2%,r3%,r4%
    PRINT r1%/400,r2%/400,r3%/400,r4%/400

The coordinates are returned in millipoints; dividing them by 400 converts them to graphics units.

You may get answers such as:

       1.2   -0.6   66.84   21.9

The first two should, in theory, be zero as they are given relative to the bottom left-hand corner of the string on the screen. The SWI is a little imprecise, due to rounding errors, but it will do for our purposes.

Now repeat the operation, this time replacing 'Hello' with 'Goodbye'. You should get similar results to these:

       1.44   -6.6   120.12   22.44

In the first situation, the minimum x and y coordinates were within one pixel of zero. In the second case, though, the minimum y coordinate has grown to a larger negative number.

The reason for this is the tail of the 'y'. The coordinates are given relative to the base line of the characters and the 'y' has a 'descender' which goes below this; therefore the bounding box has a lower minimum y coordinate.

To make use of all this in our application, we rewrite PROCtext:

 5140 DEFPROCtext(x%,y%,col%,RETURN coords%)
 5150 fh%=coords%!4:coords%+=8
 5160 SYS "Font_SetFont",fh%
 5170 SYS "XFont_StringBBox",,coords% TO ,fminx%,fminy%,fmaxx%,fmaxy%
 5180 fminx%=(fminx% DIV 400)-1:fminy%=(fminy% DIV 400)-1:fmaxx%=(fmaxx% DIV 400)+1:fmax
y%=(fmaxy% DIV 400)+1
 5190 IF b%!28<=x%+fmaxx% AND b%!32<=y%+fmaxy% AND b%!36>=x%+fminx% AND b%!40>=y%+fminy% THEN
 5200   SYS "Wimp_SetFontColours",,1,col%
 5210   SYS "Font_Paint",,coords%,%10000,x%,y%
 5220 ENDIF
 5230 WHILE ?coords%>=32:coords%+=1:ENDWHILE
 5240 coords%+=1:WHILE (coords% MOD 4)<>0:coords%+=1:ENDWHILE
 5250 coords%+=4
 5260 ENDPROC
 5270 :

This version of the listing is page_237.

We've moved the line which sets the current font up to line 5160 to put it nearer the start of the procedure.

The coordinates of the rectangle to be redrawn were returned by Wimp_RedrawWindow or Wimp_GetRectangle in the four words beginning at b%+28. You will recall that we took the precaution of setting up the calls to PDriver_DrawPage and PDriver_GetRectangle to do the same thing when we wrote PROCprint. By the time we get to this procedure, they will still be there as the block hasn't been modified by PROCredraw or PROCdraw.

Line 5170 gets the minimum and maximum coordinates of the string bounding box and the following line converts them from millipoints to graphics units, subtracting 1 from each of the minimum coordinates and adding it to each of the maximum ones to ensure that the box fully covers the position of the string on the screen.

These coordinates are all relative to the bottom left-hand corner of the string on the screen, at (x%,y%).

Line 5190, which carries out the comparison, has the following meaning:

IF the rectangle minimum x coordinate is less than the string maximum x

AND the rectangle minimum y coordinate is less than the string maximum y

AND the rectangle maximum x coordinate is greater than the string minimum x

AND the rectangle maximum y coordinate is greater than the string minimum y

then we need to redraw the string.

You should notice a reduction in the time it takes to redraw the window unless all your text strings need redrawing at the same time. If you want to prove that it works, insert a VDU 7 command in the IF ... THEN ... ENDIF structure. You should find that you get a beep when you drag a menu over some text, but not when you drag it over the remainder of the window.

Updating the Title Bar

You've probably noticed when using Edit, Draw etc. or your favourite word processor that you start off with a blank window whose title bar says '<untitled>' until you save something, after which it shows the pathname of the file you saved. If you load in a file, it also shows the pathname.

The title bar also acquires an asterisk as soon as you modify the contents of the window, which disappears when you save the file. The asterisk means, of course, that you have data which you've changed since you last saved it.

To incorporate these features into our application, we first have to modify the main window template. Load it into WinEd and change the text in the title bar to '<untitled>'. Ensure that it is indirected, with an indirected data buffer length of 256 bytes, then resave the file.

We will refer to the buffer which holds the title bar text as titlebuf%. Add a line to PROCload_templates, after loading the main window template but before creating the window:

 1380 REM ****** load and create main window ******
 1390 SYS "Wimp_LoadTemplate",,stack%,ws%,wsend%,-1,"Main",0 TO ,,ws%
 1400 titlebuf%=!(stack%+72):PROCterm(titlebuf%)
 1410 SYS "Wimp_CreateWindow",,stack% TO main%

We are using a similar technique to the one we used to find the indirected data buffers in icons, except that this time we're looking in the main body of the window block. The title data begins at stack%+72 and, as it is indirected, that is where we find the address of its buffer.

The title will be marked as changed (with an asterisk added) when we call PROCadd_item or PROCdelete_item by clicking Select or Adjust over the window, and marked as unchanged when we load or save a file. In the latter cases, the entire title will be updated.

We will create a variable called changed% to keep a note of whether or not we have unsaved data, and set it to FALSE in PROCinit:

 1650 quit%=FALSE:printing%=FALSE:changed%=FALSE

Now we add a line near the end of PROCadd_item to call the next procedure:

 3180   IF plot%=0 PROCadd_text(coords%)
 3190   coords%!4=-1
 3200   PROCforce_redraw(main%)
 3210   PROCchanged
 3220 ENDIF
 3230 ENDPROC
 3240 :

We also need one in PROCdelete_item:

 3450 IF coords%>list% THEN
 3460   coords%-=4
 3470   IF (!coords% AND &FF000000)=0 coords%-=!coords%:SYS "Font_LoseFont",coords%!4
 3480   !coords%=-1
 3490   PROCchanged
 3500 ELSE
 3510   VDU 7
 3520 ENDIF
 3530 PROCforce_redraw(main%)
 3540 ENDPROC
 3550 :

In both cases, the call is placed within the IF ... THEN ... ENDIF structure which is only called if we actually change the data; there is no point in marking the window as changed if we click Adjust on an empty window!

We use two procedures here; one to change the window title and another to redraw the title bar:

 7520 DEFPROCchanged
 7530 IF changed%=FALSE THEN
 7540   $titlebuf%+=" *"
 7550   changed%=TRUE
 7560   PROCupdate_titlebar
 7570 ENDIF
 7580 ENDPROC
 7590 :
 7600 DEFPROCupdate_titlebar
 7610 LOCAL c%,tbbottom%
 7620 c%=FNstack(36)
 7630 !c%=main%:SYS "Wimp_GetWindowState",,c%
 7640 tbbottom%=c%!16
 7650 SYS "Wimp_GetWindowOutline",,c%
 7660 SYS "Wimp_ForceRedraw",-1,c%!4,tbbottom%,c%!12,c%!16
 7670 PROCunstack(c%)
 7680 ENDPROC
 7690 :

Adding the Asterisk

Unless we save the data after every addition or deletion, we will keep calling PROCchanged when changed% is already TRUE, so we first examine it and skip the rest of the procedure if it is already set. Lines 6690 and 6700 add a space and a star to the end of the title, then set changed% to TRUE.

The job of redrawing the title bar is dealt with by a separate procedure so that it can also be called when resetting to the 'unchanged' state. We do it by forcing a redraw of a rectangle covering the window's visible title area.

The SWI Wimp_GetWindowState only tells us the coordinates of the window's visible work area; that is, the part we draw in, inside the window. It tells us nothing about the window gadgets surrounding this area. Although we could assume that our window's title bar is the standard height of 42 OS units, this is not guaranteed because the window tool sprites can be redefined by the user. If we assumed a fixed value of 42 OS units, we might either fail to redraw enough of the title bar or else cause too much of the screen to be redrawn, perhaps leading to unwanted flickering. Therefore it's a good idea not to make any assumptions, but to calculate the actual height of the title bar.

First of all, we call Wimp_GetWindowState and make a note of the maximum y coordinate in a local variable, tbbottom%; the top of our visible work area is also the bottom edge of the title bar. Next we call Wimp_GetWindowOutline. This works much like Wimp_GetWindowState except that the coordinates it returns are those of the entire bounding box of the window, including the tools around its border. (Unlike Wimp_GetWindowState, though, the call returns no other information, and a window must have been opened before the call will work.) We then redraw the window with the new values returned by this call, but substituting the remembered value, tbbottom%, in place of the minimum y coordinate. This redraws exactly the area we want to update.

It may strike you as odd that it's necessary to go through such convolutions to achieve such a common and simple task as redrawing a title bar, and indeed it is. However, for a long time, there were only two approaches to redrawing title bars, with the one just explained being the better approach. The alternative way of forcing the redraw of a title bar is to toggle a window's input focus status briefly, forcing the Wimp to redraw its borders when they change colour. Neither approach is ideal, though. In the case of toggling input focus, the entire window border is redrawn (not just the title) and you run the risk of upsetting another application that owns the input focus. In the case of redrawing the title bar via Wimp_ForceRedraw, as we do here, you may cause the unnecessary redrawing of another window if it overlaps the title bar being redrawn.

In fact there is a simple call that will, with one line of code, perform the task we require. Unfortuantely it is a quite recent addition to RISC OS, and was only introduced for widespread use with RISC OS 4. The method that works on all systems has therefore been used in our program. However, if you know that your software will never need to run on a computer with an operating system earlier than RISC OS 4, you could use the new call, which is a modification of the existing SWI, Wimp_ForceRedraw. It takes the following form:

    SYS "Wimp_ForceRedraw",main%,&4B534154,3

The value in R0 is the handle of the window whose title bar is to be redrawn. R1 contains the same magic number, comprising the ASCII codes for the word "TASK", that we saw being passed to Wimp_Initialise back in Section 1. R2 contains a value of 3, which is just a code that means 'redraw the title bar'.

Having written the procedure to redraw the title bar, the first time you click Select or Adjust on the window, an asterisk should appear on the end of the title. In order to get rid of it again, we must add a line to the end of PROCload:

 4060 IF ((b%!32) AND 1<<16)=0 THEN
 4070   SYS "Wimp_OpenWindow",,b%
 4080 ELSE
 4090   PROCforce_redraw(main%)
 4100 ENDIF
 4110 PROCunchanged
 4120 ENDPROC
 4130 :

We also need a similar call in the equivalent place in PROCcommand, for when the user has double-clicked on a file to launch our program:

 7390   IF ((b%!32) AND 1<<16)=0 THEN
 7400     SYS "Wimp_OpenWindow",,b%
 7410   ELSE
 7420     PROCforce_redraw(main%)
 7430   ENDIF
 7440   PROCunchanged
 7450 ENDIF
 7460 ENDPROC
 7470 :

Finally, we need to add another to PROCsave2:

 4780 DEFPROCsave2
 4790 n%=FNend2+4
 4800 SYS "XOS_CLI","SAVE "+$savestr%+" "+STR$~list%+" "+STR$~n% TO err%;flags%
 4810 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err%
 4820 SYS "XOS_CLI","SETTYPE "+$savestr%+" 012" TO err%;flags%
 4830 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err%
 4840 SYS "Wimp_CreateMenu",,-1
 4850 PROCunchanged
 4860 ENDPROC
 4870 :

In each case, the call comes at the end of the procedure, when the load or save has been completed successfully and the string at savestr% has been updated with the pathname of the file.

 7700 DEFPROCunchanged
 7710 $titlebuf%=$savestr%
 7720 changed%=FALSE
 7730 PROCupdate_titlebar
 7740 ENDPROC
 7750 :

This procedure simply makes the window title the same as the pathname, resets changed% and redraws the title bar.

The version of the listing up to this point is supplied as page_242.

Quitting with Unsaved Data

If you try to quit your word processor without saving what you've typed, it warns you and gives you the opportunity to change your mind.

We're now in a position to do something similar, as we have the variable changed% which keeps track of whether or not we have unsaved data.

Our first requirement is a dialogue box, which again we can again obtain by plundering another program. Many editors will have a window similar to the following illustration in their Templates; find one and use WinEd to import it into our own Templates file, then change it to read as follows (or design one from scratch if you prefer):

(Discard/Cancel warning window)

When you open this window, you will probably find that its central icon just contains a question mark, as the template is designed to convey a number of messages. Its parent application will insert an appropriate message and change the window title.

Incidentally, if you look at the validation string of this icon, you will probably find that it contains the command 'L40'. The 'L' (or 'l') command means that the text in the icon may be formatted vertically; that is, it may be split between several lines. The '40' suffix is the line spacing in OS units. In fact, for a long time, RISC OS ignored this value and always set the spacing to 40, but the value is honoured by RISC OS 5 and later versions of RISC OS 4, so don't specify a value of 0 or else the lines of text may be written on top of each other! Note that you must not use this command in a writable icon, because the Wimp cannot handle the caret correctly in a multi-line text icon. You must also avoid using an anti-aliased font (other than the desktop font), and text that appears in multi-line icons will always be both vertically and horizontally centred, regardless of the state of the justification flag bits.

The 'Discard' button should be icon 0 and the 'Cancel' button icon 2. Both have button type 3, meaning that a single click notifies the application. The text message is icon 1.

Naturally we need some more lines in PROCload_templates to load and create this window:

 1520 REM ****** load and create Save box ******
 1530 SYS "Wimp_LoadTemplate",,stack%,ws%,wsend%,-1,"xfer_send",0 TO ,,ws%
 1540 savestr%=!(stack%+88+32*2+20)
 1550 SYS "Wimp_CreateWindow",,stack% TO saveas%
 1560 REM ****** load and create Quit dialogue box ******
 1570 SYS "Wimp_LoadTemplate",,stack%,ws%,wsend%,-1,"quit",0 TO ,,ws%
 1580 SYS "Wimp_CreateWindow",,stack% TO quitwind%
 1590 REM ****** end of window creation ******
 1600 SYS "Wimp_CloseTemplate"
 1610 ENDPROC
 1620 :

We can't call our new window handle quit% because we're already using the name for the variable which controls when the application closes down.

Intercepting the Quit option from the main menu is quite simple. We simply change one line in PROCmenuclick:

 2270 SYS "Wimp_DecodeMenu",,topmenu%,b%,c%
 2280 CASE $c% OF
 2290   WHEN "Quit":IF changed% shutdown%=FALSE:!c%=quitwind%:
        SYS "Wimp_GetWindowState",,c%:
        PROCshowmenu(quitwind%,c%!4,c%!16) ELSE quit%=TRUE
 2300   WHEN "Options...  F2":!c%=options%:SYS "Wimp_GetWindowState",,c%:
        SYS "Wimp_OpenWindow",,c%
        etc.

We will use the new variable shutdown% later on, when we deal with the CloseDown message sent by the Wimp. If changed% is FALSE, the line sets quit% to TRUE as before so that the program closes down. If changed% is TRUE, it just opens the quit dialogue box and returns to Wimp_Poll. We are using the same technique to open it as we did earlier in this section when we arranged for the Save box to be opened as a transient dialogue box, using PROCshowmenu to open it as a menu. This means that the window will be closed if we press Escape or click the mouse anywhere outside it on the screen.

If you were to start up the application at this stage, and to do something to cause the 'changed' asterisk to appear in the title bar, when you opened the main icon bar menu and chose Quit, the warning dialogue box would appear but clicking on either of its buttons would have no effect as we haven't made an addition to PROCmouseclick yet. Hence it would be quite difficult to quit the application, though choosing Quit from the menu in the Task Manager window, just as we did in Section 4, would work.

Adding just a few more lines to PROCmouseclick, though, will deal with clicks on both buttons in this window:

  540   WHEN main%:PROCwindow_click
  550   WHEN options%:PROCopt_box(b%!8,b%!16)
  560   WHEN saveas%:PROCsavebox
  570   WHEN quitwind%:
  580     CASE b%!16 OF
  590       WHEN 0:IF shutdown%:PROCreply ELSE quit%=TRUE
  600     OTHERWISE
  610       SYS "Wimp_CreateMenu",,-1
  620     ENDCASE
  630 ENDCASE
  640 ENDPROC
  650 :

We can ignore the call to PROCreply for the moment as we set shutdown% to FALSE in PROCmenuclick.

If we click on icon 0 (the 'Discard' button), quit% is set to TRUE so that the application closes down. If we click on the 'Cancel' button, the window (which was opened as a menu) is closed by calling Wimp_CreateMenu with -1 in R1.

This version of the listing is supplied as page_245.

How to Avoid Being Shut Down by the Wimp

We saw in Section 4 how our application receives a 'CloseDown' message from the Task Manager with message action code zero when we choose Quit from a menu in the Task Manager window. This also happens if we shut down the machine, either from the Task Manager main menu or by pressing Shift-Ctrl-F12. When this message is received, the task must close down. If there's any unsaved data, it's too late.

So how do we avoid being closed down if we have unsaved data? We make use of another message that the Task Manager always sends before the CloseDown one, called Message_PreQuit. This message has an action code of 8 and is either sent to the specific task being closed down or broadcast to all tasks if the machine is being shut down. In the latter case, the Task Manager receives the message back again, which tells it that no task has objected to it.

To object to the PreQuit message, we reply to it. We send it back to its originator with a reason code of 19: 'User_Message_Acknowledge'. If the Task Manager receives this, it doesn't issue the CloseDown message.

An extra line in PROCreceive detects the PreQuit message and calls a new procedure to handle it:

 1820 DEFPROCreceive
 1830 REM handles messages received from the Wimp with reason codes 17 or 18
 1840 CASE b%!16 OF
 1850   WHEN 0:quit%=TRUE
 1860   WHEN 2:PROCsave
 1870   WHEN 3:PROCload
 1880   WHEN 5:PROCdata_open
 1890   WHEN 8:PROCprequit
 1900   WHEN &400C0:PROCmenu_message
 1910 ENDCASE
 1920 ENDPROC
 1930 :

 7860 DEFPROCprequit
 7870 IF changed% THEN
 7880   b%!12=b%!8
 7890   sender%=b%!4
 7900   SYS "Wimp_SendMessage",19,b%,sender%
 7910   IF ((b%!20) AND 1)=0 shutdown%=TRUE ELSE shutdown%=FALSE
 7920   !b%=quitwind%:SYS "Wimp_GetWindowState",,b%:PROCshowmenu(quitwind%,b%!4,b%!16)
 7930 ENDIF
 7940 ENDPROC
 7950 :

If changed% is FALSE, we do not have unsaved data and there is no problem, so the remainder of the procedure is skipped.

Lines 6980 to 7000 reply to the PreQuit message, as well as keeping a note of the Task Manager's task handle, which we shall need later.

If the PreQuit message was sent as a part of the machine's shutdown routine, we will have interrupted this routine, so we must restart it. Under RISC OS 2, it was not possible to tell the difference between a PreQuit message that was sent exclusively to the application and one that was broadcast to all tasks as part of a shutdown routine, with the result that a correctly written application could shut down the entire machine when it had been told to quit with unsaved data.

Fortunately, from RISC OS 3 onwards, the operating system is now able to remember why it sent the PreQuit message and ignores any attempt to restart the shutdown routine if it was only telling one task to close down. As an additional safeguard, the PreQuit message has a set of flags in the word at b%+20. If bit zero of this word is set, we don't make the machine close down.

Line 7050 looks at this bit and sets shutdown% to TRUE if it is set or FALSE if not. This variable, you will remember, was made FALSE by PROCmenuclick when we chose Quit from our own menu.

In the following line we open the quit dialogue box in the same way as we did previously.

Now look again at the lines we added to PROCmouseclick. If we click on the 'Discard' button when shutdown% is FALSE, we simply set quit% to TRUE and close down. If shutdown% is TRUE, we call a new procedure instead:

 7960 DEFPROCreply
 7970 changed%=FALSE
 7980 SYS "Wimp_GetCaretPosition",,b%
 7990 b%!24=&1FC
 8000 SYS "Wimp_SendMessage",8,b%,sender%
 8010 ENDPROC
 8020 :

The purpose of this procedure is to make the Task Manager think we've pressed Shift-Ctrl-F12, which we do by sending it a message. This is not a user-type message with reason code 17, 18 or 19 but a key-pressed event message, with a data block identical to the one we get on a return from Wimp_Poll with a reason code of 8.

We first call Wimp_GetCaretPosition, not because we have any interest in the position of the caret but because it returns with the first six words of the data block filled with the same information as we require in the first six words of the block when we send the message. The seventh word contains the code for the key pressed.

We saw earlier in this section how the Wimp returns a code for a function key F0 to F9 with &100 added to the number to avoid confusion with ASCII codes between 128 and 255. A brief list is as follows:

Key   Alone   Shift   Ctrl   Shift-Ctrl
Print (F0)&180&190&1A0&1B0
F1 - F9&181 - 189&191 - 199&1A1 - 1A9&1B1 - 1B9
F10 - 12&1CA - 1CC&1DA - 1DC&1EA - 1EC&1FA - 1FC

A more complete list is in the Programmer's Reference Manual.

You can see from this table that &1FC corresponds to Shift-Ctrl-F12, which is added to the block by line 7090. The following line sends the message, using the Task Manager's task handle which was obtained in PROCprequit.

You will notice that we don't set quit% to TRUE in this operation. RISC OS does not seem to take kindly to a task which sends a shutdown message and then closes itself down. We just send the message and wait to be told to close down, having already set changed% to FALSE so that we will ignore the next PreQuit message when it comes in.

The listing to date is supplied as page_248.

Saving as a Third Option

When hunting for a window template with 'Discard' and 'Cancel' buttons, you probably came across a very similar template with a third action icon labelled 'Save'. This third button has been been presented as a default action button with a channeled R6-type border.

You may feel this would be more appropriate for your application under certain circumstances. You could arrange things so that a click on 'Save' (or a press of Return) will open the Save box and the application will shut down when the save is completed.

As this application is a single document editor, you should also use one of these warning boxes if you try to load a new file with unsaved data. This also applies if you choose Clear from the window menu under the same circumstances.

Opening a Window with the Close Icon

If you have RISC OS 3 or later, try the following with an application such as Edit or Draw:

Either save a file or load a previously-saved one, so that you are working with a file whose full pathname is in the title bar of the window, then close the directory window which contains the file. Now close the document window by clicking on the window's Close tool with the Adjust button.

You will notice that, as well as the fact that the document window disappears, the directory window reopens. This is a useful feature as it helps reduce the clutter of windows on the screen. It also enhances the user's impression that the file being edited is part of the disc's directory structure, in much the same way as closing a Filer window with Adjust opens its parent directory display.

You can load a file into the application using Adjust, either to double-click on the file icon or to drag it to the icon bar. This will close the Filer window and help keep the screen uncluttered. When you've finished with the document, however, you may wish to load another one from the same directory, so you will want the directory window back on the screen. This is where closing the document with Adjust comes in. By closing the document window in this way, you get the directory window back again.

This would make a final small refinement to our application. To achieve it, we first have to change the line in PROCpoll which closes windows to make it call a procedure:

  310 DEFPROCpoll
  320 REM main program Wimp polling loop
  330 SYS "Wimp_Poll",&3831,b% TO r%
  340 CASE r% OF
  350   WHEN 1:PROCredraw(b%)
  360   WHEN 2:SYS "Wimp_OpenWindow",,b%
  370   WHEN 3:PROCclose_window
        etc.

Here is the final procedure, which brings our !RunImage file up to 813 lines:

 8030 DEFPROCclose_window
 8040 LOCAL n%
 8050 SYS "Wimp_GetPointerInfo",,b%+4
 8060 SYS "Wimp_CloseWindow",,b%
 8070 IF (b%!12 AND 1)<>0 AND b%!16=main% AND b%!20=-3 AND 
      INSTR($savestr%,"::")<>0 AND 
      INSTR($savestr%,"$.")<>0 THEN
 8080   n%=LEN$savestr%
 8090   WHILE savestr%?n%<>ASC"." n%-=1:ENDWHILE
 8100   OSCLI("Filer_OpenDir "+LEFT$($savestr%,n%))
 8110 ENDIF
 8120 ENDPROC
 8130 :

The final instalment of our now-complete listing can be found as page_250.

The job of PROCclose_window basically consists of closing the window and deciding whether or not to send a command to open a Filer window.

We can base our decision partly on information that has been returned by Wimp_GetPointerInfo. We have to make this call before we close the window, however, as otherwise the Wimp will assume that the window is no longer there, even though it won't actually have redrawn the screen at this time.

We mustn't disturb the window handle in the word at b% as this is required by Wimp_CloseWindow, so we tell Wimp_GetPointerInfo that the data block starts at b%+4. This means that all the information which it returns will be four bytes higher in memory than where we usually find it.

Line 8070 checks everything involved in the decision about whether or not to reopen the Filer window. The first part checks to see if we used the Adjust button by making sure that bit zero of b%+12 is set. We then make sure that the mouse was clicked over the main window by testing for its window handle, main%.

The next part checks the icon handle in b%+20. Although the mouse was clicked over the window surround, we can still get an icon handle: all the control icons have them, and they are all negative numbers. The close icon is number -3. This precaution ensures that the window is being closed because we clicked the mouse on its close icon and not for some other reason.

The last part of the line was copied from PROCchecksave. This is the bit which checks the filename string at savestr% to ensure that it contains a full pathname; if it doesn't, we can't open a directory window.

If all these conditions are satisfied, we go ahead and open the Filer window. The command in line 8100 may look a little like the name of a SWI but it is actually a star-command. We must use the OSCLI command, though, because we have to follow the command with the directory pathname. Note that there is a space between 'Filer_OpenDir' and the closing quotes.

The string at savestr% contains the full pathname of the file in the window, including the leafname of the file itself, but we want the pathname of the directory, excluding the leafname. To find this, we set n% to point to the end of the string and progressively reduce it until it points to a full stop, which will be the last directory separator before the filename. The value of n% is then the length of the part of the string that we want, and we use it in line 8100.

Economical Menus

We saw in Section 18 how we could conjure up a submenu of font names by moving the mouse pointer over the arrow pointing to it and using the Wimp message system to give us a chance to create it there and then.

There is no reason why all submenus should not be created in this way. It would certainly save quite a lot of memory, as you would only have to create the one being accessed at any one time. You could wait until Menu is clicked somewhere over the application before creating the appropriate top menu, using FNmake_menu, then calling Wimp_CreateMenu. When you get a message telling you that the pointer has moved over a submenu arrow, you could use a look-up table to tell you which submenu to create. You may even be able to create dialogue boxes in the same way, similarly getting rid of them with Wimp_DeleteWindow when you've finished with them.

Each menu or submenu would need only 28 bytes, plus 24 for each item and any indirected data necessary. You would, of course, have to keep a note of any item that had a tick or was greyed out; the latter would require a modification to FNmake_menu to handle it.

And that's it. Our application is complete.

If you've had any problems, the Appendix contains a full listing of the final version of the Shapes application.

Shapes is not intended to be 'bullet-proof'; it was designed to show how a lot of the most important principles of Wimp programming work, rather than to be a highly polished application. It would certainly be possible to enhance it with new features, and you may be able to think of ways in which its existing features could be made to work better. If so, then by all means experiment further with it. You may like to work on some enhancements to this existing program before embarking on an entire new application of your own.

Conclusion

You now have an application that will allow you to draw pictures and text in a window, save, reload and print them. More importantly, though, if you've understood everything you've read in this guide so far, you now know many of the basic techniques of Wimp programming. If you haven't, read it again!

Obviously, there's a lot more to the Wimp than what we have covered here, but by now you should be in a position to experiment for yourself and find out how to do other things, especially if you arm yourself with a copy of the invaluable Programmer's Reference Manual!

You will doubtless be eager to create your own multi-tasking programs. The final thing you need is a Wimp shell on which to base your own applications...

previousmain indexnext

 
© Martyn & Christine Fox 2004