mailto: blog -at- heyrick -dot- eu

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

FYI! Last read at 18:32 on 2024/11/21.

BBC BASIC goes multitasking - a better way

Templates?

First up, grab a copy of the Utils package from the DrWimp distribution - save the contents of this link. Unpack it on RISC OS.

If you are using a RaspberryPi and have installed RISC OS from an SD image, you will find Doctor Wimp already installed for you at - you can find the Template editors at $.Programming.DrWimp.Utils.

Contained within are two Template editors - TemplEd and WinEd. My preferred is TemplEd, but either are much much better than Acorn's icky FormEd (it is so naff, the modern DDE doesn't even bother to distribute it!).

You may also be interested in looking at Doctor Wimp. It is a library, resources, and tutorials aimed at making it simple to write a proper multitasking application in BASIC. Once you have played with the code that I present for you today, I strongly recommend that you give Doctor Wimp consideration for more advanced projects.

 

Designing a template

Start !TemplEd and then click on its icon bar icon.
Two windows will open.

On the upper left, the window browser. It is initially empty, but when window definitions are created, they will appear in here.

On the upper right, window/icon information. As the mouse passes over icons, information on them will be shown here. You can click on the toggle size icon (the square to the right of the title bar) to display additional information including the size and position of the icon.

 

Click Menu (middle mouse button) over the window browser. Follow the arrow to the right of the Create option, and in the writeable icon that appears, type the name of the window. This is our main window, so we shall call it "main". Type main and then press Return.

The window browser will now look like this:

The yellowish highlight to the window icon indicates that the window is visible on-screen.

Indeed it is. Near the centre of the screen is a new blank window:

Drag the resize icon (lower right) to make the window a bit larger. We can shrink it down to the desired size later.
Then double-click on the window's title bar.

We are going to give the window a better title than "main", so delete the word "main" from the icon (it'll open with the text cursor in the right place for us) and enter "Centigrade to Fahrenheit conversion" instead. Then click on Update & Exit.
Just to clarify - the main window is still called main. We've just changed its visible title.

Now click Menu in the window we're editing, and choose the Create icon... option. This will open the Icon palette:

The palette provides default choices for a variety of different icon types.

Try to "draw a box" (click-drag) around the Label icon, and the bigger white one to its right, like this:

Let go of the mouse button, and both icons should appear sort of inverted (a white background for the label, and a black one for the writeable).

The next step is a little more complex. Hold down Shift and then drag the label icon from the palette window over to the main window that we are editing. You should see the icons appear in the main window.
Don't try to drag from the writeable icon. This responds to clicks and such, so the result may be...unexpected.

 

Click the writeable icon (in the main window) and press the right arrow to move the icon to the right a ways (hold down Shift for larger steps).

Now double-click on the label icon to edit its properties.
Set the text to "Centigrade" and tick R Justified. Then Update/Exit.

You will see that the text does not fit in the icon. So we need to make the icon bigger. Move the pointer to the right side of the icon and then hold down the right mouse button. After a moment, the pointer will indicate that you can now resize the icon by dragging that edge. If you have the icon information window open to display the co-ordinates, resize the icon until it is 200×44.
Then click on the icon and use the cursor keys to move it to the position -18, 6, 206, -62. You'll see what I mean as it moves.

Now resize the writeable icon (from either the left or the right) until its size is 150×56. Then alter it's position until it is lined up with the label, at position -12, 210, 360, -68.

The next step is to prepare the writeable icon. Double-click on it to access the properties, and then set the Text to "-999.99", the max size to 8, and the validation to exactly "A0-9.\-;Kant;Pptr_write". Then Update/Exit.
As I explained yesterday, the A0-9.\- permits only numbers, a full stop, and a minus sign to be entered. The Kant controls the arrows (up/down), Tab behaviour, and to notify the application of all keypresses. Finally, Pptr_write instructs the Wimp to change the mouse pointer to "ptr_write", which is a caret shaped icon, to indicate that this icon accepts typed input.

The main window should look like this:

Here comes some magic. We want to repeat all of the above to set up an icon pair for Fahrenheit. You have two choices. Either rewind and repeat all of that, or carry on reading...

Drag a box around the Centigrade label and its associated writeable icon. When they are both highlighted, press Ctrl D to make a copy downwards (in other words, underneath). Then press the Down cursor twice to leave a little bit of space between the sets of icons.
There. That was simple. All that remains to be done is to change the Centigrade text to say Fahrenheit instead. So call up the properties and do that.

The final icon that we require is the Quit button.
Hold down Shift and then click-drag (in one motion) the Cancel button in the palette. Place this to the right of the Fahrenheit writeable.
The default size is okay, so change the text to say "Quit", and then nudge its position to -76, 390, 518, -128.

The last thing we need to do is to resize the window. Double-click in the window, anywhere that is not part of an icon, to open the window properties. Untick the Back icon, then Update/Exit.
Click Menu over the window and follow the arrow to the right of the Work area entry. Set the window's size (the middle part) to be 530×140.
OK that, and the window will shrink to the desired size. Call up the window properties again and untick Toggle, V Scroll, Adjust, and H Scroll. Click Update & Exit one last time.

In the window browser, click Menu, go down to Save, and then call the file "Templates" and put it in the same place as you put the program code yesterday.

 

Now on with the coding!

Make a copy of yesterday's code. Call it "code2" or something.

Change the memory allocation, from:

DIM wimp% 400 : REM 256 for Wimp blocks, but need more for window def.
to:
DIM wimp% 399 : REM 256 for Wimp blocks, but need more for window def.
DIM icon% 399

This is because the Wimp needs a buffer for loading the window definition and somewhere to put the indirected icon data.
We also change to 399 instead of 400 as this is how you correctly request 400 bytes. BASIC counts from zero, remember, to the request for 400 would actually have allocated 404 bytes.

With glee and extreme prejudice, delete everything between the call to Wimp_Initialise and Wimp_CreateWindow. All that data bodging malarkey? Nuke it. Nuke it all.

Replace it with this:

REM Now get the Wimp to create this window
SYS "Wimp_OpenTemplate",, "<CtoF$Dir>.Templates"
SYS "Wimp_LoadTemplate",, wimp%, icon%, (icon%+400), -1, "main", 0
SYS "Wimp_CreateWindow",, wimp% TO win%
SYS "Wimp_CloseTemplate"

There. Isn't that much simpler than what we did yesterday?

RISC OS works by considering directories prefixed with an '!' to be applications. Upon running the application (double-clicking on it), RISC OS will run the !Run file, which should set up some environment and then run the program proper. As such, we no longer need to worry about programs having bits and pieces of resources lying around. These can all be hidden within the application directory, and the whole lot considered "a program".

As such, go to the top of the program. That stuff about the memory allocation? Get rid of it. It is the job of the !Run file to tell the Wimp how much memory your application requires.

At the bottom of the program, add this function:

DEFPROCSetIconText(wnd%, icn%, txt$)
  wimp%!0 = wnd%
  wimp%!4 = icn%
  SYS "Wimp_GetIconState",, wimp%
  $(wimp%!28) = txt$
  wimp%!0 = wnd%
  wimp%!4 = icn%
  wimp%!8 = 0
  wimp%!12= 0
  SYS "Wimp_SetIconState",, wimp%  
ENDPROC
And we can start to use it by inserting the following after the call to Wimp_OpenWindow:
PROCSetIconText(win%, 1, "0")
PROCSetIconText(win%, 3, "32")

Thing is, if the user wants to enter some data now, they must click the window. This is lame. Let's fix that.

Following on from the above, add this:

SYS "Wimp_SetCaretPosition", win%, 1, 0, 0, -1, 1
This sets the caret in the upper writeable icon, after the first character. We know there is exactly one character (the '0') as we just put it there.

Now go to where the conversions are made, and replace the two bits of code to write and update the other icon each with a call to PROCSetIconText.

You may have noticed that pressing F12 (or ^F12) in our application does nothing. This is not good behaviour, we should kick the hotkeys back to the Wimp in case anything else can act upon them.

Go to the CASE clause for the key pressed event, and just prior to examining wimp%!0 = win%, insert the following line:

              IF (wimp%!24 > 255) THEN SYS "Wimp_ProcessKey", wimp%!24

The function keys (and such) are given codes with bit eight set so they can be distinguished from, for example, Alt-keypresses for high-bit ASCII (accented characters and the like). So we're just passing on every key code greater than 255.

Remember I said the Wimp makes a copy of the window data? Well, it is good form to tidy up after ourselves. Insert the following just before the call to Wimp_CloseDown:

wimp%!0 = win%
SYS "Wimp_DeleteWindow",, wimp%

The last code modification is to fix something annoying. What is 36°C in F? The answer? "96.7999". Stupidly overprecise, don't you think?
Insert the following right at the top after the first REMs but before initialising as a Wimp task:

REM Set precision level
oldat% = @%
@% = "+G0.7"
This sets the "general" format (not exponent, not fixed point), with a field width of zero and up to seven digits to be printed. The + means that this also applies to STR$.
Notice that we saved the initial value in oldat%, so go to the Wimp_CloseDown call and between it and the END statement, insert this:
@% = oldat%

While we're here... did you remember there is a second exit point? It's in the PROCWimpError function. If you have not already done so, add the call to Wimp_DeleteWindow and the code to restore @% here too.

 

Making it an 'app'

We can't run our program yet. If we try, it'll say "Filename '.Templates' not recognised at line ###".

So we must now create an "app" style setup for our program.

Create a new directory. Call it "!CtoF".

Open the directory by double-clicking it while holding Shift.

Copy the Templates file into here.
Copy also the modified program code. Rename it as "!RunImage". You don't have to rename it, but you should, as it is convention that all programs are called that.

Now create the following file in your editor:

Set CtoF$Dir <Obey$Dir>
WimpSlot -min 24K -max 24K
Run <CtoF$Dir>.!RunImage

Save this file as "!Run" into the new directory, and then set its filetype to be Obey.

What the Obey file does:

 

That's it.

While there are more things normally required in an application (some sprites for the app's icon, for instance), this is the minimum required to get an application going.

 

Where to go from here

What I aimed to demonstrate with these two programs was firstly how a single stand-alone piece of BASIC can exist within the multitasking Wimp environment. That was the purpose of the first program, as scary as it was.
We then refined this to work a little better, and using some external resources, to work a little lot simpler.

You can do better.

You can use a library. Akin to their counterparts in C, library functions are prewritten pieces of code that slot in to make complicated stuff easier. Let's face it, the Wimp provides an API that makes the most sense to assembler programmers and its use in BASIC is a huge pile of faffing around with memory and pointers to things. But, wait, why? Why should you? Everybody who programmed the Wimp "back in the day" has their own library of routines to get stuff done. I have my own, based upon a program called "WimpEd", but I'm not going to share any of that - it is quite out of date as I code in C nowadays.
Instead, I will point you at the go-to resource. Unlike my code, this one comes with tutorials and help and all sorts.

Doctor Wimp

Go, take a look at Doctor Wimp.

If you are using a RaspberryPi and have installed RISC OS from an SD image, you will find Doctor Wimp already installed for you at $.Programming.DrWimp.

There are many things that you can do with BASIC on RISC OS. It will never beat the raw power of writing programs in C, though this doesn't mean that it is useless. Think back to TemplEd, that you used to create the Templates file.
That's written in BASIC. Don't take my word for it, open the directory (hold Shift, remember!) and take a look.
Do you have Dave Higton's MTP program to allow you to connect your mobile phone (etc) to RISC OS? More BASIC.
Here's the mind-bending one. PhotoDesk - the RISC OS answer to PhotoShop. It is written in a mixture of BASIC and assembler.

And, finally, perhaps the largest BASIC program I've ever written. My RISC OS teletext software. A full UI pointy-clicky teletext product that communicates with a EuroCCT style teletext receiver and includes a script interpreter for ease of extracting data from teletext.
It's a bit pointless now, since most broadcasters no longer offer or support teletext, which is why the program hasn't been touched in nearly 16 years.
Here's the source code, and here's the assembler source to the helper module, in case you're interested.

 

Full program source (to modified version)

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

REM Set precision level
oldat% = @%
@% = "+G0.7"

REM Initialise ourselves as a Wimp task
DIM wimp% 400 : REM 256 for Wimp blocks, but need more for window def.
DIM icon% 400
task$ = "Temp Conversion"
ON ERROR PROCWimpError : END
SYS "Wimp_Initialise", 200, &4B534154, task$ TO version%, task%

REM Now get the Wimp to create this window
SYS "Wimp_OpenTemplate",, "<CtoF$Dir>.Templates"
SYS "Wimp_LoadTemplate",, wimp%, icon%, (icon%+400), -1, "main", 0
SYS "Wimp_CreateWindow",, wimp% TO win%
SYS "Wimp_CloseTemplate"

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%
xpixel% = ((xres% + 1) << xeig%)
ypixel% = ((yres% + 1) << yeig%)

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

REM Set some defaults
PROCSetIconText(win%, 1, "0")
PROCSetIconText(win%, 3, "32")

REM Set the caret
SYS "Wimp_SetCaretPosition", win%, 1, 0, 0, -1, 1

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.

  CASE event% OF

    WHEN  2 : REM Open window
              REM +0 = Window handle; +4-+24 = Position; +28 = Where in stack
              SYS "Wimp_OpenWindow",, wimp%

    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%!24 > 255) THEN SYS "Wimp_ProcessKey", wimp%!24

              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
                  PROCSetIconText(win%, 3, LEFT$(STR$(degC), 7))
                  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
                  PROCSetIconText(win%, 1,  LEFT$(STR$(degF), 7))
                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

REM If we come here, the app is exiting
wimp%!0 = win%
SYS "Wimp_DeleteWindow",, wimp%
SYS "Wimp_CloseDown", task%, &4B534154
@% = oldat%
END

:
:

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

  wimp%!0 = win%
  SYS "Wimp_DeleteWindow",, wimp%
  SYS "Wimp_CloseDown", task%, &4B534154
  @% = oldat%
  END

ENDPROC
:
DEFPROCSetIconText(wnd%, icn%, txt$)
  wimp%!0 = wnd%
  wimp%!4 = icn%
  SYS "Wimp_GetIconState",, wimp%
  $(wimp%!28) = txt$
  wimp%!0 = wnd%
  wimp%!4 = icn%
  wimp%!8 = 0
  wimp%!12= 0
  SYS "Wimp_SetIconState",, wimp%
ENDPROC

 

Well... Gee... that was a really long and complex way to say "No, you don't use INPUT these days", wasn't it? ☺

Download the software (Zip archive, 4KiB)

 

 

Your comments:

No comments yet...

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

 
Your email (optional):

 
Validation:
Please type 78826 backwards.

 
Your comment:

 

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

Search:

See the rest of HeyRick :-)