mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 05:18 on 2024/11/24.

BBC BASIC goes multitasking

Précis

My friend, Mick, has expressed a desire to get himself a Raspberry Pi (perhaps Santa brought him one yesterday?) and he would like to muck around programming it.

Now, he could load up RISC OS Pico (or press F12 from the Desktop and type *BASIC) and then do stuff like this:

But this doesn't sit well within the multitasking world.

So what we are going to do, for the lulz, is to create a completely self-contained program that looks like this:

Now, there's an easy way and a hard way to do this. The easy way to to design a template file, much as you would design the layout of a window in VisualBasic, and load that. The hard way to to create the window definitions from the ground up.
Since I said "self-contained program", and a template file is something else, I guess this means that it'll be the hard way.

This is, I should point out, something of an exercise in self-flagellation. Any sane person would use templates. Just so you're aware of that...

 

Just to reiterate very clearly

This is NOT how you should write a program under RISC OS. This is an experiment to show that an old-style simple BASIC program can be written.
Do not write programs like this. There are better, easier, ways.

 

What's a SWI?

SWI stands for "SoftWare Interrupt". It is conceptually similar to an INT call in DOS/Windows. If you are used to the BBC Micro and the likes of OSWORD and OSWRCH; a SWI is an enhanced version of this. Akin to the x86 INT, instead of jumping to specific addresses, you instead load the registers in a way applicable to the function you want, and then you call the SWI with the number of the desired function.

To give an example, we are going to print a winking smiley face to the standard output in assembler for the BBC MOS, for an x86 DOS system, and finally using SWIs under RISC OS.

First, by calling OSWRCH on the BBC MOS:

LDA #59     \ Load accumulator with ASCII code for ';'
JSR &FFEE   \ Call OSWRCH to output character
LDA #45     \ ASCII code for '-'
JSR &FFEE
LDA #41     \ ASCII code for ')'
JSR &FFEE

Now, an example using DOS INT calls. Here I'm using the DOS INT 21h call (instead of the BIOS 10h call):

mov dl, ';'  ; load character code for ';'
mov ah, 2    ; select function 2 (write output to screen)
int 21h      ; call interrupt
mov dl, '-'  ; character code for '-'
mov ah, 2
int 21h
mov dl, ')'  ; character code for ')'
mov ah, 2
int 21h

And here's the RISC OS SWI version:

MOV  R0, #':'
SWI  &0         ; OS_WriteC
MOV  R0, #'-'
SWI  &0
MOV  R0, #')'
SWI  &0
The objasm assembler will assemble that. BASIC isn't quite so smart, so will need to be told the ASCII code to use. You can use ASC(";")...

 

Having now compared SWIs to OS calls on other systems, it is time to briefly look at why SWIs are so important.

RISC OS is described as a "modular" operating system. That is, there is a kernel and above that lie a veritable array of "modules". There are over a hundred in a basic RISC OS system. A module is a small independent piece of code that "does something". I know it is a vague description, however it needs to be vague to encompass the scope of what a module is. The Desktop environment is a collection of modules, the most important of which is the WindowManager. The filing system is also a collection of modules, such as FileCore (low level stuff), FileSwitch (higher level filing system), SDFS (the SD card filing system), Filer (the generic UI interface), blah blah blah. You may know that I have an OLED attached to my Pi. This is controlled by way of an OLED module that I wrote. Likewise, my computer talks to my (musical) keyboard via the MIDI module that I wrote. When you run the Ovation Desktop Publisher package, additional keypresses are available because there's a module that deals with this.

It is not necessary for a module to provide SWIs, however many of them do as it is how they work, and some of them can provide dozens of SWIs. A module can (usually) offer up to 64 SWIs, and the WindowManager offers 62 of them. Calls to start up and close down tasks; to create, show, and destroy windows and icons, to handle menus, to handle the "polling" system, to... the list goes on.

You will need to consult the Programmer's Reference Manuals for information on the facilities available.

When I say "out of date", the PRMs were originally written for RISC OS 3.10 (circa 1992), and updated (with an additional volume) to cover RISC OS 3.5 (circa 1995ish). Acorn made two further RISC OS releases, RISC OS Ltd made a slew of RISC OS 4/Select/Adjust releases, and the currently maintained branch of RISC OS is RISC OS 5 running in 32 bit mode on modern hardware that bears exactly zero relationship to the stuff described in the PRMs.
It is woeful that the API documentation hasn't sensibly kept step with the developments of the OS, but there go. It, maybe 2000 odd pages of documentation, has relevance as a lot of the API calls are the same, but there are a great many caveats to be aware of. On a modern machine, you handle the PSR entirely differently to that which is stated in the PRMs. You don't expect flags to be preserved, you can't push bits into PC to set flags, high-bit-set addresses may be valid addresses, the list goes on.
Even for those flaws, the PRMs (in any version you like) remain the primary resource for dealing with RISC OS on its own terms, as opposed to using some sort of pre-baked library.

 

Calling SWIs from BASIC

The basic form of calling SWIs from BASIC is:
  SYS "<swi>" <reglist> TO <reglist>

Confusing? Let's break it down.

First, the SWI. This may be provided as a number in the number form of your choice that is supported by BASIC. Alternatively, in BASIC, you can provide the SWI's name and BASIC will translate that on the fly for you.
Therefore, the following are identical:

  SYS &0C, ...
  SYS 12, ...
  SYS %1100, ...
  SYS "OS_GBPB", ...

The register list is a comma separated list of values to insert into the registers. You can set R0-R9. If you leave a value 'empty', zero will be assumed. If you only need to write a couple of registers, you can omit the rest (zero will be assumed for the omitted ones).
Examples:

  a%, b%, , c%    - R0 is a%, R1 is b%, R2 is zero, R3 is c%, R4-R9 are zero.
  ,,,,,a%         - R5 is a%, all the rest are zero.
  a%              - R0 is a%, all the rest are zero.

If you expect to have data returned to you, you should use TO followed by a register list. Just like the above, registers will be copied into the specified registers.
Examples:

  a%, b%, , c%    - a% is R0, b% is R1, c% is R3.
  ,,,,,a%         - a% is R5.
  a%              - a% is R0.

The returned registers are optional, and are only necessary if you are expecting some sort of response.
Examples:

  SYS "OS_WriteC", 65               - outputs 'A' (doesn't return anything)
  SYS "OS_ReadMonotonicTime" TO t%  - sets t% to value of centisecond ticker

But - wait! If there is an error, it'll be raised and BASIC's error handler will be invoked. Can this be dealt with?
Actually, yes. You need to set bit 17 of the SWI number. You can do this by adding &20000 to the number, or if calling a SWI by name, by prefixing it with 'X', like "XOS_WriteC".
Then, add "; f%" to the end of the TO part of the SWI call.

Like this:

  SYS "XOS_WriteC", 65 TO ; f%
The information written to f% are a subset of the processor flags. Bit 0 is the oVerflow flag, bit 1 is the Carry flag, bit 2 is the Zero flag, and bit 3 is he Negative flag. Or %NZCV as a binary value.

If an error occurred, RISC OS by convention sets the oVerflow flag. So if bit zero is set (IF (f% AND 1)), then an error has occurred. Usually in this case, R0 will be a pointer to an error block, which is a word giving the error number, followed by a null terminated string giving the error message.

 

Wimp tasks and polling

A multitasking program (hereafter called a "task" or an "application") must be started. Then, windows loaded or defined, and so on.

When the task is ready to interact with the user, it enters what is called the polling loop. The call to Wimp_Poll serves two functions.
The first function is that you are indicating that your task is able to be mapped out of memory. You see, the clue to how the whole shebang works should have come when I said earlier that every task believes that it is based at address &8000. Obviously this is not possible, everything can't be at the same address. What really happens is that one task is mapped in at that address, and all of the other tasks are in limbo (or maybe purgatory). The Wimp provides lots of smoke and mirrors and by constantly messing with the memory mapping, every task just thinks that it is located at that address.
When you make the call to Wimp_Poll, your task is frozen and mapped out. Any number of other tasks will then have a chance to run using the same mechanism. You should not make any assumptions about what happens between calling the SWI and the call returning. If you tell the Wimp that you don't want Null events (which is wise if you don't need them), the duration of time elapsed may be from seconds to days. If your task is on the icon bar but the user isn't using it, or has gone to bed, then the Wimp is not going to bother you when nothing has happened.
When the Wimp_Poll call returns to you, it means that something has happened that requires attention. A window is to be opened, closed, or redrawn. The user clicked a button. The pointer left a window. A menu option was chosen. That sort of thing.

The method of multitasking described is cooperative multitasking. You should realise that this involves some degree of collusion from each task in order to keep the system running. If there is complicated stuff to be done, it should be broken into steps and polling performed regularly.
By contrast, while Windows and Linux (etc) software has an event processing loop similar to the polling loop, the actual method of multitasking is pre-emptive. This means that the system passes control to a task and lets it do what it wants to do. After an elapsed amount of time (depending on the system), the system will yank control away from the task (the task has no say in this) and gives control to another task.
Entire thesis and a billion blog articles have been written about the attributes of CMT s PMT. The major downside of CMT is that a crashed application risks freezing the machine (at least, until the user does something to remove the crashed task). The upside of CMT is that the application is in control of how much time it claims. While some applications are greedy and block the system while chundering (I'm looking at you, ChangeFSI), the flip side of the coin is that an application that only needs to do something simple can do it quickly and then kick control back to the Wimp to let another task run. There's no need to sit in a fixed timeslice when the time isn't required.

Both approaches have their good points and bad points. I'd say that a well designed CMT system will easily beat a badly designed PMT system; however to make effective use of a CMT system, all tasks need to be well behaved and written with CMT in mind. You can't just "crunch a million numbers and let the OS swap me in and out", as it won't.

 

On y va!

Those of you who remember David Tennant's Doctor may remember he was fond of "Allons y!", which means "Let's go!". The version I have given as the section title is similar, and can mean the same thing, but as an interjection means "Here we go!".
So... Here we go!

 

REM >code
REM
REM This is intended as an example of a wholly self-contained
REM multitasking application written in BASIC.
REM There are better ways to do this, but this method requires
REM nothing whatsoever other than this program.
REM
REM © 2015 Rick Murray
REM http://www.heyrick.co.uk/blog/index.html?diary=20151226
REM
REM For Mick.
REM
A program should start by describing what it is.

REM REM Constrain our memory usage, we only need 24K
HIMEM = ((32+23) * 1024) : REM Set Himem to 32K (base) plus 23K
SYS "Wimp_SlotSize", (24 * 1024), -1 : REM Set slot to 24K
REM SlotSize takes into account apps are based at &8000
REM HIMEM does not, thus explaining the difference. We leave
REM a 1K gap just to be on the safe side...
Normally a program is run using an Obey file, which is a sort of script of *commands. One of these commands is invariably the WimpSlot command, which constrains the required amount of memory. If you do not use this call, you will receive whatever the "Next" slot is set to (it is usually 640KiB).
As we are clipping the end off of workspace, we first need to fiddle with HIMEM so BASIC no longer stores data in memory that we are going to be discarding. This process is a little bit confusing as all programs believe that they are based at address &8000. BASIC claims some private workspace for the program, then sets PAGE to &8F00.
As a factor of this, when you are setting BASIC's memory address variables, you must add in the missing 32KiB (&0 - &8000). This space may or may not be OS workspace. It isn't for you to make any assumptions, you should pretend that the first 32KiB of memory doesn't exist.
The Wimp_SlotSize command, our first SWI call, is aware of this behaviour, so you only need to say that you want 24KiB.

 

REM Initialise ourselves as a Wimp task
DIM wimp% 400 : REM 256 for Wimp blocks, but need more for window def.
task$ = "Temp Conversion"
ON ERROR PROCWimpError : END
SYS "Wimp_Initialise", 200, &4B534154, task$ TO version%, task%
The data block required for polling is 256 bytes; however since we are first using this bit of memory for our window definition, we claim 400 bytes so it is big enough for that.
Next, we set the name that our task will be given in the Task Manager.
Then we set up a better error handler, as the default (printing the error) won't work in the Desktop.
Finally, the magic. We call the SWI to initialise ourselves as a task.
The first register (R0) specifies WindowManager v2.00 or later (RISC OS 2).
The next register (R1) is the value of "TASK", a dumb ugly hack that Acorn put in place to tell a proper multitasking task apart from the pretty-UI-but-single-tasking method of Arthur. Given the short lifespan of Arthur, it is sad that we're saddled with rubbish like this because Acorn's API sucks, but you'll get used to it. Some parts of the Wimp API suck harder than the telling of it (example? check out what values can be passed in R0 and what effects that will have - goodness what an awful mess).
The last register (R2) is our task name. Notice that we're passing a string to the SWI. BASIC will null terminate it and provide the expected pointer to it for you. It's just easier that way.

In return, the Wimp returns two pieces of information. The first (in R0) is the version of the WindowManager, which is similar to (but not the same as) the OS version.
The second register (R1) is our task's handle. We need to remember this.

 

What follows now is the window definition. It is scary-crazy. Remember I said sane people use templates? This is a small window with five icons. 'nuff said!
The comments explain what each piece of data is, but this is perhaps best read in tandem with documentation. Feel free to skip over this part and just know it's an example of the rubbish involved in 'creating' a window.

REM Create the main window; refer to PRM 3-87.
wimp%!0  = 0        : REM Visible min X
wimp%!4  = 0        : REM Visible min Y
wimp%!8  = 530      : REM Visible max X
wimp%!12 = 140      : REM Visible max Y
wimp%!16 = 0        : REM Scroll offset X
wimp%!20 = 0        : REM Scroll offset Y
wimp%!24 = -1       : REM Open at top
wimp%!28 = %10000110000000000011000000010010 : REM Window flags
REM Window has Close icon and title bar; must stay on screen;
REM passes hotkeys to app; can be redrawn by the Wimp; moveable
wimp%!32 = &01070207: REM Work back/fore, Title back/fore colours
wimp%!36 = &000C0103: REM Input focus, scroll inner/outer colours
wimp%!40 = 0        : REM Work area min X
wimp%!44 = -140     : REM Work area min Y
wimp%!48 = 530      : REM Work area max X
wimp%!52 = 0        : REM Work area max Y
wimp%!56 = &27 << 24: REM Title colours
wimp%!56+= %100111101:REM Title bar flags
REM Data is indirected, is filled, centred H+V, border, is text
wimp%!60 = 0        : REM Window button type is Never
wimp%!64 = 1        : REM Use Wimp sprite area
wimp%!68 = 0        : REM Min. width/height of window (0 = Title width)
wimp%!72 = wimp%+256: REM Pointer to title text
wimp%!76 = -1       : REM Pointer to validation string (-1 = None)
wimp%!80 = 36       : REM Length of text buffer
wimp%!84 = 5        : REM Number of icons that follow
$(wimp%+256) = "Centigrade to Fahrenheit conversion"+CHR$(0) : REM Title text

REM Create the icons (each icon block is 32 bytes), refer to PRM 3-93.
REM Icon 0 is a label that says "Centigrade".
REM Icon 1 is a text box associated to Centigrade, accepting numerical input.
REM Icon 2 is a label that says "Fahrenheit".
REM Icon 3 is a text box associated to Fahrenheit, accepting numerical input.
REM Icon 4 is a button marked "Quit".
REM The layout is:
REM   .--------------------------------. < Y zero is here, decreasing downwards
REM   | Centigrade  [textbox]          | < so this label is -18 (top) to -62
REM   | Fahrenheit  [textbox]   [Quit] | < and this one is -78 to -122
REM   '--------------------------------'
REM   ^ X zero is here, increasing to left
REM
REM Data allocations...
REM     +0 = Window data
REM    +88 = Icon data (5 x 32)
REM   +256 = Window title text (36 bytes)
REM   +294 = "Centigrade" text (11 bytes)
REM   +306 = "Fahrenheit" text (11 bytes)
REM   +318 = Indirected text for "Centigrade" writeable (8 bytes)
REM   +326 = Indirected text for "Fahrenheit" writeable (8 bytes)
REM   +334 = Indirected control data for C writeable (24 bytes)
REM   +358 = Indirected control data for F writeable (24 bytes)
REM   +382 = Indirected text for "Quit" button (5 bytes)
REM   +394 = Indirected control data for Quit button (5 bytes)
REM   =400 = End of data.

icon% = wimp% + 88  : REM Set base of icon data for icon zero
icon%!0  = 6        : REM Min X bounding (left)
icon%!4  = -62      : REM Min Y bounding (bottom)
icon%!8  = 206      : REM Max X bounding (right)
icon%!12 = -18      : REM Top
icon%!16 = (&17<<24): REM Icon bg/fg colours
icon%!16+= %1100010001:REMFlags: Right aligned, Indirected, Vcentred, Text.
icon%!20 = wimp%+294: REM Pointer to text
icon%!24 = -1       : REM Pointer to indirected data (-1 for none)
icon%!28 = 11       : REM Length of buffer
$(wimp%+294) = "Centigrade"+CHR$(0) : REM Icon text

icon% = icon% + 32  : REM Set base of icon data for icon one
icon%!0  = 210      : REM Min X bounding (left)
icon%!4  = -68      : REM Min Y bounding (bottom)
icon%!8  = 360      : REM Max X bounding (right)
icon%!12 = -12      : REM Top
icon%!16 = (&07<<24): REM Icon bg/fg colours
icon%!16+= (15<<12) : REM Button type is writeable
icon%!16+= %100111101:REMFlags: Indirected, filled, H/Vcentred, border, text
icon%!20 = wimp%+318: REM Pointer to text
icon%!24 = wimp%+334: REM Pointer to indirected data
icon%!28 = 8        : REM Length of buffer
$(wimp%+318) = ""+CHR$(0) : REM Icon text (none)
$(wimp%+334) = "A0-9.\-;Kant;Pptr_write"+CHR$(0)
REM Allow: digits 0-9, period, and minus.
REM Key: Up/down to next icon; Notify of ALL keys; Tab to next icon
REM Pointer: Use "ptr_write" (caret icon) as pointer over this icon

icon% = icon% + 32  : REM Set base of icon data for icon two
icon%!0  = 6        : REM Mostly the same as icon zero.
icon%!4  = -122
icon%!8  = 206
icon%!12 = -78
icon%!16 = (&17<<24)
icon%!16+= %1100010001
icon%!20 = wimp%+306
icon%!24 = -1
icon%!28 = 11
$(wimp%+306) = "Fahrenheit"+CHR$(0) : REM Icon text

icon% = icon% + 32  : REM Set base of icon data for icon three
icon%!0  = 210      : REM Mostly the same as icon one
icon%!4  = -128     : REM Min Y bounding (bottom)
icon%!8  = 360      : REM Max X bounding (right)
icon%!12 = -72      : REM Top
icon%!16 = (&07<<24): REM Icon bg/fg colours
icon%!16+= (15<<12) : REM Button type is writeable
icon%!16+= %100111101:REMFlags: Indirected, filled, H/Vcentred, border, text
icon%!20 = wimp%+326: REM Pointer to text
icon%!24 = wimp%+358: REM Pointer to indirected data (uses same as before)
icon%!28 = 8        : REM Length of buffer
$(wimp%+326) = ""+CHR$(0) : REM Icon text (none)
$(wimp%+358) = "A0-9.\-;Kant;Pptr_write"+CHR$(0) : REM No, we can't dupe it...

icon% = icon% + 32  : REM Set base of icon data for icon four
icon%!0  = 390
icon%!4  = -128
icon%!8  = 518
icon%!12 = -76
icon%!16 = (&17<<24): REM Icon bg/fg colours
icon%!16+= (3<<12) : REM Button type is click
icon%!16+= %100111101:REMFlags: Indirected, filled, H/Vcentred, border, text
icon%!20 = wimp%+382: REM Pointer to text
icon%!24 = wimp%+394: REM Pointer to indirected data (uses same as before)
icon%!28 = 5        : REM Length of buffer
$(wimp%+382) = "Quit"+CHR$(0) : REM Icon text
$(wimp%+394) = "R5,3"+CHR$(0) : REM Action button, highlights to colour 3
Note that the indirected data (+318 to +400) is memory in use by the application. While the Wimp makes its own copy of your window data, the indirected data must be preserved.

Okay, you can open your eyes now. ☺

 

REM Now get the Wimp to create this window
SYS "Wimp_CreateWindow",, wimp% TO win%
A simple call. Pass the obscene mess of data in R1, receive a handle to the created window in R0. The Wimp has made its own copy of the window data, you don't need to preserve this, only the indirected data.

What we're going to do now is to open the window in the centre of the screen. So first, we must read the resolution of the screen (which is provided as pixels counting from zero) and the eigen values of the resolution.

REM Work out the dimensions of the screen
SYS "OS_ReadModeVariable", -1,  4 TO ,,xeig%
SYS "OS_ReadModeVariable", -1,  5 TO ,,yeig%
SYS "OS_ReadModeVariable", -1, 11 TO ,,xres%
SYS "OS_ReadModeVariable", -1, 12 TO ,,yres%
The video display does not work with pixels. It works with "OS units", which is where the eigen values come in. In the old days, you had square pixel modes for high resolution (as in "VGA"!) monitors, and rectangular pixel modes for TV style monitors. These days, practically nobody uses rectangular pixel modes, but we should still query the eigen values and shift accordingly.

xpixel% = ((xres% + 1) << xeig%)
ypixel% = ((yres% + 1) << yeig%)
The results are not pixels, they're OS units. I just call them "pixel" because "osunit" is a mouthful.

 

REM Tell the Wimp to open the window CENTRED
!wimp% = win%
SYS "Wimp_GetWindowState",, wimp%
xpos% = (wimp%!12 - wimp%!4)
ypos% = (wimp%!16 - wimp%!8)
wimp%!4  = ((xpixel% - xpos%) / 2)
wimp%!8  = ((ypixel% - ypos%) / 2)
wimp%!12 = ((xpixel% + xpos%) / 2)
wimp%!16 = ((ypixel% + ypos%) / 2)
wimp%!28 = -1 : REM Open on top
SYS "Wimp_OpenWindow",, wimp% : REM PRM 3-109
The SWI call GetWindowState reads the 'state' of a window, the important part for us are the offsets from +4 to +16 which provide the window's position, plus +28 which tells us where the window is in the window stack. So we work out how big the window is, then we fudge its position so it is centred on the screen.
The data block is, conveniently, the same as we need to give to the OpenWindow SWI, that makes the window appear on the screen.

 

Now we are ready to enter the polling loop. We set the "poll mask" to %11100000110001 - which means we're passing the following flags (the ones in bold):

bit 0 Don't return Null events
bit 1 Don't return Redraw events, queue them
bit 4 Don't return Pointer Leaving Window events
bit 5 Don't return Redraw events, queue them
bit 6 Don't return Mouse Click events, queue them
bit 8 Don't return Key Pressed events, queue them
bit 11 Don't return Lose Caret events
bit 12 Don't return Gain Caret queue them
bit 13 Don't return Pollword Nonzero events
bit 17 Don't return User Message events
bit 18 Don't return User Message Recorded events
bit 19 Don't return User Message Acknowledge events
bit 22 R3 is pointer to pollword
bit 23 Scan pollword at high priority
bit 24 Save floating point state
(all other bits should be zero)

As said, we just call the Poll SWI, and the Wimp will page our task out, and return to us later on when there is something for us to do.

REM The polling loop
finished% = FALSE
mask% = %11100000110001
REM Don't return PollWord, Lose/Gain caret, pointer entering/leaving, nullpoll

REPEAT
  SYS "Wimp_Poll", mask%, wimp% TO event% : REM PRM3-112

  REM This "yields" to another application. The Wimp swaps us out and another
  REM task in, and repeats this lots of times as necessary for the functioning
  REM of the Desktop.
  REM When the Wimp gets back to us, it means something has happened and we
  REM should deal with it.

As the comment says, when the Wimp gets back to us, event% contains the event code of an event for us to deal with, and wimp% is updated accordingly.

There are 19 events, which are:

0 Null Nothing happened.
1 Redraw Window A (part of a) window needs to be redrawn.
2 Open Window Size changed or window scrolled.
3 Close Window Window's close icon clicked
4 Pointer Leaving Window The pointer has left a window's visible area.
5 Pointer Entering Window The pointer has entered a window's visible area.
6 Mouse Click Something has been clicked.
7 Drag Box A drag operation has finished.
8 Key Pressed There's a keypress to deal with.
9 Menu Selection A menu entry has been chosen.
10 Scroll RRequest When scrollbar clicked (not dragged).
11 Lose Caret When another window claims the caret (and input focus).
12 Gain Caret When you claim the caret (and input focus).
13 Pollword Nonzero The specified pollword has become nonzero.
14 - not used
15 - not used
16 - not used
17 User Message A message (from the Wimp or another task) has been received.
18 User Message Recorded A recorded delivery message has been received.
19 User Message Acknowledge Sent by the Wimp if recorded message not acknowledged.

Some notes on the above:

Here's the handling of the events we're interested in:

  CASE event% OF

    WHEN  3 : REM Close window (closing it ends the app)
              REM +0 = Window handle (but since we only have one window...)
              finished% = TRUE

    WHEN  6 : REM Mouse click (clicking on Quit ends the app)
              REM +8 = Button clicked; +12 = Window handle; +16 = Icon handle
              IF (wimp%!12 = win%) THEN
                IF (wimp%!16 = 4) THEN finished% = TRUE
              ENDIF

    WHEN  8 : REM Key press
              REM +0 = Window handle, +4 = Icon handle; +24 = Charcode of key

              IF (wimp%!0 = win%) THEN
                IF (wimp%!4 = 1) THEN
                  REM Keypress in the Centigrade icon
                  REM Read what is currently in that icon
                  SYS "Wimp_GetIconState",, wimp%
                  degC = VAL($(wimp%!28))
                  REM Perform the calculation
                  degC = degC * 1.8 : REM 9/5
                  degC = degC + 32
                  REM Write the result to the other icon
                  wimp%!0 = win%
                  wimp%!4 = 3 : REM F icon
                  SYS "Wimp_GetIconState",, wimp%
                  $(wimp%!28) = LEFT$(STR$(degC), 7) : REM Clip to 7 chars max
                  REM Force the icon to be redrawn
                  wimp%!0 = win%
                  wimp%!4 = 3
                  wimp%!8 = 0 : wimp%!12 = 0
                  SYS "Wimp_SetIconState",, wimp%
                  wimp%!4 = 0 : REM So following code isn't triggered! ;-)
                ENDIF

                IF (wimp%!4 = 3) THEN
                  REM Keypress in the Fahrenheit icon
                  SYS "Wimp_GetIconState",, wimp%
                  degF = VAL($(wimp%!28))
                  degF = degF - 32
                  degF = degF * 0.5556 : REM 5/9
                  wimp%!0 = win%
                  wimp%!4 = 1 : REM C icon
                  SYS "Wimp_GetIconState",, wimp%
                  $(wimp%!28) = LEFT$(STR$(degF), 7) : REM Clip to 7 chars max
                  wimp%!0 = win%
                  wimp%!4 = 1
                  wimp%!8 = 0 : wimp%!12 = 0
                  SYS "Wimp_SetIconState",, wimp%
                ENDIF
              ENDIF

    WHEN 17 : REM Message request
              REM +16 = Message code; 0 means Quit
              IF (wimp%!16 = 0) THEN finished% = TRUE

  ENDCASE
UNTIL finished% = TRUE
Briefly - an AppQuit message will quit the task. Clicking on the Quit icon will quit the task. Closing the window will quit the task.
Otherwise, if the user presses a key in the Centigrade icon, calculate a Fahrenheit value and write it to the other icon. Likewise, if the user presses a key in the Fahrenheit icon, calculate the Centigrade value and display it.
We don't need to sanitise the input to the VAL function as the icon has been set to only allow '-', '.', and numbers.

If we fall out of the REPEAT ... UNTIL loop, that means we're quitting. So do that.

REM If we come here, the app is exiting
SYS "Wimp_CloseDown", task%, &4B534154
END

The final thing? Convert the error message into a Desktop style error box. This error box, many people will tell you it has been "deprecated", as it effectively locks the Wimp from doing anything else while it is on-screen. They'll tell you that you should use a multitasking error box so the Wimp can continue while your task is peeing itself. The problem is, no such thing exists. You need to write it yourself. And while it is possible, it is also interesting coding an error mechanism that needs to poll and also needs to be able to handle polling of other parts of the task that may or may not be in a useful state. RISC OS doesn't do "zombie tasks" like Windows, either our task polls, or it doesn't. If it does, it needs to try to sensibly handle whatever the user might want to do. We could ignore window activity entirely, but stuff like that gets people stressy. However, it is an awful lot of code that doesn't make sense in the context of an example application. So stuff the detractors, I'm using ReportError and that's that.

:
:

DEFPROCWimpError
  REM If there is an error, report it nicely. Then die.
  ON ERROR OFF
  wimp%!0 = ERR
  $(wimp% + 4) = REPORT$+" at line "+STR$(ERL)
  SYS "Wimp_ReportError", wimp%, 1, task$ TO , response%

  SYS "Wimp_CloseDown", task%, &4B534154
  END

ENDPROC

 

We have arrived!

Okay then. One simple (!) BASIC program to convert C to F or vice versa as a Desktop application. It is, by necessity, larger. But it benefits from existing outside of the world of single tasking full screen applications that use PRINT and INPUT as their interface. Those ideas belong to the eighties. And this isn't the eighties, as can be clearly determined by noticing the tame hair on women and the lack of fuzzy jumpers on men, not to mention an awful lot of crap in the Top 40 - how many times does Justin Bieber feature on that list? Good God. At the time I'm writing this: 1, 2, 4, 17, 29 (featured), 30, 37, 39, and 40. I don't get it. Or maybe I don't get him. Nearly a quarter of the Top 40 is bloody Bieber. WTF? This makes me feel like I want to grab my Erasure EP cassette and hunt around the house to see if I have anything capable of playing it. I could rip the album off YouTube, but actual spinning tape is more nostalgic.

 

Where's the code? Show me the code! SHOW ME THE CODE!

Download the program code (10.7KiB)

The program is tokenised BASIC. Download on a RISC OS machine, then set its type to &FFB. You can edit tokenised BASIC programs in Edit, Zap, or StrongEd.

 

And tomorrow...

Tomorrow - I'll present you with a smaller better version. It'll use templates (no no scary data block nonsense), and it'll do more, because the example above is the minimum necessary to get the job done.

 

 

Your comments:

VinceH, 26th December 2015, 20:49
"Any sane person would use templates." 
 
Would they? ;)

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 22675 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)