Beginner's Guide to WIMP Programming
Everything you need to know to start writing your own applications.
Chapter 5: Opening Windows
We now have an application which we can load and close down again, and which puts its icon on the icon bar. 'How exciting!', you must be saying, 'When do we get to the windows?' Soon, very soon, but first...
If you've been entering the listings yourself or have done any experimenting, it is highly likely that, at some stage, you made a mistake and were rewarded with an error message for your pains. The desktop would have produced an error box containing a message, such as 'Mistake', but with no line number - not very helpful!
To deal with this situation, we need an error handler instruction to close down the program properly. For now, we will make do with a simple one, just to tell us where an error was detected if things go wrong. This is contained in one line, line 50, located before the Wimp_Initialise instruction, so that the beginning of the program now looks like this:
10 REM >!RunImage 20 REM (C) Martyn Fox 30 REM Wimp training program 40 REM version 0.01 (date) 50 ON ERROR PROCclose:REPORT:PRINT" at line ";ERL:END 60 SYS "Wimp_Initialise",200,&4B534154,"Test" TO ,task%
The new line calls PROCclose, which contains the Wimp_CloseDown call, then tells the system to print the error message, followed by the line number. You can try it out easily by temporarily adding a line to PROCmouseclick so that it looks like this:
480 DEFPROCmouseclick 490 REM handles mouse clicks in response to Wimp_Poll reason code 6 500 REM b%!0=mousex,b%!4=mousey:b%!8=buttons:b%!12=window handle (-2 for icon bar):b%!16=icon handle 510 CASE b%!12 OF 520 WHEN -2:CASE b%!8 OF 530 WHEN 1:quit%=TRUE 540 WHEN 4:Z 550 ENDCASE 560 ENDCASE 570 ENDPROC 580 :
Line 540 contains the simplest type of error; the expression "Z" on its own means nothing! Basic will try to execute this statement if you click Select on the icon bar icon which will cause PROCmouseclick to be called with a value of 4 in b%+8.
If you are editing the program using a text editor such as Edit without displaying line numbers, it is simple to find the error line; just knock a nought off the end of the line number in the error message and go to that line in the edit window. This is because, unless you've configured things differently, Edit numbers the lines in tens. If, for example, you get a message saying 'Unknown or missing variable at line 540', choose the Go to option from Edit's Edit submenu (or press F5) and choose line 54. (The listing is supplied as page_028.)
A Debugging AidIt would be much more convenient in this situation to be able to run the program from Basic's command level, as the error handler will put us straight back into the command level where we can examine the state of the various variables.
With some versions of RISC OS, you could enter Basic from the star prompt, load the !RunImage file and run it. The Wimp produces a display with an icon bar containing just our program's icon, returning to a blank screen when the program closes down. This is useful for testing some aspects of the program but could not be used for later stages, for example when we want to pass messages to and from the filing system for loading and saving.
In any case, some versions of RISC OS will only allow us to do this if we first exit from the desktop, via the Task manager menu, otherwise we would get an error message saying 'Wimp is currently active at line 60'. Fortunately, there is a better method, one which will allow our application to interact with the rest of the desktop.
Use your text editor to create a command file. This is like an obey file except that the machine acts as though everything in the file were typed at the keyboard. Type in the following:
|TestRun *Basic SYS "Wimp_CloseDown" *Set Test$Dir !Test *Iconsprites <Test$Dir>.!Sprites CHAIN "<Test$Dir>.!RunImage"
The first line is a comment reminding you of the filename. You could, of course, use any filename you like.
The second line of the file opens a command window and starts up Basic's command level. Because this is a command file, not an obey file, all subsequent lines will be sent to the Basic interpreter, not the command line interpreter, so operating system commands have to be preceded by a star.
Re-initialising the WimpThe next line of the command file closes down the Wimp with respect to this task so that line 60 of the !RunImage file can re-initialise it without any trouble.
The following line sets up Test$Dir and the penultimate line loads the sprite file. These lines are necessary as we are not running the !Run file, so we have to do its job for it. There is no command file equivalent of Obey$Dir to automatically set up the directory for us. You have a choice of how to deal with this situation.
One option is to enter the complete pathname of your application directory into the file, instead of just the name "!Test". The easiest way to do this is to drag the !Test icon into the text editor window with Shift held down.
The other option is to set the Currently Selected Directory to be your application directory before you run the file so that you don't need the full pathname. If you have RISC OS 4 or later, you can do this by choosing an option on the Filer's menu (it will be called either Set work directory or just Set directory). If you have an earlier version of RISC OS, the easiest way to do it is with an Obey file. Create a file called, say, "Here", consisting of one line:
If you save this file in your application directory (or any directory you wish), then run it, the Currently Selected Directory will be set to the directory containing the file.
The CHAIN command in the final line loads and runs the !RunImage file. The application will take its place in the desktop display in the usual way. When we get onto loading and saving, you will be able to drag icons to and from it. When you quit it, however, a command window will appear in the centre of the screen containing the Basic prompt. You can now list the variables or make temporary changes to the program, which you could RUN again without resaving. If you want to be able to use the whole screen, change the screen mode. To get back to normal, just type 'QUIT' and the desktop will reappear.
Note that this method doesn't set the Wimp slot. If you wish to check that your program will run in the available memory, go to the Task manager window and change the 'Next' slot to an appropriate figure before you run the command file.
And Now to Windows!
The Window Data BlockCreating a window involves setting up a block of memory containing all the necessary data and then calling Wimp_CreateWindow. There are two ways of doing this - the hard way, by typing in the data, and the easy way, by loading a template file that we've previously created, using a window template editor. We shall create our first window the hard way, as it's important to understand the way in which windows work, and this will teach us. After that, we will investigate the use of templates.
Before you start entering the data, add one line to PROCinit so that the procedure looks like this:
370 DEFPROCinit 380 REM initialisation before polling loop starts 390 DIM b% 1023 400 quit%=FALSE 410 PROCcreate_window 420 ENDPROC 430 :
During the initialisation process, line 410 calls the new procedure which we're about to add.
If you're typing in the listing, take a deep breath; the window data contains 84 bytes, plus 32 for each icon in the window. Fortunately, most of these are in groups of four.
The next listing shows the window data block. If you're typing it in, there is no need to type in the REMs; they have only been added for clarity.
620 DEFPROCcreate_window 630 REM sets up window data block and creates window 640 b%!0=168:REM visible area minimum x 650 b%!4=364:REM visible area minimum y 660 b%!8=804:REM visible area maximum x 670 b%!12=872:REM visible area maximum y 680 b%!16=0:REM scroll x offset relative to work area origin 690 b%!20=0:REM scroll y offset relative to work area origin 700 b%!24=-1:REM handle to open window behind (-1 means top, -2 means bottom) 710 b%!28=&FF030012:REM window flags 720 b%?32=7:REM Title foreground and window frame colour 730 b%?33=2:REM Title background colour 740 b%?34=7:REM Work area foreground colour 750 b%?35=1:REM Work area background colour 760 b%?36=3:REM Scroll bar outer colour 770 b%?37=1:REM Scroll bar inner (slider) colour 780 b%?38=12:REM Title background colour when highlighted 790 b%?39=0:REM Reserved - must be 0 800 b%!40=0:REM Work area minimum x coordinate 810 b%!44=-700:REM Work area minimum y coordinate 820 b%!48=1000:REM Work area maximum x coordinate 830 b%!52=0:REM Work area maximum y coordinate 840 b%!56=&3D:REM Title bar icon flags 850 b%!60=&3000::REM Work area flags giving button type 860 b%!64=1:REM Sprite area control block pointer (1 for Wimp sprites) 870 b%!68=0:REM Minimum width and height of window 880 $(b%+72)="Test Window":REM Title data 890 b%!84=0:REM Number of icons 900 SYS "Wimp_CreateWindow",,b% TO main% 910 ENDPROC 920 :
We will go through this listing shortly, once our window is up and running. For now, note that, after setting up the data block, we call Wimp_CreateWindow with the address of the block, b%, in R1. The routine returns, giving us the window handle in R0, which we put into a variable that we shall call main%, to stand for 'main window' (remembering that a finished application will usually have several windows, each of which will require a helpfully-named handle). Whenever we tell the Wimp to do anything with this window in future, we refer to it by the window handle.
Now that the window has been created, we no longer need the information in the data block, as the Wimp has stored it elsewhere, so we can reuse the block for other purposes.
Although our window now exists, we still need to open it in order to see it. Once we've made a few additions to do this, the program will do something new, so there is a new listing in the files, called page_032.
We add two lines to PROCpoll and one to PROCmouseclick:
260 DEFPROCpoll 270 REM main program Wimp polling loop 280 SYS "Wimp_Poll",,b% TO r% 290 CASE r% OF 300 WHEN 2:SYS "Wimp_OpenWindow",,b% 310 WHEN 3:SYS "Wimp_CloseWindow",,b% 320 WHEN 6:PROCmouseclick 330 WHEN 17,18:PROCreceive 340 ENDCASE 350 ENDPROC 360 : 510 DEFPROCmouseclick 520 REM handles mouse clicks in response to Wimp_Poll reason code 6 530 REM b%!0=mousex,b%!4=mousey:b%!8=buttons:b%!12=window handle (-2 for icon bar):b%!16=icon handle 540 CASE b%!12 OF 550 WHEN -2:CASE b%!8 OF 560 WHEN 1:quit%=TRUE 570 WHEN 4:!b%=main%:SYS "Wimp_GetWindowState",,b%:SYS "Wimp_OpenWindow",,b% 580 ENDCASE 590 ENDCASE 600 ENDPROC 610 :
In PROCmouseclick, line 570 opens the window when you click Select on the icon. Although we entered all the window information when we created it, we have to remind the Wimp of the current position on the screen of the visible area, the positions of the scroll bars and the window flags.
All this information is contained in the first 32 bytes of the window block, and must be present in our data block, preceded by the window handle, when we call Wimp_OpenWindow. To put it there, we first put the window handle, main%, at the start of the block at b%, then call Wimp_GetWindowState. This sets up the data block and gives us a chance to modify the data, should we wish, before calling Wimp_OpenWindow.
If we change the visible area, by scrolling or moving the window, the Wimp will want us to call Wimp_OpenWindow again. It does this by returning from Wimp_Poll with a reason code of 2. Line 300 in PROCpoll deals with this request. We do not need to call Wimp_GetWindowState on this occasion, as all the information has already been put in the data block by Wimp_Poll.
You should now find that clicking Select on the icon causes the window to open and appear on the screen. All the control icons surrounding it will be present, and will work correctly without requiring any programming from us, as these matters are all taken care of by the Wimp.
All the control icons surrounding the window will be present
If you click on its close icon, the window will disappear. This has been caused by a return from Wimp_Poll with a reason code of 3, which is handled by line 310. The Wimp knows which window to close when we call Wimp_CloseWindow, as Wimp_Poll returns with the window handle in b%.
If you click on your icon again, the window will reappear with the same size, shape and position it had when you closed it. This is the result of the call to Wimp_GetWindowState in line 570.
Now we can see the window on the screen, we can go through the various parameters in PROCcreate_window, and examine the effect of changing some of them. If you want to try modifying the window, it would be wise to first make a copy of the unmodified !RunImage file, so you can return to the original version.
The first four words (lines 640 to 670) contain the actual position on the screen of the visible part of the window. These are expressed in RISC OS graphics units, the same as the ones used in Basic's DRAW and MOVE commands. The next two words, at b%+16 and b%+20, contain the position in the window of its visible area, determined by the scroll bars. We've set both of these to zero, which means that the vertical scroll bar is at the top, and the horizontal scroll bar is to the left. As a result, the visible area shows the top left-hand corner of the window.
We have the option to open the window behind another window, if we already know its handle. We probably won't want to do this, so b%+24 contains -1, meaning that our window is opened on top of any others on the screen.
The next four bytes at b%+28 contain the window flags. Those that most concern us at present are as follows, with the ones in bold type set to 1 in our example:
The parts of the data block we've looked at so far merely set the initial state of the window when it's first created. All these details are modified each time we move the window, change its size or scroll it.
Bytes 32 to 38 set the colours of the various parts of the window, using colour numbers from the Wimp palette (this palette can most easily be seen by editing a 16-colour sprite in Paint). The ones in our example are Acorn's recommended colours. The colour in byte 38 is that of the title bar when the window has the input focus, which usually means the caret is somewhere in the window, perhaps in a writable icon. The title bar is then usually a cream colour.
Work Area Extent and OriginThe four words beginning with byte 40 are the work area extent, which is the size of the entire window, including any invisible part but excluding its surround. Although it is expressed in graphics units like the visible area, its coordinates are measured from the work area origin, which is the top left-hand corner of the work area. For this reason, the minimum x and maximum y coordinates are always zero, and the minimum y coordinate is a negative number, because it refers to a point below the work area origin.
The work area origin does not have to be on the screen at all; indeed, the work area extent can be a lot larger than the screen. The Desktop Publishing package being used to create this guide shows all the pages in one enormous window whose height is 152 times the height of the screen!
Understanding CoordinatesIt is important to understand the difference between screen coordinates and window coordinates. Both are measured in graphics units of the same size. Screen coordinates refer to the position of something on the screen and are usually measured from the bottom left-hand corner of the screen. You will be familiar with these if you have used Basic's MOVE and DRAW commands.
Window coordinates refer to the position of something in a window, such as an icon. If the window is dragged, the icon moves with it. As we've just seen, these coordinates are measured from the work area origin, and need not necessarily be on the screen.
The title bar icon flags at byte 56 are like other icon flags, which we will deal with shortly. These simply tell us that the window title is centred vertically and horizontally in the title bar. The work area flags at byte 60 set the button type for the window, which determines how it responds to mouse clicks. Type 3, in this case, means that the task is notified (through a return from Wimp_Poll with a reason code of 6) if the mouse is clicked once over the window. This is a feature we will use later.
The minimum width and height data are two-byte quantities; the width in bytes 68 and 69 and the height in bytes 70 and 71. Both are zero here, which means that the minimum size of the window is determined by the title and scroll bars.
The 12 bytes beginning at byte 72 contain the window title string. What, you may be wondering, if the title is more than 12 characters long? In this case, the string would have to be indirected. We will find out what this means later when we're dealing with icons.
The final word contains the number of icons in our window. We could have put in a number such as 1 or 2, and added a further 32 bytes for each icon, but you would probably like a rest at this stage! There is nothing to stop us from adding icons later, as we shall see.