Programming for non programmers
Part 9 - Making the program work
David Holden continues his series.
In the last session we got as far as writing a 'shell' program that would start up, open a window on the desktop, and shut down when told to do so by the user. A significant step, but the program couldn't actually do anything useful.
You will notice that if you click the mouse over the top white, writable, icon the caret will appear there and you can type text into the icon. There isn't any code in the program itself to do this, but there doesn't need to be; it is handled entirely by the Wimp. When the window was created this icon was defined as 'writable' and once this has been done it will deal with text entry in that icon, including the standard keypresses for deletion and moving back and forth along the line.
You will recall that I explained that the data (that is, the text) in this icon is indirected, which means that is is held in a block of RAM that the program has allocated. This enables us to both read the text in the icon by examining this indirected data, and to write or modify this text by altering the indirected data.Talking with icons
There are two SWIs which can be used to do this. They are Wimp_GetIconState and Wimp_SetIconState. Both of these use a data block to pass information. Before you can use them you need to know something about the way that icons are defined. As windows are usually designed with a template editor there is normally no need to understand all the intricacies of icon definitions, but if you want to modify an icon then you will have to know a little about it.The Icon Block
An icon, any icon in any window, is defined in just 32 bytes. This is pretty compact, especially when you think of all the widely different types of icons that exist. The way that this 32 bit icon block is set up hasn't really changed since the beginnings of the Acorn Wimp, but the use of indirected data has enabled it to be expanded to incorporate things like the present 3D effects.
An icon data block is made up as follows, the offsets are in bytes from the start of the icon block.
The first four define the size and position of the icon relative to the window.
The icon flags are 32 bits or one 4 byte word of data. Each bit or group of bits defines some aspect of the icon.
To clarify some of these. An Exclusive Selection Group or ESG is a group of icons, normally, but not necessarily round button icons, only one of which can be selected at any one time. These are often called 'radio buttons' after the type of push-button presets used in older radios. When the user pressed a button the previously selected button popped out so that only one was operative. If a group of icons belong to the same ESG then only only one of them can normally be selected at once, usually to allow the user to choose between a number of mutually exclusive options.
The foreground and background colours of an icon are defined in 4 bits, which means a number from 0 to 15. As you might expect, these correspond to the 16 standard Wimp colours.
The icon button types (bits 12 to 15) define what, if anything, happens when the user clicks on an icon. At this stage you don't need to know exactly what these are as they would normally be set when the icon is defined in the template editor.
The part of the icon block we are most interested in at this stage is the icon data in the three words at bytes 20 to 31. What these mean depends upon the setting of three of the icon flags, bit 8 (icon data is indirected), bit 1 (icon is a sprite) and bit 0 (icon contains text). This gives us a total of 8 possible different combinations and so 8 possible different meanings for the three icon data words.
If the icon data is not indirected then in practice it can be only a text or sprite icon. If it's a sprite then the icon data holds the name of the sprite. If it's text, then it holds the text. As there are only 12 bytes of icon data the sprite name or text can not be longer than 12 characters. This isn't a problem with a sprite name, they can't be longer than this anyway, but text might need to be a lot bigger, which is why text is usually indirected.
With three bits setting the options there are 8 possible combinations of indirected icon data. However, only three of these are actually practical, text only, sprite only, or text plus sprite. In each case the meaning of the three words at +20, +24 and +28 is shown.
With a sprite only icon the sprite may be a Wimp sprite, that is, a sprite which is in the Wimp spritepool such as an application or filetype sprite or some other sprite which has been added using the iconsprites command. Alternatively it can be a sprite which exists in specially set up block of RAM in the program's own workspace and so known only to that particular program. More about this later in the series.
The validation string was originally used to enable the programmer to restrict what characters could be typed in a writable icon. When 3D icons were introduced with RISC OS 3 its use was expanded to enable an icon to be defined with one of the standard 3D icons. There are also various other options that can be set in a validation string, such as changes in mouse pointer when over the icon, what happens when you press the RETURN key, etc.Reading icon text
If you have read the preceding section you will be able to work out that an icon will be defined like this;
Now with an indirected text only icon we also know that the word at +20 is actually a pointer to the text string. If we can find out what this pointer is we can use it to read the text which has been typed into the icon. This is where Wimp_GetIconState comes in. This is called with a pointer to a data block in R1. Before the SWI is called the data block should be set up with the window handle of the window the icon is in in the first word and the icon handle or icon number in the second word. When the call returns these two words are unchanged but after them, staring at block+8, is a copy of the 32 byte icon definition.
We can therefore extract the pointer from this and use it to read the text. As the icon definition begins 8 bytes from the start of the data block the pointer will be the word at block+28. With this information we can now write a function to read the text in any text only or text plus sprite indirected icon.
DEFFNget_icon_text(win%,icn%) LOCAL p% !block% = win% block%!4 = icn% SYS "Wimp_GetIconState",,block% p%=block%!28 WHILE ?p%>31 : p%+=1 : ENDWHILE ?p%=13 = $(block%!28)
The two parameters passed to the function are the window handle and icon handle and it returns a string containing the text.
The syntax of the last line of the function can be a bit confusing. Remember that the word at block%+28 is not the text but a pointer to the text. So $(block%!28) can be interpreted as;
The string which can be found at the address held in the word at block%+28. Another way to write this, though using an extra line of code and an additional variable would be -
pointer% = block%!28 = $(pointer%)
You need to make sure you understand just how this sort of 'data pointer' works as this method is used a lot with data blocks and it is easy to confuse the data itself with pointers to data with disastrous results.
Although the text pointed to is normally terminated with a CR the specification, as with most other strings passed to and from the Wimp, says that it should be 'control character terminated'. This means any character with an ASCII value of 31 or less. To convert this into a Basic string, as is done on the final line of the function, it must be terminated with a CR character, ASCII 13. This function therefore creates a LOCAL variable, p%. This is set to the address of the start of the string (block%!28) and then scans the string looking for the terminating character and substituting a CR.
If you are sure that the string in your icon will always be CR terminated then you could dispense with this, which would make the function slightly faster. In this case it would become.
DEFFNget_icon_text(win%,icn%) !block% = win% block%!4 = icn% SYS "Wimp_GetIconState",,block% = $(block%!28)
If you use this shorter function and the string is not CR terminated then your program will crash.Altering an icon
As you might imagine, if you want to change the characteristics of an icon, including any text in it, you can use Wimp_GetIconState to find out where it is and then alter it. There's one slight problem with this; although the icon definition has been changed the Wimp won't yet change the display on screen. We therefore need to use Wimp_SetIconState as this will force the Wimp to redraw the icon.
Like Wimp_GetIconState this SWI uses a data block to set the parameters that you want to alter and is called with a pointer to the block in R1. This block is set up as follows
The third and forth words in this block require some explanation. As this SWI is primarily intended for setting the icon flags these two words perform bitwise manipulation of the icon flags. What this means will be described later in the series, but if both these values are set to zero then it effectively means that the icon flags aren't changed. Calling Wimp_SetIconState with block+8 and block+12 both set to zero will force a redraw of the icon but without any changes to the icon flags, but, since the icon is redrawn, any other changes made to other parts of the icon definition will also be reflected, for example, if you have changed the indirected text.Writing text into an icon
So, to alter or set the text in an indirected icon we have to perform three steps.
We can do all of this in single procedure.
DEFPROCset_icon_text(win%,icn%,text$) !block% = win% block%!4 = icn% SYS "Wimp_GetIconState",,block% $(block%!28) = text$ block%!8 = 0 block%!12 = 0 SYS "Wimp_SetIconState",,block% ENDPROC
This procedure takes three parameters, the window and icon handles and the text we want to put in the icon.
It begins with a call to Wimp_GetIconState. This gives us the address of the indirected icon text. This time we don't want to read the text, just replace it with the new text, text$, which is 'poked' into the address held in the word at block%+28. There follows a call to Wimp_SetIconState which makes the Wimp redraw the icon to show the new text.
One very important point. When creating a window the maximum size of any indirected text has to be specified and enough indirected space is allocated for this. When the user types text into a writable icon they can't enter more than the maximum specified amount, the Wimp just ignores any excess characters. However, when your program writes a string to the indirected workspace there are no such checks. It is therefore up to the programmer to make sure that any text written using the system shown above does not exceed the allocated space. If it does it will overflow into other workspace, perhaps, if it overwrites a validation string, altering the appearance of the icon, or part of the text may appear in another icon, or in extreme cases it might overwrite some other program data and crash the program.Putting it together
In the program we wrote in the last session we learned how to open a window on screen and make the program respond to a mouse click on a button. Now that we can read and write text to and from icons it should be straightforward to make this program 'work' properly. The program will just sit in its 'idle' state until the user clicks on either the 'Add' or 'Subtract' button. It must then perform three operations.
Before moving on to the full program code here is an illustration of the window, showing the icons whose numbers we are interested in.
Adding the additional functions and procedures to the previous program we get -
REM Program to add or subtract VAT ON ERROR ERROR EXT 0,REPORT$+"at line "+STR$(ERL):PROCdie PROCsetup PROCopen_window(main_handle%) REPEAT SYS "Wimp_Poll",0,blk% TO event% CASE event% OF WHEN 2:SYS "Wimp_OpenWindow",,blk% WHEN 3:SYS "Wimp_CloseWindow",,blk% WHEN 6:PROCmouse_click(blk%!12,blk%!16) WHEN 17,18:PROCmessage_received(blk%!16) ENDCASE UNTIL FALSE END DEFPROCmouse_click(win%,icon%) IF win%<>main_handle% THEN ENDPROC CASE icon% OF WHEN 4:PROCdie WHEN 5:PROCadd_vat WHEN 6:PROCsubtract_vat ENDCASE ENDPROC DEFPROCadd_vat LOCAL a$,cash a$=FNget_icon_text(main_handle%,1) cash=VAL(a$) cash=cash*1.175 a$=STR$(cash) PROCset_icon_text(main_handle%,2,a$) ENDPROC DEFPROCsubtract_vat LOCAL a$,cash a$=FNget_icon_text(main_handle%,1) cash=VAL(a$) cash=cash/1.175 a$=STR$(cash) PROCset_icon_text(main_handle%,2,a$) ENDPROC DEFPROCmessage_received(type%) IF type%=0 THEN PROCdie ENDPROC DEFPROCdie SYS "Wimp_CloseDown",task_handle%,&4B534154 ENDPROC DEFPROCopen_window(handle%) !tmp%=handle% SYS "Wimp_GetWindowInfo",,tmp% tmp%!28=-1 SYS "Wimp_OpenWindow",,tmp% ENDPROC DEFPROCset_icon_text(win%,icn%,text$) !tmp%=win% tmp%!4 = icn% SYS "Wimp_GetIconState",,tmp% $(tmp%!28)=text$ tmp%!8 = 0 tmp%!12 = 0 SYS "Wimp_SetIconState",,tmp% ENDPROC DEFFNget_icon_text(win%,icn%) LOCAL p% !tmp%=win% tmp%!4=icn% SYS "Wimp_GetIconState",,tmp% p%=tmp%!28 WHILE ?p%>31:p%+=1:ENDWHILE ?p%=13 =$(tmp%!28) DEFPROCsetup DIM tmp% 1024, blk% 255, indirected% 512 SYS "Wimp_Initialise",200,&4B534154,"VAT Calculator" TO ,task_handle% SYS "Wimp_OpenTemplate",,"
The program now contains four new sections; PROCset_icon_text and FNget_icon_text as just described to write and read the text in an icon. There are also the procedures to do the actual work, PROCadd_vat and PROCsubtract_vat. These are invoked from PROCmouse_click when the user clicks on button icons 5 or 6 respectively. (You may recall that in the earlier version of the program these were left blank in anticipation of the additional routines being added later).
PROCadd_vat and PROCsubtract_vat work in the same way. They create a couple of LOCAL variables that are required for their internal workings. They then use FNget_icon_text to read the text in icon number 1 into the LOCAL variable a$. This is then converted to a real or floating point number 'cash' using Basic's VAL function. It is then either multiplied or divided by 1.175 to calculate the plus or ex VAT amount, which is converted to a string using the Basic STR$ function. This string is then written to the 'result' icon (number 2) using PROCset_icon_text.
Just one final observation. PROCadd_vat and PROCsubtract_vat are actually more complex than they really need to be. This has been done to make it easier to understand how they work. Because of Basic's ability to use expressions in place of numbers these could actually be written as a single line of code. For example, the whole of PROCadd_vat could be condensed to -
Rather more cryptic than the original, but it works exactly the same. It might be a useful exercise for you to 'break down' this line of code and decipher exactly how it works.
In the next session we'll add a few more refinements to the program to make it a bit more 'user friendly'.