Chapter 7: Windows: The Easy Way
We've already looked at the hard way of producing windows and icons, by typing in all the necessary data. We did this deliberately, not to wear out your fingers or to bamboozle you with information, but so that you would understand what a window block consists of and how the information is used.
You'll be pleased to know that we can now look at the other way of doing it, namely by making a Wimp template file. We do this using a template editor. There are several freeware ones to choose from. The one supplied with this guide is called WinEd, written by Tony Houghton.
This description of WinEd refers to version 2.87. A template file is represented by a browser which displays an icon for each window in the template. Windows may be opened by double-clicking on the appropriate icon. For a full description of WinEd, see the manual that accompanies the software.
If you don't want to create your own template file, you can find one ready-made inside the among the accompanying files, with the filename "Templates1". You should copy this file into your application directory, renaming it "Templates". It's best to read the following description, though, because it explains a number of things about templates and icons.
Having loaded WinEd, click Select on its icon. A window will appear called '<Untitled>' which will show the contents of our "Templates" file. Click Menu on this window and open the Create window dialogue box. Each window in a template has to have an identifier, which has nothing to do with the window title. We might as well call ours 'Main'. When you've typed this in and pressed Return or clicked on Create, an icon will appear in the window, labelled "Main".
Double-clicking on this icon will open three windows; you can close any of them that you don't want to use, to avoid screen clutter. The window with a button bar alongside it is the window that we're working on, the one we've just created. The Monitor window shows which window and icon (if any) the pointer is over at any moment. It can be expanded to show the size and position of the window's visible area or the size and position of any icon, depending on what is under the pointer. The Icon picker window, as you might expect, contains a variety of icons of different types, any of which may be dragged to our new window.
Drag the new window to wherever you want it on the screen, and enlarge it by dragging its size icon, then either click Menu on it and choose Edit window or click on the icon on the button bar which shows a little picture of a window with a pen pointing into its work area. A dialogue box will open which will let you choose various parameters of the window, such as which scroll bars and other control icons it has.
There is a work area button type which determines what happens when you click the mouse over the window's work area, just as there is for an icon. We will still be making use of our routine for creating icons, so we want the program to respond to clicks on its work area. Use the WA Btn Type menu button to change the button type from "Never" to "Click".
Whenever you make a change, you can click on the Update icon to update the window. Note that the version of the window that you see on the screen is a special editable version which doesn't behave in the same way as the window you're actually creating. It will, for example, always have all its control icons, even if you've turned some of them off. To see what the real window will look like and how it will behave, click Menu over the window and choose Preview window. To switch back again, choose Edit template.
Now either click Menu on the window again, or click on the icon on the button bar with a little picture of a window and a pen pointing into its title bar, and open the Edit title dialogue box. Type in a title of your choosing. If you've used 12 or more characters, you will find when you click on Update that the Indirected tick-box will turn itself on; you have just made your first use of indirected data.
Now we will create an icon. If you turned off the Icon picker dialogue box, click Menu on WinEd's icon bar icon and open it again. Choose the button labelled "Cancel" and drag it into your window. Once it's there, you can change its size if you wish, by dragging its edges with Adjust. You can also move it around by dragging with Select.
... change its size by dragging with the Adjust button ...
Click Menu over your new icon. This will cause the icon background to turn black, indicating that the icon is selected and inverted, but that need not trouble us at the moment. You will see a menu item Icon #0. The number, incidentally, is the icon handle. Unlike window handles, which are produced purely by the Wimp when the window is created, the icon handle refers to the number of the icon in the window. This is the first one, so it's given an icon handle of zero.
Go to the Icon #0 submenu, then choose Edit. This will open another dialogue box, similar to the one that we used to set up the window but for editing the icon. You can also select the icon and open the Edit icon dialogue box simply by double-clicking on the icon.
Icons With Fancy BordersIt has probably occurred to you that the icons we created earlier had plain borders, but the one we've just created has a 3D-looking slabbed one. You'll also be aware that many icons have various types of border, some of which change when the icon is clicked on.
Earlier, we created icons which had indirected data. We saw how 12 bytes of the icon definition contained three four-byte words. One of these was the address of the icon's validation string, which we didn't use at the time, instead putting -1 where the address should be.
The validation string can contain a number of commands which affect the icon, all of which are listed in the Programmer's Reference Manual as part of the description of SWI Wimp_CreateIcon. If the string contains several commands, they have semi-colons (;) between them.
The command that interests us at the moment is the "R" or "r" command which defines the icon border. The letter is followed by a number, as follows:
You can see the validation string in the Edit dialogue box, so you can experiment with changing the number and seeing the effect. The "Action button" border number 5 may look the same as the "Slab out" border number 1 but it changes from slabbed out to slabbed in when the icon is clicked.
When you've finished experimenting, leave the validation string as "R5,3". The second number controls the colour that the icon background turns to while it is is in its alternate, slabbed-in state.
Change the text in the icon to "Click me". As in the case of the window title, if you put more than 12 characters into your icon, the Indirected tick-box will be turned on when you click the Update icon. Make sure that it is turned on anyway, otherwise the validation string will not work. It's also necessary for the icon's Border flag to be turned on for slabbed borders to work.
Click on the Minimise button next to the "Max text length" box. This will set the size of the indirected data buffer used to hold the icon text to the smallest possible figure; in this case, nine bytes: one for each character in the title string, plus one for a terminating character.
Now drag the writable icon from the icon picker window and open its Edit icon dialogue box. Change the icon text to whatever you like and also increase the number in the "Max text length" box to 50. This will ensure that the indirected data space allocated to this icon's text is 50 bytes, enough for us to type in a longer string than the icon contains at present.
You will notice that the validation string for this icon contains two new commands. The "P" command causes the mouse pointer to change shape when it is over the icon, its new shape being determined by the sprite whose name follows the command. In this case, the sprite is called "ptr_write" which gives the pointer its "I" shape.
The other command is the "K" command. This controls what happens if certain keys are pressed while this icon has the caret. In this case, the "t" suffix means that the caret will move on to the next writable icon if the Tab key is pressed, or the previous one if Shift-Tab is pressed, and the "a" suffix means that the same thing will happen if you press the Up or Down arrow keys.
Let's save the template file and find out how to use it. First close the window, either by clicking on its close box or choosing Close window from its menu. Open your application directory, ready to save the file, then click Menu on the main WinEd window. The Save item leads to the Save box, with the default filename 'Templates'. This is as good a filename as any, and is the one normally used by applications, so drag the icon to your application directory window.
We will be making alterations to the template file later on so make a copy of it, along with your various copies of the !RunImage file, called "Templates1".
Major Changes to the !RunImage FileAs you'll have gathered, we're making radical alterations to the way that our application creates windows. If you typed in the listing of the window data block yourself, you may be in for something of a shock; we don't need it any more! You can delete the whole of PROCcreate_window. First ensure that you've kept a copy of the last version of the !RunImage file, though, because that procedure has all the details of what the window data block contains. We're going add a new procedure to load the templates.
First, we need to modify PROCinit. We do not need the definition of ind% any more, as we're going to make other arrangements for it, but we do need workspace for the indirected data. We also need to call our new templates procedure instead of PROCcreate_window so modify PROCinit to read:
370 DEFPROCinit 380 REM initialisation before polling loop starts 390 DIM b% 1023,ws% 1023 400 wsend%=ws%+1024 410 quit%=FALSE 420 PROCload_templates 430 ENDPROC 440 :
In line 390, we set up a second memory block, at ws%, which will be our indirected data workspace. The Wimp will use this to store whatever indirected data comes with the window definition. We also set up variable wsend%, which contains the address of the end of this workspace; we will need it when we load in the templates, so that the Wimp knows how much workspace it may use.
In practice we will not need anything like 1,024 bytes for the indirected data, any more than we need that many bytes for our data block, but never mind. It gives us plenty of space in which to experiment, and you can always adjust the numbers when you actually come to write an application.
Now let's enter our new procedure for loading the template. This version is in the files as page_058.
1170 DEFPROCload_templates 1180 REM opens window template file, loads and creates window 1190 SYS "Wimp_OpenTemplate",,"<Test$Dir>.Templates" 1200 SYS "Wimp_LoadTemplate",,b%,ws%,wsend%,-1,"Main",0 TO ,,ws% 1210 ind%=!(b%+88+32*1+20) 1220 SYS "Wimp_CreateWindow",,b% TO main% 1230 SYS "Wimp_CloseTemplate" 1240 ENDPROC 1250 :
When we created the !Run file, we set up our system variable, called Test$Dir. We saw that it was not strictly necessary at that stage, but here's where we can now make use of it. The variable, you will remember, contains the complete pathname of our application directory, and that's where you saved the templates file (provided, of course, that you dragged the file icon into the correct directory window!).
Loading the TemplateThe first thing to do is to open the template file, which is the job of line 1190. The next line loads the window from the file. As you can see, a rather complicated set of parameters is to be passed to and from the system call when we do so. Register R1 contains the address of the data buffer where we want the window data block to be loaded, which is b%. R2 contains the address where the indirected data is to be put, ws%, and R3 the end of the indirected data workspace, wsend%.
R4 contains data connected with fonts. As we aren't using any, we put -1 into it. R5 holds the address of the name of the window identifier - hence the reason why all windows in a template file need a name. The SYS command does the same as it did when we first installed our application with Wimp_Initialise; it loads R5 with the address where Basic has stored the identifier name.
R6 tells the Wimp where to start looking for the window in the template file. This is necessary because it is possible to use 'wildcarded' names, with one or more characters replaced by '#' or '?'. Naturally, we want to search from the beginning of the file, so R6 contains zero.
When the program returns from the call, R2 contains the address of the unused portion of the indirected data workspace, which we put back into ws%. In this way, we can load another window, again quoting ws% as the address for its indirected data, without overwriting the data of our first window.
The window data is now in the block, starting at b%, and its indirected data in the block starting at the old value of ws%.
Where Did the Text Go?When we created our new window with the template editor, we gave it two icons, both of which had indirected text. We want to use the text of the second icon (with icon handle 1), so we need to find out where the text has been stored by the Wimp.
As you may recall from doing it the hard way, the window data consists of 88 bytes, plus 32 for each icon. In the icon block, the 12 bytes of data relating to the text begin with byte 20. In the window block as a whole, then, the address of this byte can be calculated as:
The block starts at b%, the window data accounts for the next 88 bytes, we add on 32 bytes for each preceding icon (zero if we are interested in the first icon), and finally 20 bytes for the position in the icon block of the byte that we want.
The word at this address contains the address of the text we put into the icon when we created it. This will be somewhere in the indirected data workspace.
Line 1210 puts this address into variable ind%. We're still using our old procedure for creating icons and, as before, all the icons will be sharing the same indirected text buffer and they will still want ind% to point to it.
Be warned! When the Wimp put the text string into the workspace, it followed it not with a Return character, but with a zero. Basic will not like this if you try to load this text into a string, for example with a command such as:
Basic likes all its strings to end with Return characters, so, if you want to use the data in this way, you will have to write some code along the lines of:
n%=0 WHILE ind%?n%>31 n%+=1 ENDWHILE ind%?n%=13
This routine searches along the string until it finds a byte containing a value less than 32. This should be the zero terminating byte. It then replaces it with 13, the code for a Return.
We will be adding a procedure to do this later, when we need it.
Having done whatever we want with the window data block, we're now ready to create the window. We do this in line 1220, in exactly the same way as when we did it the hard way, and we end up with the window handle, main%, returned in R0.
The only job remaining is to close the template file, which we do with the call in line 1230.
When you run your new version, it should work in exactly the same way as before, except that the first two icons are already on the screen.
Because we still have our routine for creating icons in place, you can create more of them by clicking Select in the window. They will still be white, writable ones, and they will contain the string which you put in your original writable icon, because, of course, they all store their indirected text in the same place.
Clicking on IconsWhen we created our window in the template file, we gave it two icons. The first of these was an action button, which we created by dragging an icon with "Cancel" in its text across from the icon picker window.
Such an icon is clearly meant to be clicked on to make the program do something and we invited this by changing its text to "Click me"! We could have a number of icons in our window, each of which makes the program do something when you click on it and, indeed, this is how most dialogue boxes work. Without provision in our program for handling mouse clicks on icons, though, all that would happen when we clicked on this icon would be that it slabbed inwards and its background colour changed. Both of these things happen, as we saw earlier, as a result of the "R" command in the icon's validation string.
Take a look at the REM statements at the beginning of PROCmouseclick and PROCwindow_click. We've listed what is in the data block when Wimp_Poll returns with a reason code of 6. We've made use of the window handle in b%+12 (which gets us from PROCmouseclick to PROCwindow_click), and the state of the mouse buttons, in b%+8. We've also used the mouse x and y positions in b%+0 and b%+4 to determine where to create a new icon, and we made use of the icon handle in b%+16 within PROCkill_icon when deleting the icon from the window.
In the current version of the !RunImage file, we modify PROCwindow_click so that it looks like this:
780 DEFPROCwindow_click 790 REM handles mouse clicks on window 800 REM b%!0=mousex,b%!4=mousey:b%!8=buttons 805 REM b%!12=window handle (-2 for icon bar):b%!16=icon handle 810 CASE b%!16 OF 820 WHEN 0:VDU 7 830 OTHERWISE 840 CASE b%!8 OF 850 WHEN 4:PROCnew_icon(b%!0,b%!4) 860 ENDCASE 870 ENDCASE 880 ENDPROC 890 :
Because clicking Adjust on a writable icon will no longer delete it, we've removed the line which called PROCkill_icon. The procedure is still in the !RunImage file, though, so you can still use it if you find a way to modify the program.
PROCwindow_click now starts by checking the icon handle in b%+16 with its first CASE ... OF ... ENDCASE structure to see which icon (if any) was clicked on. Only if this was not icon 0 (the first one we created, with "Click me" in its text) does the routine proceed to the "OTHERWISE" part of the structure. This will happen if the mouse was clicked on another icon and also if it was clicked where there was no icon at all, in which case b%+16 would contain -1.
We could write a procedure which would be called by line 820 if the icon was clicked but, for this simple demonstration, we'll just make the program beep with a VDU 7 command.
Updating IconsLet's finish this section by tying up a loose end. You will remember that we could click on one of the writable icons and change its text, but that the other icons were only redrawn when we scrolled them out of the visible area and back again. What we really need is a way to detect when we press Return, and then redraw all the icons, in the same way as we drew them when we created them.
... you can create more icons ...
We achieve the first part by adding one more line to the Wimp_Poll routine, so that the procedure now looks like this:
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 8:PROCkeypress 340 WHEN 17,18:PROCreceive 350 ENDCASE 360 ENDPROC 370 :
This new version is called page_063 in the files.
When you press a key, the Wimp deals with it, if it can. If it is a character that can be shown on the screen, and the caret is in a writable icon, the Wimp inserts the character into the icon, and into the icon's text buffer. If, however, you press a key such as Return, and the caret is in one of our icons, we get a return from Wimp_Poll with a reason code of 8, meaning that a key has been pressed that the Wimp can't deal with. The ASCII code for the key pressed is in b%+24.
The next procedure deals with this situation:
1270 DEFPROCkeypress 1280 REM processes keypresses in response to Wimp_Poll reason code 8 1290 IF b%!24=13 THEN 1300 SYS "Wimp_WhichIcon",main%,b%+64,1<<23,0 1310 n%=64 1320 !b%=main%:b%!8=0:b%!12=0 1330 WHILE b%!n%<>-1 1340 b%!4=b%!n% 1350 SYS "Wimp_SetIconState",,b% 1360 n%+=4 1370 ENDWHILE 1380 ELSE 1390 SYS "Wimp_ProcessKey",b%!24 1400 ENDIF 1410 ENDPROC 1420 :
When this procedure is called, we first check the code for the key pressed, to make sure it was a Return, and skip the next part if it wasn't.
Line 1300 makes a call to Wimp_WhichIcon. This call makes a list of the handles of all the icons in the window that haven't been deleted, followed by -1, to mark the end of the list. It does this by checking the state of bit 23 of the icon flags, which is set to 1 if the icon has been deleted. Register R0 contains the window handle, R1 the data block for the list, R2 the bits to be checked (just bit 23 in this case) and R3 the bit pattern to match (zero).
As we are going to want part of the data block when redrawing the icons, the list starts at b%+64, well clear of the bit that we'll want to use.
The rest of the procedure goes through the list until it encounters -1, using Wimp_SetIconState to redraw the icons in the same way as we did in PROCnew_icon.
The last part is especially important. Line 1390 handles the situation if the key pressed wasn't Return. If we were to do nothing, any other keypresses that our program did not handle would be lost; the Wimp would ignore them and would not pass them on to other tasks that may wish to handle them. This would mean, for example, that pressing F12 would fail to call the command line for as long as the caret was in a writable icon.
To prevent this from happening, we must pass on the keypress by calling Wimp_ProcessKey with the ASCII code for the key pressed in R0. This should be done for all keypresses that our program does not handle by itself. In other words, if our program ever receives a keypress code but does not want to act on it, it must pass it on via Wimp_ProcessKey.
Now that you have found out how to load a template into your program, feel free to experiment with the template editor, and see what you can do with it.