Copyright: Version 1.0
Citation: OPPDev, Simon Berridge 1997


OPP Development System

Welcome to the OPP Development system which was designed to complement Andy Clarkson's OPP Psion OPL pre-compiler.

If you are new to this development system then please read the Start Here topic. Then read the Setup section.

If you are new to the OPP system then you should thoroughly read the OPP manual supplied with OPP.

If you require specifc help then press your 'Find' button or have a look at the Index.

Importing an Existing Project into OPPDev.

Click here for a full definition of the OPL keywords.


Start Here!

This section assumes that you have a working knowledge of both OPL for the Psion Series 5 as well as a working knowledge of OPP.

The OPPDev system is a PC extension for OPP and does a lot of the coding work required for the OPP pre-compiler. Certain aspects of the development process are in-built to OPPDev and these are described within this section.

OPPDev Concepts

*	All projects that you create within OPPDev will be stored in a sub-directory of their own. This sub-directory will be created by OPPDev and will have the same name as the project itself. The sub-directory will follow the OPP convention and be created below the 'Include' sub-directory which is below the main OPP directory.

If you create a new project called 'TEST01' then OPPDev will create a sub-directory called 'TEST01' below the 'Include' sub-directory below the 'OPP' directory. Below is a picture of the directory structure on my own PC and shows the two projects that I use, namely '5XPense' and 'Xpenses'.

*	Within the new sub-directory a number of files will be created by OPPDev and these files form the basis of any new project.

The files that you will see are as follows:

*	CONSTS.OPH

*	GLOBALS.OPH

*	MAIN.OPH

*	STRUCTS.OPH

*	TEST01.OPP

In addition to these files you will see a MAIN.HED file, which will be explained in the next section.

CONSTS.OPH

This file is used by OPPDev to maintain a list of the CONSTants that you wish to use within your project. This file is included in the **.OPP file. See Adding a Constant.

GLOBALS.OPH

This file is used by OPPDev to maintain a list of the GLOBAL variables that you wish to use within your project. This file is included in the MAIN.OPH file immediately below the PROC main: line. See Adding a Global Variable.

STRUCTS.OPH

This file is used by OPPDev to maintain a list of the STRUCTures that you wish to use within your project. This file is included in the **.OPP file. See Adding a Structure.

TEST01.OPP

This file is created and maintained byOPPDev, you will not need to modify or edit this file in any way. Basically it has a list of #include directives for OPP and ensures that the right files are submitted to the compiler in the right order and that the procedure main: is the first procedure called.

*	Each OPH file created or added to the project has a corresponding .HED file, which stands for 'Header File'. This is the case for all the OPH files except CONSTS.OPH, GLOBALS.OPH and STRUCTS.OPH.

The header file is used within the project to control the 'real' procedure name(s) that are created. This means that you can utilise very long procedure names and they will be translated by OPP to a very short name for submission to the OPL compiler.

An example would be as follows. I have added a new procedure to my source code and the source code looks like this:

PROC ThisIsALongProcedureName:

ENDP

In the HED file will be the following line:

#define ThisIsALongProcedureName pr1

This means that every time OPP finds the name ThisIsALongProcedureName it will replace it with pr1. This will optimise the resultant compiled program as well as making a REVTRAN virtually unreadable or, at the very least, extremely difficult to follow.

The header file is maintained by OPPDev and you do not have access to the source code.


OPPDEV Index

OPPDev Desktop

Button Bar	OPL Keywords

Directories	Advanced Topics

Menu Wizard	Series 5 Database

Setting Up OPPDev	Source Code Window

Importing an Existing Project into OPPDev


Menu Wizard

This feature allows the user to create a new menu structure for the Series 5.

In essence, the user will dynamically create a menu on-screen by adding and removing elements to the menu structure displayed on the left hand side of the window. A representation of the window is shown below.

When the window is activated the new menu structure is initialised to a blank window. There are three buttons on the right hand side of the window that allow you to add a main (Top Level) menu item, add a sub-item and delete items. All the results of the user input are displayed graphically within the left hand window.

To create a menu that looks like this

File

  Open

  Save

  New

  More

    Save As

Help

  Contents

the user would press the Add Main button and enter the text 'File'. Then click on the 'File' icon in the left hand window and then press the 'Add Sub' button. In here the user has to enter both the text for the sub-menu item as well as its hot key, in the normal Psion form of '%?' where '?' represents the upper or lower case letter.

When adding the 'More' item use a setting of 16 for the code. Then select the 'More' icon in the left hand window and click on Add Sub again. The result of this will be to create a menu below the More menu item which, in turn, is subservient to the File main menu item.

Clicking on the OK button will generate the OPL program code necessary to operate the menu and place it in the source code at the cursor position.

The above example would generate the following OPL code:

mINITmCASC "More","Save As",%a

mCARD "File","Open",%o,"Save",%s,"New",%n,"More>",16

mCARD "Help","Contents",%c


Directories

The directories option is used to tell OPPDev where certain necessary programs reside and to which directory the output file is sent.

The two directories are where the OPP program is held and where the C: drive for the Series 5 emulator is. Both of these fields are defaulted to C:\OPP for the OPP directory and C:\EPOC32\WINS\C for the Series 5 emulator C: drive. If you have installed the software in different places then either change the defaults by overtyping their directories or by pressing the speedbuttons next to the corresponding fields. These speedbuttons will open a directory list box in order that you may navigate to the necessary directory.


Setting up OPPDev

See also: Directories

If you followed the setting up operation within the readme.txt file supplied with OPPDev then you will have the OPPDev.exe and OPPDev.hlp files residing within the directory structure C:\OPPDev\. This section will assume that setup.

The only thing that is necessary for you to undertake is to point OPPDev at the two directories it needs to generate OPL code, that is the EPOC32 Series 5 drive C directory on your PC and the directory of the OPP program.

Select File, then Directories.

Select or type the full paths of the directories required and press the OK button. From now on you are ready to start your first project.


Andy Clarkson

Andy Clarkson is the author of OPP, which OPPDev compliments.

He is available to answer questions and receive registrations for his programs (Plan (3a/3c/S5), OPP and OPPDbg).

His email address is andyc@compuserve.com

Andy has his own home page on http://ourworld.compuserve.com/homepages/andyc


OPPDev Desktop

Development Worktop

Apart from the top control panel OPPDev has up to three separate windows that the developer can work with at any one time.

When a project is loaded there are two default windows; the code editor and the project control window. The third window, the procedure translator, can be opened by clicking on the procedure mapping button within the code editor.

The project control window and the code editor windows work together. The project control window displays a list of the oph files that make up the whole project. It displays the full path name and file name of each file within the project. Double clicking on any of the files within the project control window will bring up the source code associated with that file in the code editor.

The code editor has a number of separate elements contained within it.

The main part of the window is the actual code editor. This is a simple edit control where you can write your source code. Tabbing within the source code should be undertaken by using the space bar rather than embedding tabs within the source code. Pressing the enter key at the end of each line of code will move the cursor onto a new line and indented to the start of the previous line. This is an automatic function of OPPDev.

At the top of the code editor window is a drop-down list control that shows the names of the procedures within the program file. Dropping down the list and selecting a procedure name will find the start of that procedure within the file and highlight it.

On the right hand side of the window is a list of the OPL keywords that are available for the Series 5 Psion. This is a full list of the available keywords. These keywords can be embedded within your source code by placing the cursor where you want the keyword to appear and then double clicking on the OPL keyword. Help on the keywords can be obtained by clicking on the keyword, then right clicking the mouse. This will open a small menu with two items; Help and Insert. Selecting Help will open help for the keyword, whilst clicking on Insert functions the same way as double clicking on the keyword itself; the keyword is embedded within the source code.


Adding a CONSTant


Adding a Global Variable


Adding a STRUCT


Button Bar

OPPDev's menu options area available by clicking on one of the Speed Buttons displayed on the button bar.

The button bar buttons are shown here:

You can access the help for the button bar by pressing on the button for which you want help. The order of the buttons is as follows:

New Project

Open Existing Project

Add OPH file to Project

Remove OPH file from Project

Add a Constant Value

Add a Global Value

Add a new OPH file to the Project

Add a Structure

Dialog Wizard


New Project

Press this button to create a new project.

A dialog box will be displayed that asks for the name of the new project. It is suggested that you use a name not exceeding 8 characters in length. Note that no extension should be entered as the extensions are managed by OPPDev.

The result of this dialog box is to create a new sub-directory beneath C:\OPP\INCLUDE. If you chose a new project name like 'TEST01' then a new sub-directory will be created as C:\OPP\INCLUDE\TEST01.

In addition, the New Project option will create the base files for the project. These files are

*	CONSTS.OPH

*	GLOBALS.OPH

*	MAIN.OPH

*	MAIN.HED (unseen by OPPDev)

*	STRUCTS.OPH

*	TEST01.OPP


Open Project


Add OPH file to Project


Remove OPH file from Project


Add a new OPH file to Project


Dialog Wizard


Exit System


OPL Keywords

ABS

ACOS

ADDR

ADJUSTALLOC

ALERT

ALLOC

APP

APPEND

APPENDSPRITE

ASC

ASIN

AT

ATAN

BACK

BEEP

BEGINTRANS

BOOKMARK

BREAK

BUSY

BYREF

CACHE

CACHEHDR

CACHEREC

CACHETIDY

CALL

CANCEL

CAPTION

CHANGESPRITE

CHR$

CLEARFLAGS

CLOSE

CLOSESPRITE

CLS

CMD$

COMMITTRANS

COMPACT

COMPRESS

CONTINUE

CONST

COPY

COS

COUNT

CREATE

CREATESPRITE

CURSOR

DATETOSECS

DATIM$

DAY

DAYNAME$

DAYS

DAYSTODATE

dBUTTONS

dCHECKBOX

dCHOICE

dDATE

DECLARE EXTERNAL

DECLARE OPX

dEDIT

dEDITMULTI

DEFAULTWIN

DEG

DELETE

DELETE

dFILE

dFLOAT

DIALOG

DIAMINIT

DIAMPOS

dINIT

DIR$

dLONG

DO...UNTIL

DOW

dPOSITION

DRAWSPRITE

dTEXT

dTIME

dXINPUT

EDIT

TRAP EDIT

ELSE/ELSEIF/ENDIF

ENDA

ENDV

ENDWH

ENTERSEND

ENTERSEND0

EOF

ERASE

ERR

ERR$

ERRX$

ESCAPE OFF

EVAL

EXIST

EXP

EXT

FIND

FINDFIELD

FINDLIB

FIRST

FIX$

FLAGS

FLT

FONT

FREEALLOC

gAT

gBORDER

gBOX

gBUTTON

gCIRCLE

gCLOCK

gCLOSE

gCLS

gCOLOR

gCOPY

gCREATE

gCREATEBIT

gDRAWOBJECT

gELLIPSE

GEN$

GET

GET$

GETCMD$

GETDOC$

GETEVENT

GETEVENT32

GETEVENTA32

GETEVENTC

GETLIBH

gFILL

gFONT

gGMODE

gGREY

gHEIGHT

gIDENTITY

gINFO

gINFO32

gINVERT

GIPRINT

gLINEBY

gLINETO

gLOADBIT

gLOADFONT

GLOBAL

gMOVE

gORDER

gORIGINX

gORIGINY

GOTO

GOTOMARK

gPATT

gPEEKLINE

gPOLY

gPRINT

gPRINTB

gPRINTCLIP

gRANK

gSAVEBIT

gSCROLL

gSETPENWIDTH

gSETWIN

gSTYLE

gTMODE

gTWIDTH

gUNLOADFONT

gUPDATE

gUSE

gVISIBLE

gWIDTH

gX

gXBORDER

gXPRINT

gY

HEX$

HOUR

IABS

ICON

IF...ENDIF

INCLUDE

INPUT

TRAP INPUT

INSERT

INT

INTF

INTRANS

KEY

KEY$

KEYA

KEYC

KILLMARK

KMOD

LAST

LCLOSE

LEFT$

LEN

LENALLOC

LINKLIB

LN

LOADLIB

LOADM

LOC

LOCAL

LOCK

LOG

LOPEN

LOWER$

LPRINT

MAX

mCARD

mCASC

MEAN

MENU

MID$

MIN

mINIT

MINUTE

MKDIR

MODIFY

MONTH

MONTH$

mPOPUP

NEWOBJ

NEWOBJH

NEXT

NUM$

ODBINFO

OFF

ONERR

OPEN

OPENR

OS

PARSE$

PATH

PAUSE

PEEK functions

PI

POINTERFILTER

POKE commands

POS

POSITION

POSSPRITE

PRINT

PUT

RAD

RAISE

TRAP RAISE

RANDOMIZE

REALLOC

RECSIZE

REM

RENAME

REPT$

RETURN

RIGHT$

ROLLBACK

RMDIR

RND

SCI$

SCREEN

SCREENINFO

SECOND

SECSTODATE

SEND

SETDOC

SETFLAGS

SETNAME

SETPATH

SIN

SPACE

SQR

STATUSWIN

STATWININFO

STD

STOP

STYLE

SUM

TAN

TESTEVENT

TRAP

UADD

UNLOADLIB

UNLOADM

UNTIL

UPDATE

UPPER$

USE

USR

USR$

USUB

VAL

VAR

VECTOR

WEEK

WHILE...ENDWH

YEAR


ABS

Usage:a=ABS(x)

Returns the absolute value of a floating-point number that is, without any +/- sign for example ABS(-10.099) is 10.099

If x is an integer, you won't get an error, but the result will be converted to floating-point for example ABS(-6) is 6.0. Use IABS to return the absolute value as a long integer.


ACOS

Usage:a=ACOS(x)

Returns the arc cosine, or inverse cosine (COS-1) of x.

x must be in the range -1 to +1. The number returned will be an angle in radians. To convert the angle to degrees, use the DEG function.


ADDR

Usage:

a&=ADDR(variable)

Returns the address at which variable is stored in memory.

The values of different types of variables are stored in bytes starting at ADDR(variable). See PEEK for details.

The maximum address is guaranteed to be less than 64K on the Series 3c, while it is not on the Series 5. The return type therefore must be a long integer on the Series 5, but may be an integer on the Series 3c.

See SETFLAGS if you require the 64K limit to be enforced on the Series 5. If the flag is set to restrict the limit, a& is guaranteed to fit into an integer.

See UADD, USUB.


ADJUSTALLOC

Usage:

pcelln&=ADJUSTALLOC(pcell&,off&,am&)

Opens or closes a gap at off& (off%) within the allocated cell pcell& (pcell%), returning the new cell address or zero if out of memory. off& (off%) is 0 for the first byte in the cell. Opens a gap if the amount am& (am%) is positive, and closes it if negative.

The number of bytes allocated is restricted to 64K on the Series 3c, while it is not on the Series 5. The return type therefore must be a long integer on the Series 5, but may be an integer on the Series 3c.

Cells are allocated lengths that are the smallest multiple of four greater than the size requested. An error will be raised if the cell address argument is not in the range known by the heap.

See also SETFLAGS if you require the 64K limit to be enforced on the Series 5. If the flag is set to restrict the limit, pcelln& is guaranteed to fit into an integer.

See ALLOC.


ALERT

Usage:any of

    r%=ALERT(m1$,m2$,b1$,b2$,b3$)

    r%=ALERT(m1$,m2$,b1$,b2$)

    r%=ALERT(m1$,m2$,b1$)

    r%=ALERT(m1$,m2$)

    r%=ALERT(m1$)

Presents an alert - a simple dialog - with the messages and keys specified, and waits for a response. m1$ is the message to be displayed on the first line, and m2$ on the second line. If m2$ is not supplied or if it is a null string, the second message line is left blank.

Up to three keys may be used. b1$, b2$ and b3$ are the strings (usually words) to use over the keys. b1$ appears over an Esc key, b2$ over Enter, and b3$ over Space. This means you can have Esc, or Esc and Enter, or Esc, Enter and Space keys. If no key strings are supplied, the word CONTINUE is used above an Esc key.

The key number 1 for Esc, 2 for Enter or 3 for Space is returned.

Constants for these return values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.


ALLOC

Usage:

pcell&=ALLOC(size&)

pcell%=ALLOC(size%)

Allocates a cell on the heap of the specified size, returning the pointer to the cell or zero if there is not enough memory.

The number of bytes allocated is restricted to 64K on the Series 3c, while it is not on the Series 5. The return type therefore must be a long integer on the Series 5, but may be an integer on the Series 3c.

Cells are allocated lengths that are the smallest multiple of four greater than the size requested. An error will be raised if the cell address argument is not in the range known by the heap.

See also SETFLAGS if you require the 64K limit to be enforced. If the flag is set to restrict the limit, pcelln& is guaranteed to fit into an integer.

See ADJUSTALLOC, REALLOC, FREEALLOC. See also the 'Advanced Topics' chapter.


APP

Usage:

APP caption,uid&

...

ENDA

APP caption

...

ENDA

Begins definition of an OPL application. caption is the application's name (or caption) in the machine's default language.  Note that although caption is a string, it is not enclosed in quotes.

uid& is the application's UID. For distributed applications, official reserved UIDs must be used.  These can be obtained by contacting Psion Software plc.


APPEND

Usage:APPEND

Adds a new record to the end of the current data file. The record which was current is unaffected. The new record, the last in the file, becomes the current record.

The record added is made from the current values of the field variables A.field1$, A.field2$, and so on, of the current data file. If a field has not been assigned a value, zero will be assigned to it if it is a numeric field, or a null string if it is a string field.

Example:

PROC add:

  OPEN "address",A,f1$,f2$,f3$

  PRINT "ADD NEW RECORD"

  PRINT "Enter name:",

  INPUT A.f1$

  PRINT "Enter street:",

  INPUT A.f2$

  PRINT "Enter town:",

  INPUT A.f3$

  APPEND

  CLOSE

ENDP

To overwrite the current record with new field values, use UPDATE.

On the Series 5, INSERT, PUT and CANCEL should be used in preference to APPEND and UPDATE, although APPEND and UPDATE are still supported for Series 3c compatibility. However, note that APPEND can generate a lot of extra (intermediate) erased records. COMPACT should be used to remove them, or alternatively use SETFLAGS to set auto-compaction on.


APPENDSPRITE

Usage:APPENDSPRITE time%,bit$(), dx%,dy%

or APPENDSPRITE time%,bit$()

Appends a single bitmap-set to the current sprite.

time% gives the duration in tenths of seconds for the bitmap-set to be displayed before going on to the next bitmap-set in the sequence.

bit$() contains the names of bitmap files in the set, or "" to specify no bitmap. The array must have at least 6 elements:

bit$(1) for setting black pixels

bit$(2) for clearing black pixels

bit$(3) for inverting black pixels

bit$(4) for setting grey pixels

bit$(5) for clearing grey pixels

bit$(6) for inverting grey pixels

All the bitmaps in a single bitmap-set must be the same size or %Invalid argument' error (-2) is raised on attempting to draw the sprite. Bitmaps in different bitmap-sets may differ in size.

dx% and dy%, if supplied, are the (x,y) offsets from the sprite position to the top-left of this bitmap-set, with positive for right and down. The default value of each is zero.

On the Series 5, sprites are handled by the built-in Sprite OPX. See the 'Using OPXs on the Series 5' chapter for more details.


ASC

Usage:a%=ASC(a$)

Returns the character code of the first character of a$.

For the Series 5, see Appendix D for the character set and for the Series 3c, see the character set in the back of the User Guide for the character codes. Alternatively, use A%=%char to find the code for char - e.g. %X for %X'.

If a$ is a null string ("") ASC returns the value 0.

Example: A%=ASC("hello") returns 104, the code for h.


ASIN

Usage:a=ASIN(x)

Returns the arc sine, or inverse sine (SIN-1) of x.

x must be in the range -1 to +1. The number returned will be an angle in radians. To convert the angle to degrees, use the DEG function.


AT

Usage:AT x%,y%

Positions the cursor at x% characters across the text window and y% rows down. AT 1,1 always moves to the top left corner of the window. Initially, the window is the full size of the screen, but you can change its size and position with the SCREEN command.

A common use of AT is to display strings at particular positions in the text window. For example:

    AT 5,2 :PRINT "message".

PRINT statements without an AT display at the left edge of the window on the line below the last PRINT statement (unless you use , or ;) and strings displayed at the top of the window eventually scroll off as more strings are displayed at the bottom of the window.

Displayed strings always overwrite anything that is on the screen - they do not cause things below them on the screen to scroll down.

Example:

PROC records:

LOCAL k%

  OPEN "clients",A,name$,tel$

  DO

    CLS

    AT 1,7

    PRINT "Press a key to"

    PRINT "step to next record"

    PRINT "or Q to quit"

    AT 2,3 :PRINT A.name$

    AT 2,4 :PRINT A.tel$

    NEXT

    IF EOF

      AT 1,6 :PRINT "EndOfFile"

      FIRST

    ENDIF

    k%=GET

  UNTIL k%=%Q OR k%=%q

  CLOSE

ENDP


ATAN

Usage:a=ATAN(x)

Returns the arc tangent, or inverse tangent (TAN-1) of x.

The number returned will be an angle in radians. To convert the angle to degrees, use the DEG function.


BACK

Usage:BACK

Makes the previous record in the current data file the current record.

If the current record is the first record in the file, then the current record does not change.


BEEP

Usage:BEEP time%,pitch%

Sounds the buzzer. The beep lasts for time%/32 seconds so for a beep a second long make time%=32, etc. The maximum is 3840 (2 minutes).

The pitch (frequency) of the beep is 512/(pitch%+1) KHz.

BEEP 5,300 gives a comfortably pitched beep.

If you make time% negative, BEEP first checks whether the sound system is in use (perhaps by another OPL program), and returns if it is. Otherwise, BEEP waits until the sound system is free.

Example a scale from middle C:

PROC scale:

LOCAL freq,n%REM n% relative to middle A

  n%=3 REM start at middle C

  WHILE n%&lt;16

    freq=440*2**(n%/12.0) REM middle A = freq 440Hz

    BEEP 8,512000/freq-1.0

    n%=n%+1

    IF n%=4 OR n%=6 OR n%=9 OR n%=11 OR n%=13

      n%=n%+1

    ENDIF

  ENDWH

ENDP

Alternatively, sound the buzzer with this statement: PRINT CHR$(7). This beeps at a fixed pitch for a fixed length of time.

Alternatively, sound the buzzer with this statement: PRINT CHR$(7). This produces a click sound.

Note that on the Series 5 if your batteries are low BEEP may not produce the desired effect: the buzzer will be used instead to produce the %beep'. The buzzer produces a higher pitched sound.


BEGINTRANS

Usage: BEGINTRANS

Begins a transaction on the current database. The purpose of this is to allow changes to a database to be committed in stages. Once a transaction has been started on a view (or table) then all database keywords will function as usual, but the changes to that view will not be made until COMMITTRANS is used.

See also COMMITTRANS, ROLLBACK, INTRANS.


BOOKMARK

Usage: b%=BOOKMARK

Puts a bookmark at the current record of the current database view. The value returned can be used in GOTOMARK to make the record current again. Use KILLMARK to delete the bookmark.


BREAK

Usage:BREAK

Makes a program performing a DO...UNTIL or WHILE...ENDWH loop exit the loop and immediately execute the line following the UNTIL or ENDWH statement.


BUSY

Usage:any of

    BUSY str$,c%,delay%

    BUSY str$,c%

    BUSY str$

    BUSY OFF

BUSY str$ displays str$ in the bottom left of the screen, until BUSY OFF is called. Use this to indicate %Busy' messages, usually when an OPL program is going to be unresponsive to keypresses for a while.

If c% is given, it controls the corner in which the message appears:

0 top left

1 bottom left (default)

2 top right

3 bottom right

Constants for these corner values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

delay% specifies a delay time (in half seconds) before the message should be shown. Use this to prevent %Busy' messages from continually appearing very briefly on the screen.

Only one message can be shown at a time. The maximum string length of a BUSY message is 80 characters on the Series 5, and an %Invalid argument' error is returned for any value in excess of this.  On the Series 3c, the maximum length is 19 characters.


BYREF

Usage:BYREF variable

Passes variable by reference to an OPX procedure when used in a procedure argument list.  This means that the value of the variable may be changed by the procedure.

See the 'Using OPXs on the Series 5' chapter for more details.


CACHE

Usage:any of

    CACHE init%,max%

    CACHE ON

    CACHE OFF

CACHE creates a procedure cache of a specified initial number of bytes init% which may grow up to the maximum size max%. You should usually TRAP this.

Once a cache has been created, CACHE OFF prevents further cacheing, although the cache is still searched when calling subsequent procedures. CACHE ON may then be used to re-enable cacheing.

Series 5 procedures are automatically cached, so this command is not required.


CACHEHDR

Usage:CACHEHDR addr(hdr%())

Read the current cache index header into array hdr%(), which must have at least 11 integer elements.

See the 'Advanced Topics' chapter for more details.

Series 5 procedures are automatically cached, so this command is not required.


CACHEREC

Usage:CACHEREC addr(rec%()),off%

Read the cache index record at offset off% into array rec%(), which must have at least 18 integer elements.

See the 'Advanced Topics' chapter for more details.

Series 5 procedures are automatically cached, so this command is not required.


CACHETIDY

Usage:CACHETIDY

Remove from the cache any procedures that have returned to their callers.

Series 5 procedures are automatically cached, so this command is not required.


CALL

Usage:e%=CALL(s%,bx%,cx%,dx%,si%,di%)

This function enables you to make Operating System calls. To use it requires extensive knowledge of the Operating System and related programming techniques. The syntax of this command is included here for completeness only.

The INT number itself is the least significant byte of s%. The AH value (the subfunction number) is the most significant byte of s%. The values of the other arguments are passed to the corresponding 8086 registers. The value of the AX register is returned.

The Series 5 supports calls to the operating system using OPXs.  Full description of their design is beyond the scope of this manual and is documented instead in the EPOC32 C++ Software Development Kit (SDK) which is available from Psion Software plc. See the 'Using OPXs on the Series 5' chapter for details of built-in OPXs.


CANCEL

Usage: CANCEL

Marks the end of a database's INSERT or MODIFY phase and discards the changes made during that phase.


CAPTION

Usage:CAPTION caption$,languageCode%

Specifies an OPA's public name (or caption) for a particular language, which will appear below its icon on the Extras bar and in the list of %Programs' in the %New File' dialog (assuming the setting of FLAGS allows these) when the language is that used by the machine. CAPTION may only be used inside an APP...ENDA construct.

The language code specifies for which language variant the caption should be used, so that the caption need not be changed when used on a different language machine. If used, for whatever language, CAPTION causes the default caption given in the APP declaration to be discarded. Therefore CAPTION statements must be supplied for every language in which the application is liable to be used, including the language of the machine on which the application is originally developed.

The values of the language code should be one of the following:

English	1

French	2

German	3

Spanish	4

Italian	5

Swedish	6

Danish	7

Norwegian	8

Finnish	9

American	10

Swiss-French	11

Swiss-German	12

Portuguese	13

Turkish	14

Icelandic	15

Russian	16

Hungarian	17

Dutch	18

Belgian-Flemish	19

Australian	20

Belgian-French	21

Austrian	22

New Zealand	23

International French	24

Constants for the language codes are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

The maximum length of caption$ is 255 characters. However, you should bear in mind that a caption longer than around 8 characters will not fit neatly below the application's icon on the Extras bar.

See APP. See also the 'OPL applications' section in the 'Advanced Topics' chapter.


CHANGESPRITE

Usage:CHANGESPRITE ix%,time%,bit$(),dx%,dy%

or CHANGESPRITE ix%,time%,bit$()

Changes the bitmap-set specified by ix% (1 for the first bitmap-set) in the current sprite, using the supplied bitmap files, offsets and duration in the same way as for APPENDSPRITE.

On the Series 5, sprites are handled by a built-in OPX. See the 'Using OPXs on the Series 5' chapter for more details.


CHR$

Usage:a$=CHR$(x%)

Returns the character with character code x%.

You can use it to display characters not easily available from the keyboard. For example, the instruction PRINT CHR$(133) displays an ellipsis (...).

The full character set for the Series 5 is in Appendix D. For the Series 3c, see the User Guide.


CLEARFLAGS

Usage: CLEARFLAGS flags&

Clears the flags given in flags& if they have previously been set by SETFLAGS, returning to the default.

See SETFLAGS.


CLOSE

Usage:CLOSE

Closes the current view on a database. If there are no other views open on the database then the database itself will be closed. See SETFLAGS for details of how to set auto-compaction on closing files.

Closes the current file (that is, the one which has been OPENed and most recently USEd).

If you've used ERASE to remove some records, CLOSE recovers the memory used by the deleted records, provided it is held either in the internal memory, on a memory disk (on the Series 5) or on a RAM SSD (on the Series 3c).


CLOSESPRITE

Usage:CLOSESPRITE id%

Closes the sprite with ID id%.

On the Series 5, sprites are handled by a built-in OPX. See the 'Using OPXs on the Series 5' chapter for more details.


CLS

Usage:CLS

Clears the contents of the text window.

The cursor then goes to the beginning of the top line. If you have used CURSOR OFF the cursor is still positioned there, but is not displayed.


CMD$

Usage:c$=CMD$(x%)

Returns the command-line arguments passed when starting a program. Null strings may be returned. x% should be from 1 to 3 for the Series 5 and may be up to 5 on the Series 3c. CMD$(2) to CMD$(5) are only for OPL applications.

CMD$(1) returns the full path name used to start the running program.

CMD$(2) returns the full path name of the file to be used by an OPL application. On the Series 5, if the CMD$(3)="R" (see below), a default filename, including path, is passed in CMD$(2).

CMD$(3) returns "C" for "Create file" or "O" for "Open file" and may also return "R" on the Series 5. If the OPL application is being run with a new filename, this will return "C". This happens the very first time the OPL application is used, and whenever a new filename is used to run it. Otherwise, the OPA is being run with the name of an existing file, and CMD$(3) will return "O" if it is selected directly from the system screen. "R" (Series 5 only) is returned if your application has been run from the Program editor or has been selected via the Application's icon on the Extras bar, and not by the selection or creation of one of your documents from the system screen.

Constants for the array elements (1-3) and for the return values of CMD$(3) are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

CMD$(4) returns the alias information, if any. In practice this has no relevance for OPL applications.

CMD$(5) returns the application name, as declared with the APP keyword.

See the 'Advanced Topics' chapter for more details of OPL applications.

See also GETCMD$.


COMMITTRANS

Usage: COMMITTRANS

Commits the transaction on the current view.

See also BEGINTRANS, ROLLBACK, INTRANS.


COMPACT

Usage:COMPACT file$

Compacts the database file$, rewriting the file in place. All views on the database and the hence the file itself should be closed before calling this command. This should not be done to often since it uses considerable processor power.

Compaction can also be done automatically on closing a file by setting the appropriate flag using SETFLAGS.


COMPRESS

Usage:COMPRESS src$,dest$

Copies data file src$ to another data file dest$. If dest$ already exists, the records in src$ are appended to the end of dest$.

Deleted records are not copied. This makes COMPRESS particularly useful when copying from a Flash SSD. (The space used by deleted records on a RAM SSD or in internal memory is automatically freed when you close the file.)

If you want src$ to overwrite instead of append to dest$, use, TRAP DELETE dest$ before the COMPRESS statement.

You can use wildcards if you wish to copy more than one file at a time, but if the first name contains any wildcards, the second name must not include a filename, just the device and directory to which the files are to be copied under their original names.

Example: to copy all the data files on A: (in \OPD, the default directory) to B:\BCK\:

    COMPRESS "A:*.ODB","B:\BCK\"

(Remember the final backslash on the directory name.)

See COPY for copying any type of file.

This command is replaced by COMPACT on the Series 5.


CONTINUE

Usage:CONTINUE

Makes a program immediately go to the UNTIL... line of a DO...UNTIL loop or the WHILE... line of a WHILE...ENDWH loop i.e. to the test condition.

See also BREAK.


CONST

Usage:CONST KConstantName=constantValue

Declares constants which are treated as literals, not stored as data. The declarations must be made outside any procedure, usually at the beginning of the module. KConstantName has the normal type-specification indicators (%, &, $ or nothing for floating-point numbers). CONST values have global scope, and are not overridden by locals or globals with the same name: in fact the translator will not allow the declaration of locals or globals of the same name. By convention, all constants should be named with a leading K to distinguish them from variables.

It should be noted that it is not possible to define constants with values -32768 (for integers) and -214748648 (for long integers) in decimals, but hexadecimal notation may be used instead (i.e. values of $8000 and &80000000 respectively).


COPY

Usage:COPY src$,dest$

Copies the file src$, which may be of any type, to the file dest$. Any existing file with the name dest$ is deleted. You can copy across devices.  On the Series 3c you can use the appropriate file extensions to indicate the type of file, and on all machines use wildcards if you wish to copy more than one file at a time.

If src$ contains wildcards, dest$ may specify either a filename similarly containing wildcards or just the device and directory to which the files are to be copied under their original names.

Example: To copy all the files from internal memory (in \OPL) to D:\ME\:

COPY "C:\OPL\*","D:\ME\"

(Remember the final backslash on the directory name.)

If src$ contains wildcards, dest$ must not specify a filename, just the device and directory to which the files are to be copied under their original names.

You must specify either an extension or .* on the first filename. The file type extensions are listed in the User Guide.

Example: To copy all the OPL files from internal memory (in \OPL) to B:\ME\:

COPY "M:\OPL\*.OPL","B:\ME\"

(Remember the final backslash on the directory name.)

See COMPRESS for more control over copying data files. If you use COPY to copy a data file, deleted records are copied and you cannot append to another data file.

There are more details of full file specifications in the 'Advanced topics' chapter.


COS

Usage:c=COS(x)

Returns the cosine of x, where x is an angle in radians.

To convert from degrees to radians, use the RAD function.


COUNT

Usage:c%=COUNT

Returns the number of records in the current data file.

This number will be 0 if the file is empty.

If you try to count the number of records in a view while updating the view an %Incompatible update mode' error will be raised (This will occur between assignment and APPEND / UPDATE or between MODIFY / INSERT and PUT).


CREATE

Usage:CREATE tableSpec$,log,f1,f2,...

Creates a table in a database. The database is also created if necessary. Immediately after calling CREATE, the file and view (or table) is open and ready for access.

tableSpec$ contains the database filename and optionally a table name and the field names to be created within that table.  For example:

CREATE "clients FIELDS name(40), tel TO phone", D, n$, t$

The filename is clients. The table to be created within the file is phone. The comma-separated list, between the keywords FIELDS and  TO, specifies the field names whose types are specified by the field handles (i.e. n$, t$).

The name field has a length of 40 bytes, as specified within the brackets that follow it. The tel field has the default length of 255 bytes. This mechanism is necessary for creating some indexes. See the 'Using OPXs on the Series 5' chapter for more details on index creation.

The filename may be a full file specification of up to 255 characters. A field name may be up to a maximum of  64 characters long. Text fields have a default length of 255 bytes.

log specifies the logical file name A to Z. This is used as an abbreviation for the file name when you use other data file commands such as USE.

049 Compatibility with the Series 3c

As on the Series 3c, the table specification may contain just the filename. In this case the table name will default to Table1 and the field names will be derived from the handles: "$" replaced by "s",  "%" by "i", and "&" by "a". E.g. n$ becomes ns. Knowing this allow views to be opened on tables (called Table1) that were created with the Series 3c OPL method. However, it would be better to create the fields with proper names in the first place.

For example:

    CREATE "clients",A,n$,t%,d&

is a short version of

    CREATE "clients FIELDS ns,ti,da TO Table1",A,n$,t%,d&

both creating Table1. Database clients is also created if it does not yet exist.

Usage: CREATE file$,log,f1,f2,85

Creates a data file called file$.

The filename may be a full file specification of up to 128 characters. Field names may be up to 8 letters/numbers.

The file may have up to 32 fields, as specified by f1, f2... (if viewed in the Data application, field f1 starts on the top line of the window, f2 is below it, etc.)

The logical view name log can be any letter in the range A to D and is used to identify the view to other database commands such as USE.

Immediately after the CREATE statement, the file is open and can be accessed.

Example:

CREATE "CLIENTS",B,NM$,PHON$

would create a data file in the internal memory with the name CLIENTS and the logical name B.

See also the 'Data File Handling' chapter and the 'Series 5 Database Handling' chapter.


CREATESPRITE

Usage:id%=CREATESPRITE

Creates a sprite, returning the sprite ID.

On the Series 5, sprites are handled by a built-in OPX. See the 'Using OPXs on the Series 5' chapter for more details.


CURSOR

Usage:any of

    CURSOR ON

    CURSOR OFF

    CURSOR id%

    CURSOR id%,asc%,w%,h%

    CURSOR id%,asc%,w%,h%,type%

CURSOR ON switches the text cursor on at the current cursor position. Initially, no cursor is displayed.

You can switch on a graphics cursor in a window by following CURSOR with the ID of the window. This replaces any text cursor. At the same time, you can also specify the cursor's shape, and its position relative to the baseline of text.

asc% is the ascent - the number of pixels (-128 to 127) by which the top of the cursor should be above the baseline of the current font. h% and w% (both from 0 to 255) are the cursor's height and width.

If you do not specify them, the following default values are used:

asc%	font ascent

h%	font height

w%	2

If type% is given, it can have these effects:

2	not flashing

4	grey

You can add these values together to combine effects - if type% is 6 a grey non-flashing cursor is drawn.  The Series 3c also supports an obloid cursor by specifying a type% of 1.  Using type%=1 on the Series 5 just displays a default graphics cursor, as though no type had been specified.

Constants for these types are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

An error is raised if id% specifies a bitmap rather than a window.

CURSOR OFF switches off any cursor.


DATETOSECS

Usage:s&=DATETOSECS(yr%,mo%,dy%,hr%,mn%,sc%)

Returns the number of seconds since 00:00 on 1/1/1970 at the date/time specified.

Raises an error for dates before 1/1/1970.

The value returned is an unsigned long integer. (Values up to +2147483647, which is 03:14:07 on 19/1/2038, are returned as expected. Those from +2147483648 upwards are returned as negative numbers, starting from -2147483648 and increasing towards zero.)

See also SECSTODATE, HOUR, MINUTE, SECOND.


DATIM$

Usage:d$=DATIM$

Returns the current date and time from the system clock as a string - for example: "Fri 16 Oct 1992 16:25:30". The string returned always has this format - 3 mixed-case characters for the day, then a space, then 2 digits for the day of the month, and so on.

Constants for offsets of each elements within the string (to be used with MID$, for example) are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it. The Date OPX provides a large set of procedures for manipulating dates and for accurate timing. See the 'Using OPXs on the Series 5' chapter for more details.


DAY

Usage:d%=DAY

Returns the current day of the month (1 to 31) from the system clock.


DAYNAME$

Usage:d$=DAYNAME$(x%)

Converts x%, a number from 1 to 7, to the day of the week, expressed as a three letter string. E.g. d$=DAYNAME$(1) returns MON.

Example:

PROC Birthday:

LOCAL d&,m&,y&,dWk%

  DO

    dINIT

    dTEXT "","Date of birth",2

    dTEXT "","eg 23 12 1963",$202

    dLONG d&,"Day",1,31

    dLONG m&,"Month",1,12

    dLONG y&,"Year",1900,2155

    IF DIALOG=0 :BREAK :ENDIF

    dWk%=DOW(d&,m&,y&)

    CLS :PRINT DAYNAME$(dWk%),

    PRINT d&,m&,y&

    dINIT dTEXT "","Again?",$202

    dBUTTONS "No",%N,"Yes",%Y

  UNTIL DIALOG<>%y

ENDP

See also DOW.


DAYS

Usage:d&=DAYS(day%,month%,year%)

Returns the number of days since 1/1/1900.

Use this to find out the number of days between two dates.

Example:

PROC deadline:

LOCAL a%,b%,c%,deadlin&

LOCAL today&,togo%

  PRINT "What day? (1-31)"

  INPUT a%

  PRINT "What month? (1-12)"

  INPUT b%

  PRINT "What year? (19??)"

  INPUT c%

  deadlin&=DAYS(a%,b%,1900+c%)

  today&=DAYS(DAY,MONTH,YEAR)

  togo%=deadlin&-today&

  PRINT togo%,"days to go"

  GET

ENDP

See also dDATE, SECSTODATE.

The Date OPX provides a large set of procedures for manipulating dates and for accurate timing. See the 'Using OPXs on the Series 5' chapter for more details.


DAYSTODATE

Usage: DAYSTODATE days&,year%,month%,day%

This converts days&, the number of days since 1/1/1900, to the corresponding date, returning the day of the month to day%, the month to month% and the year to year%. This is useful for converting the value set by dDATE,  which also gives days since 1/1/1900.


dBUTTONS

Usage:any of

    dBUTTONS p1$,k1%,p2$,k2%,p3$,k3%

    dBUTTONS p1$,k1%,p2$,k2%

    dBUTTONS p1$,k1%

The Series 5 allows more than 3 buttons which may be added in the same way.

Defines exit keys to go at the bottom (or the side on the Series 5: see dINIT) of a dialog.

From one to three (or more on the Series 5) exit keys may be defined. Each pair of p$ and k% specifies an exit key; p$ is the text to be displayed on it (above it on the Series 3c), while k% is the keycode of the shortcut key. DIALOG returns the keycode of the key pressed (in lower case for letters).

For alphabetic keys, use the % sign - %A means %the code of A', and so on. The shortcut key is then Ctrl+alphabetic key on the Series 5, but just the alphabetic key without a modifier on the Series 3c. An appendix lists the codes for keys (such as Tab) which are not part of the character set. If you use the code for one of these keys, its name (e.g. %Tab', or %Enter') will be shown in the key.

On the Series 5, the following effects may be obtained by adding the appropriate constants to the shortcut key keycode:

effect	constant value

display a button with no shortcut key label underneath it	256 ($100)

use the key alone (without the Ctrl modification) as the shortcut key	512 ($200)

Constants for these flags and keycodes for Esc and other keys are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

If you use a negative value for a k% argument, that key is a %Cancel' key. The corresponding positive value is used for the key to display and the value for DIALOG to return, but if you do press this key to exit, the var variables used in the commands like dEDIT, dTIME etc. will not be set. For the Series 5, when using a negative shortcut to specify the cancel button, you must negate the shortcut together with any added flags.

The Esc key will always cancel a dialog box, with DIALOG returning 0. If you want to show the Esc key as one of the exit keys, use -27 as the k% argument (its keycode is 27) so that the var variables will not be set if Esc is pressed.

There can be only one dBUTTONS item per dialog.

The buttons take up two lines on the screen. dBUTTONS may be used anywhere between dINIT and DIALOG; the postion of its use does not affect the position of the buttons in the dialog.

The buttons take up three lines on the screen. dBUTTONS must be the last dialog command you use before DIALOG itself.

Some keypresses cannot be specified, for example, those using the Control key for the Series 3c.

This example presents a simple query, returning 'False' for No, or %True' for Yes, providing shortcut keys of N and Y respectively and without labels beneath the keys.

PROC query:

  dINIT

  dTEXT "","FORGET CHANGES",2

  dTEXT "","Sure?",$202

  dBUTTONS "No",-(%N OR $300),"Yes",%Y OR $300

  RETURN DIALOG=%y

ENDP

On the Series 3c, the same shortcut keys can be specified using, although labels are always visible on the Series 3c:

dBUTTONS "No",%N,"Yes",%Y

See also dINIT.


dCHECKBOX

Usage:dCHECKBOX chk%,prompt$

Creates a dialog checkbox entry. This is similar to a choice list with two items, except that the list is replaced by a checkbox with the tick either on or off. The state of the checkbox is maintained across calls to the dialog.

Initially you should set the live variable chk% to 0 to set the tick symbol off and to any other value to set it on. chk% is then automatically set to 0 if the box is unchecked or -1 if it is checked when the dialog is closed.

See also dINIT.


dCHOICE

Usage:

dCHOICE var choice%,p$,list$

or

dCHOICE var choice%,p$,list1$+",..."

dCHOICE var choice%,"",list2$+",..."

...

dCHOICE var choice%,"",listN$

Defines a choice list to go in a dialog.

p$ will be displayed on the left side of the line. list$ should contain the possible choices, separated by commas - for example, "No,Yes". One of these will be displayed on the right side of the line, and  can be used to move between the choices.

choice% must be a LOCAL or a GLOBAL variable. It specifies which choice should initially be shown &#151; 1 for the first choice, 2 for the second, and so on. When you finish using the dialog, choice% is given a value indicating which choice was selected &#151; again, 1 for the first choice, and so on.

On the Series 5, dCHOICE supports an unrestricted number of items (up to memory limits).  To extend a dCHOICE list, add a comma after the last item on the line followed by "..." (three full-stops), as shown in the usage above. choice% must be the same on all the lines, otherwise an error is raised.  For example, the following specifies items

    i1, i2, i3, i4, i5, i6:

    dCHOICE ch%,prompt$,"i1,i2,..."

    dCHOICE ch%,"","i3,14,..."

    dCHOICE ch%,"","i5,i6"

See also dINIT.


dDATE

Usage:dDATE var lg&,p$,min&,max&

Defines an edit box for a date, to go in a dialog.

p$ will be displayed on the left side of the line.

lg&, which must be a LOCAL or a GLOBAL variable, specifies the date to be shown initially. Although it will appear on the screen like a normal date, for example 15/03/92, lg& must be specified as "days since 1/1/1900".

min& and max& give the minimum and maximum values which are to be allowed. Again, these are in days since 1/1/1900. An error is raised if min& is higher than max&.

When you finish using the dialog, the date you entered is returned in lg&, in days since 1/1/1900.

The system setting determines whether years, months or days are displayed first.

See also DAYS, SECSTODATE, DAYSTODATE, dINIT.


DECLARE EXTERNAL

Usage:DECLARE EXTERNAL

Causes the translator to report an error if any variables or procedures are used before they are declared. It should be used at the beginning of the module to which it applies, before the first procedure. It is useful for detecting %Undefined externals' errors at translate-time rather than at runtime.

For example, with DECLARE EXTERNAL commented out, the following translates and raises the error, %Undefined externals, i' at runtime. Adding the declaration causes the error to be detected at translate-time instead.

REM DECLARE EXTERNAL

PROC main:

LOCAL i%

  i%=10

  PRINT i

  GET

ENDP

If you use this declaration, you will need to declare all subsequent variables and procedures used in the module, using EXTERNAL.

See also EXTERNAL.


DECLARE OPX

Usage:

    DECLARE OPX opxname,opxUid&,opxVersion&

    ...

    END DECLARE

Declares an OPX. opxname is the name of the OPX, opxUid& its UID and opxVersion& its version number.

Declarations of the OPX's preocedures should be made inside this structure.

See the 'Using OPXs on the Series 5' chapter for more details.


dEDIT

Usage:dEDIT var str$,p$,len%

ordEDIT var str$,p$

Defines a string edit box, to go in a dialog.

p$ will be displayed on the left side of the line.

str$ is the string variable to edit. Its initial contents will appear in the dialog. The length used when str$ was defined is the maximum length you can type in.

len%, if supplied, gives the width of the edit box (allowing for widest possible character in the font). The string will scroll inside the edit box, if necessary. If len% is not supplied, the edit box is made wide enough for the maximum width str$ could possibly be.

See also dTEXT.


dEDITMULTI

Usage: dEDITMULTI var pData&,p$,widthInChars%,numLines%,maxLen%

Defines a multi-line edit box to go into a dialog. Normally the resulting text would be used in a subsequent dialog, saved to file or printed using the Printer OPX (see the 'Using OPXs on the Series 5' chapter). It is also possible to paste text into the buffer from other applications and vice versa, although any formatting or embedded objects contained in text pasted in will be removed.

pData& is the address of a buffer to take the edited data. It could be the address of an array as returned by ADDR, or of a heap cell, as returned by ALLOC (see ADDR and ALLOC).  The buffer may not be specified directly as a string and may not be read as such.  Instead it should be peeked, byte by byte (see PEEK).  The leading 4 bytes at ptrData& contain the initial number of bytes of data following. These bytes are also set by dEDITMULTI to the actual number of bytes edited. For this reason it is convenient to use a long integer array as the buffer, with at least 1+(maxLen%+3)/4 elements. The first element of the array then specifies the initial length.

If an allocated cell is used (probably because more than 64K is required), the first 4 bytes of the cell must be set to the initial length of the data. If this length is not set then an error will be raised. For example if a cell of 100000 bytes is allocated, you would need to poke a zero long integer in the start to specify that there is initially no text in the cell. For example:

    p&=ALLOC(100000)

    POKEL p&,0REM Text starts at p&+4

Special characters such as line breaks and tab characters may appear in the buffer. Constants for the codes of these are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

The prompt, p$ will be displayed on the left side of the edit box.  widthInChars% specifies the width of the edit box within which the text is wrapped, using a notional average character width.  The actual number of characters that will fit depends on the character widths, with e.g. more %i's fitting than %w's.  numLines% specifies the number of full lines displayed. Any more lines will be scrolled. maxLen% specifies the length in bytes of the buffer provided (excluding the bytes used to store the length).

The Enter key is used by a multi-line edit box which has the focus before being offered to any buttons. This means that Enter can't be used to exit the dialog, unless another item is provided that can take the focus without using the Enter key.  Normal practice is to provide a button that does not use the Enter key to exit a dialog whenever it contains a multi-line edit box. The Esc key will always cancel a dialog however, even when it contains a multi-line edit box.

The following example presents a three-line edit box which is about 10 characters wide and allows up to 399 characters:

CONST KLenBuffer%=399

PROC dEditM:

LOCAL buffer&(101)

REM 101=1+(399+3)/4 in integer arithmetic

LOCAL pLen&,pText&

LOCAL i%

LOCAL c%

  pLen&=ADDR(buffer&(1))

  pText&=ADDR(buffer&(2))

  WHILE 1

    dINIT "Try dEditMulti"

    dEDITMULTI pLen&,"Prompt",10,3,KLenBuffer%

    dBUTTONS "Done",%dREM button needed to exit dialog

    IF DIALOG=0 :BREAK :ENDIF

    PRINT "Length:";buffer&(1)

    PRINT "Text:"

    i%=0

    WHILE i%&lt;buffer&(1)

      c%=PEEKB(pText&+i%)

      IF c%&gt;=32

        PRINT CHR$(c%);

      ELSE

        REM just print a dot for special characters

        PRINT ".";

      ENDIF

      i%=i%+1

    ENDWH

  ENDWH

ENDP

See also dINIT.


DEFAULTWIN

Usage:DEFAULTWIN mode%

Change the default window (ID=1) to enable or disable the use of grey (on the Series 3c) or change the colour mode (on the Series 5).

For the Series 5:

The default window uses 4-colour mode initially.

mode%=1 just clears the screen, leaving the window in 4-colour mode. Clearing of the screen ensures compatibility with Series 3c (see above).  mode% of 0 changes the screen to 2-colour mode (actually results in a mapping of greys to white or black) and mode% of 2 changes to 16-colour mode, as expected.

Using DEFAULTWIN with either of these values also clears the screen.

Using 4-colour mode uses more power than using 2-colour mode and using 16-colour mode uses more power that either of these. See the 'Graphics' chapter for more information.

Constants for the modes of DEFAULTWIN are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and see Appendix E for a listing of it.

For the Series 3c:

Initially grey cannot be used in the default window.

mode%=1 enables the use of grey. mode%=0 disables the use of grey.

A side-effect of DEFAULTWIN is to clear the default window.

Using grey uses more memory than using black only.

You are advised to call DEFAULTWIN once and for all near the start of your program if you need to change the colour mode of the default window on the Series 5 or use grey on the Series 3c. If it fails with %Out of memory' error, the program can then exit cleanly without losing vital information.

See also gGREY, gCOLOR, gCREATE.


DEG

Usage:d=DEG(x)

Converts from radians to degrees.

Returns x, an angle in radians, as a number of degrees. The formula used is: 180*x/PI

All the trigonometric functions (SIN,COS etc.) work in radians, not degrees. You can use DEG to convert an angle returned by a trigonometric function back to degrees:

Example:

PROC xarctan:

LOCAL arg,angle

  PRINT "Enter argument:";

  INPUT arg

  PRINT "ARCTAN of",arg,"is"

  angle=ATAN(arg)

  PRINT angle,"radians"

  PRINT DEG(angle),"degrees"

  GET

ENDP

To convert from degrees to radians, use RAD.


DELETE

Usage:DELETE filename$

Deletes any type of file.

You can use wildcards for example, to delete all the files in D:\OPL

DELETE "D:\OPL\*"

You can use wildcards for example, to delete all the OPL files in B:\OPL

DELETE "B:\OPL\*.OPL"

The file type extensions are listed in the User Guide.

See also RMDIR.

or DELETE

Usage:DELETE dbase$,table$

This deletes the table, table$, from the database, dbase$. To do this all views of the database, and hence the database itself, must be closed.


dFILE

Usage:

dFILE var file$,p$,f%

dFILE var file$,p$,f%,uid1&,uid2&,uid3&

Defines a filename edit box or selector, to go in a dialog. A 'Folder' and %Disk' selector are automatically added on the following lines (on the Series 3c, a %Disk' selector only).

By default no prompts are displayed for the file, folder and disk selectors on the Series 5. A comma-separated prompt list should be supplied. For example, for a filename editor with the standard prompts use:

dFILE f$,"File,Folder,Disk",1

The disk selector is automatically supplied with a prompt on the Series 3c and p$ will be the prompt on the left of the filename selector.

f% controls the type of file editor or selector, and the kind of input allowed. You can add together any of the following values:

value	meaning

0	use a selector

1	use an edit box

2	allow directory names

4	directory names only

8	disallow existing files

16	query existing files

32	allows null string input

64	don't display file extension

128	obey/allow wildcards

256	allow ROM files to be selected

512	allow files in the System folder to be selected

The first of the list is the most crucial. If you add 1 into f%, you will see a file edit box, as when creating a new file. If you do not add 1, you will see the 'matching file' selector, used when choosing an existing file.

The value 64 (to omit file extensions) is not valid on the Series 5 since file extensions are no longer treated as special components of the filename.

If performing a %copy to' operation, you might use 1+2+16, to specify a file edit box, in which you can type the name of a directory to copy to, and which will produce a query if you type the name of an existing file.

If asking for the name of a directory to remove, you might use 4, to allow an existing directory name only.

'Query existing' is ignored if %disallow existing' is set. These two, as well as %allow null string input', only work with file edit boxes, not 'matching file' selectors.

For file selectors, dFILE supports file restriction by UID, or by type from the user's point of view. Documents are identified by three UIDs which identify which application created the document and what kind of file it is. Specifying all three UIDs will restrict the files as much as is possible, and specifying fewer will provide less restriction. You can supply 0 for uid1& and uid2& if you only want to restrict the list to uid3&. This may be useful when dealing with documents from one of your own applications: you can easily find out the third UID as it will be the UID you specified in the APP statement. Note that UIDs are ignored for editors.  For example, if your application has UID KUidMyApp&, then the following will list only your application-specific documents:

dFILE f$,p$,f%,0,KUidOplDoc&,KUidMyApp&

REM KUidOplDoc& for OPL docs

Some OPL-related UIDs are given in Const.oph. See the 'Calling Procedures' for details of how to use this file and Appendix E for a listing of it.

file$ is the string variable to edit. Its initial contents always control the initial drive and directory used. Any filename part of file$ is shown initially in the filename box. For a 'matching file' selector, you can use wildcards in the filename part (such as *.tmp) to control which filenames are matched. To do this, you must add 128 to f%. 128 also allows wildcard specifications to be entered (returned in str$), for both 'matching' and %new file' selectors.

On the Series 3c, if str$ does not contain any drive or directory information, the path as set by SETPATH is used. If SETPATH has not been used, the \OPD directory on the default drive (usually M:, %Internal') is used.

With a matching file selector (as opposed to an edit box) the value 8 restricts the selection to files which match the filename/extension in file$.

Matching file selectors can also use 64, in which case files with the same extension as that in file$ are shown without this extension. (Many Psion file selectors are like this.)

You can always press Tab to produce the full file selector with a dFILE item.

file$ must be declared to be 255 bytes long, since file names may be up to this length, and if it is shorter an error will be raised. On the Series 3c, it must be at least 128 bytes long.

Constants for the flags used by dFILE and for some OPL-related UIDs are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

See also dINIT.


dFLOAT

Usage:dFLOAT var fp,p$,min,max

Defines an edit box for a floating-point number, to go in a dialog.

p$ will be displayed on the left side of the line.

min and max give the minimum and maximum values which are to be allowed. An error is raised if min is higher than max.

fp must be a LOCAL or a GLOBAL variable. It specifies the value to be shown initially. When you finish using the dialog, the value you entered is returned in fp.

See also dINIT.


DIALOG

Usage:n%=DIALOG

Presents the dialog prepared by dINIT and commands such as dTEXT and dCHOICE. If you complete the dialog by pressing Enter, your settings are stored in the variables specified in dLONG, dCHOICE etc., although you can prevent this with dBUTTONS.

If you used dBUTTONS when preparing the dialog, the keycode which ended the dialog is returned. Otherwise, DIALOG returns the line number of the item which was current when Enter was pressed. The top item (or the title line, if present), has line number 1.

If you cancel the dialog by pressing Esc, the variables are not changed, and 0 is returned.

See also dINIT.


DIAMINIT

Usage:DIAMINIT pos%,str1$,str2$...

Initialises the diamond list (discarding any existing list). str1$, str2$ etc. contain the text to be displayed in the status window for each item in the list.

pos% is the initial item on to which the <img src="..\group\diamond.gif indicator should be positioned, with pos%=1 specifying the first item. (Any value greater than the number of strings specifies the final item.) If pos%&gt;=1 you must supply at least this many strings.

If pos% is not supplied or if pos%=0, or if DIAMINIT is used on its own with no arguments, no bar is defined.

If pos%=-1 the diamond bar is removed as for the small status window on the Series 3c.

The Series 5 has no status windows. You should use a toolbar instead. See the 'Friendlier Interaction' chapter for more details of toolbar usage.


DIAMPOS

Usage:DIAMPOS pos%

Positions the <img src="..\group\diamond.gif indicator on the <img src="..\group\diamond.gif list.

Positioning outside the range of the items wraps around in the appropriate way. pos%=0 causes the diamond symbol to disappear.

The Series 5 has no status windows. You should use a toolbar instead. See the 'Friendlier Interaction' chapter for more details of toolbar usage.


dINIT

Usage: any of

dINIT

dINIT title$

dINIT title$,flags%

Prepares for definition of a dialog, cancelling any existing one. Use dTEXT, dCHOICE etc. to define each item in the dialog, then DIALOG to display the dialog.

If title$ is supplied, it will be displayed at the top of the dialog.

Any supplied title$ will be centred and with a line across the dialog below it.

Any supplied title$ will be positioned in a grey box at the top of the dialog.

flags% can be any added combination of the following constants to achieve the following effects,

value	effect

1	buttons on the right rather than at the bottom

2	no title bar (any title in dINIT is ignored)

4	use the full screen

8	don't allow the dialog box to be dragged

16	pack the dialog densely (not buttons though)

Constants for these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

It should be noted that dialogs without titles cannot be dragged regardless of the %No drag' setting. Dense packing enables more lines to fit on the screen for larger dialogs.

On the Series 5, if an error occurs when adding an item to a dialog, the dialog is deleted and dINIT needs calling again. This is necessary to avoid having partially specified dialog lines.

In practical terms, this means that where the following artificial example would work on the Series 3c, giving just a long integer editor, it will raise a 'Structure fault' error on the Series 5.

    dINIT

    ONERR e1

    REM bad arg list gives argument error

    dCHOICE ch%,"ChList","a,b,,,,c"

    e1::

    ONERR OFF

    dLONG l&,"Long",0,12345

    DIALOG


DIR$

Usage:d$=DIR$(filespec$)

then d$=DIR$("")

Lists filenames, including subdirectory names, matching a file specification. You can include wildcards in the file specification. If filespec$ is just a directory name, include the final backslash on the end for example, "\TEMP\" . Use the function like this:

DIR$(filespec$) returns the name of the first file matching the file specification.

DIR$("") then returns the name of the second file in the directory.

DIR$("") again returns the third, and so on.

When there are no more matching files in the directory, DIR$("") returns a null string.

Example, listing all the files whose names begin with A in C:\ME\

PROC dir:

LOCAL d$(255)

  d$=DIR$("C:\ME\A*")

  WHILE d$&lt;&gt;""

    PRINT d$

    d$=DIR$("")

  ENDWH

  GET

ENDP

Example, listing all the.DBF files in M:\DAT:

PROC dir:

LOCAL d$(128)

  d$=DIR$("M:\DAT\*.DBF")

  WHILE d$&lt;&gt;""

    PRINT d$

    d$=DIR$("")

  ENDWH

  GET

ENDP


dLONG

Usage: dLONG var lg&,p$,min&,max&

Defines an edit box for a long integer, to go in a dialog.

p$ will be displayed on the left side of the line.

min& and max& give the minimum and maximum values which are to be allowed. An error is raised if min& is higher than max&.

lg& must be a LOCAL or a GLOBAL variable. It specifies the value to be shown initially. When you finish using the dialog, the value you entered is returned in lg&.

See also dINIT.


DO...UNTIL

Usage:

    DO

    statement

    ...

    UNTIL condition

DO forces the set of statements which follow it to execute repeatedly until the condition specified by UNTIL is met.

This is the easiest way to repeat an operation a certain number of times.

Every DO must have its matching UNTIL to end the loop.

If you set a condition which is never met, the program will go round and round, locked in the loop forever.

You can escape by pressing Ctrl+Esc, provided you haven't set ESCAPE OFF. If you have set ESCAPE OFF, you will have to return to go to the Task list, select your program in the list and click the %Close file' option.

You can escape by pressing Psion-Esc, provided you haven't set ESCAPE OFF. If you have set ESCAPE OFF, you will have to return to the System screen, move to the program name under the RunOpl icon, and press Delete.


DOW

Usage:d%=DOW(day%,month%,year%)

Returns the day of the week from 1 (Monday) to 7 (Sunday) given the date.

day% must be between 1 and 31, month% from 1 to 12 and year% from 1900 to 2155.

For example, D%=DOW(4,7,1992) returns 6, meaning Saturday.

Constants for the numeric values assigned to the days of the week are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.


dPOSITION

Usage:dPOSITION x%,y%

Positions a dialog. Use dPOSITION at any time between dINIT and DIALOG.

dPOSITION uses two integer values. The first specifies the horizontal position, and the second, the vertical. dPOSITION -1,-1 positions to the top left of the screen; dPOSITION 1,1 to the bottom right; dPOSITION 0,0 to the centre, the usual position for dialogs.

dPOSITION 1,0, for example, positions to the right-hand edge of the screen, and centres the dialog half way up the screen.

Constants for the positions are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

See also dINIT.


DRAWSPRITE

Usage:DRAWSPRITE x%,y%

Draws the current sprite in the current window with top-left at pixel position x%,y%.

On the Series 5, sprites are handled by the built-in Sprite OPX. See the 'Using OPXs on the Series 5' for more details.


dTEXT

Usage:dTEXT p$,body$,t%

ordTEXT p$,body$

Defines a line of text to be displayed in a dialog.

p$ will be displayed on the left side of the line, and body$ on the right side. If you only want to display a single string, use a null string ("") for p$, and pass the desired string in body$. It will then have the whole width of the dialog to itself. An error is raised if body$ is a null string.

body$ is normally displayed left aligned (although usually in the right column). You can override this by specifying t%:

t%	effect

0	left align body$

1	right align body$

2	centre body$

However, note that on the Series 5, alignment of body$ is only supported when p$ is null, with the body being left aligned otherwise. In addition, you can add any or all of the following three values to t%, for these effects:

$100	use bold for body$

$200	draw a line below this item

$400	allow this item to be selected

$800	specify this item as a text separator

Note that on the Series 5, bold dialog text is not supported. You can display a line separator between any dialog items by setting the flag $800 on an item which has null p$ and body$. (If p$ and/or body$ are not null, then the flag is ignored and no separator is drawn.)  The separator counts as an item in the value returned by DIALOG. On the Series 3c, only one line can be drawn across a dialog using the flag $200. It will be below the last item which asks for it, whether the title from dINIT (Series 3c only) or a dTEXT item. The flag $400 only allows the prompt, and not the body, to be selected.

Constants for the text types are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

See also dEDIT, dINIT


dTIME

Usage:dTIME var lg&,p$,t%,min&,max&

Defines an edit box for a time, to go in a dialog.

p$ will be displayed on the left side of the line.

lg&, which must be a LOCAL or a GLOBAL variable, specifies the time to be shown initially. Although it will appear on the screen like a normal time, for example 18:27, lg& must be specified as seconds after 00:00. A value of 60 means one minute past midnight; 3600 means one o'clock, and so on.

min& and max& give the minimum and maximum values which are to be allowed. Again, these are in seconds after 00:00. An error is raised if min& is higher than max&.

When you finish using the dialog, the time you entered is returned in lg&, in seconds after 00:00.

t% specifies the type of display required, as follows:

t%	time display

0	absolute time no seconds

1	absolute time with seconds

2	duration no seconds

3	duration with seconds

4	time without hours

8	absolute time in 24 hours

Constants for dTIME types are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

For example, 03:45 represents an absolute time while 3 hours 45 minutes represents a duration.

Absolute times are displayed in 24-hour or am/pm format according to the current system setting. 8 displays the time in 24 hour clock, regardless of the system setting on the Series 5.

Absolute times always display am or pm as appropriate, unless 24 hour clock is being used.  Durations never display am or pm.  Note, however, that if you use the flag  4 (no hours) then the am/pm symbol will be displayed and the flag 2 must be added if you wish to hide it.

See also dINIT.


dXINPUT

Usage:dXINPUT var str$,p$

Defines a secret string edit box, such as for a password, to go in a dialog.

p$ will be displayed on the left side of the line.

str$ is the string variable to take the string you type.

str$</cs> must be less than 16 characters long on the Series 5 and must be at least eight characters long on the Series 3c.

Initially the dialog does not show any characters for the string; the initial contents of str$ are ignored. A special symbol will be displayed for each character you type, to preserve the secrecy of the string.

See also dINIT.


EDIT

Usage: EDIT a$

Displays a string variable which you can edit directly on the screen. All the usual editing keys are available the arrow keys move along the line, Esc clears the line, and so on.

When you have finished editing, press Enter to confirm the changes. If you press Enter before you have made any changes, then the string will be unaltered.

If you use EDIT in conjunction with a PRINT statement, use a comma at the end of the PRINT statement, so that the string to be edited appears on the same line as the displayed string:

    ...

    PRINT "Edit address:",

    EDIT A.address$

    UPDATE

    ....


TRAP EDIT

If the Esc key is pressed while no text is on the input line, the %Escape key pressed' error (number -114) will be returned by ERR provided that the EDIT has been trapped. You can use this feature to enable the user to press the Esc key to escape from inputting a string.

See also INPUT, dEDIT.


ELSE/ELSEIF/ENDIF

See IF.


ENDA

See APP.


ENDV

See VECTOR


ENDWH

See WHILE.


ENTERSEND

Usage:ret%=ENTERSEND(pobj%,m%,var p1,...)

This is the same as SEND except that, if the method leaves, the error code is returned to the caller. Otherwise the value returned is as returned by the method.

OPL now handles leaving without the need to use this function.


ENTERSEND0

Usage:ret%=ENTERSEND0(pobj%,m%,var p1,...)

This is the same as ENTERSEND except that, if the method does not leave, zero is returned.

OPL now handles leaving without the need to use this function.


EOF

Usage:e%=EOF

Finds out whether you're at the end of a file yet.

Returns -1 (true) if the end of the file has been reached, or 0 (false) if it hasn't.

When reading records from a file, you should test whether there are still records left to read, otherwise you may get an error.

Example:

PROC eoftest:

  OPEN "myfile",A,a$,b%

  DO

    PRINT A.a$

    PRINT A.b%

    NEXT

    PAUSE -40

  UNTIL EOF

  PRINT "The last record"

  GET

  RETURN

ENDP


ERASE

Usage:ERASE

Erases the current record in the current file.

The next record is then current. If the erased record was the last record in a file, then following this command the current record will be null and EOF will return true.


ERR

Usage:e%=ERR

Returns the number of the last error which occurred, or 0 if there has been no error.

Example:

    ...

    PRINT "Enter age in years"

    age::

    TRAP INPUT age%

    IF ERR=-1

    PRINT "Number please:"

    GOTO age

    ENDIF

    ...

You can set the value returned by ERR to 0 (or any other value) by using TRAP RAISE 0. This is useful for clearing ERR.

To clear the value of ERR on the Series 3c, you need to do the following,

    ONERR e0

    RAISE 0

    e0::

    ONERR OFF

See also ERR$, ERRX$. See the 'Error Handling' chapter for full details, including the list of error numbers and messages.


ERR$

Usage:e$=ERR$(x%)

Returns the error message for the specified error code x%.

ERR$(ERR) gives the message for the last error which occurred. Example:

    TRAP OPEN "\FILE",A,field1$

    IF ERR

    PRINT ERR$(ERR)

    RETURN

    ENDIF

See also ERR, ERRX$. See the 'Error Handling' chapter for full details, including the list of error numbers and messages.


ERRX$

Usage:x$=ERRX$

Returns the current extended error message (when an error has been trapped), e.g.

%Error in MODULE\PROCEDURE,EXTERN1,EXTERN2,...'

which would have been presented as an alert if the error had not been trapped. This allows the list of missing externals, missing procedure names, etc. to be found when an error has been trapped by a handler.

See ERR, ERR$. See the %Error Handling' chapter for full details, including the list of error numbers and messages.


ESCAPE OFF

Usage:

    ESCAPE OFF

    ...

    ESCAPE ON

ESCAPE OFF stops Ctrl+Esc on the Series 5 or Psion-Esc on the Series 3c being used to break out of the program when it is running. ESCAPE ON enables this feature again.

ESCAPE OFF takes effect only in the procedure in which it occurs, and in any sub-procedures that are called. Ctrl+Esc or Psion-Esc is always enabled when a program begins running.

If your program enters a loop which has no logical exit, and ESCAPE OFF has been used, you will have to go to the Task list, move to the program name, and select the %Close file' option.

If your program enters a loop which has no logical exit, and ESCAPE OFF has been used, you will have to return to the System screen, move to the program name under the RunOpl icon, and press the Delete key.


EVAL

Usage:d=EVAL(s$)

Evaluates the mathematical string expression s$ and returns the floating-point result. s$ may include any mathematical function or operator. Note that floating-point arithmetic is always performed.

On the Series 5, EVAL runs in the "context" of the current procedure, so globals and externals can be used in s$, procedures in loaded modules can be called and the current values of gX and gY, can be used etc. LOCAL variables cannot be used in s$ (because the translator cannot deference them).

For example:

    DO

    AT 10,5 :PRINT "Calc:",

    TRAP INPUT n$

    IF n$="" :CONTINUE :ENDIF

    IF ERR=-114 :BREAK :ENDIF

    CLS :AT 10,4

    PRINT n$;"=";EVAL(n$)

    UNTIL 0

See also VAL.


EXIST

Usage:e%=EXIST(filename$)

Checks to see that a file exists.

Returns -1 (%True') if the file exists and 0 ('False') if it doesn't.

Use this function when creating a file to check that a file of the same name does not already exist, or when opening a file to check that it has already been created:

    IF NOT EXIST("CLIENTS")

    CREATE "CLIENTS",A,names$

    ELSE

    OPEN "CLIENTS",A,names$

    ENDIF

    ...


EXP

Usage:e=EXP(x)

Returns ex - that is, the value of the arithmetic constant e (2.71828...) raised to the power of x.


EXT

Usage:EXT name$

Gives the file extension of files used by an OPA.

This can only be used between APP and ENDA.

See the 'Advanced Topics' chapter for more details of OPAs.

OPL application documents do not have file extensions on the Series 5, so this command is not used.


EXTERNAL

Usage:EXTERNAL variable

or EXTERNAL prototype

Required if DECLARE EXTERNAL is specified in the module.

The first usage declares a variable as external. For example, EXTERNAL screenHeight%

The second usage declares the prototype of a procedure (prototype includes the final : and the argument list). The procedure may then be referred to before it is defined. This allows parameter type-checking to be performed at translate-time rather than at runtime and also provides the necessary information for the translator to coerce numeric argument types. This is reasonable because OPL does not support argument overloading. The same coercion occurs as when calling the built-in keywords.

Following the example of C and C++, you would normally provide a header file declaring prototypes of all the procedures and INCLUDE this header file at the beginning of the module which defines the declared procedures to ensure consistency. The header file would also be INCLUDEd in any other modules which call these procedures. Then you should use DECLARE EXTERNAL at the beginning of modules which include the header file so that the translator can ensure that these procedures are called with correct parameter types or types which can be coerced.

The following is an example of usage of DECLARE EXTERNAL and EXTERNAL:

DECLARE EXTERNAL

EXTERNAL myProc%:(i%,l&)

REM or INCLUDE "myproc.oph" that defines all your procedures

PROC test:

LOCAL i%,j%,s$(10)

REM j% is coerced to a long integer

REM as specified by the prototype.

  myProc%:(i%,j%)

  REM translator %Type mismatch' error:

  REM string can't be coerced to numeric type

  myProc%:(i%,s$)

  REM wrong argument count gives translator error

  myProc%:(i%)

ENDP

PROC myProc%:(i%,l&)

REM Translator checks consistency with prototype above

  ...

ENDP

See DECLARE EXTERNAL.


FIND

Usage:f%=FIND(a$)

Searches the current data file (or view on the Series 5) for fields matching a$. The search starts from the current record, so use NEXT to progress to subsequent records. FIND makes the next record containing a$ the current record and returns the number of the record found. Capitals and lower-case letters match.

You can use wildcards:

?	matches any single character

*	matches any group of characters

To find a record with a field containing Dr and either BROWN or BRAUN, use:

    F%=FIND("*DR*BR??N*")

FIND("BROWN") will find only those records with a field consisting solely of the string BROWN.

You can only search string fields.

See also FINDFIELD.


FINDFIELD

Usage:f%=FINDFIELD(a$,start%,no%,flags%)

Like FIND, finds a string, makes the record with this string the current record, and returns the number of this record.

a$ is the string for which to search: the search will be carried out in no% fields in each record, starting at the field with number start% (1 is the number of  the first field). start% and no% may refer to string fields only and other types will be ignored. The flag% argument specifies the type of search as explained below. If you want to search in all fields, use start%=1 and for no% use the number of fields you used in the OPEN/CREATE command.

flags% should be specified as follows:

flags%	search direction

0	backwards from current record

1	forwards from current record

2	backwards from end of file

3	backwards from start of file

Constants for these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and see Appendix E for a listing of it.

Add 16 to the value of flag% given above to make the search case-dependent, where case-dependent means that the record will exactly match the search string in case as well as characters. Other wise the search will case-independent which means that upper case and lower case characters will match.

See also the 'Data File Handling' chapter.


FINDLIB

Usage:ret%=FINDLIB(var cat%,name$)

Find DYL category name$ (including .DYL extension) in the ROM. On success returns zero and writes the category handle to cat%.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


FIRST

Usage:FIRST

Positions to the first record in the current data file (or view on the Series 5).


FIX$

Usage:f$=FIX$(x,y%,z%)

Returns a string representation of the number x, to y% decimal places. The string will be up to z% characters long.

Example: FIX$(123.456,2,7) returns "123.46".

If z% is negative then the string is right-justified for example FIX$(1,2,-6) returns "  1.00" where there are two spaces to the left of the 1.

If z% is positive then no spaces are added for example FIX$(1,2,6) returns "1.00".

If the number x will not fit in the width specified by z%, then the string will just be asterisks, for example FIX$(256.99,2,4) returns "****".

See also GEN$, NUM$, SCI$.


FLAGS

Usage:FLAGS flags%

Replaces TYPE on the Series 5. flags% values as follows:

1 specifies an application which can create files. It will then be included in the list of applications offered when the user creates a new file from the System screen.

2 prevents the application from appearing in the Extras bar. It is very unusual to have this flag set.

Constants for these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

FLAGS may only be used within the APP85ENDA construct.

See APP. See also the section on OPAs in the 'Advanced Topics' chapter.


FLT

Usage:f=FLT(x&)

Converts an integer expression (either integer or long integer) into a floating-point number. Example:

PROC gamma:(v)

LOCAL c

  c=3E8

  RETURN 1/SQR(1-(v*v)/(c*c))

ENDP

You could call this procedure like this: gamma:(FLT(a%)) if you wanted to pass it the value of an integer variable without having first to assign the integer value to a floating-point variable.

See also INT and INTF.


FONT

Usage:

FONT id&,style%

FONT id%,style%

Sets the text window font and style.

Constants for the font UIDs are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

See 'The text and graphics windows' in the 'Graphics' chapter for more details.


FREEALLOC

Usage:

FREEALLOC pcell&

FREEALLOC pcell%

Frees a previously allocated cell at pcell& (pcell%).

The number of bytes allocated is restricted to 64K on the Series 3c, while it is not on the Series 5. The input value is therefore a long integer on the Series 5 and an integer on the Series 3c.

See also SETFLAGS if you require the 64K limit to be enforced on the Series 5. If the flag is set to restrict the limit, pcell& is guaranteed to fit into a short integer.


gAT

Usage:gAT x%,y%

Sets the current position using absolute co-ordinates. gAT 0,0 moves to the top left of the current drawable.

See also gMOVE.


gBORDER

Usage:gBORDER flags%,width%,height%

or gBORDER flags%

Draws a one-pixel wide, black border around the edge of the current drawable. If width% and height% are supplied, a border shape of this size is drawn with the top left corner at the current position. If they are not supplied, the border is drawn around the whole of the current drawable.

flags% controls three attributes of the border a shadow to the right and beneath, a one-pixel gap all around, and the type of corners used:

flags%	effect

1	single pixel shadow

2	removes a single pixel shadow (leaves a gap for single pixel shadow on Series 3c)

3	double pixel shadow

4	removes a double pixel shadow (leaves a gap for double pixel shadow on Series 3c)

$100	one pixel gap all round

$200	more rounded corners

Constants for the values of these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

The shadows on the Series 5 will not appear in the same way as shadows on other objects such as dialogs and menu panes appear. To display such shadows on a window, you must specify them when using gCREATE. Hence you should use gCREATE (and  gXBORDER) in preference to gBORDER on the Series 5.

You can combine the values to control the three different effects. (1, 2, 3 and 4 are mutually exclusive you cannot use more than one of them.) For example, for rounded corners and a double pixel shadow, use flags%=$203.

Set flags%=0 for no shadow, no gap, and sharper corners.

For example, to de-emphasise a previously emphasised border, use gBORDER with the shadow turned off:

    gBORDER 3 REM show border

    GET

    gBORDER 4 REM border off

See also gXBORDER.


gBOX

Usage:gBOX width%,height%

Draws a box from the current position, width% to the right and height% down. The current position is unaffected.


gBUTTON

Usage:any of

gBUTTON text$,type%,w%,h%,st%

gBUTTON text$,type%,w%,h%,st%,bmpId&

gBUTTON text$,type%,w%,h%,st%,bmpId&,maskId&

gBUTTON text$,type%,w%,h%,st%,bmpId&,maskId&,layout%

Draws a 3-D black and grey button at the current position in a rectangle of the supplied width w% and height h%, which fully encloses the button in all its states. text$ specifies up to 64 characters to be drawn in the button in the current font and style. You must ensure that the text will fit in the button.

type%=1 draws a Series 3c button;  type%=2 specifies Series 5.

state%=0 draws a raised button, state%=1 a semi-depressed (flat) button and state%=2 a fully-depressed (sunken) button. On the Series 3c, an error is raised if the current window has no grey plane.

On the Series 5, there is added support so that bitmaps may be used on buttons. Three extra optional arguments can be passed which give the bitmap ID, the mask ID and the layout for the button respectively.  maskId% can be 0 to specify no mask.

The following constants should be used for layout% to specify relative positions of the text and icon on a button,

position of text	layout%

right	0

bottom	1

top	2

left	3

The following constants can be added to the values above to specify how a button's excess space is to be allocated,

share	0

to text	$10

to picture	$20

Constants for all these layout types and for the button states are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

When the layout is such that the text is at the top or the bottom, then text and picture are centred vertically and horizontally in the space allotted to them.  If the layout has text to the left or right, then the text is left aligned in the space allotted to it and the picture is right or left aligned respectively.  Both text and picture are centred vertically in this case.

Examples:

layout%	description

$13	creates a button with text on the left and left aligned in any excess space.

$20	creates a button with text on the right and the picture left aligned in any excess space.

$10	creates a standard toolbar button, putting the text on the right.

For a picture only with no text use text$="".

'Invalid arguments' error is raised if you use OPL windows for gBUTTON.  Read-only bitmaps may also be loaded using the Bitmap OPX. See the 'Using OPXs on the Series 5' for more details of how to do this.

Note that a button is a purely graphical entity and so doesn't own the bitmaps. Therefore the bitmap may not be unloaded while the button is still in use.


gCIRCLE

Usage:gCIRCLE radius%

or gCIRCLE radius%,fill%

Draws a circle with the centre at the current position in the current drawable. If the value of  radius% is negative then no circle is drawn.

If fill% is supplied and if fill%&lt;&gt;0 then the circle is filled with the current pen colour.

See gELLIPSE, gCOLOR.


gCLOCK

Usage: any of

    gCLOCK ON/OFF

    gCLOCK ON,mode%

    gCLOCK ON,mode%,offset&

    gCLOCK ON,mode%,offset&,format$

    gCLOCK ON,mode%,offset&,format$,font%

    gCLOCK ON,mode%,offset&,format$,font%,style%

Note: offset& is replaced by offset% and font& by font% on the Series 3c.

Displays or removes a clock showing the system time. The current position in the current window is used. Only one clock may be displayed in each window.

mode% controls the type of clock.

Values are:

6	black and grey medium, system setting

7	black and grey medium, analog

8	second type medium, digital

9	black and grey extra large

10	formatted digital (described below)

11	formatted digital (described below)

Constants for the modes are supplied in Const.oph.  See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

On the Series 3c, modes 1 to 5 are provided for Series 3 compatibility, and produce small (digital), medium (system setting), medium (analog), medium (digital), and large (analog) clocks respectively.

You can also OR the mode with any of these to create the following effects:

$10	shows the date in all except the extra large and formatted clocks

$20	shows seconds in small digital, large analog, black and grey medium analog and extra large clocks

$40	shows am/pm in small digital and black medium clocks only.

$80	specifies that a clock is to be drawn in the grey plane (only for clocks that do not contain both black and grey: i.e. all except the black and grey, medium, analog clock and the extra large clock).

It is possible to draw clocks that include grey in windows that have no grey plane.

On the Series 5, there are additional features on the basic clocks which partially replace these effects.  The digital clock (mode%=8) automatically displays the day of the week and day of  the month below the time.  The extra large analog clock (mode%=9) automatically displays a second hand.

Do not use gSCROLL to scroll the region containing a clock. When the time is updated, the old position would be used. The whole window may, however, be moved using gSETWIN.

Digital clocks display in 24-hour or 12-hour mode according to the system-wide setting.

offset& specifies an offset in minutes from the system time to the time displayed. This allows you to display a clock showing a time other than the system time.

On the Series 5, a flag, which has the value $100, may be ORed with mode% so that offset& may be specified in seconds rather than minutes. The offset is a long integer to enable a whole day to be specified when the offset is in seconds.

If these arguments are not supplied, mode% is taken as 1, and offset& as 0.

The system setting for the clock type (i.e. digital or analog) can be changed by an OPL program using the procedure LCSETCLOCKFORMAT: in Date OPX This is function should be used to implement, for example, tapping a toolbar clock to change its type as in the built-in Series 5 applications. See the 'Using OPXs on the Series 5' section for full details of this procedure.

format$, font% and style% are used only for formatted digital clocks (mode% 10 on the Series 3c and 11 on the Series 5). The values for font& and style% are as for gFONT and gSTYLE. The default font for gCLOCK is the system font. The default style is normal (0).

For the formatted digital clock, a format string (up to 255 characters long) specifies how the clock is to be displayed. The format string contains a number of format specifiers in the form of a % followed by a letter. (Upper or lower case may be used.)

For the Series 5, the format string may contain the following symbols to obtain the required effects:

%%	Insert a single % character in the string

%*	Abbreviate following item.  (The asterisk should be inserted between the % and the number or letter, e.g. %*1).  In most cases this amounts to omitting any leading zeros, for example if it is the first of the month "'F %*M" will display as 1 rather than 01.

%:n	Insert a system time separator character.  n is an integer between zero and three inclusive which indicates which time separator character is to be used. For European time settings, only n=1 and n=2 are used, giving the hours/minutes separator and minutes/seconds separator respectively.

%/n	Insert a system date separator character.  n is an integer between zero and three inclusive which indicates which date separator character is to be used. For European time settings, only n=1 and n=2 are used, giving the day/month separator and month/year separator respectively.

%1	Insert the first component of a three component date (i.e. a date including day, month and year) where the order of the components is determined by the system settings. The possibilities are: dd/mm/yyyy, (European), mm/dd/yyyy (American), yyyy/mm/dd (Japanese).

%2	Insert the second component of a three component date where the order has been determined by the system settings. See %1.

%3	Insert the third component of a three component date where the order has been determined by the system settings. See %1.

%4	Insert the first component of a two component date (i.e. a date including day and month only) where the order has been determined by system settings. The possibilities are: dd/mm, (European), mm/dd (American), mm/dd (Japanese).

%5	Insert the second component of a two component date where the order has been determined by the system settings. See %4.

%A	Insert am or pm according to the current language and time of day. Text is printed even if 24 hour clock is in use. Text may be specified to be printed before or after the time, and a trailing or leading space as appropriate will be added. The abbreviated version (%*A) removes this space.

Optionally, a minus or plus sign may be inserted between the % and the A. This operates as follows: %-A causes am/pm text to be inserted only if the system setting of the am/pm symbol position is set to display before the time. Similarly, %+A causes am/pm text to be inserted only if the system setting of the am/pm symbol is set to display after the time. No am/pm text will be inserted before the time if a + is inserted in the string.  For example you could use,  "%-A%H%:1%T%+A" to insert the am/pm symbol either before or after the time, according to the system setting. %+A and %-A cannot be abbreviated.

%B	As %A, except that the am/pm text is only inserted if the system clock setting is 12 hour.  (This should be used in conjunction with %J.)

%D	Insert the two-digit day number in month (in conjunction with %1 etc.).

%E	Insert the day name. Abbreviation is language specific (3 letters in English).

'F	Use this at the beginning of a format string to make the date/time formatting independent of the system setting. This fixes the order of the following day/month/year component(s) in their given order, removing the need to use %1 to %5, allowing individual components of the date to be printed.  (No abbreviation.)

%H	Insert the two-digit hour component of the time in 24 hour clock format.

%I	Insert the two-digit hour component of the time in 12 hour clock format.  Any leading zero is automatically suppressed, regardless of whether an asterisk is inserted or not.

%J	Insert the two-digit hour component of time in either 12 or 24 hour clock format depending on the corresponding system setting. When the clock has been set to 12 hour format, the hour's leading zero is automatically suppressed regardless of whether an asterisk has been inserted between the % and J.

'm	Insert the two-digit month number (in conjunction with %1 etc.).

%N	Insert the month name (in conjunction with %1 etc.).  When using system settings (i.e. not using 'F) this causes all months following %N in the string to be written in words.  When using fixed format (i.e. when using 'F) %N may be used alone to insert a month name. Abbreviation is language specific (3 letters in English).

'S	Insert the two-digit second component of the time.

%T	Insert the two-digit minute component of the time.

%W	Insert the two-digit week number in year, counting the first (part) week as week 1.

%X	Insert the date suffix. When using system settings (i.e. not using 'F), this causes a suffix to be put on any date following %X in the string.  When using fixed format (i.e. using 'F), %X following any date appends a suffix for that particular date.  Cannot be abbreviated.

%Y	Insert the four digit year number (in conjunction with %1 etc.). The abbreviation is the last two digits of the year.

%Z	Insert the three digit day number in year.

Some examples of the use of these format strings are as follows.  The example use is 1:30:05 pm on Wednesday, 1st January 1997, with the system setting of European dates and with am/pm after the time:

"%-A%I:%T:'S%+A" will print the time in 12 hour clock, including seconds, with the am/pm either inserted before or after the time, depending on the system setting.   So the example time would appear as, 1:30:05 pm.

"'F%E %*D%X %N %Y" will print the day of the week followed by the date with suffix, the month as a word and the year.  For example, Wednesday 1st January 1997.

"%E %D%X%N%Y %1 %2 %3" will use the locale setting for ordering the elements of the date, but will use a suffix on the day and the month in words.  For example, Wednesday 01st January 1997.

"%*E %*D%X%*N%*Y %1 %2 '%3" will be similar to 3., but will abbreviate the day of the week, the day, the month and the year, so the example becomes "Wed 1st Jan 97".

"'m%Y%D%1%/0%2%/0%3" will appear as 01/01/1997.  This demonstrates that the ordering of the %D, 'm and %Y is irrelevant when using locale-dependent formatting.  Instead the ordering of the date components is determined by the order of the %1, %2, and %3 formatting commands.

style% may take any of the values used to specify gSTYLE, other than 2 (underlined).

A note should also be made that a 'General Failure' error will result if you attempt to use an invalid format.  Invalid formats include using %: and %/ followed by 0 or 3 when in European locale setting (when these separators are without meaning) and using %+ and %- followed by characters other than A or B.

The format string for the Series 3c may be defined in a similar manner to the Series 5, although generally less functionality is available. Any item may be abbreviated by using a * after the %. For example, %*T at 11:05 pm abbreviates 05 to 5. In the following list of specifiers, those which produce numbers will do so without any leading zero if you use %* instead of %. Other abbreviations are marked:

%%	Insert a single % character in the string.

%:, %/	Insert a system time, date separator character.

%A	Insert am or pm text, according to the system time. (Abbreviation: 1st letter only)

%D, %W, 'm	Insert the day, week, month number as two digits, in the range 01-31, 01-53 and 01-12, respectively

%E, %N 	Insert the day, month name. (Abbreviation: language dependent - first 3 letters in English)

%H, %I	Insert the hour in 24-hour, 12-hour format, in the range 00-23 and 01-12, respectively

'S, %T	Insert the seconds, minutes, in the range 00-59.

%X	Insert the suffix string for day number, e.g. st in %1st', nd in %2nd'

%Y	Insert the year as a four digit number (Abbreviation: discards the century, i.e. last two digits)

%1, %2, %3	Insert the day, month, year ordered according to the system setting. E.g. European setting is day/month/year, so %1=%D, %2='m, %3=%Y. So to display a date in correct format use "%1/%2/%3". (Abbreviation: see 'G, %P, %U)

%4, %5	Insert the day, month as ordered in the system setting.

'F, %O 	Toggles days, months (displayed by %1, %2 and %3) between numeric and name formats. On 9th March 1993, with European date type, "%1'F%1'F%1" gives 09Tuesday09.

'G, %P, %U	Toggles %1, %2 and %3 between long form and abbreviation. On 9th March 1993, with European date type, "'F%1'G%1'G%1" gives TuesdayTueTuesday.

%L	Toggles the suffix on the day number for %1, %2, %3 (in numeric form only). On 9th March 1993, with European date type, "'G%1%L%1%L%1" gives 99th9.

%6, %7	Inserts the hour and am/pm text according to the system setting. With am-pm format, %6=%I and %7=%A. With 24-hour format, %6=%H and %7 gives no %am/pm' characters.

For example, the format strings

"%H, m:%T" at 11:05 pm, displays a running clock as h:23, m:05.

"%1%/%2%/%3" automatically generates a clock with day, month and year in the order as selected in the Time application.

"%4%/%5" gives a clock with just day and month in selected order.

"%6%:%T%:'S%7" gives a clock with hour, minute and second automatically conforming to the system configuration.

Note that for those specifiers that toggle between two different options (e.g. 'F), the state of toggle is remembered only within one format string and not from one string to the next - i.e. the toggle state is restored to the default setting when displaying a new clock.

As a final example, assuming that the settings in the Time application are for %day/month/year' date format, %am-pm' time format and %:' time separator and that the time is 11:30:05 pm on 9th March 1993, "'G%L%P%O%*E, %1 %2 %3 %6%:%T:'S%" generates Tue, 9th Mar 1993 11:30:05pm. With the same setup except for 'month/day/year' date format in %24-hour' mode, the same string generates Tue, Mar 9th 1993 23:30:05.


gCLOSE

Usage:gCLOSE id%

Closes the specified drawable that was previously opened by gCREATE, gCREATEBIT or gLOADBIT.

If the drawable closed was the current drawable, the default window (ID=1) becomes current.

An error is raised if you try to close the default window.


gCLS

Usage:gCLS

Clears the whole of the current drawable and sets the current position to 0,0, its top left corner.


gCOLOR

Usage:gCOLOR red%,green%,blue%

Sets the pen colour of the current window. The red%,green%,blue% values specify a colour which will be mapped to white, black or one of the greys on non-colour screens. Note that if the values of red%, green% and blue% are equal, then a pure grey results, ranging from black (0) to white (255).


gCOPY

Usage:gCOPY id%,x%,y%,w%,h%,mode%

Copies a rectangle of the specified size (width w%, height h%) from the point x%,y% in drawable id%, to the current position in the current drawable.

On the Series 5, it is unadvisable to use gCOPY to copy from windows as it is very slow. It should only be used for copying from bitmaps to windows or other bitmaps.

As this command can copy both set and clear pixels, the same modes are available as when displaying text. Set mode% = 0 for set, 1 for clear, 2 for invert or 3 for replace. 0, 1 and 2 act only on set pixels in the pattern; 3 copies the entire rectangle, with set and clear pixels.

The current position is not affected in either window.

gCOPY is affected by the setting of gGREY (in the current window) as follows: with gGREY 0 it copies black to black; with gGREY 1 it copies grey to grey, or black to grey if source is black only; with gGREY 2 it copies grey to grey and black to black, or black to both if source is black only.


gCREATE

Usage:any of

id%=gCREATE(x%,y%,w%,h%,v%)

id%=gCREATE(x%,y%,w%,h%,v%,grey%)

id%=gCREATE(x%,y%,w%,h%,v%,flags%)

Creates a window with specified position and size (width w%, height h%), and makes it both current and foreground. Sets the current position to 0,0, its top left corner. If v% is 1, the window will immediately be visible; if 0, it will be invisible.

Returns id% which identifies this window for other keywords.

flags% specifies the graphics mode to use and shadowing on the window.  By default the graphics mode is 2-colour and there is no shadow.

The least significant 4 bits of flags% gives the colour-mode as before 0 (2 colour-mode), 1 (4 colour-mode), 2 (16 colour-mode).

The next 4 bits may be set to specify the shadowing on the window. If 0, the window has no shadow. The next 4 bits give the shadow height relative to the window behind it (a height of N units gives a shadow of N x 2 pixels).

The flags% argument is most easily specified in hexadecimal:

flags%	description

$412	16 colour-mode ($2), shadowed window ($1), with height 4 units ($4) above the previous window with a shadow of 8 pixels.

$010	2 colour-mode (black and white) shadowed window at the same height as the previous window.

$101	4 colour mode window with no shadow (height ignored if shadow disabled).

$111	4 colour mode window with shadow of 1 unit above window behind, i.e. 2 pixel shadow.

Constants for specifying various of the arguments taken by gCREATE are given in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

Note that 63 windows may be open at any time and it is recommended that you use many small windows rather than a few large ones.

If grey% is not given or is 0, the window will not have a grey plane. If grey% is 1, it will have one.

See also gCLOSE, gGREY, DEFAULTWIN.


gCREATEBIT

Usage:

id%=gCREATEBIT(w%,h%)

id%=gCREATEBIT(w%,h%,mode%)

Creates a bitmap with the specified width and height, and makes it the current drawable. Sets the current position to 0,0, its top left corner.

Returns id% which identifies this bitmap for other keywords.

gCREATEBIT may be used with an optional third parameter which specifies the graphics mode of the bitmap to be created.  The values of these are as given in gCREATE. By default the graphics mode of a bitmap is 2-colour.

See also gCLOSE, gCREATE.


gDRAWOBJECT

Usage:gDRAWOBJECT type%,flags%,w%,h%

Draws the scaleable graphics object specified by type%, scaled to fit in the rectangle with top left at the current graphics cursor position and with the specified width w% and height h%.

The Series 3c has only one object type (set type%=0) a %lozenge'. This is a 3-D rounded box lit from the top left, with a shadow at bottom right and a grey body. (For an example, see the text %City' in the top left of the World application.)

For type%=0, flags% specifies the corner roundness:

0	for normal roundness

1	for more rounded

2	for a single pixel removed from each corner

An error is raised if the current window has no grey plane.


gELLIPSE

Usage: gELLIPSE hRadius%,vRadius%

orgELLIPSE hRadius%,vRadius%,fill%

Draws an ellipse with the centre at the current position in the current drawable. hRadius% is the horizontal distance in pixels from the centre of the ellipse to the left (and right) of the ellipse. vRadius% is the vertical distance from the centre of the ellipse to the top (and bottom). If the length of either radius is less than zero, then no ellipse is drawn.

If fill% is supplied and if fill%&lt;&gt;0 then the ellipse is filled with the current pen colour.

See gCIRCLE, gCOLOR.


GEN$

Usage:g$=gen$(x,y%)

Returns a string representation of the number x. The string will be up to y% characters long.

Example GEN$(123.456,7) returns "123.456" and GEN$(243,5) returns "243".

If y% is negative then the string is right-justified - for example GEN$(1,-6) returns  "     1" where there are five spaces to the left of the 1.

If y% is positive then no spaces are added for example GEN$(1,6) returns "1".

If the number x will not fit in the width specified by y%, then the string will just be asterisks, for example GEN$(256.99,4) returns "****".

See also FIX$, NUM$, SCI$.


GET

Usage:g%=GET

Waits for a key to be pressed and returns the character code for that key.

For example, if the A key is pressed with Caps Lock off, the integer returned is 97 (a), or 65 (A) if A was pressed with the Shift key down.

The character codes of special keys, such as Pg Dn, are given in Appendix D.

You can use KMOD to check whether modifier keys (Shift, Control, Psion (on the Series 3c), Fn (on the Series 5) and Caps Lock) were used.

See Appendix D for the full character set for the Series 5. For the Series 3c, see the User Guide.

See also KEY.


GET$

Usage:g$=GET$

Waits until a key is pressed and then returns which key was pressed, as a string.

For example, if the A key is pressed in lower case mode, the string returned is "a".

You can use KMOD to check whether any modifier keys (Shift, Control, Psion (on the Series 3c), Fn (on the Series 5) and Caps Lock) were used.

See also KEY$.


GETCMD$

Usage:w$=GETCMD$

Returns new command-line arguments to an OPA, after a "change files" or "quit" event has occurred. The first character of the returned string is "C", "O" or "X". If the return is "C" or "O", the rest of the string is a filename.

The first character has the following meaning:

"C" - close down the current file, and create the specified new file,

"O" - close down the current file, and open the specified existing file,

"X" - close down the current file (if any) and quit the OPA.

Constants for these return values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

You can only call GETCMD$ once for each system message.

See the 'Advanced Topics' chapter for more details of OPAs.

See also CMD$.


GETDOC$

Usage:docname$=GETDOC$

Returns the name of the current document.

See also SETDOC.


GETEVENT

Usage:GETEVENT var a%()

Waits for an event to occur. Returns with a%() specifying the event. The data returned in a%() depends on the type of event that occurred. If the event is a key-press, (a%(1) AND $400) is guaranteed to be zero. For other events (a%(1) AND $400) is guaranteed to be non-zero.

If a key has been pressed:

a%(1)	keycode (as for GET)

a%(2) AND $00ff	modifier (as for KMOD)

a%(2)/256	auto-repeat count (ignored by GET)

If a program has moved to

foreground:	a%(1)= $401

background:	a%(1)= $402

If the machine has switched on:

a%(1)= $403

If the Psion wants an OPA to

change files or exit:	a%(1)= $404

If the date changes:

a%(1)= $405

Note that date change events are not detected by the Series 5.

Also note that GETEVENT responds to all events to which GETEVENT32 responds, including pen events. The same codes are written to ev%(1) as GETEVENT32 ev&() writes to ev&(1), but the array elements ev%(2) to ev%(6) are only set as detailed above.

It is strongly recommended that you use GETEVENT32 rather than GETEVENT if you are using the Series 5. GETEVENT is supported only for backward compatibility and cannot be used to handle pen events in a satisfactory way.

For a key-press event, the modifier is returned in a%(2) and is not returned by KMOD.

If a non-key event such as %foreground' occurs while a keyboard keyword such as GET, INPUT, MENU or DIALOG is being used, the event is discarded. So GETEVENT must be used if non-key events are to be monitored. If you need to use these keywords in OPAs, use LOCK ON / LOCK OFF around them, so that the System screen won't send messages to switch files or shutdown while the application cannot respond.

The array (or string of integers) must be at least 6 integers long.

See also TESTEVENT, GETCMD$, GETEVENT32.


GETEVENT32

Usage: GETEVENT32 ev&()

Gets all event types handled by GETEVENT ev%() and additionally pointer (pen) events.  The latter are too large to fit into the array of integers provided for GETEVENT ev%(). ev&() must have at least 16 elements.

All events return a 32-bit time stamp. The window ID mentioned below refers to the value returned by the gCREATE keyword. The modifier values and scancode values for a keypress (which specify a location on the keyboard) are given in the 'Advanced Topics' chapter and Appendix D.

GETEVENT32 returns more information than GETEVENT, as listed below:

If a key has been pressed

(ev&(1) AND &400)=0

ev&(1)	keycode

ev&(2)	time stamp

ev&(3)	scan code

ev&(4)	modifier

ev&(5)	repeat

Note that unlike the repeat for GETEVENT, the repeat for GETEVENT32 is strictly a repeat, i.e. if there is only one keypress, then the value of ev&(5) is 0.

For all the other event types, ev&(1) is greater than &400:

If the program has moved to foreground:

ev&(1)=&401

ev&(2)	time stamp

If the program has moved to background:

ev&(1)=&402

ev&(2)	time stamp

If the machine is switched on

ev&(1)=&403

ev&(2)	time stamp

Note that this event is not enabled unless the appropriate flag is set (by default it is not): see SETFLAGS.

If the Series 5 wants the OPL application to switch files or exit:

ev&(1)=&404

If this event is received, GETCMD$ should be called to find out what action should be taken: see GETCMD$.

If a key is pressed down:

ev&(1)=&406

ev&(2)	time stamp

ev&(3)	scan code

ev&(4)	modifiers

If a key is released:

ev&(1)=&407

ev&(2)	time stamp

ev&(3)	scan code

ev&(4)	modifiers

If a pen event occurs:

ev&(1)=&408

ev&(2)	time stamp

ev&(3)	window ID

ev&(4)	pointer type (see below)

ev&(5)	modifiers

ev&(6)	x-co-ordinate

ev&(7)	y-co-ordinate

ev&(8)	x-co-ordinate relative to parent window

ev&(9)	y-co-ordinate relative to parent window

For pen events, ev&(4) has one of the following values:

0	pen down

1	pen up

6	drag

Other values:

If a pen enters contact with the screen:

ev&(1)=&409

ev&(2)	time stamp

ev&(3)	window ID

If a pen exits contact with the screen:

ev&(1)=&40A

ev&(2)	time stamp

ev&(3)	window ID

Constants for the array subscripts and the return values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

Some pointer events, and pointer enters and exits, can be filtered out to avoid being swamped by unwanted event types. See POINTERFILTER.

Note that for other unknown events, ev&(1) contains &1400 added to the code returned by the window server. ev&(2) is the timestamp and ev&(3)is the window ID, and the rest of the data returned by the window server is put into ev&(4), ev&(5), etc.

If a non-key event such as %foreground' occurs while a keyboard keyword such as GET, INPUT, MENU or DIALOG is being used, the event is discarded. So GETEVENT must be used if non-key events are to be monitored. If you need to use these keywords in OPAs, use LOCK ON / LOCK OFF around them, so that the System screen won't send messages to switch files or shutdown while the application cannot respond.

See also GETEVENT, GETEVENTA32.


GETEVENTA32

Usage:GETEVENTA32 status%,ev&()

Asynchronous version of GETEVENT32.  GETEVENTA32 returns the same codes to the array ev&() as GETEVENT32.

See GETEVENTC, GETEVENT32, GETEVENT. See also the 'I/O functions and commands' section in the 'Advanced Topics' chapter for details of asynchronous I/O functions.


GETEVENTC

Usage:GETEVENTC(var stat%)

Cancels the previously called GETEVENTA32 function with status stat%. Note that GETEVENTC consumes the signal (unlike IOCANCEL), so IOWAITSTAT should not be used after GETEVENTC.

See the 'Advanced Topics' chapter for details.


GETLIBH

Usage:cat%=GETLIBH(num%)

Convert a category number num% to a handle. If num% is zero, gets the handle for OPL.DYL.


gFILL

Usage:gFILL width%,height%,gMode%

Fills a rectangle of the specified size from the current position, according to the graphics mode specified.

The current position is unaffected.


gFONT

Usage:

gFONT fontUid&

gFONT fontId%

Sets the font for current drawable to fontId%. The font may be one of the predefined fonts in the ROM or a user-defined font. See the 'Graphics' chapter for more details of fonts.

Constants for the font UIDs are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and see Appendix E for a listing of it.

User-defined fonts must first be loaded by gLOADFONT, then the font UIDs of the loaded fonts may be used with gFONT. Note that this is not the ID returned by gLOADFONT (which is the font file ID), but the UID defined in the font file itself.

User-defined fonts must first be loaded by gLOADFONT, which returns the fontId% which may be used with gFONT.

See also gLOADFONT, FONT.


gGMODE

Usage:gGMODE mode%

Sets the effect of all subsequent drawing commands gLINEBY, gBOX etc. on the current drawable.

Mode%	pixels will be

0	set

1	cleared

2	inverted

Constants for the mode are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and see Appendix E for a listing of it.

When you first use drawing commands on a drawable, they set pixels in the drawable. Use gGMODE to change this. For example, if you have drawn a black background, you can draw a white box outline inside it with either gGMODE 1 or gGMODE 2, followed by gBOX.


gGREY

Usage:gGREY mode%

Changes the pen colour between black and grey. mode% has the following effects:

mode%=1 sets the foreground colour of the current drawable to light grey.  This is the same colour as would be achieved by using gCOLOR $aa,$aa,$aa.

mode% of any other value sets the foreground colour to black (the default).

Controls whether all subsequent graphics drawing and graphics text in the current window draw to the grey plane, the black plane or to both.

mode%=0 for black plane only (default)

mode%=1 for grey plane only

mode%=2 for both planes

It is helpful to think of the black plane being in front of the grey plane, so a pixel set in both planes will appear black. See the 'Graphics' chapter for details.

To enable the use of grey in the default window (ID=1) use DEFAULTWIN 1 at the start of your program. If grey is required in other windows you must create the windows with a grey plane using gCREATE.

gGREY cannot be used with bitmaps which have only one plane.

See also DEFAULTWIN and gCREATE.


gHEIGHT

Usage:height%=gHEIGHT

Returns the height of the current drawable.


gIDENTITY

Usage:id%=gIDENTITY

Returns the ID of the current drawable.

The default window has ID=1.


gINFO

Usage:gINFO var i%()

Gets general information about the current drawable and about the graphics cursor (whichever window it is in). The information is returned in the array i%() which must be at least 32 integers long.

The information is about the drawable in its current state, so e.g. the font information is for the current font in the current style.

The following information is returned:

i%(1)	lowest character code

i%(2)	highest character code

i%(3)	height of font

i%(4)	descent of font

i%(5)	ascent of font

i%(6)	width of '0' character

i%(7)	maximum character width

i%(8)	flags for font (see below)

i%(9-17)	name of font

i%(18)	current graphics mode (gGMODE)

i%(19)	current text mode (gTMODE)

i%(20)	current style (gSTYLE)

i%(21)	cursor state (ON=1,OFF=0)

i%(22)	ID of window containing cursor (-1 for text cursor)

i%(23)	cursor width

i%(24)	cursor height

i%(25)	cursor ascent

i%(26)	cursor x position in window

i%(27)	cursor y position in window

i%(28)	1 if drawable is a bitmap

i%(29)	cursor effects

i%(30)	gGREY setting

i%(31)	reserved (window server ID of drawing)

i%(32)	reserved

i%(8) specifies a combination of the following font characteristics:

value	meaning

1	font uses standard ASCII characters (32-126)

2	fond uses Code Page 850 characters (128-255)

4	font is bold

8	font is italic

16	font is serifed

32	font is mono-spaced

$8000	font is stored expanded for quick drawing

Use PEEK$(ADDR(i%(9))) to read the name of the font as a string.

If the cursor is on (i%(21)=1), it is visible in the window identified by i%(22).

i%(29) has bit 0 set (i%(29) AND 1) if the cursor is obloid, bit 1 set (i%(29) AND 2) if not flashing, and bit 2 set (i%(29) AND 4) if grey.

If the cursor is off (i%(21)=0), or is a text cursor (i%(22)=-1), i%(23) to i%(27) and i%(29)should be ignored.


gINFO32

Usage:gINFO32 var i&()

Gets general information about the current drawable and about the graphics cursor (whichever window it is in). This replaces gINFO because the information available has changed.  i&() must have 48 elements (although elements 37 to 48 are currently unused).  The same information is returned to the array elements as for gINFO except for the following,

i&(1)	reserved

i&(2)	reserved

i&(9)	the font UID as used in gFONT

i&(10-17)	unused

i&(30)	graphics colour-mode of current window

i&(31)	gCOLOR red% of foreground

i&(32)	gCOLOR green% of foreground

i&(33)	gCOLOR blue% of foreground

i&(34)	gCOLOR red% of background

i&(35)	gCOLOR green% of background

i&(36)	gCOLOR blue% of background

Additionally note that on the Series 5, i&(8)=2 means that Code Page 1252 is used (rather than Code Page 850) and also that there is no obloid cursor, so bit 0 will never be set in i&(29).

See also gINFO, gFONT, gCOLOR, gCREATE.


gINVERT

Usage:gINVERT width%,height%

Inverts the rectangle width% to the right and height% down from the cursor position, except for the four corner pixels.


GIPRINT

Usage:GIPRINT str$,c%

or GIPRINT str$

Displays an information message for about two seconds, in the bottom right corner of the screen. For example, GIPRINT "Not Found" displays Not Found. If a string is too long for the screen, it will be clipped.

If c% is given, it controls the corner in which the message appears:

c%	corner

0	top left

1	bottom left

2	top right

3	bottom right (default)

Constants for these corner values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

Only one message can be shown at a time. You can make the message go away - for example, if a key has been pressed - with GIPRINT "".


gLINEBY

Usage:gLINEBY dx%,dy%

Draws a line from the current position to a point dx% to the right and dy% down. Negative dx% and dy% mean left and up respectively.

The Series 5 never draws the end point, so for gLINEBY dx%,dy%, point gX+dx%,gY+dy% is not drawn.

Note, however, that OPL specially plots the point when the start and end-point coincide.

For horizontal lines, the line includes the pixel with the lower x co-ordinate and excludes the pixel with the higher x co-ordinate. Similarly for vertical lines, the line includes the pixel with the lower y co-ordinate and excludes the pixel with the higher y co-ordinate. For oblique lines (where the x and y co-ordinates change), the line is drawn minus one or both end points.

For all machines, the current position moves to the end of the line drawn.

gLINEBY 0,0 sets the pixel at the current position.

See also gLINETO, gPOLY.


gLINETO

Usage:gLINETO x%,y%

Draws a line from the current position to the point x%,y%. The current position moves to x%,y%.

The Series 5 never draws the end point, so for gLINETO x%,y%,  point x%,y% is not drawn

Note, however, that OPL specially plots the point when the start and end-point coincide.

For horizontal lines, the line includes the pixel with the lower x co-ordinate and excludes the pixel with the higher x co-ordinate. Similarly for vertical lines, the line includes the pixel with the lower y co-ordinate and excludes the pixel with the higher y co-ordinate. For oblique lines (where the x and y co-ordinates change), the line is drawn minus one or both end points.

To plot a single point on all machines, use gLINETO to the current position (or gLINEBY 0,0).

See also gLINEBY, gPOLY.


gLOADBIT

Usage:any of

    id%=gLOADBIT(name$,write%,index%)

    id%=gLOADBIT(name$,write%)

    id%=gLOADBIT(name$)

Loads a bitmap from the named bitmap file and makes it the current drawable. Sets the current position to 0,0, its top left corner.

gLOADBIT does not add a default filename extension to the input argument name.

Note that on the Series 5, gLOADBIT loads EPOC Picture files, which are naturally in the same file format that is saved by gSAVEBIT. EPOC Picture files can also be generated by exporting files created by the Sketch application. These are called multi-bitmap files (MBMs), though often containing just one bitmap as in the case of gSAVEBIT or Sketch files, and are often given an extension .MBM.

If name$ has no file extension, .PIC is used.

The bitmap is kept as a local copy in memory.

Returns id% which identifies this bitmap for other keywords.

write%=0 sets read-only access. Attempts to write to the bitmap in memory will be ignored, but the bitmap can be used by other programs without using more memory. write%=1 allows you to write to and re-save the bitmap. This is the default case.

Constants for the values of write% are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

For bitmap files which contain more than one bitmap, index% specifies which one to load. For the first bitmap, use index%=0, which is also the default value.

Bitmap files saved with gSAVEBIT have only one bitmap if they are saved from an in-memory bitmap rather than from a window. Saving a black/grey/white window on the Series 3c saves two planes black to index%=0 and grey to index%=1.

See also gCLOSE.


gLOADFONT

Usage:

fileId%=gLOADFONT(file$)

fontId%=gLOADFONT(name$)

Loads the user-defined font. This is done differently between machines as follows:

Loads the user-defined fonts specified in the file file$ and returns the file ID of the font file, which can be used only with gUNLOADFONT.  The maximum number of font files which may be loaded at any one time is 16.

To use the fonts in a loaded font file you need to use their published UIDs which will be defined in the font file itself, for example:

    fileId%=gLOADFONT("Music1")

    gFONT KMusic1Font1&

    ...

    gUNLOADFONT fileId%

Loads the user-defined font name$ and returns a font ID which can be used with gFONT to make the current drawable use this font. If name$ does not contain a file extension, .FON is used.

gFONT itself is very efficient, so you should normally load all required fonts at the start of a program.

Note that the built-in fonts are automatically available, and do not need loading.

See gUNLOADFONT.


GLOBAL

Usage:GLOBAL variables

Declares variables to be used in the current procedure (as does the LOCAL command) and (unlike LOCAL) in any procedures called by the current procedure, or procedures called by them.

The variables may be of 4 types, depending on the symbol they end with:

Variable names not ending with $, %, & or () are floating-point variables, for example price, x

Those ending with a % are integer variables, for example x%, sales92%

Those ending with an & are long integer variables, for example x&, sales92&.

Those ending with a $ are string variables. String variable names must be followed by the maximum length of the string in brackets for example names$(12), a$(3)

Array variables have a number immediately following them in brackets which specifies the number of elements in the array. Array variables may be of any type, for example: x(6),y%(5),f$(5,12),z&(3).

When declaring string arrays, you must give two numbers in the brackets. The first declares the number of elements, the second declares their maximum length. For example surname$(5,8) declares five elements, each up to 8 characters long.

Variable names may be any combination of up to 32 numbers and alphabetic characters and the underscore character. They must start with a alphabetic character or an underscore.

Variable names may be any combination of up to 8 numbers and alphabetic letters. They must start with a letter.

The length of a variable name includes the %, & or $ sign, but not the () in string and array variables.

More than one GLOBAL or LOCAL statement may be used, but they must be on separate lines, immediately after the procedure name.

See also LOCAL.


gMOVE

Usage:gMOVE dx%,dy%

Moves the current position dx% to the right and dy% downwards, in the current drawable.

A negative dx% causes movement to the left; a negative dy% causes upward movement.

See also gAT.


gORDER

Usage:gORDER id%,position%

Sets the window specified by id% to the selected foreground/background position, and redraws the screen. Position 1 is the foreground window, position 2 is next, and so on. Any position greater than the number of windows is interpreted as the end of the list.

On creation, a window is at position 1 in the list.

Raises an error if id% is a bitmap.

See also gRANK.


gORIGINX

Usage:x%=gORIGINX

Returns the gap between the left side of the screen and the left side of the current window.

Raises an error if the current drawable is a bitmap.


gORIGINY

Usage: y%=gORIGINY

Returns the gap between the top of the screen and the top of the current window.

Raises an error if the current drawable is a bitmap.


GOTO

Usage:

    GOTO label or  GOTO label::

    ...

    label::

Goes to the line following the label:: and continues from there. The label

Must be in the current procedure

Must start with a letter and end with a double colon, although the double colon is not necessary in the GOTO statement

May be up to 32 characters long on the Series 5, or 8 on the Series 3c, excluding the colons.


GOTOMARK

Usage:GOTOMARK b%

Makes the record with bookmark b%, as returned by BOOKMARK, the current record. b% must be a bookmark in the current view.


gPATT

Usage:gPATT id%,width%,height%,mode%

Fills a rectangle of the specified size from the current position with repetitions of the drawable id%.

As with gCOPY, this command can copy both set and clear pixels, so the same modes are available as when displaying text. Set mode%=0 for set, 1 for clear, 2 for invert or 3 for replace. 0, 1 and 2 act only on set pixels in the pattern; 3 copies the entire rectangle, with set and clear pixels.

If you set id%=-1 a pre-defined grey pattern is used.

The current position is unaffected.

gPATT is affected by the setting of gGREY (in the current window) in the same way as gCOPY: with gGREY 0 it copies black to black; with gGREY 1 it copies grey to grey, or black to grey if source is black only; with gGREY 2 it copies grey to grey and black to black, or black to both if source is black only.


gPEEKLINE

Usage:

gPEEKLINE id%,x%,y%,d%(),ln%

gPEEKLINE id%,x%,y%,d%(),ln%,mode%

Reads a horizontal line from the black plane of the drawable id%, length ln%, starting at x%,y%. The leftmost 16 pixels are read into d%(1), with the first pixel read into the least significant bit.

If you set id% to 0, this just reads from the whole screen, not from any particular window.

gPEEKLINE has an extra optional parameter mode% to specify the colour mode:

mode%	colour mode	colour of pixel which sets bits

-1	black and white	black

0	black and white	white

1	4-colour mode	white

2 	16-colour mode	white

The default mode% is -1.  For 4 and 16-colour modes, 2 and 4 bits per pixel respectively are used.  This is to enable the colour of the pixel to be ascertained from the bits which are set.  White results in all 2 or 4 bits being set, while black sets none of them. For example, in a 4-colour window, with the colour set by

gCOLOR 16,16,16

a pixel of a line would peek as 0001 in binary.  Similarly, a pixel of a line with the colour set to

gCOLOR 80,80,80

would result in the value 0101 in binary when peeked.

The array d%() must be long enough to hold the data. You can work out the number of integers required with ((ln%+15)/16) (using whole-number division).

Note that if the optional parameter mode% is used on the Series 5, the array size allowed must be adjusted accordingly: it must be at least twice as long as the array needed for black and white if the line you wish to peek in 4-colour mode and four times as long in 16-colour mode.

On the Series 3c, if you add $8000 to id%, the grey plane (not the black plane) will be peeked, but note that $8000 ORed with the id% on the Series 5 will raise an %Invalid arguments' error.


gPOLY

Usage:gPOLY a%()

Draws a sequence of lines, as if by gLINEBY and gMOVE commands.

The array is set up as follows:

a%(1)	starting x position

a%(2)	starting y position

a%(3)	number of pairs of offsets

a%(4)	dx1%

a%(5)	dy1%

a%(6)	dx2%

a%(7)	dy2% etc.

Constants for the first five array subscripts are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

Each pair of numbers dx1%,dy1%, for example specifies a line or a movement. To draw a line, dy% is the amount to move down, while dx% is the amount to move to the right multiplied by two.

To specify a movement (i.e. without drawing a line) work out the dx%,dy% as for a line, then add 1 to dx%.

(For drawing/movement up or left, use negative numbers.)

gPOLY is quicker than combinations of gAT, gLINEBY and gMOVE.

Example, to draw three horizontal lines 50 pixels long at positions 20,10, 20,30 and 20,50:

    a%(1)=20 :a%(2)=10 REM 20,10

    a%(3)=5 REM 5 operations

    a%(4)=50*2  :a%(5)=0 REM draw right 50

    a%(6)=0*2+1 :a%(7)=20 REM move down 20

    a%(8)=-50*2 :a%(9)=0REM draw left 50

    a%(10)=0*2+1 :a%(11)=20REM draw left 50

    a%(12)=50*2 :a%(13)=0REM draw right 50

    gPOLY a%()


gPRINT

Usage:gPRINT list of expressions

Displays a list of expressions at the current position in the current drawable. All variable types are formatted as for PRINT.

Unlike PRINT, gPRINT does not end by moving to a new line. A comma between expressions is still displayed as a space, but a semicolon has no effect. gPRINT without a list of expressions does nothing.

See also gPRINTB, gPRINTCLIP, gTWIDTH, gXPRINT, gTMODE.


gPRINTB

Usage:any of

    gPRINTB t$,w%,al%,tp%,bt%,m%

    gPRINTB t$,w%,al%,tp%,bt%

    gPRINTB t$,w%,al%,tp%

    gPRINTB t$,w%,al%

    gPRINTB t$,w%

Displays text t$ in a cleared box of width w% pixels. The current position is used for the left side of the box and for the baseline of the text.

al% controls the alignment of the text in the box 1 for right aligned, 2 for left aligned, or 3 for centred.

tp% and bt% are the clearances between the text and the top/bottom of the box. Together with the current font size, they control the height of the box. An error is raised if tp% plus the font ascent is greater than 255.

m% controls the margins. For left alignment, m% is an offset from the left of the box to the start of the text. For right alignment, m% is an offset from the right of the box to the end of the text. For centring, m% is an offset from the left or right of the box to the region in which to centre, with positive m% meaning left and negative meaning right.

If values are not supplied for some arguments, these defaults are used:

al%	left

tp%	0

bt%	0

m%	0

Constants for the layout features and the defaults are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

See also gPRINT, gPRINTCLIP, gTWIDTH, gXPRINT.


gPRINTCLIP

Usage:w%=gPRINTCLIP(text$,width%)

Displays text$ at the current position, displaying only as many characters as will fit inside width% pixels. Returns the number of characters displayed.

See also gPRINT, gPRINTB, gTWIDTH, gXPRINT, gTMODE.


gRANK

Usage:rank%=gRANK

Returns the foreground/background position, from 1 to 64 on the Series 5, and 1 to 8 on the Series 3c, of the current window.

Raises an error if the current drawable is a bitmap.

See also gORDER.


gSAVEBIT

Usage:gSAVEBIT name$,width%,height%

orgSAVEBIT name$

Saves the current drawable as the named bitmap file. If width% and height% are given, then only the rectangle of that size from the current position is copied.

gSAVEBIT does not add a default filename extension to the input argument name if none is provided on the Series 5, while on the Series 3c, if name$ has no file extension .PIC is used.

Saving a window to file when it includes grey will save both planes to the file, black bitmap first followed by grey.


gSCROLL

Usage:gSCROLL dx%,dy%,x%,y%,wd%,ht%

or gSCROLL dx%,dy%

Scrolls pixels in the current drawable by offset dx%,dy%. Positive dx% means to the right, and positive dy% means down. The drawable itself does not change its position.

If you specify a rectangle in the current drawable, at x%,y% and of size wd%,ht%, only this rectangle is scrolled.

The areas dx% wide and dy% deep which are "left behind" by the scroll are cleared.

The current position is not affected.


gSETPENWIDTH

Usage:gSETPENWIDTH width%

Sets the pen width in the current drawable to width% pixels.


gSETWIN

Usage:gSETWIN x%,y%,width%,height%

orgSETWIN x%,y%

Changes position and, optionally, the size of the current window.

An error is raised if the current drawable is a bitmap.

The current position is unaffected.

If you use this command on the default window, you must also use the SCREEN command to ensure that the area for PRINT commands to use is wholly contained within the default window.


gSTYLE

Usage:gSTYLE style%

Sets the style of text displayed in subsequent gPRINT, gPRINTB and gPRINTCLIP commands on the current drawable.

Style%	text style

0	normal

1	bold

2	underline

4	inverse

8	double height

16	mono-spaced

32	italic

You can combine these styles by adding their values for example, to set bold, underlined and double height, use gSTYLE 11, as 11=1+2+8.

This command does not affect non-graphics commands, like PRINT.

Constants for the styles are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and see Appendix E for a listing of it.


gTMODE

Usage:gTMODE mode%

Sets the way characters are displayed by subsequent gPRINT and gPRINTCLIP commands on the current drawable.

Mode%	pixels will be

0	set

1	cleared

2	inverted

3	replaced

When you first use graphics text commands on a drawable, each dot in a letter causes a pixel to be set in the drawable. This is mode%=0.

When mode% is 1 or 2, graphics text commands work in a similar way, but the pixels are cleared or inverted. When mode% is 3, entire character boxes are drawn on the screen - pixels are set in the letter and cleared in the background box.

This command does not affect other text display commands.

Constants for the modes are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and see Appendix E for a listing of it.


gTWIDTH

Usage:width%=gTWIDTH(text$)

Returns the width of text$ in the current font and style.

See also gPRINT, gPRINTB, gPRINTCLIP, gXPRINT.


gUNLOADFONT

Usage:

gULOADFONT fileId%

gUNLOADFONT fontId%

Unloads a user-defined font that was previously loaded using gLOADFONT. Raises an error if the font has not been loaded.

The built-in fonts are not held in memory and cannot be unloaded.

See also gLOADFONT.


gUPDATE

Usage:any of

    gUPDATE ON

    gUPDATE OFF

    gUPDATE

The Psion's screen is usually updated whenever you display anything on it. gUPDATE OFF switches off this feature. The screen will then be updated as few times as possible (though note that some keywords will always cause an update.) You can still force an update by using the gUPDATE command on its own.

This can result in a considerable speed improvement in some cases. You might, for example, use gUPDATE OFF, then a sequence of graphics commands, followed by gUPDATE. You should certainly use gUPDATE OFF if you are about to write exclusively to bitmaps.

gUPDATE ON returns to normal screen updating.

gUPDATE affects anything that displays on the screen. If you are using a lot of PRINT commands, gUPDATE OFF may make a noticeable difference in speed.

Note that with gUPDATE OFF, the location of errors which occur while the procedure is running may be incorrectly reported. For this reason, gUPDATE OFF is best used in the final stages of program development, and even then you may have to remove it to locate some errors.


gUSE

Usage:gUSE id%

Makes the drawable id% current. Graphics drawing commands will now go to this drawable. gUSE does not bring a drawable to the foreground (see gORDER).


gVISIBLE

Usage:gVISIBLE ON

or gVISIBLE OFF

Makes the current window visible or invisible.

Raises an error if the current drawable is a bitmap.


gWIDTH

Usage:width%=gWIDTH

Returns the width of the current drawable.


gX

Usage:x%=gX

Returns the x current position (in from the left) in the current drawable.


gXBORDER

Usage:gXBORDER type%,flags%,w%,h%

orgXBORDER type%,flags%

Draws a border in the current drawable of a specified type, fitting inside a rectangle of the specified size or with the size of the current drawable if no size is specified.

type%=1 for drawing the Series 3c 3-D grey and black border. A shadow or a gap for a shadow is always assumed.

type%=2 for drawing the Series 5 borders.

Values for flags% and their effects on the Series 5 are as follows,

border type	flags%

none	$00

single black	$01

shallow sunken	$42

deep sunken	$44

deep sunken with outline	$54

shallow raised	$82

deep raised	$84

deep raised with outline	$94

vertical bar	$22

horizontal bar	$2a

Constants for these flags and types are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

On the Series 3c, flags%=1,2,3,4 are as for gBORDER. When the shadow is enabled (1 or 3) only the grey and black parts of the border are drawn; you should pre-clear the background for the white parts. When the shadow is disabled (2 or 4) the outer and inner border lines are drawn, but the areas covered by grey/black when the shadow is enabled are now cleared. (This allows a shadow to be turned off simply by calling gXBORDER again.)

On the Series 3c, an error is raised if the current window has no grey plane.

The following values of flags% apply to all border types:

0 for normal corners

Adding $100 leaves 1 pixel gap around the border.

Adding $200 for more rounded corners

Adding $400 for losing a single pixel.

If both $400 and $200 are mistakenly supplied, $200 has priority.

See also gBORDER.


gXPRINT

Usage:gXPRINT string$,flags%

Displays string$ at the current position, with precise highlighting or underlining. The current font and style are still used, even if the style itself is inverse or underlined. If text mode 3 (replace) is used both set and cleared pixels in the text are drawn.

flags% has the following effect:

flags%	effect

0	normal, as with gPRINT

1	inverse

2	inverse, except corner pixels

3	thin inverse

4	thin inverse, except corner pixels

5	underlined

6	thin underlined

Constants for these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

Where lines of text are separated by a single pixel, the thin options maintain the separation between lines.

gXPRINT does not support the display of a list of expressions of various types.


gY

Usage:y%=gY

Returns the y current position (down from the top) in the current drawable.


HEX$

Usage:h$=HEX$(x&)

Returns a string containing the hexadecimal (base 16) representation of integer or long integer x&.

For example HEX$(255) returns the string "FF".

Notes

To enter integer hexadecimal constants (16 bit) put a $ in front of them. For example $FF is 255 in decimal. (Don't confuse this use of $ with string variable names.)

To enter long integer hexadecimal constants (32 bit) put a & in front of them. For example &FFFFF is 1048575 in decimal.

Counting in hexadecimal is done like this: 0 1 2 3 4 5 6 7 8 9 A B C D E F 10... where A stands for decimal 10, B for decimal 11, C for decimal 12 ... up to F for decimal 15. After F comes 10, which is equivalent to decimal 16. To understand numbers greater than hexadecimal 10, again compare hexadecimals with decimals. In these examples, 102 means 10<symbol codepoint=180 font="symbol10, 103 means 10<symbol codepoint=180 font="symbol10<symbol codepoint=180 font="symbol10 and so on.

253 in decimal is:

(2 x 102)+(5 x 101)+(3 x 100) = (2 x 100)+(5 x 10)+(3 x 1) = 200+50+3

By analogy, &253 in hexadecimal is:

(&2 x 162)+(&5 x 161)+(&3 x 160) =(2 x 256)+(5 x 16)+(3 x 1) =512+80+3 = 595 in decimal.

Similarly, &A6B in hexadecimal is:

(&A x 162)+(&6 x 161)+(&B x 160) =(10 x 256)+(6 x 16)+(11 x 1) =2560+96+11 = 2667 in decimal.

You may also find this table useful for converting between hex and decimal:

hex	decimal

&1 	1

&10 	16

&100	256

&1000	4096

For example, &20F9 is

(2 x &1000)+(0 x &100)+(15 x &10)+9 which in decimal is: (2 x 4096)+(0 x 256)+(15 x 16)+9 = 8441.

All hexadecimal constants are integers ($) or long integers (&). So arithmetic operations involving hexadecimal numbers behave in the usual way. For example, &3/&2 returns 1, &3/2.0 returns 1.5, 3/$2 returns 1.


HOUR

Usage:h%=HOUR

Returns the number of the current hour from the system clock as an integer between 0 and 23.


IABS

Usage:i&=IABS(x&)

Returns the absolute value, i.e. without any sign, of the integer or long integer expression x&.

For example IABS(-10) is 10.

See also ABS, which returns the absolute value as a floating-point value.


ICON

Usage:ICON mbm$

Gives the name of the bitmap file mbm$ (also known as an EPOC Picture file) to use as the icon for an OPL Application.

If the ICON command is not used inside the APP85ENDA structure, then a default icon is used, but the rest of the information in the APP..ENDA construct is still used to specify the other features of the OPL application.

On the Series 5, mbm$ is a multi-bitmap file which can contain up to three bitmap/mask pairs - the sizes are 24, 32 and 48 squares. These different sizes are used for the different zoom levels in the system screen. The sizes are read from the MBM and the most suitable size is zoomed if the exact sizes required are not provided or if some are missing.

In fact, you can use ICON more than once within the APP...ENDA construct on the Series 5. The translator only insists that all icons are paired with a mask of the same size in the final ICON list. This allows you to use pairs of MBMs containing just one bitmap as produced by the Sketch application. For example, you could specify them individually:

APP ...

ICON "icon24.mbm"

ICON "mask24.mbm"

ICON "icon32.mbm"

ICON "mask32.mbm"

ICON "icon48.mbm"

ICON "mask48.mbm"

ENDA

or with pairs in each MBM:

APP ...

ICON "iconMask24"

ICON "iconMask32"

ICON "iconMask48"

ENDA

or with all the bitmaps as just one MBM, as would normally be the case if prepared on the PC using the BMCONV tool and the AIFTOOL (description of these tools is beyond the scope of this manual and you should refer to the EPOC32 C++ Software Development Kit (SDK), which is available from Psion Software plc, for more details).

This command can only be used between APP and ENDA.

See the 'Advanced Topics' chapter for more details of OPL applications.


IF...ENDIF

Usage:

    IF condition1

    ...

    ELSEIF condition2

    ...

    ELSE

    ...

    ENDIF

Does either

the statements following the IF condition

or

the statements following one of the ELSEIF conditions (there may be as many ELSEIF statements as you like none at all if you want)

or

the statements following ELSE (or, if there is no ELSE, nothing at all). There may be either one ELSE statement or none.

After the ENDIF statement, the lines following ENDIF carry on as normal.

IF, ELSEIF, ELSE and ENDIF must be in that order.

Every IF must be matched with a closing ENDIF.

You can also have an IF...ENDIF structure within another, for example:

    IF condition1

    ...

    ELSE

    ...

    IF condition2

    ....

    ENDIF

    ...

    ENDIF

condition is an expression returning a logical value for example a&lt;b. If the expression returns logical true (non-zero) then the statements following are executed. If the expression returns logical false (zero) then those statements are ignored. For more details about logical expressions, see Appendix B.


INCLUDE

Usage:INCLUDE file$

Includes a file, file$, which may contain CONST definitions, prototypes for OPX procedures and prototypes for module procedures. The included file may not include module procedures themselves.  Procedure and OPX procedure prototypes allow the translator to check parameters and coerce numeric parameters (that are not passed by reference) to the required type.

Including a file is logically identical to replacing the INCLUDE statement with the file's contents.

The filename of the header may or may not include a path. If it does include a path, then OPL will only scan the specified folder for the file. However, the default path for INLCUDE is \System\Opl\, so when INCLUDE is called without specifying a path, OPL looks for the file firstly in the current folder and then in \System\Opl\ in all drives from Y: to A: and then in Z:, excluding any remote drives.

See CONST, EXTERNAL.  See also the 'Using OPXs on the Series 5' chapter.


INPUT

Usage:INPUT variable

or INPUT log.field

Waits for a value to be entered at the keyboard, and then assigns the value entered to a variable or data file field.

You can edit the value as you type it in. All the usual editing keys are available: the arrow keys move along the line, Esc clears the line and so on.

If inappropriate input is entered, for example a string when the input was to be assigned to an integer variable, a ? is displayed and you can try again. However, if you used TRAP INPUT, control passes on to the next line of the procedure, with the appropriate error condition being set and the value of the variable remaining unchanged.

INPUT is usually used in conjunction with a PRINT statement:

PROC exch:

LOCAL pds,rate

  DO

    PRINT "Pounds Sterling?",

    INPUT pds

    PRINT "Rate (DM)?",

    INPUT rate

    PRINT "=",pds*rate,"DM"

    GET

  UNTIL 0

ENDP

Note the commas at the end of the PRINT statements, used so that the cursor waiting for input appears on the same line as the messages.


TRAP INPUT

If a bad value is entered (for example "abc" for a%) in response to a TRAP INPUT, the ? is not displayed, but the ERR function can be called to return the value of the error which has occurred. If the Esc key is pressed while no text is on the input line, the %Escape key pressed' error (number -114) will be returned by ERR (provided that the INPUT has been trapped). You can use this feature to enable someone to press the Esc key to escape from inputting a value.

See also EDIT. This works like INPUT, except that it displays a string to be edited and then assigned to a variable or field. It can only be used with strings.


INSERT

Usage:INSERT

Inserts a new, blank record into the current view of a database. The fields can then be assigned to before using PUT or CANCEL.


INT

Usage:i&=INT(x)

Returns the integer (in other words the whole number) part of the floating-point expression x. The number is returned as a long integer.

Positive numbers are rounded down, and negative numbers are rounded up for example INT(-5.9) returns -5 and INT(2.9) returns 2. If you want to round a number to the nearest integer, add 0.5 to it (or subtract 0.5 if it is negative) before you use INT.

In the Calculator, you need to use INT to pass a number to a procedure which requires a long integer parameter. This is because the Calculator passes all numbers as floating-point by default.

See also INTF.


INTF

Usage: i=INTF(x)

Used in the same way as the INT function, but the value returned is a floating-point number. For example, INTF(1234567890123.4) returns 1234567890123.0

You may also need this when an integer calculation may exceed integer range.

See also INT.


INTRANS

Usage: i&=INTRANS

Finds out whether the current view is in a transaction. Returns -1 if it is in a transaction or 0 if it is not.

See also BEGINTRANS.


I/O functions

These functions are covered in detail in the 'Advanced Topics' chapter.

r%=IOA(h%,f%,var status%,var a1,var a2)

This has the same form as IOC, but it returns an error value of the request is not completed successfully. IOC should be used in preference to IOA.

IOC(h%,f%,var status%,var a1,var a2)

Make an I/O request with guaranteed completion. The device driver opened with handle h% performs the asynchronous I/O function f% with two further arguments, a1 and a2. The argument status% is set by the device driver. If an error occurs while making a request, status% is set to an appropriate values, but IOC always returns zero, not an error value. .An IOWAIT or IOWAITSTAT must be performed for each IOC. IOC should be used in preference to IOA.

r%=IOCANCEL(h%)

Cancels any outstanding asynchronous I/O request (IOC or IOA). Note, however, that the request will still complete, so the signal must be consumed using IOWAITSTAT.

r%=IOCLOSE(h%)

Closes a file with the handle h%.

r%=IOOPEN(var h%,name$,mode%)

Creates or opens a file called name$. Defines h% for use by other I/O functions. mode% specifies how to open the file. For unique file creation, use IOOPEN(var h%,addr%,mode%)

r%=IOREAD(h%,addr&,maxLen%)

r%=IOREAD(h%,addr%,maxLen%)

Reads from the file with the handle h%. address% is the address of a buffer large enough to hold a maximum of maxLen% bytes. The value returned to r% is the actual number of bytes read or, if negative, is an error value.

r%=IOSEEK(h%,mode%,var off&)

Seeks to a position in a file that has been opened for random access. mode% specifies how the offset argument off& is to be used. Values for mode% may be found in the 'Advanced topics' chapter. off& may be positive to move forwards or negative to move backwards. IOSEEK sets the variable off& to the absolute position set.

IOSIGNAL

Signals an asynchronous I/O function's completion.

r%=IOW(h%,func%,var a1,var a2)

The device driver opened with handle h% performs the synchronous I/O function func% with the two further arguments.

IOWAIT

Waits for an asynchronous I/O function to signal completion.

IOWAITSTAT var stat%

Waits for an asynchronous function, called with IOC or IOA, to complete.

IOWAITSTAT32 var stat&

Takes a 32-bit status word. IOWAITSTAT32 should be called only when you need to wait for completion of a request made using a 32-bit status word when calling an asynchronous OPX procedure.

The initial value of a 32-bit status word while it is still pending (i.e. waiting to complete) is &80000001 (KStatusPending32& in Const.oph: see the 'Calling Procedures' chapter for details of how to use this file). For a 16-bit status word the %pending value' is -46 (KErrFilePending%).

r%=IOWRITE(h%,addr&,length%)

r%=IOWRITE(h%,addr%,length%)

Writes length% bytes in a buffer at address% to the file with the handle h%.

IOYIELD

Ensures that any asynchronous handler set up with IOC or IOA is given a chance to run. IOYIELD must always be called before polling status words on the Series 5, i.e. before reading a 16-bit status word if IOWAIT or IOWAITSTAT have not been used first.

Note the following example when you use #:

On the Series 3c you would call IOSEEK using,

    ret%=IOSEEK(h%,mode%,#ptrOff%)

but on the Series 5 you would use,

    ret%=IOSEEK(h%,mode%,#ptrOff&)

passing the long integer ptrOff&.

See also the '32-bit addressing' section in the 'Advanced Topics' chapter.


KEY

Usage:k%=KEY

Returns the character code of the key last pressed, if there has been a keypress since the last use of the keyboard by INPUT, EDIT, GET, GET$, KEY, KEY$, MENU and DIALOG.

If no key has been pressed, zero is returned.

See Appendix D for a list of special key codes. You can use KMOD to check whether modifier keys (Shift, Control, Fn (on the Series 5), Psion (on the Series 3c) and Caps Lock) were used.

This command does not wait for a key to be pressed, unlike GET.


KEY$

Usage:k$=KEY$

Returns the last key pressed as a string, if there has been a keypress since the last use of the keyboard by INPUT, EDIT, GET, GET$, KEY, KEY$, MENU and DIALOG.

If no key has been pressed, a null string ("") is returned.

See Appendix D for a list of special key codes. You can use KMOD to check whether modifier keys (Shift, Control, Fn (on the Series 5), Psion (on the Series 3c) and Caps Lock) were used.

This command does not wait for a key to be pressed, unlike GET$.


KEYA

Usage:err%=KEYA(var stat%,var key%(1))

This is an asynchronous keyboard read function.

See the 'Advanced Topics' chapter for details.

Cancel with KEYC.


KEYC

Usage:err%=KEYC(var stat%)

Cancels the previously called KEYA function with status stat%. Note that KEYC consumes the signal (unlike IOCANCEL), so IOWAITSTAT should not be used after KEYC.

See the 'Advanced Topics' chapter for details.


KILLMARK

Usage:KILLMARK b%

Removes the bookmark b%, which has previously been returned by BOOKMARK, from the current view of a database.

See BOOKMARK, GOTOMARK.


KMOD

Usage:k%=KMOD

Returns a code representing the state of the modifier keys (whether they were pressed or not) at the time of the last keyboard access, such as a KEY function. The modifiers have these codes:

decimal code	hex code

2	Shift down

4	Control down

$4

8	Psion down

$6

16	Caps Lock on

$10

128	Fn down

$80

Constants for these codes are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

If there was no modifier, the function returns 0. If a combination of modifiers was pressed, the sum of their codes is returned - for example 20 is returned if Control (4) was held down and Caps Lock (16) was on.

Always use immediately after a KEY/KEY$/GET/GET$ statement.

The value returned by KMOD has one binary bit set for each modifier, as shown above. By using the logical operator AND on the value returned by KMOD you can check which of the bits are set, in order to see which modifier keys were held down. For more details on AND, see Appendix B.

Example:

PROC modifier:

LOCAL k%,mod%

  PRINT "Press a key" :k%=GET

  CLS :mod%=KMOD

  PRINT "Key code",k%,"with"

  IF mod%=0

    PRINT "no modifier"

  ENDIF

  IF mod% AND 2

    PRINT "Shift down"

  ENDIF

  IF mod% AND 4

    PRINT "Control down"

  ENDIF

  IF mod% AND 8REM only needed on Series 3c

    PRINT "Psion down"

  ENDIF

  IF mod% AND 16

    PRINT "Caps Lock on"

  ENDIF

  IF mod% AND 128REM only needed on Series 5

    PRINT "Fn down"

  ENDIF

ENDP


LAST

Usage:LAST

Positions to the last record in a data file.


LCLOSE

Usage:LCLOSE

Closes the device opened with LOPEN. (The device is also closed automatically when a program ends.)


LEFT$

Usage:b$=LEFT$(a$,x%)

Returns the leftmost x% characters from the string a$.

For example if n$ has the value Charles, then b$=LEFT$(n$,3) assigns Cha to b$.


LEN

Usage:a%=LEN(a$)

Returns the number of characters in a$.

E.g. if a$ has the value 34 Kopechnie Drive then LEN(a$) returns 18.

You might use this function to check that a data file string field is not empty before displaying:

    IF LEN(A.client$)

    PRINT A.client$

    ENDIF


LENALLOC

Usage:

len&=LENALLOC(pcell&)

len%=LENALLOC(pcell%)

Returns the length of the previously allocated cell at pcell& (pcell% on the Series 3c).

Cells are allocated lengths that are the smallest multiple of four greater than the size originally requested. An error will be raised if the cell address argument is not in the range known by the heap.

See also SETFLAGS if you require the 64K limit to be enforced on the Series 5. If the flag is set to restrict the limit, len& is guaranteed to fit into an integer.


LINKLIB

Usage:LINKLIB cat%

Link any libraries that have been loaded using LOADLIB.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


LN

Usage:a=LN(x)

Returns the natural (base e) logarithm of x.

Use LOG to return the base 10 log of a number.


LOADLIB

Usage:ret%=LOADLIB(var cat%,name$,link%)

Load and optionally link a DYL that is not in the ROM. If successful, this writes the category handle to cat% and returns zero. The DYL is shared in memory if already loaded by another process.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


LOADM

Usage:LOADM module$

Loads a translated OPL module so that procedures in that module can be called. Until a module is loaded with LOADM, calls to procedures in that module will give an error.

module$ is a string containing the name of the module. Specify the full file name only where necessary.

Example: LOADM "MODUL2"

Up to 8 modules on the Series 5, or 4 on the Series 3c, can be in memory at any one time, including the top level module; if you try to LOADM a ninth (fifth) module, you get an error. Use UNLOADM to remove a module from memory so that you can load a different one.

By default, LOADM always uses the folder of the top level module. It is not affected by the SETPATH command.

By default, LOADM always uses the directory of the initial running program, or the one specified by a OPA application. It is not affected by the SETPATH command.


LOC

Usage:a%=LOC(a$,b$)

Returns an integer showing the position in a$ where b$ occurs, or zero if b$ doesn't occur in a$. The search matches upper and lower case.

Example: LOC("STANDING","AND") would return the value 3 because the substring AND starts at the third character of the string STANDING.


LOCAL

Usage:LOCAL variables

Used to declare variables which can be referenced only in the current procedure. Other procedures may use the same variable names to create new variables. Use GLOBAL to declare variables common to all called procedures.

The variables may be of 4 types, depending on the symbol they end with:

Variable names not ending with $, %, & or () are floating-point variables, for example price, x

Those ending with a % are integer variables, for example x%, sales92%

Those ending with an & are long integer variables, for example x&, sales92&.

Those ending with a $ are string variables. String variable names must be followed by the maximum length of the string in brackets, for example names$(12), a$(3)

Array variables have a number immediately following them in brackets which specifies the number of elements in the array. Array variables may be of any type, for example: x(6),y%(5),f$(5,12),z&(3)

When declaring string arrays, you must give two numbers in the brackets. The first declares the number of elements, the second declares their maximum length. For example surname$(5,8) declares five elements, each up to 8 characters long.

Variable names may be any combination of up to 32 numbers, alphabetic letters and the underscore character. They must start with a letter or an underscore. The length includes the %, & or $ sign, but not the () in string and array variables.

Variable names may be any combination of up to 8 numbers and alphabetic letters. They must start with a letter. The length includes the %, & or $ sign, but not the () in string and array variables.

More than one GLOBAL or LOCAL statement may be used, but they must be on separate lines, immediately after the procedure name.

See also GLOBAL, CONST and the 'Variables and Constants' chapter.


LOCK

Usage:LOCK ON

or LOCK OFF

Mark an OPA (OPL application) as locked or unlocked. When an OPA is locked with LOCK ON, the System screen will not send it events to change files or quit.

If, for example, you move to the task list or the document name in the System screen try to stop that running OPA by using the %Close file' button or Ctrl+E respectively, a message will appear, indicating that the OPA cannot close down at that moment.

If, for example, you move on to the file list in the System screen and press Delete to try to stop that running OPA, a message will appear, indicating that the OPA cannot close down at that moment.

You should use LOCK ON if your OPA uses a command, such as EDIT, MENU or DIALOG, which accesses the keyboard. You might also use it when the OPA is about to go busy for a considerable length of time, or at any other point where a clean exit is not possible. Do not forget to use LOCK OFF as soon as possible afterwards.

An OPA is initially unlocked.


LOG

Usage:a=LOG(x)

Returns the base 10 logarithm of x.

Use LN to find the base e (natural) log.


LOPEN

Usage:LOPEN device$

Opens the device to which LPRINTs are to be sent.

No LPRINTs can be sent until a device has been opened with LOPEN.

You can open any of these devices:

The parallel port, with LOPEN "PAR:A"

The serial port, with LOPEN "TTY:A"

A file on the Psion

A file on an attached computer. LOPEN the file name, e.g. on a PC:

LOPEN "REM::C:\BAK\MEMO.TXT"

or on an Apple Macintosh:

LOPEN "REM::HD40:ME:MEMO5"

Any existing file of the name given will be overwritten when you print to it.

Only one device may be open at any one time. Use LCLOSE to close the device. (It also closes automatically when a program finishes running.)

See Appendix C.


LOWER$

Usage:b$=LOWER$(a$)

Converts any upper case characters in the string a$ to lower case and returns the completely lower case string.

E.g. if a$="CLARKE", LOWER$(a$) returns the string "clarke"

Use UPPER$ to convert a string to upper case.


LPRINT

Usage:LPRINT list of expressions

Prints a list of items, in the same way as PRINT, except that the data is sent to the device most recently opened with LOPEN.

The expressions may be quoted strings, variables, or the evaluated results of expressions. The punctuation of the LPRINT statement (commas, semicolons and new lines) determines the layout of the printed text, in the same way as PRINT statements.

If no device has been opened with LOPEN you will get an error.

See PRINT for displaying to the screen.

See LOPEN for opening a device for LPRINT.

See Appendix C for an overview of printing from OPL. See also Printer OPX in the 'Using OPXs on the Series 5' chapter for details of more advanced printing features.


MAX

Usage:m=MAX(list)

or m=MAX(array(),element)

Returns the greatest of a list of numeric items.

The list can be either:

A list of variables, values and expressions, separated by commas

or

The elements of a floating-point array.

When operating on an array, the first argument must be the array name followed by (). The second argument, separated from the first by a comma, is the number of array elements you wish to operate on for example m=MAX(arr(),3) would return the value of the largest of elements arr(1), arr(2) and arr(3).


mCARD

Usage:mCARD title$,n1$,k1%

ormCARD title$,n1$,k1%,n2$,k2% etc.

Defines a menu. When you have defined all of the menus, use MENU to display them.

title$ is the name of the menu. From one to eight items on the menu may be defined, each specified by two arguments. The first is the item name, and the second the keycode for a shortcut key. This specifies a key which, when pressed together with the Ctrl (Psion on the Series 3c) key, will select the option. If the keycode is for an upper case key, the shortcut key will use both the Shift and Ctrl (Psion) keys.

The options can be divided into logical groups by displaying a separating line under the final option in a group. To do this, pass the negative value corresponding to the shortcut key keycode for the final option in the group. For example, -%A specifies shortcut key Ctrl+Shift+A (Shift-Psion-A in Series 3c) and displays a separating line under the associated option in the menu.

Series 5 supports the following menu features

Menu items without shortcuts, by specifying shortcut values between 1 and 32. For these items the value specified is still returned if the item is selected.

Menu items which are dimmed, or which have checkboxes or option buttons (sometimes known as radio buttons).

These extra properties are controlled by adding the following bits to the shortcut key keycode.

Effect	value

menu item dimmed	$1000

item has check-box	$0800

start of an option button list	$0900

middle of an option button list	$0A00

end of an option button list	$0B00

checkbox/option button symbol on	$2000

checkbox/option button symbol indeterminate	$4000

Constants for these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

The start, middle and end option buttons are for specifying a group of related items that can be selected exclusively (i.e. if one item is selected then the others are deselected). The number of middle option buttons is variable. A single menu card can have more than one set of option buttons and checkboxes, but option buttons in a set should be kept together. For speed, OPL does not check the consistency of these items' specification.

If a separating line is required when any of these effects had been added, you must be sure to negate the whole value, not just the shortcut key keycode. In the example,

mCARD "Options","View1",%A OR $2900,"View2",-(%B OR $B00), "Another option",%C

the second shortcut key keycode and its flag value is correctly negated to display a separating line.

A %Too wide' error is raised if the menu title length is greater than or equal to 40.  Shortcut values must be alphabetic character codes or numbers between the values of 1 and 32. Any other values will raise an %Invalid arguments' error.

If any menu item fails to be added successfully, a menu is discarded.  It is therefore incorrect to ignore mCARD errors by having  an ONERR label around an mCARD call on the Series 5. If you do, the menu is discarded and a 'Structure fault' will be raised on using mCARD without first using mINIT again.  See MENU for an example of this.

See mCASC for cascaded menu items.

See also the 'Friendlier Interaction' chapter.


mCASC

Usage:mCASC title$,item1$,hotkey1%,item2$,hotkey2%

Creates a cascade for a menu, on which less important menu items can be displayed. The cascade must be defined before use in a menu card. For example, a %Bitmap' cascade under the File menu of a possible OPL drawing application could be defined like this:

    mCASC "Bitmap","Load",%L,"Merge",'m

    mCARD "File","New",%n,"Open",%o,"Save",%s,"Bitmap&gt;",16,"Exit",%e

The trailing &gt; character specifies that a previously defined cascade item is to be used in the menu at this point: it is not displayed in the menu item. A cascade has a filled arrow head displayed along side it in the menu. The cascade title in mCASC is also used only for identification purposes and is not displayed in the cascade itself. This title needs to be identical to the menu item text apart from the &gt;. For efficiency, OPL doesn't check that a defined cascade has been used in a menu and an unused cascade will simply be ignored. To display a &gt; in a cascaded menu item, you can use &gt;&gt;.

Shortcut keys used in cascades may be added to the appropriate constant values as for mCARD to enable checkboxes, option buttons and dimming of cascade items.

As is typical for cascade titles, a shortcut value of 16 is used in the example above. This prevents the display or specification of any shortcut key. However, it is possible to define a shortcut key for a cascade title if required, for example to cycle through the options available in a cascade.

See mCARD, MENU, mINIT.


MEAN

Usage:m=MEAN(list)

orm=MEAN(array(),element)

Returns the arithmetic mean (average) of a list of numeric items.

The list can be either:

A list of variables, values and expressions, separated by commas

or

The elements of a floating-point array.

When operating on an array, the first argument must be the array name followed by (). The second argument, separated from the first by a comma, is the number of array elements you wish to operate on for example m=MEAN(arr(),3) would return the average of elements arr(1), arr(2) and arr(3).

This example displays 15.0:

    a(1)=10

    a(2)=15

    a(3)=20

    PRINT MEAN(a(),3)


MENU

Usage:val%=MENU

orval%=MENU(var init%)

Displays the menus defined by mINIT, mCARD and mCASC, and waits for you to select an item. Returns the shortcut key keycode of the item selected, as defined in mCARD, in lower case.

If you cancel the menu by pressing Esc, MENU returns 0.

If the name of a variable is passed, it sets the initial menu pane and item to be highlighted. init% should be 256*(menu%)+item%; for both menu% and item%, 0 specifies the first, 1 the second and so on. If init% is 517 (=256*2+5), for example, this specifies the 6th item on the third menu.

If init% was passed, MENU writes back to init% the value for the item which was last highlighted on the menu. You can then use this value when calling the menu again.

You only need to use this technique if you have more than one menu in your program, maintaining one initialisation variable for each.

When the menu is closed and reopened the highlighted item on reopening is the one last selected.

It is necessary to use MENU(init%), passing back the same variable each time the menu is opened  if you wish the menu to reopen with the highlight set on the last selected item.

On the Series 5, it is incorrect to ignore mCARD and mCASC errors by having an ONERR label around an mCARD or mCASC call. If you do, the menu is discarded and a 'Structure fault' will be raised on using mCARD, mCASC or MENU without first using mINIT again.

The following bad code will not display the menu:

    mINIT

    ONERR errIgnore1

    mCARD "Xxx","ItemA",0REM bad shortcut

    errIgnore1::

    ONERR errIgnore2

    mCARD "Yyy",""

    REM 'Structure fault' error (mINIT discarded)

    errIgnore2::

    ONERR OFF

    MENUREM 'Structure fault' again


MID$

Usage:m$=MID$(a$,x%,y%)

Returns a string comprising y% characters of a$, starting at the character at position x%.

E.g. if name$="McConnell" then MID$(name$,3,4) would return the string Conn.


MIN

Usage:m=MIN(list)

orm=MIN(array(),element)

Returns the smallest of a list of numeric items.

The list can be either:

A list of variables, values and expressions, separated by commas

or

The elements of a floating-point array.

When operating on an array, the first argument must be the array name followed by (). The second argument, separated from the first by a comma, is the number of array elements you wish to operate on for example m=MIN(arr(),3) would return the minimum of elements arr(1), arr(2) and arr(3).


mINIT

Usage:mINIT

Prepares for definition of menus, cancelling any existing menus. Use mCARD and mCASC (on the Series 5 only) to define each menu, then MENU to display them.

On the Series 5, it is incorrect to ignore mCARD or mCASC errors by having  an ONERR label around an mCARD or mCASC call. If you do, the menu is discarded and a 'Structure fault' will be raised if there is an occurrence of mCARD, mCASC or MENU without first using mINIT again.  See also MENU for an example of this.


MINUTE

Usage:m%=MINUTE

Returns the current minute number from the system clock (0 to 59).

E.g. at 8.54am MINUTE returns 54.


MKDIR

Usage:MKDIR name$

Creates a new folder/directory.

For example, MKDIR "C:\MINE\TEMP" creates a C:\MINE\TEMP folder, also creating C:\MINE if it is not already there.

For example, MKDIR "M:\MINE\TEMP" creates a M:\MINE\TEMP directory, also creating M:\MINE if it is not already there.


MODIFY

Usage:MODIFY

Allows the current record of a view to be modified without moving the record. The fields can then be assigned to before using PUT or CANCEL.


MONTH

Usage:m%=MONTH

Returns the current month from the system clock as an integer between 1 and 12.

E.g. on 12th March 1992 m%=MONTH returns 3 to m%.

Constants for the month numbers are given in Const.oph.  For details of how to use this file, see the 'Calling Procedures chapter and for a listing of it see Appendix E.


MONTH$

Usage:m$=MONTH$(x%)

Converts x%, a number from 1 to 12, to the month name, expressed as a three-letter mixed case string.

E.g. MONTH$(1) returns the string Jan.

Constants for the month numbers are given in Const.oph.  For details of how to use this file, see the "Calling Procedures" chapter and for a lisitng of it see Appendix E.


mPOPUP

Usage: mPOPUP(x%,y%,posType%,item1$,key1%,item2$,key2%,...)

Presents a popup menu. mPOPUP returns the value of the keypress used to exit the popup menu, this being 0 if Esc is pressed.

Note that mPOPUP defines and presents the menu itself, and should not and need not be called from inside the mINIT...MENU structure.

posType% is the position type controlling which corner of the popup menu x%,y% specifies and can take the values,

posType%	corner

0	top left

1	top right

2	bottom left

3	bottom right

Constants for these corner values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

item$ and key% can take the same values as for mCARD, with key% taking the same constant values to specify checkboxes, option buttons and dimmed items. However, cascades in popup menus are not supported.

For example

    mPOPUP (0,0,0,"Continue",%c,"Exit",%e)

specifies a popup menu with 0,0 as its top left-hand corner with the items %Continue' and %Exit', with the shortcut keys Ctrl+C and Ctrl+E respectively.

See also mCARD.


NEWOBJ

Usage:pobj%=NEWOBJ(num%,clnum%)

Create a new object by category number num% belonging to the class clnum%, returning the object handle on success or zero if out of memory.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


NEWOBJH

Usage:pobj%=NEWOBJH(cat%,clnum%)

Create a new object by category handle cat% belonging to the class clnum%, returning the object handle on success or zero if out of memory.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


NEXT

Usage:NEXT

Positions to the next record in the current data file.

If NEXT is used after the end of a file has been reached, no error is reported but the current record is null and the EOF function returns true.


NUM$

Usage:n$=NUM$(x,y%)

Returns a string representation of the integer part of the floating-point number x, rounded to the nearest whole number. The string will be up to y% characters wide.

If y% is negative then the string is right-justified for example NUM$(1.9,-3) returns  "  2" where there are two spaces to the left of the 2.

If y% is positive no spaces are added: e.g. NUM$(-3.7,3) returns "-4".

If the string returned to n$ will not fit in the width y%, then the string will just be asterisks; for example NUM$(256.99,2) returns "**".

See also FIX$, GEN$, SCI$.


ODBINFO

Usage:ODBINFO var info%()

Provided for advanced use only, this keyword allows you to use OS and CALL to access data file interrupt functions not accessible with OPL keywords.

See the 'Advanced Topics' chapter for more details.

The Series 5 supports calls to the operating system using OPXs.  Full description of their design is beyond the scope of this manual and is documented instead in the EPOC32 C++ Software Development Kit (SDK) which is available from Psion Software plc. See the 'Using OPXs on the Series 5' chapter for details of built-in OPXs.


OFF

Usage:OFF

orOFF x%

Switches the Psion off.

When you switch back on, the statement following the OFF command is executed, for example:

    OFF :PRINT "Hello again"

If you specify an integer, x%, greater than 2 (between 2 and 16383 on the Series 3c; 16383 is about 4.5 hours), the machine switches off for that number of seconds and then automatically turns back on and continues with the next line of the program. However, during this time the machine may be switched on by an alarm, and of course you can turn it on as usual.

The minimum time to switch off is 5 seconds on the Series 5. EPOC32 also prevents switch off if there's an absolute timer outstanding and due to go off in less than 5 seconds.

Be careful how you use this command. If, due to a programming mistake, a program uses OFF in a loop, you may find it impossible to switch the Psion back on, and may have to reset the computer.


ONERR

Usage:

    ONERR label  or  ONERR label::

    ...

    ONERR OFF

ONERR label:: establishes an error handler in a procedure. When an error is raised, the program jumps to the label:: instead of the program stopping and an error message being displayed.

The label may be up to 32 characters long starting with a letter or an underscore.

The label may be up to 8 characters long starting with a letter.

It ends with a double colon (::), although you don't need to use this in the ONERR statement.

ONERR OFF disables the ONERR command, so that any errors occurring after the ONERR OFF statement no longer jump to the label.

It is advisable to use the command ONERR OFF immediately after the label:: which starts the error handling code.

See the %Error Handling' chapter for full details.


OPEN

Usage:OPEN query$,log,f1,f2,...

Opens an existing table (or a %view' of a table) from an existing database, giving it the logical view name log and handles for the fields f1, f2. log can be any letter in the range A to Z.

query$ specifies the database file, the required table and fields to be selected.

For example:

OPEN "clients SELECT name, tel FROM phone",D,n$,t$

The database name here is clients and the table name is phone. The field names are enclosed by the keywords SELECT and FROM and their types should correspond with the list of handles  (i.e. n$ indicates that the name field is a string).

Replacing the list of field names with * selects all the fields from the table.

query$ is also used to specify an ordered view and if a suitable index has been created, then it will be used. See the %Database OPX' section in the 'Using OPXs on the Series 5' chapter. For example,

OPEN "people SELECT name,number FROM phoneBook ORDER BY name

   ASC, number DESC",G,n$,num%

would open a view with name fields in ascending alphabetical order and if any names were the same then the number field would be used to order these records in descending numerical order.

If the specification of the database includes embedded spaces, for example in the name of the folder, the name must be enclosed in quotes, so for example the following correctly fails:

OPEN "c:\folder with spaces\file with spaces",a,name$

whereas the following works:

OPEN """c:\folder with spaces\file with spaces""",a,name$

Compatibility with the Series 3c

As on the Series 3c (see below), query$ may contain just the filename. In this case a table with the default name Table1 would be opened if it exists. The field names would then be unimportant as access will be given to as many fields as there are supplied handles. The type indicators on the field handles must match the types of the fields.

Usage:OPEN file$,log,f1,f2...

Opens an existing data file file$, giving it the logical file name log, and giving the fields the names f1, f285

You need only specify those fields which you intend to update or append, though you cannot miss out a field.

The opened file is then referred to within the program by its logical name (A, B, C or D).

Up to 4 files can be open at once.

Example:

OPEN "clients",A,name$,addr$

See also the %Data File Handling' chapter and the 'Series 5 Database Handling' chapter.

See also CREATE, USE and OPENR.


OPENR

Usage:

OPEN query$,log,f1,f2,...

OPENR file$,log,f1,f2...

This command works exactly like OPEN except that the opened file is read-only - in other words, you cannot APPEND, UPDATE or PUT the records it contains.

This means that you can run two separate programs at the same time, both sharing the same file.


OS

Usage:a%=OS(i%,addr1%)

or a%=OS(i%,addr1%,addr2%)

Calls the Operating System interrupt i%, reading the values of all returned 8086 registers and flags. The CALL function, although simpler to use, does not allow the AL register to be passed and no flags are returned, making it suitable only for certain interrupts.

The input registers are passed at the address addr1%. The output registers are returned at the address addr2% if supplied, otherwise they are returned at addr1%. Both addresses can be of an array, or of six consecutive integers.

Register values are stored sequentially as 6 integers and represent the register values in this order: AX, BX, CX, DX, SI and DI. The interrupt's function number, if required, is passed in AH.

The output array must be large enough to store the 6 integers returned in all cases, irrespective of the interrupt being called.

The value returned by OS is the flags register. The Carry flag, which is relevant in most cases, is in bit 0 of the returned value, so (a% AND 1) will be %True' if Carry is set. Similarly, the Zero flag is in bit 6, the Sign flag in bit 7 and the Overflow flag in bit 10.

For example, to find COS(pi/4):

PROC add:

LOCAL a%,b%,c%,d%,si%,di%

LOCAL result,cosArg,flags%

  cosArg=pi/4

  si%=ADDR(cosArg)

  di%=ADDR(result)

  ax%=$0100REM AH=1 for cosine

  flags%=OS(140,addr(ax%))

ENDP

The OS function requires extensive knowledge of the Operating System and related programming techniques.

See also CALL.

The Series 5 supports calls to the operating system using OPXs.  Full description of their design is beyond the scope of this manual and is documented instead in the EPOC32 C++ Software Development Kit (SDK) which is available from Psion Software plc. See the 'Using  OPXs on the Series 5' chapter for details of built-in OPXs.


PARSE$

Usage:p$=PARSE$(f$,rel$,var off%())

Returns a full file specification from the filename f$, filling in any missing information from rel$.

The offsets to the filename components in the returned string is returned in off%() which must be declared with at least 6 integers:

off%(1)	filing system offset (1 always)

off%(2)	device offset (1 always on Series 5, since the filing system is not a component of filename on the Series 5)

off%(3)	path offset

off%(4)	filename offset

off%(5)	file extension offset

off%(6)	flags for wildcards in returned string

The flag values in offset%(6) are:

0	no wildcards

1	wilcard in filename

2	wilcard in file extension

3	wildcard in both

Constants for the array subscripts and the flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

If rel$ is not itself a complete file specification, the current filing system, device and/or path are used as necessary to fill in the missing parts.

f$ and rel$ should be separate strings.

Example:

p$=PARSE$("NEW","C:\Documents\*.MBM",x%())

sets p$ to C:\Documents\NEW.MBM and x%() to (1,1,3,14,17,0).

Example:

p$=PARSE$("NEW","LOC::M:\ODB\*.ODB",x%())

sets p$ to LOC::M:\ODB\NEW.ODB and x%() to (1,6,8,13,16,0).


PATH

Usage:PATH name$

Gives the folder (directory on the Series 3c) to use for an OPA's files.

This can only be used between APP and ENDA.

See the 'Advanced Topics' chapter for more details of OPAs.

OPL Applications do not have paths on the Series 5, so this command is not used.


PAUSE

Usage:PAUSE x%

Pauses the program for a certain time, depending on the value of x%:

x%	result

0	waits for a key to be pressed

+ve	pauses for x% twentieths of a second

-ve	pauses for x% twentieths of a second or until a key is pressed

So PAUSE 100 would make the program pause for 100/20 = 5 seconds, and PAUSE -100 would make the program pause for 5 seconds or until a key is pressed.

If x% is less than or equal to 0, a GET, GET$, KEY or KEY$ will return the key press which terminated the pause. If you are not interested in this keypress, but in the one which follows it, clear the buffer after the PAUSE with a single KEY function: PAUSE -10 :KEY

You should be especially careful about this if x% is negative, since then you cannot tell whether the pause was terminated by a keypress or by the time running out.

PAUSE should not be used in conjunction with GETEVENT or GETEVENT32 because events are discarded by PAUSE.


PEEK
PEEK functions

The PEEK functions find the values stored in specific bytes.

p%=PEEKB(x&)

p%=PEEKB(x%)

Returns the integer value of the byte at address x& (x%)

p%=PEEKW(x&)

p%=PEEKW(x%)

Returns the integer at address x& (x%)

p&=PEEKL(x&)

p&=PEEKL(x%)

Returns the long integer value at address x& (x%)

p=PEEKF(x&)

p=PEEKF(x%)

Returns the floating-point value at address x& (x%)

p$=PEEK$(x&)

p$=PEEK$(x%)

Returns the string at address x& (x%)

Usually you would find out the byte address with the ADDR function. For example, if var% has the value 7, PEEKW(ADDR(var%)) returns 7.

The different types are stored in different ways across bytes:

Integers are stored in two bytes. The first byte is the least significant byte, for example:

1   0= 1

0   1= 256

ADDR returns the address of the first (least significant) byte.

Long integers are stored in four bytes, the least significant first and the most significant last, for example:

0   0   1   0= 65536

ADDR returns the address of the first (least significant) byte.

Strings are stored with one character per byte, with a leading byte containing the string length, e.g.:

3   65  66  67= "ABC"

Each letter is stored as its character code - for example, A as 65.

For example, if var$="ABC", PEEK$(ADDR(var$)) will return the string ABC. ADDR returns the address of the length byte.

Floating-point numbers are stored under IEEE format, across eight bytes. PEEKF automatically reads all eight bytes and returns the number as a floating-point. For example if var=1.3 then PEEKF(ADDR(var)) returns 1.3.

You can use ADDR to find the address of the first element in an array, for example ADDR(x%(), you can also specify individual elements of the array, for example ADDR(x%(2)).

See also the POKE commands and ADDR. See also the 'Series 5 32-bit addressing' section in the 'Advanced Topics' chapter.


PI

Usage:p=PI

Returns the value of  <symbol codepoint=112 font="symbol(3.14... ).


POINTERFILTER

Usage:POINTERFILTER filter%,mask%

Filters pointer events in the current window out or back in. Add the following flags together to achieve the desired filter% and mask%:

event	value

none	0

enter/exit	1

drag	4

Constants for these values are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

The bits set in filter% specify the settings to be used, 1 to filter out the event and 0 to remove the filter. Only those bits set in mask% will be used for filtering.  This allows the current setting of a particular bit to be left unchanged if that bit is zero in the mask. (i.e. mask% dictates what to change and filter% specifies the setting to which it should be changed). For example,

    mask%=5

    REM  =1+4 - allows enter/exit and drag settings to be changed

    POINTERFILTER 1,mask%

    REM filters out enter/exit, but not dragging

    ...

    POINTERFILTER 4,mask%

    REM filters out drag and reinstates enter/exit

Initially the events are not filtered out.

See also GETEVENT32, GETEVENTA32.


POKE
POKE commands

The POKE commands store values in specific bytes.

 POKEB x&,y%

 POKEB x%,y%

Stores the integer value y% (less than 256) in the single byte at address x& (x%)

 POKEW x&,y%

 POKEW x%,y%

Stores the integer y% across two consecutive bytes, with the least significant byte in the lower address, that is x& (x%)

 POKEL x&,y&

 POKEL x%,y&

Stores the long-integer y& in bytes starting at address x& (x%)

 POKEF x&,y

 POKEF x%,y

Stores the floating-point value y in bytes starting at address x& (x%)

 POKE$ x&,y$

 POKE$ x%,y$

Stores the string y$ in bytes starting at address x& (x%)

Use ADDR to find out the address of your declared variables.

Casual use of these commands can result in the loss of data in the Series 3c.

See PEEK for more details of how the different types are stored across bytes.


POS

Usage:p%=POS

Returns the number of the current record in the current view.  You are advised to use bookmarks instead of POS on the Series 5.

Returns the number of the current record in the current data file, from 1 (the first record) upwards.

A Series 5 file has no limit on the number of records and Series 3c files can have up to 65534 records. However, integers can only be in the range -32768 to +32767. Record numbers above 32767 are therefore returned like this:

record value	returned by POS

32767	32767

32768	32768

32769	32767

32770	32766

...	...

65534	2

To display record numbers, you can use this check:

    IF POS&lt;0

    PRINT 65536+POS

    ELSE

    PRINT POS

    ENDIF

Note that on the Series 5 the number of the current record may be greater than or equal to 65535 and hence values may need to be truncated to fit into p%, giving inaccurate results. You are particuarly advised to use bookmarks when dealing with a large number of records. Note, however, that the value returned by POS can become inaccurate if used in conjunction with bookmarks and multiple views on a table. Accuracy can be restored by using FIRST or LAST on the current view.

See BOOKMARK, GOTOMARK, KILLMARK.


POSITION

Usage:POSITION x%

Makes record number x% the current record in the current view.  By using bookmarks and editing the same table via different views, positional accuracy can be lost and POSITION x% could access the wrong record. Accuracy can be restored by using FIRST or LAST on the current view.

POS and POSITION still exist mainly for reasons of compatibility with the Series 3c and you are advised to use bookmarks instead on the Series 5.

See BOOKMARK, GOTOMARK, KILLMARK.

Makes record number x% the current record in the current data file.

If x% is greater than the number of records in the file then the EOF function will return true.


POSSPRITE

Usage:POSSPRITE x%,y%

Set the position of the current sprite to pixel x%,y%.

On the Series 5, sprites are handled by a built-in OPX. See the 'Using OPXs on the Series 5' chapter for more details.


PRINT

Usage:PRINT list of expressions

Displays a list of expressions on the screen. The list can be punctuated in one of these ways:

If items to be displayed are separated by commas, there is a space between them when displayed.

If they are separated by semicolons, there are no spaces.

Each PRINT statement starts a new line, unless the preceding PRINT ended with a semicolon or comma.

There can be as many items as you like in this list. A single PRINT on its own just moves to the next line.

Examples: On 1st January 1997,

code	display

PRINT "TODAY is", :PRINT DAY;".";MONTH;".";YEAR	TODAY is 1.1.1997

PRINT 1	1

PRINT "Hello"	Hello

PRINT "Number",1	Number 1

See also LPRINT, gUPDATE, gPRINT, gPRINTB, gPRINTCLIP, gXPRINT.


PUT

Usage:PUT

Marks the end of a database's INSERT or MODIFY phase and makes the changes permanent.

See INSERT, MODIFY, CANCEL.


RAD

Usage:r=RAD(x)

Converts x from degrees to radians.

All the trigonometric functions assume angles are specified in radians, but it may be easier for you to enter angles in degrees and then convert with RAD.

Example:

PROC xcosine:

LOCAL angle

  PRINT "Angle (degrees)?:";

  INPUT angle

  PRINT "COS of",angle,"is",

  angle=RAD(angle)

  PRINT COS(angle)

  GET

ENDP

(The formula used is (PI*x)/180).

To convert from radians to degrees use DEG.


RAISE

Usage:RAISE x%

Raises an error.

The error raised is error number x%. This may be one of the errors listed in the %Error Handling' chapter, or a new error number defined by you.

The error is handled by the error processing mechanism currently in use &#151; either OPL's own, which stops the program and displays an error message, or the ONERR handler if you have ONERR on.


TRAP RAISE

Usage:TRAP RAISE x%

Sets the value of ERR to x% and clears the trap flag.

For a full explanation of the use of RAISE, see the %Error Handling' chapter.


RANDOMIZE

Usage: RANDOMIZE x&

Gives a %seed' (start-value) for RND.

Successive calls of the RND function produce a sequence of random numbers. If you use RANDOMIZE to set the seed back to what it was at the beginning of the sequence, the same sequence will be repeated.

For example, you might want to use the same %random' values to test new versions of a procedure. To do this, precede the RND statement with the statement RANDOMIZE value. Then to repeat the sequence, use RANDOMIZE value again.

Example:

PROC SEQ:

LOCAL g$(1)

  WHILE 1

    PRINT "S: set seed to 1"

    PRINT "Q: quit"

    PRINT "other key: continue"

    g$=UPPER$(GET$)

    IF g$="Q"

      BREAK

    ELSEIF g$="S"

      PRINT "Setting seed to 1"

      RANDOMIZE 1

      PRINT "First random no:"

    ELSE

      PRINT "Next random no:"

    ENDIF

    PRINT RND

  ENDWH

ENDP


REALLOC

Usage:

pcelln&=REALLOC(pcell&,size&)

pcelln%=REALLOC(pcell%,size%)

Change the size of a previously allocated cell at pcell& (pcell%) to size& (size%), returning the new cell address or zero if there is not enough memory.

 Cells are allocated lengths that are the smallest multiple of four greater than the size requested. An error will be raised if the cell address argument is not in the range known by the heap.

See also SETFLAGS if you require the 64K limit to be enforced on the Series 5. If the flag is set to restrict the limit, pcelln& is guaranteed to fit into an integer.

See also the 'Advanced Topics' chapter.


RECSIZE

Usage: r%=RECSIZE

Returns the number of bytes occupied by the current record.

Use this function to check that a record may have data added to it without overstepping the 1022-character limit.

Example:

PROC rectest:

LOCAL n$(20)

  OPEN "name",A,name$

  PRINT "Enter name:",

  INPUT n$

  IF RECSIZE&lt;=(1022-LEN(n$))

    A.name$=n$

    APPEND

  ELSE

    PRINT "Won't fit in record"

  ENDIF

ENDP

This function is not required on the Series 5 because the character limit is larger than the OPL record length (i.e. 32<symbol codepoint=180 font="symbol256 characters, the number of fields multiplied by the maximum string length).


REM

Usage: REM text

Precedes a remark you include to explain how a program works. All text after the REM up to the end of the line is ignored.

When you use REM at the end of a line you need only precede it with a space, not a space and a colon.

Examples:

INPUT a :b=a*.175  REM b=TAX

INPUT a :b=a*.175 :REM b=TAX


RENAME

Usage:RENAME file1$,file2$

Renames file1$ as file2$. You can rename any type of file.

You cannot use wildcards.

You can rename across directories RENAME "\dat\xyz.abc","\xyz.abc" is OK. If you do this, you can choose whether or not to change the name of the file.

Example:

    PRINT "Old name:" :INPUT a$

    PRINT "New name:" :INPUT b$

    RENAME a$,b$


REPT$

Usage:r$=REPT$(a$,x%)

Returns a string comprising x% repetitions of a$.

For example, if a$="ex", r$=REPT$(a$,5) returns exexexexex.


RETURN

Usage:RETURN

or RETURN variable

Terminates the execution of a procedure and returns control to the point where that procedure was called (ENDP does this automatically).

RETURN variable does this as well, but also passes the value of variable back to the calling procedure. The variable may be of any type. You can return the value of any single array element - for example RETURN x%(3). You can only return one variable.

RETURN on its own, and the default return through ENDP, causes the procedure to return the value 0 or a null string.


RIGHT$

Usage:r$=RIGHT$(a$,x%)

Returns the rightmost x% characters of a$.

Example:

    PRINT "Enter name/ref",

    INPUT c$

    ref$=RIGHT$(c$,4)

    name$=LEFT$(c$,LEN(c$)-4)


ROLLBACK

Usage: ROLLBACK

Cancels the current transaction on the current view. Changes made to the database with respect to this particular view since BEGINTRANS was called will be discarded.

See also BEGINTRANS, COMMITTRANS.


RMDIR

Usage:RMDIR str$

Removes the directory given by str$. You can only remove empty directories.


RND

Usage:r=RND

Returns a random floating-point number in the range 0 (inclusive) to 1 (exclusive).

To produce random numbers between 1 and n e.g. between 1 and 6 for a dice use the following statement: f%=1+INT(RND*n)

RND produces a different number every time it is called within a program. A fixed sequence can be generated by using RANDOMIZE. You might begin by using RANDOMIZE with an argument generated from MINUTE and SECOND (or similar), to seed the sequence differently each time.

Example:

  PROC rndvals:

  LOCAL i%

    PRINT "Random test values:"

    DO

      PRINT RND

      i%=i%+1

      GET

    UNTIL i%=10

  ENDP


SCI$

Usage:s$=SCI$(x,y%,z%)

Returns a string representation of x in scientific format, to y% decimal places and up to z% characters wide.

Examples:

    SCI$(123456,2,8)="1.23E+05"

    SCI$(1,2,8)="1.00E+00"

    SCI$(1234567,1,-8)=" 1.2E+06"

If the number does not fit in the width specified then the returned string contains asterisks.

If z% is negative then the string is right-justified.

See also FIX$, GEN$, NUM$.


SCREEN

Usage:SCREEN width%,height%

orSCREEN width%,height%,x%,y%

Changes the size of the window in which text is displayed. x%,y% specify the character position of the top left corner; if they are not given, the text window is centred in the screen.

An OPL program can initially display text to the whole screen.

See SCREENINFO.


SCREENINFO

Usage: SCREENINFO var info%()

Gets information on the text screen (as used by PRINT, SCREEN etc.)

This keyword allows you to mix text and graphics. It is required because while the default window is the same size as the physical screen, the text screen is slightly smaller and is centred in the default window. The few pixels gaps around the text screen, referred to as the left and top margins, depend on the font in use.

On return, the array info%(), which must have at least 10 elements, contains this information:

info%(1) left margin in pixels

info%(2) top margin in pixels

info%(3) text screen width in character units

info%(4) text screen height in character units

info%(5) reserved (window server id for default window)

The font ID is a 32-bit integer on the Series 5 and therefore would not fit into a single element of info%().  Hence, the least significant 16 bits of the font ID are returned to info%(9) and the most significant 16 bits to info%(10).

info%(6) unused

info%(7) pixel width of text window character cell

info%(8) pixel height of text window line

info%(9) least significant 16 bits of the font ID

info%(10) most significant 16 bits of the font ID

Constants for these array subscripts are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

On the Series 3c, the font ID is returned to info%(6).

info%(6) font ID

info%(7) pixel width of text window character cell

info%(8) pixel height of text window line

info%(9), info%(10) reserved

Initially SCREENINFO returns the values for the initial text screen. Subsequently any keyword which changes the size of the text screen font, such as FONT or SCREEN, will change some of these values and SCREENINFO should therefore be called again.

See also FONT, SCREEN.


SECOND

Usage:s%=SECOND

Returns the current time in seconds from the system clock (0 to 59).

E.g. at 6:00:33 SECOND returns 33.


SECSTODATE

Usage:SECSTODATE s&,var yr%,var mo%,var dy%,var hr%,var mn%,        var sc%,var yrday%

Sets the variables passed by reference to the date corresponding to s&, the number of seconds since 00:00 on 1/1/1970. yrday% is set to the day in the year (1-366).

s& is an unsigned long integer. To use values greater than +2147483647, subtract 4294967296 from the value.

See also DATETOSECS, HOUR, MINUTE, SECOND, dDATE, DAYS.


SEND

Usage:ret%=SEND(pobj%,m%,var p1,...)

Send a message to the object pobj% to call the method number m%, passing between zero and three arguments (p1...) depending on the requirements of the method, and returning the value returned by the selected method.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


SETDOC

Usage:SETDOC file$

Sets the file file$ to be a document.  This command should be called immediately before the creation of file$ if it is to be recognised as a document. SETDOC may be used with the commands CREATE, gSAVEBIT and IOOPEN.

The string passed to SETDOC must be identical to the name passed to the following CREATE or gSAVEBIT otherwise a non-document file will be created. Example of document creation:

    SETDOC "myfile"

    CREATE "myfile",a,a$,b$

SETDOC should also be called after successfully opening a document to allow the System screen to display the correct document name in its task list.

In case of failure in creating or opening the required file, you should take the following action:

Creating - try to re-open the last file and if this fails display an appropriate error dialog and exit. On reopening, call SETDOC back to the original file so the Task list is correct.

Opening - as for creating, but calling SETDOC again is not strictly required.

Database documents, created using CREATE, and multi-bitmap documents, created using gSAVEBIT, will automatically contain your application UID in the file header. For binary and text file documents created using IOOPEN and LOPEN, it is the programmer's responsibility to save the appropriate header in the file. This is a fairly straight-forward process and the following suggests one way of finding out what the header should be:

Create a database or bitmap document in a test run of you application using SETDOC as shown above

Use a hex editor or hex dump program to find the 1st 16 bytes, or run the program below which reads the four long integer UIDs from the test document.

Write these four long integers to the start of the file you create using IOOPEN.

INCLUDE "Const.oph"

DECLARE EXTERNAL

EXTERNAL readUids:(file$)

PROC main:

LOCAL f$(255)

  WHILE 1

    dINIT "Show UIDs in document header"

    dPOSITION 1,0

    dFILE f$,"Document,Folder,Drive",0

    IF DIALOG=0

      RETURN

    ENDIF

    readUids:(f$)

    ENDWH

ENDP

PROC readUids:(f$)

LOCAL ret%,h%

LOCAL uid&(4),i%

  ret%=IOOPEN(h%,f$,KIoOpenModeOpen% OR KIoOpenFormatBinary%)

  IF ret%&gt;=0

    ret%=IOREAD(h%,ADDR(uid&()),16)

    PRINT "Reading ";f$

    IF ret%=16

      WHILE i%&lt;4

        i%=i%+1

        PRINT "  Uid"+num$(i%,1)+"=",hex$(uid&(i%))

      ENDWH

    ELSE

      PRINT "  Error reading: ";

      IF ret%&lt;0

        PRINT err$(ret%)

      ELSE

        PRINT "Read ";ret%;" bytes only ";

        PRINT "(4 long integers required)"

      ENDIF

    ENDIF

    IOCLOSE(h%)

  ELSE

    PRINT "Error opening: ";ERR$(ret%)

  ENDIF

ENDP

Creating text file documents using IOOPEN or LOPEN has two special requirements:

You will need to save the required header as the first text record. This will insert the standard text file line delimiters CR LF (hex 0D 0A) at the end of the record.

The specific 16 bytes required for your application may itself however contain CR LF. Since you should know when this is the case, you will need to read records until you have reached byte 16 in the document. This is clearly not a desirable state of affairs but is inescapable given that text files were not designed to have headers. It is recommended that you request a new UID for your application if it contains CR LF.

See the 'Advanced Topics' chapter.

See also GETDOC$.


SETFLAGS

Usage: SETFLAGS flags&

Sets flags to produce various effects when running programs. Use CLEARFLAGS to clear any flags which have been set.  The effects which can be achieved are as follows:

flags%	effect

1	restructs the memory available to your application to 64K, emulating the Series 3c. This setting should be used at the beginning of your program only, if required. Changing this setting repeatedly will have unpredictable effects.

2	enables auto-compaction on closing databases. This can be slow, but it is advisable to use this setting when lots of changes have been made to a database.

4	enables raising of overflow errors when floating-point values are greater than or equal to 1.0E+100 in magnitude, instead or allowing 3-digit exponents (for backwards compatibility)

$1000	enables GETEVENT, GETEVENT32 and GETEVENT32A to return the event code $403 to ev&(1) when the machine switches on

Constants for these flags are supplied in Const.oph. See the 'Calling Procedures' chapter for details of how to use this file and Appendix E for a listing of it.

By default these flags are cleared.

See the 'Advanced Topics' and 'Series 5 Database Handling' chapters.

See also GETEVENT32, CLEARFLAGS.


SETNAME

Usage:SETNAME name$

Sets the name of the running OPA to name$ and redraws any status window, using that name below the icon.

SETDOC serves a similar purpose on the Series 5.


SETPATH

Usage:SETPATH name$

Sets the current path for file access for example,

SETPATH "C:\Documents\".

SETPATH "a:\docs\".

SETPATH needs the final backslash to be passed otherwise it ignores everything beyond the last backslash. LOADM continues to use the path of the initial program, but all other file access will use the new path.


SIN

Usage:s=SIN(angle)

Returns the sine of angle, an angle expressed in radians.

To convert from degrees to radians, use the RAD function.


SPACE

Usage:s&=SPACE

Returns the number of free bytes on the device on which the current (open) data file is held.

Example:

PROC stock:

  OPEN "c:\stock\stock",A,a$,b%

  PRINT SPACE,"bytes free on C:"

ENDP

Example:

PROC stock:

    OPEN "a:\stock",A,a$,b%

  WHILE 1

    PRINT "Item name:";

    INPUT A.a$

    PRINT "Number:";

    INPUT A.b%

    IF RECSIZE&gt;SPACE

      PRINT "Disk full"

      CLOSE

      BREAK

    ELSE

      APPEND

    ENDIF

  ENDWH

ENDP


SQR

Usage:s=SQR(x)

Returns the square root of x.


STATUSWIN

Usage:any of

    STATUSWIN ON,type%

    STATUSWIN ON

    STATUSWIN OFF

Displays or removes a %permanent' status window.

If type%=1 the small status window is shown. If type%=2 the large status window is shown. STATUSWIN ON on its own displays an appropriate status window; on the Series 3c this will always be the large status window.

There is only one type of status window on the Siena, which will be displayed whatever type% you use.

The permanent status window is behind all other OPL windows. In order to see it, you must use FONT (or both SCREEN and gSETWIN) to reduce the size of the text and graphics windows. You should ensure that your program does not create windows over the top of it.

The Series 5 has no status windows, but they are replaced by the toolbar. See the 'Friendlier Interaction' chapter for more details.


STATWININFO

Usage:t%=STATWININFO(type%,var xy%())

Sets xy%(1),xy%(2), xy%(3) and xy%(4) to the top left x, top left y, width and height respectively of the specified type of status window. type%=1 is the small status window; type%=2 is the large status window; type%=3 is the Series 3 compatibility mode status window; type%=-1 is whichever status window is current.

STATWININFO returns t%, the type of the current status window (with values as for type%, or zero if there is no current status window).

The Series 5 has no status windows, but they are replaced by the toolbar. See the 'Friendlier Interaction' chapter for more details.


STD

Usage:s=STD(list)

ors=STD(array(),element)

Returns the standard deviation of a list of numeric items.

The list can be either:

A list of variables, values and expressions, separated by commas

or

The elements of a floating-point array.

When operating on an array, the first argument must be the array name followed by (). The second argument, separated from the first by a comma, is the number of array elements you wish to operate on for example m=STD(arr(),3) would return the standard deviation of elements arr(1), arr(2) and arr(3).

This function gives the sample standard deviation, using the formula:

<dd><img src="opchapt15-equation1.gif</dd>

To convert to population standard deviation, multiply the result by SQR((n-1)/n).


STOP

Usage:STOP

Ends the running program.

Note that STOP may not be used during an OPX callback and will raise the Series 5 error, 'STOP used in callback' if it is. See the 'Using OPXs on the Series 5' chapter.


STYLE

Usage:STYLE style%

Sets the text window character style. style% can be 2 for underlined, or 4 for inverse.

See %The text and graphics windows' section at the end of the 'Graphics' chapter for more details.


SUM

Usage:s=SUM(list)

ors=SUM(array(),element)

Returns the sum of a list of numeric items.

The list can be either:

A list of variables, values and expressions, separated by commas

or

The elements of a floating-point array.

When operating on an array, the first argument must be the array name followed by (). The second argument, separated from the first by a comma, is the number of array elements you wish to operate on for example m=SUM(arr(),3) would return the sum of elements arr(1), arr(2) and arr(3).


TAN

Usage:t=TAN(angle)

Returns the tangent of angle, an angle expressed in radians.

To convert from radians to degrees, use the DEG function.


TESTEVENT

Usage:t%=TESTEVENT

Returns %True' if an event has occurred, otherwise returns 'False'. The event is not read by TESTEVENT - it may be read with GETEVENT, or GETEVENT32 or GETEVENTA32 on the Series 5.

On the Series 5, it is recommended that you use either GETEVENT32 or GETEVENTA32 without TESTEVENT as TESTEVENT may use a lot of power, especially when used in a loop as will often be the case.


TRAP

Usage: TRAP command

TRAP is an error handling command. It may precede any of these commands:

Data file commands

APPEND, UPDATE, BACK, NEXT, LAST, FIRST, POSITION, USE, CREATE, OPEN, OPENR, CLOSE, DELETE

MODIFY, INSERT, PUT, CANCEL

File commands

COPY, ERASE, RENAME, LOPEN, LCLOSE, LOADM, UNLOADM

COMPRESS

Directory commands

MKDIR, RMDIR

Data entry commands

EDIT, INPUT

Graphics commands

gSAVEBIT, gCLOSE, gUSE, gUNLOADFONT, gFONT, gPATT, gCOPY

For example, TRAP FIRST.

Any error resulting from the execution of the command will be trapped. Program execution will continue at the statement after the TRAP statement, but ERR will be set to the error code.

TRAP overrides any ONERR.


TRAP RAISE

Usage:TRAP RAISE x%

Sets the value of ERR to x% and clears the trap flag.

See the %Error Handling' chapter for further details of TRAP usage.


TYPE

Usage:TYPE num%

Sets the type of an OPA, from 0 to 4, with num%. On the Series 3c you should set num% from $1000 to $1004 to set type 0 to 4 respectively; the $1000 allows a 48x48 black/grey icon to be used.

This can only be used between APP and ENDA.

See the 'Advanced Topics' chapter for more details of OPAs.

OPL Applications do not have types on the Series 5, but FLAGS provides similar functionality.


UADD

Usage:i%=UADD(val1%, val2%)

Add val1% and val2%, as if both were unsigned integers with values from 0 to 65535. Prevents integer overflow for pointer arithmetic on the Series 3c e.g. UADD(ADDR(text$),1) should be used instead of ADDR(text$)+1.

One argument would normally be a pointer and the other an offset expression.

Note that UADD and USUB should not be used on the Series 5 for pointer arithmetic unless SETFLAGS has been used to enforce the 64K memory limit. In general, long intger arithmetic should be used for pointer arithmetic on the Series 5.

See also USUB.


UNLOADLIB

Usage:ret%=UNLOADLIB(var cat%)

Unload a DYL from memory. If successful, returns zero.

The Series 5 supports use of OPXs only. See the 'Using OPXs on the Series 5' chapter for further details.


UNLOADM

Usage:UNLOADM module$

Removes from memory the module module$ loaded with LOADM.

module$ is a string containing the name of the translated module.

The procedures in an unloaded module cannot then be called by another procedure.

UNLOADM causes any procedures in the module that are not still running to be unloaded from memory too. Running procedures are unloaded on return. It is considered bad practice, however, to use UNLOADM on a module with procedures that are still running. On the Series 3c, the module name and procedure name will not be available for any untrapped error message.

Once LOADM has been called, procedures loaded stay in memory until the module is unloaded, so significantly more memory can be used than on the Series 3c, where procedures are unloaded when the cache is full (or on return if caching is not used).


UNTIL

See DO.


UPDATE

Usage:UPDATE

Deletes the current record in the current data file and saves the current field values as a new record at the end of the file.

This record, now the last in the file, remains the current record.

Example:

    A.count=129

    A.name$="Brown"

    UPDATE

Use APPEND to save the current field values as a new record.

MODIFY, PUT and CANCEL should normally be used instead of UPDATE on the Series 5.


UPPER$

Usage:u$=UPPER$(a$)

Converts any lower case characters in a$ to upper case, and returns the completely upper case string.

Example:

    ...

    CLS :PRINT "Y to continue"

    PRINT "or N to stop."

    g$=UPPER$(GET$)

    IF g$="Y"

    nextproc:

    ELSEIF g$="N"

    RETURN

    ENDIF

    ...

Use LOWER$ to convert to lower case.


USE

Usage:USE logical name

Selects the data file with the given logical name (A-Z for the Series 5, A, B, C or D for the Series 3c). The file must previously have been opened with OPEN, OPENR or CREATE and not yet be closed.

All the record handling commands (such as POSITION and UPDATE, and GOTOMARK, INSERT, MODIFY, CANCEL and PUT on the Series 5) then operate on this file.


USR

Usage:u%=USR(pc%,ax%,bx%,cx%,dx%)

Executes your machine code, returning an integer. The USR code (i.e. the assembler code you have written) must return with a far RET, otherwise the program will crash.

The values of ax%,bx%... are passed to the AX, BX... 8086 registers. The microprocessor then executes the machine code starting at pc%. At the end of the routine, the value in the AX register is passed back to u%.

Casual use of this function can result in the loss of data in the Psion.

This example shows a simple operation, ending with a far RET:

PROC trivial:

LOCAL t%(2),u%,ax%

  t%(1)=$c032 REM xor al,al

  t%(2)=$cb  REM retf

  ax%=$1ab

  u%=usr(addr(t%(1)),ax%,0,0,0) REM returns (ax% AND $FF00)

  PRINT u% REM 256 ($100)

  GET

ENDP

See also USR$, ADDR, PEEK, POKE.


USR$

Usage:u$=USR$(pc%,ax%,bx%,cx%,dx%)

Executes your machine code, returning a string. The USR$ code you have written must return with a far RET, otherwise the program will crash.

The values of ax%,bx%... are passed to the ax, bx... 8086 registers. The microprocessor then executes the machine code starting at pc%. At the end of the routine, the value in the ax register must point to a length-byte preceded string. This string is then copied to u$.

Casual use of this function can result in the loss of data in the Psion.

See USR for an example. See also ADDR, PEEK, POKE.


USUB

Usage:i%=USUB(val1%,val2%)

Subtract val2% from val1%, as if both were unsigned integers with values from 0 to 65535. Prevents integer overflow for pointer arithmetic on the Series 3c.

Note that UADD and USUB should not be used for pointer arithmetic on the Series 5 unless SETFLAGS has been used to enforce the 64K memory limit. In general long integer arithmetic should be used.

See also UADD.


VAL

Usage:v=VAL(numeric string)

Returns the floating-point number corresponding to a numeric string.

The string must be a valid number e.g. not "5.6.7" or "196f". Expressions, such as "45.6*3.1", are not allowed. Scientific notation such as "1.3E10", is OK.

E.g. VAL("470.0") returns 470

See also EVAL.


VAR

Usage:v=VAR(list)

or v=VAR(array(),element)

Returns the variance of a list of numeric items.

The list can be either:

A list of variables, values and expressions, separated by commas

or

The elements of a floating-point array.

When operating on an array, the first argument must be the array name followed by (). The second argument, separated from the first by a comma, is the number of array elements you wish to operate on for example m=VAR(arr(),3) would return the variance of elements arr(1), arr(2) and arr(3).

To convert to population variance, multiply the result by (n-1)/n


VECTOR

Usage:

    VECTOR i%

    label1,label2,...,labelN

    ENDV

VECTOR i% jumps to label number i% in the list. If i% is 1 this will be the first label, and so on. The list is terminated by the ENDV statement. The list may spread over several lines, with a comma separating labels in any one line but no comma at the end of each line.

If i% is not in the range 1 to N, where N is the number of labels, the program continues with the statement after the ENDV statement.

See also GOTO.


WEEK

Usage:w%=WEEK(day%,month%,year%)

Returns the week number in which the specified day falls, as an integer between 1 and 53.

day% must be between 1 and 31, month% between 1 and 12, year% between 0 and 9999 (1900 and 2155 on the Series 3c).

Each week is taken to begin on the 'Start of week' day, as specified in the Control Panel on the Series 5 or the Time application on the Series 3c. When a year begins on a different day to the start of the week, it counts as week 1 if there are four or more days before the next week starts.

The System setting of the 'Start of week' may be checked from inside OPL by using the LCSTARTOFWEEK&: procedure in the Date OPX. The week number in the year may also be calculated by different rules and also with your own choice of the start of year by using the procedure DTWEEKNOINYEAR&: in Date OPX. See the 'Using OPXs on the Series 5' for more details.


WHILE...ENDWH

Usage:

    WHILE expression

    ...

    ENDWH

Repeatedly performs the set of instructions between the WHILE and the ENDWH statement, so long as expression returns logical true non-zero.

If  expression is not true, the program jumps to the line after the ENDWH statement.

Every WHILE must be closed with a matching ENDWH.

See also DO...UNTIL and the %Loops and Branches' chapter.


YEAR

Usage:y%=YEAR

Returns the current year as an integer from the system clock.

For example, on 5th May 1997 YEAR returns 1997.


Advanced Topics

Chapter Contents

Introduction

Programs, modules, procedures

Where files are kept

Safe pointer arithmetic

OPL applications (OPAs)

Tricks

Cacheing procedures on the Series 3c and Siena

Sprite handling on the Series 3c and Siena

Scanning the keyboard directly

I/O functions and commands

Recording and playing sounds on the Series 3c and Siena

Series 3c and Siena OPL database information

Series 3c and Siena DYL handling

Dynamic memory allocation


Introduction

Many of the subjects covered in this chapter may provide benefits for all levels of programmers.

However, the subjects become progressively more technical. This manual cannot cover every OPL keyword in detail, as some give access to the innermost workings of the Psion. Some of these keywords are touched on in this chapter.


Programs, modules, procedures

Section Contents

Programs using more than one module

Cacheing procedures for speed

Calling procedures by strings

Programs using more than one module

Program is the more general word for the "finished product" - a set of procedures which can be run. A program may consist of one module containing one or more procedures:

It may also consist of a number of modules containing various procedures:

A procedure in one module can call procedures in another module, provided this module has been loaded with the LOADM command. LOADM needs the filename of the module to load.

For example, if an OPL module MAIN has these two procedures:

    PROC main:

          LOADM "library"

          pthis:                 REM run pthis: in this module

          pother:                REM run pother: in "library"

          PRINT "Finished"

          PAUSE 40

          UNLOADM "library"

    ENDP

    PROC pthis:

          PRINT "Running pthis:"

          PRINT "in this module"

          PAUSE 40

    ENDP

and a module called LIBRARY has this one:

    PROC pother:

          PRINT "Running pother:"

          PRINT "in module called LIBRARY"

          PAUSE 40

    ENDP

then running MAIN would display Running pthis: and in this module; then display Running pother: and in module called LIBRARY; and then display Finished.

You would usually only need to load other modules when developing large OPL programs.

You can use LOADM up to seven times, to load up to seven modules.

 You can use LOADM up to three times, to load up to three modules.

If you want to load any further modules, you must first unload one of those currently loaded, by using UNLOADM with the filename. To keep your program tidy, you should UNLOADM a module as soon as you have finished using it.

If there is an error in the running program, you will only be positioned in the Program editor at the place where the error occurred if you were editing the module concerned (and you ran the program from there).

In spite of its name, LOADM does not "load" the whole of another module into memory. Instead, it just loads a block of data stored in the module which lists the names of the procedures which it contains. If such a procedure is then called, the procedure will be searched for, copied from file into memory and the procedure will then be run.

The modules are searched through in the order that they were loaded, with the module loaded last searched through last. You may make considerable improvements in speed if you as few modules as possible loaded at a time (so avoiding a long search for each procedure) and if you load the modules with the most commonly called procedures first.

Cacheing procedures for speed

 On the Series 5, procedures are automatically cached. A procedure is loaded into memory when first called and kept in memory until the module which contains it is unloaded. A large Series 5 OPL program can therefore use much more memory than the equivalent Series 3c program, where procedures are unloaded on return or when the cache, with maximum size 64K, is full. In some circumstances it would therefore be adviseable to unload very large modules whose procedures are seldom used, rather than leaving all modules loaded until the program exits.

On the Series 3c, without procedure cacheing, procedures are loaded from file whenever they are called, and discarded when they return. This is true even when procedures are all in one module. (With more than one module, LOADM simply loads a map listing procedure names and their positions in the module file so that they can be loaded fairly efficiently. It does not load procedures into memory.)If a well-designed program calls procedures regularly, you can speed it up by cacheing procedures. This keeps the code for a procedure loaded in memory after it returns, so that if it is called again there is no need to fetch it from file again. The CACHE command takes two arguments the initial size of the cache and the maximum size (both in bytes). These can be up to 32,767 bytes. The minimum in both cases is 2000 bytes.For a small program, you might use CACHE 2000,2000 at the start of the program. Up to 2000 bytes of procedure code will be cached. If the cache fills up, and a procedure is called which is not in the cache, space will be made for it in the cache by removing other procedures from it.For a much larger program, you might use CACHE 10000,10000. You may wish to change the settings and find the smallest setting which produces the maximum speed improvement.Once a cache has been created, CACHE OFF prevents further cacheing, although the cache is still searched when calling subsequent procedures. CACHE ON may then be used to re-enable cacheing.

Calling procedures by strings

Procedures can be called using a string expression for the procedure name. Use an @ symbol, optionally followed by a character to show what type of value is returned for example, % for an integer. Follow this with the string expression in brackets. You can use upper or lower case characters.

Here are examples showing the four types of value which can be returned:

i% = @%(name$) : (parameters)    for integer

l& = @&(name$) : (parameters)    for long integer

s$ = @$(name$) : (parameters)    for string

f  = @(name$)  : (parameters)    for floating-point

So, for example, test$:(1.0,2) and @$("test"):(1.0,2) are equivalent.

Note that the string expression does not itself include a symbol for the type (%, & or $).

You may find this useful if, in a more complex program, you want to "work out" the name of a procedure to call at a particular point. The chapter on Friendlier interaction includes an example program which uses this method.


Where files are kept

Section Contents

Where files are kept on the Series 5

Using file specifications in Series 5 OPL

Current folder on the Series 5

Control of folders on the Series 5

Where files are kept on the Series 3c and Siena

Using file specifications in OPL on the Series 3c and Siena

Control of directories on the Series 3c and Siena

File specifications on REM:: for Series 3c and Siena

On the Series 3c the Siena and the Series 5, the internal memory and disks (memory disks on the Series 5; SSDs on the Series 3c) use a DOS-compatible directory structure, the same as that used on disks on PCs. However, the two machines differ quite considerably in this area as described below.

Where files are kept on the Series 5

To specify a file completely, the Series 5 uses a drive (or device), folder and filename:

The drive is the area on the Series 5 where the file is kept. This can be C: (internal disk), D: (memory disk drive) E:,..., Y: or Z: (ROM).

Every drive has one root folder, usually written as a backslash (\). This can "contain" files and/or other folders, each of which can contain more files and/or more folders. When you have "folders in folders" like this, theyre often called subfolders. Their names show where they are. For example, the root folder (\) could contain a folder called \JO, which might in turn contain a folder called \JO\BACKUP, which might contain some files.

Filenames are composed of up to 256 characters (but see below), optionally followed by a file extension comprised of a dot and from one to three characters.  A filename may not begin with a back or forward slash (\ or /) or a colon (:). Any trailing zeros on the filename are stripped.

The Series 5 filing system does not treat extensions as a special component of a filename except when parsing (i.e. for PARSE$) where the extension is treated as on the Series 3c (see below). So while myfile. on the Series 3c meant the same as myfile, on the Series 5 the dot is actually part of the name. You can also use long filenames with embedded spaces and any number of dots like: OPL Reference manual or Very.long.filename.

To specify a file completely, you add the three parts together. The folder part must end with a backslash. So an OPL module named TEST, in a folder called \JO in the Series 5 internal memory can be specified as C:\JO\TEST. If this file were in the \JO\BACKUP folder, it would be completely specified as C:\JO\BACKUP\TEST. If it were in the root folder, you would specify it as C:\TEST.

A full file specification may be up to 255 characters long for OPL.

In OPL, as in other applications, the files are kept on the drive and in the folder you specify.

Using file specifications in Series 5 OPL

OPL commands which specify a filename, such as OPEN, CREATE, gLOADBIT and so on, can also use any or all of the other parts that make up a full file specification. (Normally, the only other part you might use is the drive name, if the file were on a memory disk.) So for example, OPEN "C:\ADDR.TXT" tries to open a file called ADDR.TXT in the root folder of the internal disk.

You can use the PARSE$ function if you need to build up complex filenames. See the Alphabetic Listing chapter for more details of PARSE$.

Current folder on the Series 5

The current folder for all commands is always C:\ unless it has been changed by the command SETPATH.  Hence any use of a keyword which takes a filename as an argument will only look in the current folder and so if this is other than C:\, it should be specified either by SETPATH or by including it in the filename.  For example, to check whether the file Program1 in the directory D:\MyPrograms\ exists, either

    SETPATH "d:\MyPrograms\"

    ...

    IF EXISTS ("Program1")

    ...

or

    IF EXISTS ("d:\MyPrograms\Program1")

    ...

Control of folders on the Series 5

Use the MKDIR command to make a new folder. For example, MKDIR "C:\MINE\TEMP" creates a C:\MINE\TEMP folder, also creating C:\MINE if it is not already there. An error is raised if the chosen folder exists already. Use TRAP MKDIR to avoid this.

SETPATH sets the current folder for file access - for example,

    SETPATH "C:\DOCUMENTS"

LOADM continues to use the folder of the running program, but all other file access will be to the newly specified folder.

Use RMDIR to remove a folder - for example, RMDIR "C:\MINE" removes the MINE folder on C:.  A Does not exist error is raised if the folder does not exist. Use TRAP RMDIR to avoid this. A File is in use error will result if you try to remove a folder which contains open files.

Where files are kept on the Series 3c and Siena

Note that any mention of SSDs in this section refers to the Series 3c only.  The Siena does not have an SSD drive.  Other discussion refers to both machines as usual.

The Series 3c hides the complexities of the directory structure. You have only to supply a filename, and to say whereabouts a file is stored - internally, on an SSD or on another computer to which the Series 3c is linked.

In fact, in order to specify a file completely, the Series 3c uses a filing system, drive (or device), directory and filename:

The filing system usually specifies the computer, and is usually LOC:: (local the Series 3c) or REM:: (remote an attached computer). This is always three letters and two colons.

 The drive is the area on that computer where the file is kept. On the Series 3c this can be M: (internal disk), A: (left SSD drive) or B: (right SSD drive).

Every drive has one root directory, usually written as a backslash (\). This can "contain" files and/or other directories, each of which can contain more files and/or more directories. When you have "directories in directories" like this, theyre often called subdirectories. Their names show where they are. For example, the root directory (\) could contain a directory called \JO, which might in turn contain a directory called \JO\BACKUP, which might contain some files.

Filenames are composed of one to eight letters and/or numbers, optionally followed by a file extension  comprised of a dot and from one to three letters/numbers. File extensions are by convention used to group different types of files. The Series 3c uses file extensions in this way, but hides this from you.

To specify a file completely, you add the four parts together. The directory part must end with a backslash. So an OPL module named TEST, in a directory called \JO in the Series 3c internal memory can be specified as LOC::M:\JO\TEST.OPL. If this file were in the \JO\BACKUP directory, it would be completely specified as LOC::M:\JO\BACKUP\TEST.OPL. If it were in the root directory, you would specify it as LOC::M:\TEST.OPL.

A full file specification may be up to 128 characters long.

In OPL, unless you say otherwise, files are kept on the Series 3c (LOC::), in the internal memory (M:). The directories and file extensions used are:

Type of file	Directory	File extension

OPL modules	\OPL	.OPL

translated modules	\OPO	.OPO

data files	\OPD	.ODB

bitmaps	\OPD	.PIC

Using file specifications in OPL on the Series 3c and Siena

OPL commands which specify a filename, such as OPEN, CREATE, gLOADBIT and so on, can also use any or all of the other parts that make up a full file specification. (Normally, the only other part you might use is the drive name, if the file were on an SSD.) So for example, OPEN "REM::C:\ADDR.TXT"... tries to open a data file called ADDR.TXT on the root directory of a hard disk C: on an attached PC.

You can use the PARSE$ function if you need to build up complex filenames. See the Alphabetic Listing chapter for more details of PARSE$.

Unless you have a good reason, though, its best not to change directories or file extensions for files on the Series 3c. You can lose information this way, unless youre careful.

Control of directories on the Series 3c and Siena

Use the MKDIR command to make a new directory. For example, MKDIR "M:\MINE\TEMP" creates a M:\MINE\TEMP directory, also creating M:\MINE if it is not already there. An error is raised if the chosen directory exists already - use TRAP MKDIR to avoid this.

SETPATH sets the current directory for file access - for example, SETPATH "A:\DOCS". LOADM continues to use the directory of the running program, but all other file access will be to the new directory instead of \OPD.

Use RMDIR to remove a directory - for example, RMDIR "M:\MINE" removes the MINE directory on M:\.  An error is raised if the directory does not exist use TRAP RMDIR to avoid this.

You can only remove empty directories.

File specifications on REM:: for Series 3c and Siena

You should not assume that remote file systems use DOS-like file specifications for example, an Apple Macintosh specification is disk:folder:folder:filename. You can only assume that there will be four parts - disk/device, path, filename and (possibly null) extension. PARSE$, however, will always build up or break down REM:: file specifications correctly.


Safe pointer arithmetic

Note that on the Series 5, if you are using 32-bit addressing, as will be the case by default (see the 32-bit addressing section later in this chapter), you should use ordinary long integer arithmetic and should not use UADD and USUB.

However, on the Series 3c and Siena or if you have used SETFLAGS on the Series 5 to enforce the 64K memory limit, whenever you wish to add or subtract something from an integer which is an address of something (or a pointer to something) you should use UADD and USUB. If you do not, you will risk an Integer overflow error.

An address can be any value from 0 to 65535. An integer variable can hold the full range of addresses, but treats those values from 32768 to 65535 as if they were -32768 to -1. If your addition or subtraction would convert an address in the 0-32767 range to one in the 32768-65535 range, or vice versa, this would cause an Integer overflow.

UADD and USUB treat integers as if they were unsigned, i.e. as if they really held a value from 0 to 65535.

For example, to add 1 to the address of a text string (in order to skip over its leading byte count and point to the first character in the string), use i%=UADD(ADDR(a$),1), not i%=ADDR(a$)+1.

USUB works in a similar way, subtracting the second integer value from the first integer, in unsigned fashion for example, USUB(ADDR(c%),3).

USUB(x%,y%) has the same effect as UADD(x%,-y%).


OPL applications (OPAs)

Section Contents

OPAs on the Series 5

Defining a Series 5 OPA

Running the Series 5 OPA

How the Series 5 talks to an OPA

Series 5 System screen compliance

Launching help files on the Series 5

Example OPAs

When an OPA cannot respond

OPAs on the Series 3c and Siena

Defining a Series 3c OPA

Running the OPA on the Series 3c

How the Series 3c talks to an OPA

Example OPAs

When an OPA cannot respond

Designing an icon

OPAs and the status window

Other TYPE options

You can make an OPL program appear as an icon in the system screen, and behave like the other icons there, by converting it into an OPL application, or OPA. The support for OPAs on the Series 5 is changed and extended from that of the Series 3c and Siena. The following two sections deal separately with these systems.

OPAs on the Series 5

There are two settings for OPAs which are set using the FLAGS command (similar to TYPE on the Series 3c):

FLAGS 1 is used if your application can create files. It will then be included in the list of applications offered when the user creates a new file from the System screen (like Program, Word etc.).

FLAGS 2 prevents the application from appearing in the Extras bar in the System screen. It is very unusual to have this flag set.

Once created, OPAs may be used in the same way as the built in Series 5 applications: new document files may be created from the System screen using New File if the flag 1 is set and/or from the Extras bar if the flag 2 is not set. Existing files may be opened by selecting them on the System screen as usual. You can stop a running OPA by using the Task list.

Defining a Series 5 OPA

To make an OPA, your OPL file should begin with the APP keyword, followed by a name for the OPA in the machines default language and its UID. The name may be up to 250 characters. (Note that it does not have quote marks.) If the CAPTION command is used, however, this default name will be discarded (see below).

The UID specifies the UID of the application. For applications that are to be distributed, UIDs are reserved by contacting Psion. These official UIDs are guaranteed to be unique to the application and have values of &10000000 and above.  To obtain a reserved UID you should contact Psion Software in one of the following ways:

e-mail to uid_sw@software.psion.com

or use the EPOC World web site, http://software.psion.com/EPOCWorld/

or fax to +44-171-724-4048, attn UID Allocations

or write to UID Allocations, Psion Software plc, 19 Harcourt St, London W1N 1DT, England

For applications developed for personal use there is no need to reserve official UIDs, and any UID between &01000000 and &0fffffff may be selected. However, if the UIDs of two different applications did clash, then either your applications documents will have the wrong icon and selecting these will run the other application, or the other applications documents will seem to belong to your application.

The APP line may be followed by any or all of the keywords CAPTION, ICON and FLAGS. Finally, use ENDA, and then the first procedure may begin as in a normal OPL file. Here is an example of how an OPA might start:

    APP Expenses,&20000000    REM !!! Should be a reserved UID !!!

          FLAGS 1

          ICON "icon.mbm"

          CAPTION "Expenses",1        REM English name

          CAPTION "Expenses",10       REM American name

    ENDA

Here is another example:

    APP Picture,&30000000     REM !!! Should be a reserved UID !!!

          FLAGS 2

          ICON "picicon.mbm"

    ENDA

FLAGS takes an integer argument of 1 or 2. The meanings of these are outlined above. If you dont specify the flags, none are set.

ICON gives the name of a multi-bitmap file (MBM), also known as an EPOC Picture file, which contains the icons for an OPL application. These icons are used on the Extras bar and for the applications documents on the System screen. The multi-bitmap file can contain up to three bitmaps of sizes 2424, 3232 and 4848 pixels respectively, but each must be paired with a mask. Each bitmap should be followed by its mask in the multi-bitmap file, so that bitmaps and masks occur alternately. The pixels which are set in the mask specify pixels in the bitmap which are to be used. Pixels which are clear in the mask specify pixels that are not to be used from the bitmap, allowing the background to be displayed in these pixels. Only pixels which are set in the mask are drawn in the final icon.

The different sizes of bitmaps are used for the different zoom levels in the System screen. The sizes are read from the MBM and the most suitable size is zoomed if the exact sizes required are not provided or if some are missing.

You can use ICON more than once within the APP...ENDA construct. The translator only insists that all icons are paired with a mask of the same size in the final ICON list. This allows you to use MBMs containing just one bitmap, as produced by the Sketch application.  Icons may also be created on a PC (if the appropriate tools are available), or of course by another OPL program or application.

CAPTION specifies an applications public name (or caption) for a particular language, which is the name which will appear below its icon on the Extras bar and in the list of Programs in the New File dialog (assuming the setting of FLAGS allows these) when that is the language used by the machine. This name is also used as the default document name for documents launched using the Extras bar. The maximum length of the caption is 255 characters. However, note that a caption no longer than around 8 letters will look best on the Extras bar. If any CAPTION statement is included in the APP85ENDA structure, then the default caption provided by the APP declaration will be discarded. Therefore as many statements of CAPTION as are necessary to cover all the languages required, including the language of the machine on which the application is originally developed, should be used. The constants for the language codes are supplied in Const.oph. See the Calling Procedures chapter for details of how to use this file and Appendix E for a listing of it.

On creation of an application, a folder specifically for the application is also created.  For example, if the OPA has the name AppXxx the folder created will be \System\Apps\AppXxx\. The APP file itself (the translated OPL module) and the application information file (AIF) which contains three icon and mask pairs and the application caption and flags, are created in this folder.  For example, the AIF for AppXxx.app  would be \System\Apps\AppXxx\AppXxx.aif. The AIF file will be generated based on all the information contained in the APP85ENDA construct, but if any information is missing defaults will be used. These are:

for ICON: the default (question mark) icons

for CAPTION: the caption specified in the APP declaration

for FLAGS: the default value of 0.

The arguments to any of the keywords between APP and ENDA must be constants and not expressions.

Running the Series 5 OPA

Once youve successfully translated the OPL file, the applications icon will automatically appear on the Extras bar and/or on the Program list in the New File dialog (as specified by the FLAGS setting).  The new documents of the application may then be created and existing ones opened as with other built in Series 5 applications.

The first thing a file-based OPA should do is to get the name of the file to use, and check whether it is meant to create it or open it. CMD$(2) returns the full name of the file to use; CMD$(3) returns "C" for "Create", "O" for "Open" or "R" for "Run".

All document-based OPAs should handle all of these cases; for example, if a "Create" fails because the file exists already, or an "Open" fails because it does not, OPL raises the error, and the OPA should take suitable action. Note however, that in general the System screen will not allow such events to occur and therefore they are unlikely to happen.

"R" means that your application has been run from the OPL Program editor or has been selected via the applications icon on the Extras bar, and not by the selection or creation of one of its documents from the system screen. A default filename, including path, is passed in CMD$(2). When "R" is passed, an application should always try to open the last-used document. This is the document that was in use when the application was last closed, not the document that was most recently opened. The name of this document should be stored in a .INI file with the same name and in the same folder as your application but always on drive c:. So for example, AppXxx.app would have .INI file c:\System\Apps\AppXxx\AppXxx.ini. If the .INI file does not exist or cannot be opened for any reason, or if the document listed there no longer exists, you should create the document named in CMD$(2). CMD$(2) is a default name provided by the System based on your applications caption. If the .INI file is corrupt it should be deleted before going on to create CMD$(2). No error message should be displayed in this case.

Constants for the array indices of CMD$ and the return values of CMD$(3) are supplied in Const.oph. See the Calling Procedures chapter for details of how to use this file and Appendix E for a listing of it.

How the Series 5 talks to an OPA

When the Series 5 wants an OPA to exit or to switch files, it sends it a System message, in the form of an event. For example, this would happen if the Close file option in the Task list were used to stop a running OPA.

TESTEVENT and GETEVENT32 (synchronous) and GETEVENTA32 (asynchronous) check for certain events, including both keypresses and System messages. All types of OPA must use these keywords to check for both keypresses and System messages; keyboard commands such as GET, KEY and KEYA cause other events to be discarded.

GETEVENT32 waits for an event whereas TESTEVENT simply checks whether an event has occurred without getting it. If TESTEVENT returns non-zero, an event has occurred, and can be read with GETEVENT32. However, it is recommended that you use either GETEVENT32 or GETEVENTA32 without TESTEVENT as the machine will switch off only when waiting on an event. If the program just runs around a loop using TESTEVENT to decide when there is something to do, it will simply consume the battery power for no purpose. GETEVENT32 may be used if you only wish to pick up window server events, but otherwise you should use the asynchronous GETEVENTA32 together with any other asynchronous requests which you require. TESTEVENT should generally only be used when polling while doing something else in background.

GETEVENT32 and GETEVENTA32 both take one argument: the name of a long integer array, for example, GETEVENT32 ev&(). The array should contain at least 16 long integers.

For example, if the event is a keypress(ev&(1) AND &400) = 0 and,

ev&(1) = keycode (as for GET)

ev&(2) = time stamp (gives the time of the keypress)

ev&(3) = scan code (locates the key on the keyboard)

ev&(4) = modifier code (e.g. Shift, Control)

ev&(5) = repeat.  Note that this is strictly the repeat value, i.e. if there is only one keypress, then the value of ev&(5) is 0.

For non-key events (ev&(1) AND &400) will be non-zero. On the Series 5, these include pointer events (pen events). For an application, it may be suitable to filter out certain pointer events. This may be done using the POINTERFILTER command. POINTERFILTER takes two arguments: a filter and a mask. Each of the bits in these two arguments represents a certain pointer event, allowing a flag to be set to say whether that event should be filtered out. The bits which are set in the mask specify bits in the filter which are to be used. Bits which are clear in the mask specify bits that are not to be used from the filter. This makes it possible to filter out some pointer events, filter back in some others and leave the settings of some alone all in one call to POINTERFILTER. To set or clear flags you would set and clear them in the filter and set all the flags that require changing in the mask. To leave some bits in the filter alone just dont set the bits in the mask. See the Alphabetic Listing chapter for more details of POINTERFILTER and the values returned to ev&() by GETEVENT32 and GETEVENTA32.

If the event is a System message to change files or quit, ev&(1)=&404. You should then use GETCMD$ to find the action required.

GETCMD$ returns a string, whose first character is "C", "O" or "X".

You can only call GETCMD$ once for each event. You should do so as soon as possible after reading the event. Assign the value returned by GETCMD$ to a string variable so that you can extract its components.

If you have c$=GETCMD$, the first character, which you can extract with LEFT$(c$,1), has the following meaning:

"C" - close the current document, and create the specified new file.

"O" - close the current document, and open the specified existing file.

"X" - save the name of the current document (if any) to the .INI file, close the current document and quit the OPA.

Again with c$=GETCMD$, MID$(c$,2,255) is the easiest way to extract the filename.

Note that events are ignored while you are using keywords which pause the execution of the program GET, GET$, EDIT, INPUT, PAUSE, MENU and DIALOG. If you need to use these keywords, use LOCK ON / LOCK OFF (described later) around them to prevent the System screen from sending messages.

Series 5 System screen compliance

A well-behaved Series 5 application should obey the following important guidelines:

All applications should respond in the standard way to the initial command-line and to any System screen messages to switch files. See the How the Series 5 talks to an OPA section above.

All applications should support a toolbar. Toolbar.opo is supplied in the ROM and provides the set of procedures required for this support. See the Friendlier Interaction chapter.

All Series 5 applications should save non-document files (external files) to their so-called application directory. For an application called AppXxx, the application directory is \System\Apps\AppXxx\. This is also the directory where the application itself is saved.

You can find out the full file specification of external files by using PARSE$. For example,p$=PARSE$("NEW",LEFT$(CMD$(1),LEN(CMD$(1)-4),x%())CMD$(1) gives the device and the path of the OPL application and the name of the file.

By default the system screen hides the System folder and its subfolders. Use the Preferences option in the Tools menu (Ctrl+K) in the System screen and check the checkboxes for both Show hidden files and Show System folder to show all applications and their files.

The FLAGS keyword in the APP...ENDA construct should have the value 1 if the application supports documents (a file that will run your application when selected from the System screen). Without this flag the application will not be listed when the user chooses to create a new file from the System screen (see sections above).

You should set ESCAPE OFF so that it is impossible for the user to stop the application running by hitting Ctrl+Esc.

Launching help files on the Series 5

It is possible to write your own Help for your own application, which should be created in the Data application.  The file should be similar to the built-in help, using two labels only for each entry to give a title and the explanation itself. The file should be stored in the applications folder with the filename extension .hlp. The procedure RUNAPP&: in System OPX (see the Using OPXs on the Series 5 chapter for more details) may then be used to launch Data and open the application help file. For example, if you want to run a help file called Myapp.hlp, the help file for your own application called Myapp, you should use:

    drv$=LEFT$(CMD$(1),2)

    RUNAPPP&:("Data",drv$+"\System\Apps\Myapp\Myapp.hlp","",0)

You should make it possible for users of your application to launched your help file from the applications menus. Following the Series 5 style guide, the Help item should appear on the Tools menu at the bottom of the section above Infrared, so it will be followed by a separating line. The shortcut key should be Ctrl+Shift+H.

Example OPAs

Here is an OPA which just prints the keys you press. It is not a document-based application (like Calc, but unlike Word which is document-based)  so it uses FLAGS 0. The keyboard procedure getk&: returns the key pressed, as with GET, but jumps to a procedure endit: if a System message to close down is received. (OPAs with flags set to 0 do not receive "change file" messages.)

    CONST KUidAppMyApp0&=&40000000

                                        REM !!! Should be a reserved UID!!!

    APP myapp0,KUidAppMyApp0&

          CAPTION "Get Key",1

          ICON "myapp0.mbm"

    ENDA

    PROC start:

          GLOBAL a&(10),k&

          FONT 11,16

          PRINT "Q to Quit"

          PRINT " or select Close file"

          PRINT " from the Task List"

          DO

                k&=getk&:

                PRINT CHR$(k&);

          UNTIL (k& AND &ffdf)=%Q            REM Quick way to do uppercase

    ENDP

    PROC getk&:

          DO

                GETEVENT32 a&()

                IF a&(1)=&404

                      IF LEFT$(GETCMD$,1)="X"

                            endit:

                      ENDIF

                ENDIF

          UNTIL a&(1)&lt;256

          RETURN a&(1)

    ENDP

    PROC endit:

          STOP

    ENDP

Here is a similar document-based OPA. It does the same as the previous example, but System messages to change files cause the procedure fset: to be called. The relevant files are opened or created. A proper application with a toolbar would call TBarSetTitle: to change its title. See the Friendlier Interaction chapter.

    CONST KUidAppMyApp1&=&50000000

                                  REM !!! Should be a reserved UID!!!

    APP myapp1,KUidAppMyApp1&

          FLAGS 1

          CAPTION "Get Key Doc",1

          ICON "myapp1.mbm"

    ENDA

    PROC start:

          GLOBAL a&(10),k&,w$(255)

          FONT 11,16 :w$=CMD$(2)

          fset:(CMD$(3))

          PRINT "Q to Quit"

          PRINT "use the Task list"

          PRINT "to create/switch files"

          DO

                k&=getk&:

                PRINT CHR$(k&);

          UNTIL (k& AND &ffdf)=%Q

    ENDP

    PROC getk&:

          LOCAL t$(1)

          DO

                GETEVENT32 a&()

                IF a&(1)=$404

                      w$=GETCMD$

                      t$=LEFT$(w$,1)

                      w$=MID$(w$,2,255)

                      IF t$="X"

                            endit:

                      ELSEIF t$="C" OR t$="O"

                            TRAP CLOSE

                            IF ERR

                                  GIPRINT ERR$(ERR)

                                  CONTINUE

                            ENDIF

                            fset:(t$)

                      ENDIF

                ENDIF

          UNTIL a&(1)&lt;256

          RETURN a&(1)

    ENDP

    PROC fset:(t$)

          LOCAL p&(6)

          IF t$="C"

                TRAP DELETE w$

                SETDOC w$

                TRAP CREATE w$,A,A$

          ELSEIF t$="O"

                SETDOC w$

                TRAP OPEN w$,A,A$

          ENDIF

          IF ERR

                CLS :PRINT ERR$(ERR)

                REM should revert to old file if possible

                GET :STOP

          ENDIF

    ENDP

    PROC endit:

          STOP

    ENDP

You should, as in both these examples, be precise in checking for the System message; if in future the GETCMD$ function were to use values other than "C", "O" or "X", these procedures would ignore them.

If you need to check the modifier keys for the returned keypress, use a&(4) instead of KMOD.

SETDOC called just before the creation of the file ensures that the created file is a document, i.e. that the file launches its associated application when selected. The strings passed to SETDOC and CREATE (or gSAVEBIT) should be exactly the same, otherwise a non-document file will be created. SETDOC should also be called when opening a document to allow the System screen to display the correct document name in its task list. OPL explicitly supports database and multi-bitmap files as documents.

To be strict, whenever creating a file, an OPA should first use PARSE$ to find the disk and directory requested. It should then use TRAP MKDIR to ensure that the directory exists.

When an OPA cannot respond

The LOCK command marks an OPA as locked or unlocked. When an OPA is locked with LOCK ON, the System will not send it events to change files or quit. If, for example, you attempt to close down the OPA from the Task list, a message will appear, indicating that the OPA cannot close down at that moment.

You should use LOCK ON if your OPA uses a keyword, such as MENU, DIALOG and EDIT, which pauses the execution of the program. You might also use it when the OPA is about to go busy for a considerable length of time, or at any other point where a clean exit is not possible. Do not forget to use LOCK OFF as soon as possible afterwards.

An OPA is initially unlocked.

OPAs on the Series 3c and Siena

There are five different types of OPA, called type 0 to type 4:

TYPE 0 (like Calc): The OPA uses no files.

TYPE 1: Only one file is used. A type 1 OPA will look the same as a type 0. The only difference is that the type 1 is using a file, of the same name as the OPA.

TYPE 2 (like World): You can have more than one file, but only one can be in use (bold) at any time.

When you pick a new file to use, its name becomes bold, and the one that was previously bold reverts to normal. What has actually happened is that the running OPA has switched files - it has not closed down, and no new copy of the OPA is run.

TYPE 3 (like Data, Word, Agenda, Sheet): You can have more than one file, and any number may be open (bold) at a given time.

When you select a new file, one of the running OPAs normally switches to this file, as with type 2 OPAs. You can, however, with Shift-Enter, start a new OPA running just for this file, without a different file exiting.

TYPE 4 (like RunOpl): Many files can be used, and any number may be in use at a given time. When you select a new file, a new version of the OPA is always run, to use the new file.

Types 3 and 4 allow more than one file to be in use (i.e. have their names in bold). When this happens a separate version of the OPA runs for each bold file. With types 0, 1 and 2, only one version of the OPA can be running at any time.

Initially, the OPAs name appears beneath the icon. If you move onto this name and press Enter, file-based OPAs (types 1 to 4) will use a file of this name. Types 2, 3 and 4 allow you to create lists of files below the icon (with the New file option). You use the file lists in the same way as the lists under the other icons in the System screen.

You can stop a running OPA by moving the cursor onto its bold name and pressing Delete. After a Confirm dialog, the System screen tells the OPA to stop running.

Defining a Series 3c OPA

To make an OPA, your OPL file should begin with the APP keyword, followed by a name for the OPA. The name should begin with a letter, and comprise of 1 to 8 letters and/or numbers. (Note that it does not have quote marks.) The APP line may be followed by any or all of the keywords PATH, EXT, ICON and TYPE. A Series 3c OPA should also add $1000 to the type if it has its own 48x48 pixel, black/grey icon (see the discussion of ICON below for details). Finally, use ENDA, and then the first procedure may begin as in a normal OPL file. Here is an example of how an OPA might start:

    APP Expenses

          TYPE $1003

          PATH "\EXP"

          EXT "EXP"

          ICON "\OPD\EXPENSES.PIC"

    ENDA

Here is another example:

    APP Picture

          TYPE 1

    ENDA

TYPE takes an integer argument from 0 to 4. The various types of OPA are outlined earlier. If you dont specify the type, 0 is used.

PATH gives the directory to use for this OPAs files. If you do not use this, the normal \OPD directory will be used. The maximum length, including a final \, is 19 characters. Dont include any drive name in this path.

EXT gives the file extension of files used by this OPA. If you do not specify this, .ODB is used. Note that the files used by an OPA do not have to be data files, as the I/O commands give access to files of all kinds. EXT does not define the file type, just the file extension to use. However, for simplicitys sake, examples in this section use data files.

(PATH and EXT provide information for the System screen - they do not affect the program itself. The System screen displays under the OPA icon all files with the specified extension in the path you have requested.)

ICON gives the name of the bitmap file to use as the icon for this OPA. If no file extension is given, .PIC is used. If you do not use ICON, the OPA is shown on the System screen with a standard OPA icon.

As mentioned above, you should add $1000 to the argument to TYPE for a Series 3c icon. This specifies that the icon has size 48x48 pixels (instead of 24x24 as it was on the Series 3). If the first bitmap has size 24x24, it is ignored and the following two bitmaps must be the 48x48 black and grey icons respectively. If the first bitmap is 48x48, it is assumed to be the black icon and the grey icon must follow. If $1000 is not set, a scaled up 24x24 icon will be used. The translator does not check the size of the icons. If you want to design your own icon using an OPL program, see gSAVEBIT for details on saving both black and grey planes to a bitmap file.

The arguments to any of the keywords between APP and ENDA must be constants and not expressions. So, for example, you must use TYPE $1003 instead of TYPE $1000 OR 3.

Running the OPA on the Series 3c

Once youve translated the OPL file, return to the System screen and use Install on the App menu to install the OPA in the System screen. (You only need to do this once.) Once installed, file-based OPAs are shown with the list of available files, if any are found. Otherwise, the name used after the APP keyword appears below the icon.

Note that the translated OPA is saved in a \APP directory. If you previously translated the module without the APP...ENDA at the start, the old translated version will still be listed under the RunOpl icon, and should be deleted.

The first thing a file-based OPA should do is to get the name of the file to use, and check whether it is meant to create it or open it. CMD$(2) returns the full name of the file to use; CMD$(3) returns "C" for "Create" or "O" for "Open". All file-based OPAs (types 1 to 4) should handle both these cases; if a "Create" fails because the file exists already, or an "Open" fails because it does not, OPL raises the error, and the OPA should take suitable action - perhaps even just exiting.

How the Series 3c talks to an OPA

When the Series 3c wants an OPA to exit or to switch files, it sends it a System message, in the form of an event. This would happen if you press Delete to stop a running OPA, or select a new file for a type 2 or 3 OPA.

TESTEVENT and GETEVENT check for certain events, including both keypresses and System messages. All types of OPA must use these keywords to check for both keypresses and System messages; keyboard commands such as GET, KEY and KEYA cause other events to be discarded.

GETEVENT waits for an event whereas TESTEVENT simply checks whether an event has occurred without getting it.

If TESTEVENT returns non-zero, an event has occurred, and can be read with GETEVENT. This takes one argument, the name of an integer array for example, GETEVENT a%(). The array should be at least 6 integers long. (This is to allow for future upgrades you only need use the first two integers.)

If the event is a keypress:

a%(1) = keycode (as for GET)

a%(2) AND $00ff = modifier (as for KMOD)

a%(2)/256 = auto-repeat count (ignored by GET; you can ignore it too)

For non-key events (a%(1) AND $400) will be non-zero. If the event is a System message to change files or quit, a%(1)=$404. You should then use GETCMD$ to find the action required.

GETCMD$ returns a string, whose first character is "C", "O" or "X". If it is "C" or "O", the rest of the string is a filename.

You can only call GETCMD$ once for each event. You should do so as soon as possible after reading the event. Assign the value returned by GETCMD$ to a string variable so that you can extract its components.

If you have c$=GETCMD$, the first character, which you can extract with LEFT$(c$,1), has the following meaning:

"C" - close down the current file, and create the specified new file.

"O" - close down the current file, and open the specified existing file.

"X" - close down the current file (if any) and quit the OPA.

Again with c$=GETCMD$, MID$(c$,2,128) is the easiest way to extract the filename.

Note that events are ignored while you are using keywords which pause the execution of the program GET, GET$, EDIT, INPUT, PAUSE, MENU and DIALOG. If you need to use these keywords, use LOCK ON / LOCK OFF (described later) around them to prevent the System screen from sending messages.

Example OPAs

Here is a type 0 OPA, which just prints the keys you press. The keyboard procedure getk%: returns the key pressed, as with GET, but jumps to a procedure endit: if a System message to close down is received. (Type 0 OPAs do not receive "change file" messages.)

getk%: does not return events with values 256 ($100) or above, as they are not simple keypresses. This includes the non-typing keys like Menu ($100-$1FF), hot-keys ($200-$3FF), and non-key events ($400 and above).

    APP myapp0

          TYPE $1000

          ICON "\opd\me"

    ENDA

    PROC start:

          GLOBAL a%(6),k%

          STATUSWIN ON :FONT 11,16

          PRINT "Q to Quit"

          PRINT " or press Delete in"

          PRINT " the System screen"

          DO

                k%=getk%:

                PRINT CHR$(k%);

          UNTIL (k% AND $ffdf)=%Q       REM Quick way to do uppercase

    ENDP

    PROC getk%:

          DO

                GETEVENT a%()

                IF a%(1)=$404

                      IF LEFT$(GETCMD$,1)="X"

                            endit:

                      ENDIF

                ENDIF

          UNTIL a%(1)&lt;256

          RETURN a%(1)

    ENDP

    PROC endit:

          STOP

    ENDP

Here is a similar type 3 OPA. It does the same as the previous example, but System messages to change files cause the procedure fset: to be called. The relevant files are opened or created; the name of the file in use is shown in the status window.

    APP myapp3

          TYPE $1003

          ICON "\opd\me"

    ENDA

    PROC start:

          GLOBAL a%(6),k%,w$(128)

          STATUSWIN ON :FONT 11,16 :w$=CMD$(2)

          fset:(CMD$(3))

          PRINT "Q to Quit"

          PRINT " or press Delete in"

          PRINT "the System screen"

          PRINT " or create/swap files in"

          PRINT "the System screen"

          DO

                k%=getk%:

                PRINT CHR$(k%);

          UNTIL (k% AND $ffdf)=%Q

    ENDP

    PROC getk%:

          LOCAL t$(1)

          DO

                GETEVENT a%()

                IF a%(1)=$404

                      w$=GETCMD$

                      t$=LEFT$(w$,1)

                      w$=MID$(w$,2,128)

                      IF t$="X"

                            endit:

                      ELSEIF t$="C" OR t$="O"

                            TRAP CLOSE

                            IF ERR

                                  CLS :PRINT ERR$(ERR)

                                  GET :CONTINUE

                            ENDIF

                            fset:(t$)

                      ENDIF

                ENDIF

          UNTIL a%(1)&lt;256

          RETURN a%(1)

    ENDP

    PROC fset:(t$)

          LOCAL p%(6)

          IF t$="C"

                TRAP DELETE w$            REM SYS.SCREEN DOES ANY "OVERWRITE?"

                TRAP CREATE w$,A,A$

          ELSEIF t$="O"

                TRAP OPEN w$,A,A$

          ENDIF

          IF ERR

                CLS :PRINT ERR$(ERR)

                GET :STOP

          ENDIF

          SETNAME w$

    ENDP

    PROC endit:

          STOP

    ENDP

You should, as in both these examples, be precise in checking for the System message; if in future the GETCMD$ function were to use values other than "C", "O" or "X", these procedures would ignore them.

If you need to check the modifier keys for the returned keypress, use a%(2) AND $00ff instead of KMOD.

SETNAME extracts the main part of the filename from any file specification (even one that is not DOS-like), in the same way as PARSE$. Using SETNAME ensures that the correct name will be used in the file list in the System screen. If an OPA lets you change files with its own Open file option, it should always use SETNAME to inform the System screen of the new file in use.

To be strict, whenever creating a file, an OPA should first use PARSE$ to find the disk and directory requested. It should then use TRAP MKDIR to ensure that the directory exists.

When an OPA cannot respond

The LOCK command marks an OPA as locked or unlocked. When an OPA is locked with LOCK ON, the System will not send it events to change files or quit. If, for example, you move onto the file list in the System screen and press Delete to try to stop that running OPA, a message will appear, indicating that the OPA cannot close down at that moment.

You should use LOCK ON if your OPA uses a keyword, such as EDIT, which pauses the execution of the program. You might also use it when the OPA is about to go busy for a considerable length of time, or at any other point where a clean exit is not possible. Do not forget to use LOCK OFF as soon as possible afterwards.

An OPA is initially unlocked.

Designing an icon

As discussed earlier, an OPA icon is black and grey and has size 48 by 48 pixels. The icon is stored as two 48x48 bitmaps, black followed by grey, in a bitmap file. Here is a simple example program which creates a suitable bitmap:

    PROC myicon:

          gCREATE(0,0,48,48,1,1)

          gBORDER $200

          gAT 6,28

          gPRINT "me!"

          gSAVEBIT "me"

    ENDP

Here the window is created with a grey plane (the sixth argument to gCREATE) gSAVEBIT automatically saves a window with both black and grey plane to a file in the required format.

In the OPA itself use the ICON keyword, as explained previously, to give the name of the bitmap file to use here, ICON "\opd\me".

OPAs and the status window

If you use STATUSWIN ON,2 to display the status window, it shows the OPAs own icon and the name used with the APP keyword. STATUSWIN ON,1 displays the smaller status window.

Important: The permanent status window is behind all other OPL windows. In order to see it, you must use FONT (or both SCREEN and gSETWIN) to reduce the size of the text and graphics windows. You should ensure that your program does not create windows over the top of it.

You can also display a list of modes/views for use with the  key with DIAMINIT and position the  indicator with DIAMPOS.

The name can be changed with the SETNAME command. In general, an OPA should use SETNAME whenever it changes files, or creates a new file.

Other TYPE options

You can add any of these numbers to the value you use with TYPE:

$8000 (-32768) stops the System screens New file option from working, as for the RunOpl icon (translated OPL modules).

$4000 (16384) stops the System screen from closing the OPA, as for the Time icon. You should not use this without a very good reason.

$100 (8192) causes the System screen to terminate the OPA (when Delete is pressed there) without sending a message to the OPA to quit ("X"), as for the RunOpl icon again. This should be used only for OPAs which have no data that could be lost by sudden termination.

For example, use TYPE $8001 for a type 1 OPA having the first of the features above. (Note that TYPE $8000+1 would fail to translate as the translator cannot evaluate expressions for any keywords between APP and ENDA).


Tricks

Section Contents

The calculator memories

Running a program twice

  Foreground and background

  Foreground and background

  The calculator memories

The calculator memories M0 to M9 are available as floating-point variables in OPL. You might use them to allow OPL access to results from the calculator, particularly if you use OPL procedures from within the calculator.

Its best not to use them as a substitute for declaring your own variables. Your OPL program might go wrong if another running OPL program uses them, or if you use them yourself in the calculator.

Running a program twice

Although you may never need to, you can run more than one copy of the same translated OPL module at the same time. There are two ways:

Use Copy file in the System screen to make a new copy of the module, with a different filename. Then run both files.

For the Series 3c only, run the file as normal. Then move the highlight to under the RunOpl icon, press Tab to show the file selector, and pick the name of the translated module again.

  Foreground and background

SETFLAGS $10000 tells the Series 5 to send a "machine switch on" event to the current program, whenever the Series 5 switches on, even if this program is in the background. If required, use it just once at the start of your program.

The System OPX procedure SETFOREGROUND: brings the current program to the foreground.

The System OPX procedure SETBACKGROUND: sends it to the background again.

Note that each of these should be followed by gUPDATE to ensure they take effect immediately.

Note that when a program runs in the background it can stop the "automatic turn off" feature from working. However, as soon as the program waits for a keypress or an event, with GET/GET$ or GETEVENT32, auto-turn off can occur.

Auto-turn off can also occur if the program does a PAUSE (of 2 or more 20ths of a second), but only if the program has used SETACTIVE from System OPX to mark the program as inactive.

See the Using OPXs on the Series 5 chapter for more information about System OPX procedures.

  Foreground and background

CALL ($6c8d) tells the Series 3c to send a "machine switch on" event to the current program, whenever the Series 3c switches on, even if this program is in the background. If required, use it just once at the start of your program.

CALL($198d,0,0) brings the current program to the foreground.

CALL($198d,100,0) sends it to the background again.

Each of these should be followed by gUPDATE to ensure they take effect immediately.

This example program comes to the foreground and beeps whenever you turn the Series 3c on. Be careful to enter the CALL and GETEVENT statements exactly as shown.

    PROC beepon:

          LOCAL a%(6)

          PRINT "Hello"

          CALL($6c8d) :GUPDATE

          WHILE 1

                DO

                      GETEVENT a%()

                      IF a%(1)=$404 :STOP :ENDIF :REM closedown

                UNTIL a%(1)=$403 :REM machine ON

                CALL($198d,0,0) :gUPDATE

                BEEP 5,300 :PAUSE 10 :BEEP 5,500

                CALL($198d,100,0) :gUPDATE

          ENDWH

    ENDP

Note that when a program runs in the background it can stop the "automatic turn off" feature from working. However, as soon as the program waits for a keypress or an event, with GET/GET$ or GETEVENT, auto-turn off can occur.

Auto-turn off can also occur if the program does a PAUSE (of 2 or more 20ths of a second), but only if the program has used CALL($138b) ("unmark as active")

On the Series 5, procedures are automatically cached and the cache commands are not available on  the Series 5.  Therefore, the following section is only applicable to the Series 3c and Siena.


Cacheing procedures on the Series 3c and
Cacheing procedures on the Series 3c and Siena

Section Contents

Cache size

Procedures in unloaded modules

Cache timings

Compatibility mode modules

Potential problems in existing programs

Controlling procedure cacheing

Tidying the cache

Getting cache index header information

Getting a cache index record

Without procedure cacheing, procedures are loaded from file whenever they are called and discarded when they return - LOADM simply loads a map listing procedure names and their positions in the module file so that they can be loaded fairly efficiently. The cache handling commands provide a method for keeping the code for a procedure loaded after it returns - it remains loaded until a procedure called later requires the space in the cache. The strategy is then to remove the least recently used procedures, making it more likely that all the procedures called together in a loop, for example, remain in the cache together, thus speeding up procedure calling significantly.

Cache handling keywords allow you to:

create a cache of a specified initial and maximum size using CACHE init%,max%. You can specify these up to 32,767 bytes.

If you use hex, you can even exceed this figure, if you need to - e.g. CACHE $9000,$9000. However, you cannot exceed the 64k total memory limit which each Series 3c process has.

prevent loading and removal of procedures from the cache so that a given set of procedures can be guaranteed to remain in the cache using CACHE OFF. Procedures already in the cache are still used when cacheing is off. The loading and removal of procedures can subsequently be resumed using CACHE ON.

tidy the cache by removing procedures that are no longer in use (i.e. procedures that have returned) using CACHETIDY.

for advanced use during program development, further keywords are provided for inspecting the contents of the cache at any time (see CACHEHDR and CACHEREC).

Cache size

Cacheing procedures is not a cure all. Care should be taken that the cache size is sufficient to load all procedures required for a fast loop otherwise, for example, a large procedure may cause all the small ones in a loop to be removed and equally, a small one may require the large one to be removed, so that the cache provides no benefit at all. In fact, the overhead needed for cache management can then make your program less efficient than having no cache at all. If the maximum cache size you can have is limited, careful use of CACHE OFF should prevent such problems at the expense of not fitting all the procedures in the loop in the cache. CACHE OFF is implemented very efficiently and calling it frequently in a loop should not cause much concern.

To guarantee that there is enough memory for a given cache size, create the cache passing that value as the initial size using TRAP CACHE init%,max%. TRAP ensures that if the cache creation succeeds, ERR returns zero and otherwise the negative Out of memory error is raised. After creation, the cache will grow as required up to the maximum size max% or until there is not enough free memory to grow it. On failure to grow the cache, any procedures which will not fit into the existing cache, even when unused procedures are removed, are simply loaded without using the cache and are discarded when they return.

If you want to ensure a certain minimum cache size, say 10000 bytes, but do not care how large it grows, you could use TRAP CACHE 10000,$ffff so that the cache just grows up to the limits of memory. For a relatively small program, you might want to load the whole program into cache by making the cache size the same size as the module. This will in fact be a little larger than required, unnecessarily including a procedure name table and module file header which are not loaded into the cache. The minimum cache size is 2000 bytes, which is used if any lower value is specified. If the maximum size specified is less than the initial size, the maximum is set to the initial size. The maximum cache size cannot be changed once the cache has been created and an error is returned if you attempt to do so.

The initial cache size should ideally be large enough to hold all procedures that are to be cached simultaneously. There is no advantage in growing the cache from its initial size when you know that a certain minimum size is needed.

Procedures in unloaded modules

When a module is unloaded, all procedures in it that are no longer in use are removed from the cache. Any procedure that is still in use, is hidden in the cache by changing its first character to lower case; when it finally returns, a hidden procedure is removed in the normal manner to make room for loading a new procedure when the cache is full. Note that it considered bad practice to unload a module containing procedures that are still running - e.g. for a procedure to unload its own module.

Cache timings

Calling an empty procedure that simply returns is approximately 10 times faster with a cache. This figure was obtained by calling such a procedure 10000 times in a loop, both with cacheing off and on, and subtracting the time taken running an empty loop in each case.

Clearly that case is one of the best for showing off the advantages of cacheing, and there is no general formula for calculating the speed gain. The procedures that benefit most will be those that need most module file access relative to their size in order to load them into memory. The programmer cannot reasonably write code taking this into account, so no further details are provided here.

The case described above does not require any procedures to be removed from the cache to make room for new procedures when the cache is full, and removal of procedures requires a fair amount of processing by the cache manager. If many procedures in a time-critical section of your program are loaded into the cache and not used often before removal, the speed gain may be less than expected - a larger cache may be called for to prevent too many removals.

It should be noted however, that even with the worst case of procedures being loaded into the cache for use just once before removal, having a cache is often superior to having no cache. This is because the cache manager reads module file data (required for loading the procedures into memory) in one block rather than a few bytes at a time and it is the avoidance of excessive file access which provides the primary speed gains for cacheing.

Compatibility mode modules

Procedures in modules translated for the Series 3 cannot be loaded into the cache. On encountering such a procedure, the cache manager simply loads it without using the cache and discards it when it returns. The reason for this is that a few extra bytes of data are stored in the Series 3c modules which are needed by the cache manager.

Potential problems in existing programs

It is possible that previously undiscovered bugs in existing OPL programs are brought to light simply by adding code to use the cache.

Without cacheing, the variables in a procedure are followed immediately by the code for the procedure. Writing beyond the variables (for example reading too many bytes into the final variable using such keywords as gPEEKLINE or KEYA) would have written over the code itself but would have gone unnoticed unless you happened to loop back to the corrupted code. With a cached procedure, the code no longer follows your variables, so the corruption occurs elsewhere in memory, resulting quite probably in the program crashing.

Controlling procedure cacheing

TRAP CACHE initSize%,maxSize% creates a cache of a specified initial number of bytes, which may grow up to the specified maximum. If the maximum is less than the initial size, the initial size becomes the maximum. If growing the cache fails, normal loading without the cache is used. The In use error (-9) is raised if a cache has been created previously or the Out of memory error (-10) on failure to create a cache of the specified initial size - use the TRAP command if required. Procedure code and other information needed for setting up variables are loaded into the cache when the procedure is called. If there is no space in the cache and enough space can be regained, the least recently used procedures are removed. Otherwise the procedure is loaded in the normal way without cacheing.

Once a cache has been created, CACHE OFF prevents further cacheing, although the cache is still searched when calling subsequent procedures. CACHE ON may then be used to re-enable cacheing. Note that CACHE ON or CACHE OFF are ignored if used before CACHE initSize%,maxSize%.

Tidying the cache

CACHETIDY removes any procedures from the cache that have returned to their callers. This might be called after performing a large, self-contained action in the program which required many procedures. Using CACHETIDY will then result in speedier searching for procedures called subsequently. More importantly, it will prevent the procedures being unloaded one at a time when the need arises - it is very efficient to remove a set of procedures that are contiguous in the cache as is likely to be the case in this situation.

Note that a procedure which has returned is automatically removed from the cache if you unload the module it is in, so CACHETIDY neednt be used for such a procedure.

Getting cache index header information

The CACHEHDR command is provided for advanced use and is intended for use during program development only.

CACHEHDR ADDR(hdr%()) reads the current cache index header into the array hdr%() which must have at least 11 integer elements. Note that any information returned is liable to change whenever a procedure is called, so you cannot save these values over a procedure call.

If no cache has yet been created, hdr%(10)=0 and the other data read is meaningless. Otherwise, the data read is as follows:

hdr%(1)      current address of the cache itself

hdr%(2)      number of procedures currently cached

hdr%(3)      maximum size of the cache in bytes

hdr%(4)      current size of the cache in bytes

hdr%(5)       number of free bytes in the cache

hdr%(6)      total number of bytes in cached procedures which are freeable (i.e. not running)

hdr%(7)      offset from the start of the cache index to the first free index record

hdr%(8)      offset from start of cache index to most recently used procedures record; zero if none

hdr%(9)      offset from start of cache index to least recently used       procedures record; zero if none

hdr%(10)      address of the cache index, or zero if no cache created  yet

hdr%(11)      non-zero if cacheing is on, and zero if it is off

The cache manager maintains an index for the cache consisting of an index header containing overall information for the whole cache as well as one index record for each procedure cached. All offsets mentioned above give the number of bytes from the start of the index to the procedure record specified. The index records for cached procedures form a doubly linked list, with one list beginning with the most recently used procedure (MRU), with offset given by hdr%(8), and the other with the least recently used procedure (LRU) with offset given by hdr%(9). A further singly linked list gives the offsets to free index records. The linkage mechanism is described in the discussion of CACHEREC below.

Getting a cache index record

The CACHEREC command is provided for advanced use and is intended for use during program development only.

CACHEREC ADDR(rec%()),offset% reads the cache index record (see the description of CACHEHDR above) at offset% into array rec%() which must have at least 18 integer elements. offset%=0 specifies the most recently used (MRU) procedures record if any and offset%&lt;0 the least recently used procedure (LRU) procedures record if any.

The data returned by CACHEREC is meaningless if no cache exists (in which case rec%(17)=0) or if there are no procedures cached yet (when hdr%(8)=0 as returned by CACHEHDR).

Each record gives the offset to both the more recently used and to the less recently used procedures record in the linked lists, except for the MRU and the LRU procedures records themselves which each terminate one of the lists with a zero offset. The first free index record (see CACHEHDR above) starts the free record list, in which each record gives the offset of the next free record or zero offset to terminate the list. To "walk" the cache index, you would always start by calling CACHEREC specifying either the MRU or LRU record offset, and use the values returned to read the less or more recently used procedures record respectively. Note that any information returned is liable to change whenever a procedure is called, so you cannot save these values over a procedure call.

For the free cell list, only rec%(1) is significant, giving the offset of the next free index record. For the records in the lists starting with either the LRU or MRU record, the data returned in rec%() is:

rec%(1)    offset to less recently used procedures record or zero if on LRU

rec%(2)    offset to more recently used procedures record or zero if on MRU

rec%(3)    usage count zero if not running

rec%(4)    offset in cache itself to descriptor for building the procedure frame

rec%(5)    offset in cache itself to translated code for the procedure

rec%(6)    offset in cache itself to the end of the translated code for the procedure

rec%(7)    number of bytes used by the procedure in the cache itself

rec%(8-15) leading byte counted procedure name, followed by some private data

rec%(16)   address of the procedures leading byte counted module name

rec%(17)   address of the cache index, or zero if no cache created yet

rec%(18)   non-zero if cacheing is on, and zero if it is off

For example, to print the names of procedures and their sizes from MRU to LRU:

    CACHEHDR ADDR(hdr%())

    IF hdr%(10)=0

          PRINT "No cache created yet"

          RETURN

    ENDIF

    IF hdr%(8)=0                                        REM MRU zero?

          PRINT "None cached currently"

          RETURN

    ENDIF

    rec%(1)=0                                    REM MRU first

    DO

          CACHEREC ADDR(rec%()),rec%(1)       REM less recently used proc

          PRINT PEEK$(ADDR(rec%(8))),rec%(7)       REM name and size

    UNTIL rec%(1)=0

For Sprite handling on the Series 5, see the Using OPXs on the Series 5 chapter.  Note, however, that the basic idea of sprite handling remains the same for the Series 5, and you may find some of the information given below helpful.


Sprite handling on the Series 3c and Sie
Sprite handling on the Series 3c and Siena

Section Contents

How sprites work

Why use sprites?

Creating a sprite

Appending a bitmap-set to a sprite

Drawing a sprite

Changing a bitmap-set in a sprite

Positioning a sprite

Closing a sprite

Sprite example

How sprites work

OPL includes a set of keywords for handling a sprite - a user-defined black/grey/white graphics object of variable size, displayed on the screen at a specified position.

The sprite can also be animated - you can specify up to 13 bitmap-sets which are automatically presented in a cycle, with the duration for each bitmap-set specified by you. Each bitmap-set may be displayed at a specifiable offset from the sprites notional position.

The 13 bitmap-sets are each composed of up to six bitmaps. The set pixels in each bitmap specify one of the following six actions: black pixels to be drawn; black pixels to be cleared; black pixels to be inverted; grey pixels to be drawn; grey pixels to be cleared; or grey pixels to be inverted. The bitmaps in a set must have the same size.

All the bitmaps in a set are drawn to the screen together and displayed for the specified duration, followed by the next set, and so on.

If you do not specify that a pixel is to be drawn, cleared or inverted, the background pixel is left unchanged.

Black pixels are drawn "on top of" grey pixels, so if you clear/invert just the grey pixels in the sprite they will be hidden under any pixels set black. So to clear/invert pixels on a background which has both grey and black pixels set, you need to clear/invert both black and grey pixels in the sprite.

The pixels of one colour (black or grey) which are set in one bitmap of the bitmap-set should not overlap with those of the same colour which are set in another bitmap in the same bitmap-set. This is because the order in which the bitmaps are applied is undefined. So, for example, do not specify that pixel (0,0) should have the black pixel both drawn and cleared.

Why use sprites?

A sprite is useful for displaying something in foreground without having to worry about restoring the background display. A sprite can also have any shape, leaving the background display all around it intact, and it can even be hollow - only the pixels specified by you are drawn, cleared or inverted. Typically only one bitmap-set containing two black bitmaps would be used - one for setting and one for clearing pixels.

You would not often use the sprite features in their full generality. In fact, more than one bitmap-set is needed only for animation and it is also seldom necessary to use all the available bitmaps in a single bitmap-set.

Creating a sprite

    sprId%=CREATESPRITE

creates a sprite and returns the sprite ID.

Appending a bitmap-set to a sprite

    APPENDSPRITE tenths%,bitmap$()

    APPENDSPRITE tenths%,bitmap$(),dx%,dy%

append a single bitmap-set to a sprite. These may be called up to 13 times for each sprite.

APPENDSPRITE may be called only before the sprite is drawn, otherwise it raises an error. tenths% gives the duration in tenths of seconds for the bitmap-set to be displayed before going on to the next bitmap-set in the sequence. It is ignored if there is only one bitmap-set.

bitmap$() contains the names of the six bitmap files in the set:

bitmap$(1)      for setting black pixels

bitmap$(2)      for clearing black pixels

bitmap$(3)      for inverting black pixels

bitmap$(4)      for setting grey pixels

bitmap$(5)      for clearing grey pixels

bitmap$(6)      for inverting grey pixels

Use "" to specify no bitmap. If "" is used for all the bitmaps in the set, the sprite is left blank for the specified duration.

The array must have at least 6 elements.

All the bitmaps in a single bitmap-set must be the same size, otherwise an Invalid arguments error is raised on attempting to draw the sprite. Bitmaps in different bitmap-sets may differ in size. dx% and dy% are the (x,y) offsets from the sprite position (see CREATESPRITE) to the top-left of the bitmap-set with positive for right and down. The default value of each is zero.

Sprites may use considerable amounts of memory. A sprite should generally be created, initialised and closed in the same procedure to prevent memory fragmentation. Care should also be taken in error handling to close a sprite that is no longer in use.

Creating or changing a sprite consisting of many bitmaps requires a lot of file access and should therefore be avoided if very fast sprite creation is required. Once the sprite has been drawn, no further file access is performed (even when it is animated) so the number of bitmaps is no longer important.

Drawing a sprite

    DRAWSPRITE x%,y%

draws a sprite in the current window with top-left at pixel position (x%,y%). The sprite must previously have been initialised using APPENDSPRITE or the Resource not open error (-15) is raised. If any bitmap-set contains bitmaps with different sizes, DRAWSPRITE raises an Invalid arguments error (-2).

Changing a bitmap-set in a sprite

    CHANGESPRITE index%,tenths%,var bitmap$()

    CHANGESPRITE index%,tenths%,var bitmap$(),dx%,dy%

change the bitmap-set specified by index% (1 for the first bitmap-set) in the sprite using the supplied bitmap files, offsets and duration which are all used in the same way as for APPENDSPRITE.

CHANGESPRITE can be called only after DRAWSPRITE.

Note that if all or many bitmap-sets in the sprite need changing or if each bitmap-set consists of many bitmaps, the time required to read the bitmaps from file may be considerable, especially if fast animation is in progress. In such circumstances, you should think about closing the sprite and creating a new one, which will often be more efficient.

Positioning a sprite

    POSSPRITE x%,y%

sets the position of the sprite to (x%,y%).

Closing a sprite

    CLOSESPRITE sprId%

closes the sprite with ID sprId%.

Sprite example

The following code illustrates all the sprite handling keywords using a sprite consisting of just two bitmap-sets each containing a single bitmap.

    PROC sprite:

          LOCAL bit$(6,6),sprId%

          crBits:                           REM create bitmap files

          gAT gWIDTH/2,0

          gFILL gWIDTH/2,gHEIGHT,0       REM fill half of screen

          sprId%=CREATESPRITE bit$(1)="" :bit$(2)=""

          bit$(3)="cross"              REM black cross, pixels inverted

          bit$(4)="" :bit$(5)="" :bit$(6)="

          APPENDSPRITE 5,bit$(),0,0       REM cross for half a second

          bit$(1)="" :bit$(2)="" :bit$(3)=""

          bit$(4)="" :bit$(5)="" :bit$(6)=""

          APPENDSPRITE 5,bit$(),0,0      REM blank for half a second

          DRAWSPRITE gWIDTH/2-5,gHEIGHT/2-5  REM animate the sprite

          BUSY "flash cross, c",3        REM no offset (c for central)

          GET bit$(3)="box"             REM black box, pixels inverted

          CHANGESPRITE 2,5,bit$(),0,0      REM in 2nd bitmap-set

          BUSY "cross/box, c/c",3       REM central/central

          GET

          CHANGESPRITE 2,5,bit$(),40,0      REM offset by 40 pixels right

          BUSY "cross/box, c/40",3      REM central/40

          GET

          bit$(3)=""                          REM Remove the cross in set 1

          CHANGESPRITE 1,3,bit$(),0,0       REM display for 3/10 seconds

          BUSY "flash box, 40",3        REM box at offset 40 still

          GET

          bit$(3)="cross"

          CHANGESPRITE 1,5,bit$(),0,0      REM cross centralised - set 1

          bit$(3)="box"

          CHANGESPRITE 2,5,bit$(),0,0      REM box centralised - set 2

          BUSY "Escape quits"

          DO

                POSSPRITE RND*(gWIDTH-11),RND*(gHEIGHT-11

                REM move sprite randomly

                PAUSE -20                       REM once a second

          UNTIL KEY = 27

          CLOSESPRITE sprId%

    ENDP

    PROC crBits:

          REM create bitmap files if they dont exist

          IF NOT EXIST("cross.pic") OR NOT EXIST("box.pic")

                gCREATE(0,0,11,11,1,1)

                gAT 5,0 :gLINEBY 0,11

                gAT 0,5 :gLINEBY 11,0

                gSAVEBIT "cross"

                gCLS

                gAT 0,0

                gBOX gWIDTH,gHEIGHT

                gSAVEBIT "box"

                gCLOSE gIDENTITY

          ENDIF

    ENDP


Scanning the keyboard directly

It is sometimes useful to know which keys are being pressed at a given moment and also when a key is released. For example, in a game, a certain key might start some action and releasing the key might stop it.

GETEVENT32 ev&() and GETEVENTA32 ev&() return the scan code of any key pressed to the third element of the array ev&(). It is the programmers responsibility to track to key currently being pressed. The scan codes are given in hex in the following representation of the keyboard:

CALL($288e,ADDR(scan%())) returns with the array scan%(), which must have at least 10 elements, containing a bit set for keys currently being pressed.Every key on the keyboard is represented by a unique bit. This includes the modifier keys (Shift, Control etc.) and the application buttons (System, Data, Word etc.).A set bit simply signifies a pressed key - a key pressed on its own gives one bit set; that same key with a modifier gives the same bit set with another bit for the modifier; the modifier on its own gives the same modifier bit on its own.The following table lists each key (according to the text printed on the physical key itself), the scan%() array element for that key and the hexadecimal bit mask to be ANDed with that array element to check whether the key is being pressed.

key	scan%()	mask	key	scan%()	mask

System	5	$200	Data	4	$200

Word	6	$200	Agenda	2	$200

Time	1	$200	World	3	$200

Calc	2	$100	Sheet	1	$100

Esc	8	$100	1	8	$02

2	8	$04	3	6	$40

4	5	$04	5	5	$08

6	8	$08	7	4	$40

8	3	$08	9	3	$10

0	2	$10	+	2	$08

Delete	$01	Tab	1	$04

Q	7	$02	W	7	$20

E	6	$20	R	5	$02

T	5	$10	Y	1	$08

U	4	$20	I	3	$04

O	3	$20	P	2	$20

-	2	$04	Enter	1	$01

Control	3	$80	A	7	$04

S	7	$10	D	6	$10

F	6	$02	G	5	$20

H	8	$40	4	$10

K	3	$02	L	3	$40

*	2	$40	/	2	$02

Left shift	2	$80	Z	7	$08

X	7	$40	C	6	$08

V	6	$04	B	5	$40

N	1	$40	M	4	$08

,	4	$02	.	8	$10

Up	8	$20	Right shift	4	$80

Psion	1	$80	Menu	6	$80

5	$80	Space	5	$01

Help	4	$04	Left	1	$10

Down	1	$20	Right	1	$02

For example, pressing Tab sets bit 2 of scan%(1), pressing Control sets bit 7 of scan%(3) and pressing both together sets both these bits. So Tab is being pressed if scan%(1) AND $04 is non-zero, and Control is being pressed if scan%(3) AND $80 is non-zero.A possible strategy for scanning the keys might be to wait for any key of interest using GETEVENT or GET (allowing switch off and less intensive use of the battery), start the required action, which is to be continued only while the key is being pressed, scan the keyboard, as discussed above, until the key is released, and then stop the action, wait for the next key and repeat.

Note that the key returned by GETEVENT or GET is not precisely synchronised with those scanned, so once you have waited for a relevant key you should scan for all the keys pressed, ignoring the keycode returned by GETEVENT or GET.


I/O functions and commands

Section Contents

Error handling

Handles

var variables

Opening a file with IOOPEN

Mode Category 1 - Open mode

Mode Category 2 - File format

Mode Category 3 - Access flags

Closing a file with IOCLOSE

Reading a file with IOREAD

Text files

Binary files

Writing to a file

Positioning within a file

Example - displaying a plain text file

Asynchronous requests and semaphores

Asynchronous requests

The I/O semaphore

Status words

Cancelling an asynchronous request

Waiting for a particular completion

A first example using asynchronous I/O

Polling rather than waiting

I/O device handling and the asynchronous request functions

Some useful IOW functions

Example of IOW screen functions

  Alarm example - IOC to ALM:

  Dialling example - IOW to SND:

Note that one of the fundamental differences between the Series 3c and the Series 5 is that while any OPL program on the Series 3c has a 64K limit on the memory it may use, the memory which an OPL program may use on the Series 5 is unlimited up to the constraint placed by the machine itself. Therefore while 16-bit addresses are sufficient on the Series 3c, the Series 5 requires 32-bit addressing.  As far as I/O functions are concerned, this means that any argument which is an address is an integer on the Series 3c, while it must be a long integer on the Series 5. See also the later section on 32-bit addressing.

OPL includes powerful facilities to handle input and output (I/O). These functions and commands can be used to access all types of files on the Psion, as well as various other parts of the low-level software.

This section describes how to open, close, read and write to files, and how to set the position in a file. The data file handling commands and functions have been designed for use specifically with data files. The I/O functions and commands are designed for general file access. You dont need to use them to handle data files.

These are powerful functions and commands and they must be used with care. Before using them you must read this chapter closely and have a good grounding in OPL in general.

Error handling

You should have a good understanding of error handling before using the I/O functions.

The functions in this section never raise an OPL error message. Instead they return a value - if this is less than zero an error has occurred. It is the responsibility of the programmer to check all return values, and handle errors appropriately. Any error number returned will be one of those in the list given in the error handling chapter. You can use ERR$ to display the error as usual.

Handles

Many of these functions use a handle, which must be a long integer variable on the Series 5 and an integer variable on the Series 3c (see the note above). IOOPEN assigns this handle variable a value, which subsequent I/O functions use to access that particular file. Each file you IOOPEN needs a different handle variable.

var variables

In this section, var denotes an argument which should normally be a LOCAL or GLOBAL variable. (Single elements of arrays may also be used, but not field variables or procedure parameters.) Where you see var the address of the variable is passed, not the value in it. (This happens automatically; dont use ADDR yourself.)

In many cases the function you are calling passes information back by setting these var variables.

var is just to show you where you must use a suitable variable; you dont actually type it.

For example: ret%=IOOPEN(var handle&,name$,mode%) in the syntax description indicates that IOOPEN(h&,"abc",0) is correct (provided h& is a simple variable), but IOOPEN(100,"abc",0) is incorrect.

For example: ret%=IOOPEN(var handle%,name$,mode%) in the syntax description indicates that IOOPEN(h%,"abc",0) is correct (provided h% is a simple variable), but IOOPEN(100,"abc",0) is incorrect.

It is possible, though, that you already have the address of the variable to use. It might be that this address is held in a field variable, or is even a constant value, but the most common situation is when the address was passed as a parameter to the current procedure.

If you add a # prefix to a var argument, this tells OPL that the expression following is the address to be used, not a variable whose address is to be taken.

Here is an example program:

    PROC doopen:(phandle&, name$, mode%)

          LOCAL error%

          REM IOOPEN, handling errors

          error% = IOOPEN(#phandle&, name$, mode%)

          IF error% : RAISE error% : ENDIF

    ENDP

The current value held in phandle& is passed to IOOPEN. You might call doopen: like this:

    LOCAL filhand%,...

    ...

    doopen:(ADDR(filhand%),"log.txt",$23)

    ...

The doopen: procedure calls IOOPEN with the address of filhand%, and IOOPEN will write the handle into filhand%.

Note that phandle& should be replaced by phandle% (i.e. integer rather than long integer) on the Series 3c.

If you ever need to add or subtract numbers from the address of a variable, use the UADD and USUB functions, or you run the risk of Overflow errors.

Opening a file with IOOPEN

ret%=IOOPEN(var handle%,name$,mode%

or

ret%=IOOPEN(var handle%,address&,mode%)

ret%=IOOPEN(var handle%,address%,mode%)

for unique file creation.

Creates or opens a file (or device) called name$ and sets handle% to the handle to be used by the other I/O functions.

mode% specifies how the file is to be opened. It is formed by ORing together values which fall into the three following categories:

Mode Category 1 - Open mode

One and only one of the following values must be chosen from this category.

$0000	Open an existing file (or device). The initial current position is set to the start of the file.

$0001	Create a file which must not already exist.

$0002	Replace a file (truncate it to zero length) or create it if it does not exist.

$0003	Open an existing file for appending. The initial current position is set to the end of the file. For text format files (see $0020 below) this is the only way to position to end of file.

$0004	Creates a file with a unique name. For this case, you must use the address of a string instead of name$. This string specifies only the path of the file to be created (any file name in the string is ignored). The string at address% is then set by IOOPEN to the unique file name generated (this will include the full path). The string must be large enough to take 130 characters (the maximum length file specification). For example:

    s$="C:\home\" REM C should be replaced with M on the 3c

    IOOPEN(handle%,ADDR(s$),mode%)

This mode is typically used for temporary files which will later be deleted or renamed.

Mode Category 2 - File format

One and only one of the following values must be chosen from this category. When creating a file, this value specifies the format of the new file. When opening an existing file, make sure you use the format with which it was created.

$0000	The file is treated as a byte stream of binary data with no restriction on the value of any byte and no structure imposed upon the data. Up to 16K can be read from or written to the file in a single operation.

$0020	The file is treated as a sequence of variable length records. The records are assumed to contain text terminated by any combination of the CR and LF ($0D, $0A) characters. The maximum record length is 256 bytes and Control-Z ($1A) marks the end of the file.

On the Series 5, all of these values are declared as constants in Const.oph. See the Calling Procedures chapter for details of how to use this file and Appendix E for a listing of it.

Mode Category 3 - Access flags

Any combination of the following values may be chosen from this category.

$0100	Update flag. Allows the file to be written to as well as read. If not set, the file is opened for reading only. You must use this flag when creating or replacing a file.

$0200	Choose this value if you want the file to be open for random access (not sequential access), using the IOSEEK function.

$0400	Specifies that the file is being opened for sharing for example, with other running programs. Use if you want to read, not write to the file. If the file is opened for writing ($0100 above), this flag is ignored, since sharing is then not feasible. If not specified, the file is locked and may only be used by this running program.

Closing a file with IOCLOSE

Files should be closed when no longer being accessed. This releases memory and other resources back to the system.

    ret%=IOCLOSE(handle%)

Closes a file (or device) with the handle handle% as set by IOOPEN.

Reading a file with IOREAD

ret%=IOREAD(handle%,address&,maxLen%)

ret%=IOREAD(handle%,address%,maxLen%)

Reads up to maxLen% bytes from a file with the handle handle% as set by IOOPEN. address& (or address% on the Series 3c) is the address of a buffer into which the data is read. This buffer must be large enough to hold a maximum of maxLen% bytes. The buffer could be an array or even a single integer as required. No more than 16K bytes can be read at a time.

The value returned to ret% is the actual number of bytes read or, if negative, is an error value.

Text files

If maxLen% exceeds the current record length, data only up to the end of the record is read into the buffer. No error is returned and the file position is set to the next record.

If a record is longer than maxLen%, the error value Record too large (-43) is returned. In this case the data read is valid but is truncated to length maxLen%, and the file position is set to the next record.

A string array buffer$(255) could be used, but make sure that you pass the address ADDR(buffer$)+1 (UADD(ADDR(buffer$),1) on the Series 3c) to IOREAD. This leaves the leading byte free. You can then POKEB the leading byte with the count (returned to ret%) so that the string conforms to normal string format. See the example program.

Binary files

If you request more bytes than are left in the file, the number of bytes actually read (even zero) will be less than the number requested. So if ret%&lt;maxLen%, end of file has been reached. No error is returned by IOREAD in this case, but the next IOREAD would return the error value End of file (-36).

To read up to 16K bytes (8192 integers), you could declare an integer array buffer%(8192).

Writing to a file

ret%=IOWRITE(handle%,address&,length%)

ret%=IOWRITE(handle%,address%,length%)

Writes length% bytes stored in a buffer at address& (address% on the Series 3c) to a file with the handle handle%.

When a file is opened as a binary file, the data written by IOWRITE overwrites data at the current position.

When a file is opened as a text file, IOWRITE writes a single record; the closing CR/LF is automatically added.

Positioning within a file

ret%=IOSEEK(handle%,mode%,var offset&)

Seeks to a position in a file that has been opened for random access (see IOOPEN above).

mode% specifies how the argument offset& is to be used. offset& may be positive to move forwards or negative to move backwards. The values you can use for mode% are:

1	Set position in a binary file to the absolute value specified in offset&, with 0 for the first byte in the file.

2	Set position in a binary file to offset& bytes from the end of the file

3	Set position in a binary file to offset& bytes relative to the current position.

6	Rewind a text file to the first record. offset& is not used, but you must still pass it as a argument, for compatibility with the other cases.

IOSEEK sets the variable offset& to the absolute position set.

Example - displaying a plain text file

This program opens a plain text file, such as one created with the Export as text85 option in the File menu of the Program editor on the Series 5, or the Save as option in the Word Processor or Database applications on the Series 3c, and types it to the screen. Press Esc to quit and any other key to pause the typing to the screen.

    PROC ioType:

          LOCAL ret%,fName$(128),txt$(255),address&

          LOCAL handle%,mode%,k%

          PRINT "Filename?", :INPUT fName$ : CLS

          mode%=$0400 OR $0020  REM open=$0000,text=$0020,share=$0400

          ret%=IOOPEN(handle%,fName$,mode%)

          IF ret%&lt;0

                showErr:(ret%)

                RETURN

          ENDIF

          address&=ADDR(txt$)

          WHILE 1

                k%=KEY

                IF k%                        REM if keypress

                      IF k%=27                   REM Esc pressed

                            RETURN            REM otherwise wait for a key

                      ELSEIF GET=27

                            RETURN            REM Esc pressed

                      ENDIF

                ENDIF

                ret%=IOREAD(handle%,address&+1,255)

                IF ret%&lt;0

                      IF ret%&lt;&gt;-36            REM NOT EOF

                            showErr:(ret%)

                      ENDIF

                      BREAK

                ELSE

                      POKEB address&,ret%      REM leading byte count

                      PRINT txt$

                ENDIF

          ENDWH

          ret%=IOCLOSE(handle%)

          IF ret%

                showErr:(ret%)

          ENDIF

          PAUSE -100 :KEY

    ENDP

    PROC showErr:(val%)

          PRINT "Error",val%,err$(val%)

          GET

    ENDP

Note that this example may be used for any of the machines. However, any address variables may be changed from long integers to integers if you are using the Series 3c or Siena.

Asynchronous requests and semaphores

This section provides the general background necessary for understanding how EPOC I/O devices can be accessed by an OPL application program.

Asynchronous requests

Many operating system services are implemented in two steps:

make the service request (sometimes referred to as the queuing of a request)

wait for the requested operation to complete

In most cases, as well as providing functions for each step, the system provides a function containing both the above steps. Such functions are called synchronous because they automatically synchronise the requesting process by waiting until the operation has completed. The internal function that makes the request without waiting for completion is called an asynchronous function.

Examples of asynchronous request functions are:

IOC (or IOA)

for requests on an open I/O channel

KEYA

for requests on the keyboard channel

 GETEVENTA32

for requests of events from the window server

 PLAYSOUNDA:

(in System OPX) for requests to play sound files

The synchronous versions of the above functions are IOW , GET, GETEVENT32 and PLAYSOUND: respectively.

Applications use asynchronous requests in situations like the following:

make request A

make request B

wait for either of the requested operations to complete

Processes wait for the completion of asynchronous requests by waiting on their I/O semaphore where each request is associated with a status word.

The I/O semaphore

When a process is created, the system automatically creates an I/O semaphore on its behalf (a more accurately descriptive name would have been the asynchronous request semaphore). After making one or more asynchronous requests using IOC or IOA, a process calls IOWAIT to wait on the I/O semaphore for one of the requests to complete. A typical application spends most of its time waiting on its I/O semaphore. For example, an interactive application process that is waiting for user input is waiting on the I/O semaphore.

Semaphores are provided to synchronise cooperating processes (where, in this context, a process includes a hardware interrupt). In OPL semaphores are used for synchronising the completion of asynchronous requests.

The semaphores in both the EPOC16 and EPOC32 operating systems are counting semaphores, having a signed value that is incremented by calling IOSIGNAL and decremented by calling IOWAIT. A semaphore with a negative value implies that a process must wait for the completion of an event.

The process or the hardware interrupt handler that implements the requested operation sends a signal to the process (using a generalised version of IOSIGNAL directed to the required process) to indicate that the operation has completed. If one or more wait handlers have been installed (wait handlers are described below), they may process the signal and re-signal using IOSIGNAL. In some cases, it is convenient for the requestor to use IOSIGNAL to signal itself and subsequently to process that signal in a central call to IOWAIT.

Status words

Although the arguments to asynchronous request functions vary, they all take a so-called status word argument, which subsequently contains the status of the requested operation. On the Series 3c the status word is always a 16-bit integer, like stat%. On the Series 5, the status word is also a 16-bit integer, except when calling an asynchronous OPX procedure that specifically requires a 32-bit integer status word, such as PLAYSOUNDA: in System OPX.

All asynchronous requests exhibit the following behaviour:

1.  While the request is pending, a 16-bit status word contains the negative -46.

A 32-bit status word however contains &80000001. Const.oph provides constant definitions for these values. For details of how to use this file see the Calling Procedures chapter and see Appendix E for a listing of it.

2.  When the operation has completed, a value other than -46 is written to the status word. This value is zero or positive to indicate success, or a negative error number to indicate failure. For 16-bit status words, an OPL error value is used. See the Error Handling chapter for a list of these.

For 32-bit status words on the Series 5, an EPOC32 error value is used. The EPOC32 error values are listed in Appendix G.

3.  The requesting processs I/O semaphore is signalled (after the status word has been written).

Making a request while a previous request on the same status word is still pending will normally result in a system panic where the program is killed immediately, without being able to trap the error.

When there are multiple requests, each request is associated with a different status word. After returning from IOWAIT, the caller typically polls each status word until one is found that contains other than -46. That completion is then processed (which might include renewing the asynchronous request) and IOWAIT is called again to process the next completion.

Cancelling an asynchronous request

Most asynchronous request functions made using IOC (or IOA) can be cancelled. To cancel such requests use the IOCANCEL function.

For asynchronous requests made using a mechanism other than IOC (or IOA), a specific cancelling function must be used instead:

Use KEYC to cancel a KEYA request.

Use GETEVENTC to cancel a GETEVENTA32 request.

Use STOPSOUND&: to cancel a PLAYSOUNDA: request.

The following general principles apply to all functions that cancel an asynchronous request:

the cancel precipitates the completion of the operation (it does not stop the operation from completing).

the cancel may or not be effective (that is, the operation may complete naturally before the cancel is processed).

after a cancel, you must still process the completion of the asynchronous request (typically by immediately calling IOWAITSTAT or IOWAITSTAT32, described below, to "use up" the signal). An exception to this rule is that GETEVENTC and KEYC themselves wait for the cancellation signal, so IOWAITSTAT must not be used.

A 16-bit status word is set to -48 when a request has been effective.

A 32-bit status word is set to -3 (EPOC32s error code for Cancel error) when a request using a 32-bit status word is cancelled.

Waiting for a particular completion

When waiting for the completion of a particular asynchronous request, the wait on the I/O semaphore must be sure that it is not fooled into a premature return by the completion of any other pending asynchronous request. This is done by calling IOWAITSTAT which behaves in a similar way to IOWAIT except that it only returns when the associated status word is other than -46.

For a 32-bit status word request, IOWAITSTAT32 is used instead, again behaving in a similar way to IOWAIT except that it only returns when the associated 32-bit status word is other than &80000001.

In general, IOWAITSTAT (IOWAITSTAT32) is a safer option than IOWAIT to "use up" the signal resulting from the cancelled operation. If the cancel is not immediately effective and another completion causes IOWAIT to return, the program could continue and make another request before the cancelled operation completes (which would result in a process panic).

A first example using asynchronous I/O

In the following example, the opened asynchronous timer timChan% is used to construct a synchronous function which attempts to write the passed string to the opened serial channel serChan%. If it takes more that 5 seconds to complete the write, the procedure raises the inactivity error -54. For simplicity it is assumed that there are no other outstanding events which could complete and that both requests started successfully. Thus it is certain that on return from the call to IOWAIT, one of the two asynchronous requests has completed.

    PROC strToSer:(inStr$)

          LOCAL str$(255)      REM local copy of inStr$ needed for ADDR

          LOCAL len%,timStat%,serStat%,timeout%,ret%,pStr&

          str$=inStr$

          pStr&=ADDR(str$)+1

                      REM pointer to string skipping leading count byte

          len%=LEN(str$)

          IOC(serChan%,2,serStat%,#pStr&,len%)

          REM request asynchronous serial write

          timeout%=50                        REM 5 second timeout

          IOC(timChan%,1,timStat%,timeout%)

                                              REM Relative timer - function 1

          IOWAIT

          IF serStat%=-46                  REM must have timed out

          IOCANCEL(serChan%)                  REM cancel serial request

          IOWAITSTAT serStat%            REM use up the signal

          RAISE -54                              REM inactivity timeout

          ENDIF

          IOCANCEL(timChan%)                  REM cancel timer request

          IOWAITSTAT timStat%            REM use up the signal

    ENDP

  Wait handlers

Wait handlers are functions that handle the completion of asynchronous requests from within IOWAIT (or IOWAITSTAT). Active wait handler functions are called just before IOWAIT would have otherwise returned.

Many I/O devices install a device wait handler when the device is opened.

Wait handlers are only called when the process calls IOWAIT (or a function such as IOWAITSTAT that calls IOWAIT). While an application performs a computationally intensive task that takes an extended time, it should consider calling IOYIELD (which effectively calls IOSIGNAL followed by IOWAIT) to allow any installed wait handlers to be called. Application programs must never assume that no wait handlers have been installed.

Polling rather than waiting

In a multi-tasking operating system it is extremely anti-social to wait for an operation to complete by polling the status word in a tight loop rather than call IOWAIT (because the polling will "hog" the processor to no benefit). However, when there is useful work to be done between each poll, it can be appropriate to poll - for example, to check periodically for user input while performing an extended calculation.

When a poll detects a completed status word, it is still obligatory to "use up" the signal by calling IOWAIT (otherwise you will get a "stray signal" later).

  Polling on the Series5

The status word will be set only when either IOWAIT or IOYIELD is called. For a 32-bit status word, call IOWAITSTAT32 instead.

For example:

    ...

    IOC(handle%,func%,stat%,#pBuf&,len%)

    DO

          calc:

          REM perform part of some calculation until request complete

          IOYIELD                                REM allow status word to be set

    UNTIL stat%&lt;&gt;-46

    ...

  Polling on the Series 3c and Siena

If this approach is used, you should be aware that in some cases asynchronous requests are completed by a wait handler and it is necessary to call IOYIELD before each poll to give any wait handlers a chance to run. For example:

    ...

    IOC(handle%,func%,stat%,#pBuf%,len%)

    DO

          calc:

          REM perform part of some calculation until request complete

          IOYIELD                                REM allow handler to run

    UNTIL stat%&lt;&gt;-46

    ...

This case occurs when making requests on a device driver that services hardware interrupts. For example, the serial port driver services the hardware interrupt generated by the receipt of a serial frame. A hardware interrupt handler cannot write directly to the data segment of the requesting process because that data segment may be moving when the interrupt occurs. Instead, the interrupt handler must write first to a fixed memory location (either in the operating system variables if it is an in-built driver or in a device segment if it is an external driver) and then signal the I/O semaphore of the requesting process. When the requesting process next calls IOWAIT (directly or indirectly through say IOYIELD), the device drivers wait handler is called to copy the data safely to the process data segment.

Device drivers that are implemented by a server process (for example, the window server) do not require a wait handler to complete operations.

I/O device handling and the asynchronous request functions

The following I/O functions provide access to devices. A full description is not within the scope of this manual, since these functions require extensive knowledge of the Series 5 or Series 3c operating systems and related programming techniques. The syntax and argument descriptions are provided here for completeness.

The previous section explains in detail the semantics of asynchronous I/O. In the descriptions of the asynchronous I/O functions below, a careful reading of that section is assumed.

ret%=IOW(handle%,func%,var arg1,var arg2)The device driver opened with handle% (as returned by IOOPEN) performs the synchronous I/O function func% with the two further arguments. The size and structure of these two arguments is specified by the particular device drivers documentation.

IOC(handle%,func%,var stat%,var a1,var a2)

IOC(handle%,func%,var stat%,var a1)

IOC(handle%,func%,var stat%)

Make an I/O request with guaranteed completion. The device driver opened with handle% (as returned by IOOPEN) performs the asynchronous I/O function func% with up to two further arguments. The size and structure of these arguments is specified by the particular device drivers documentation.

As explained in detail in the previous section, asynchronous means that the IOC returns immediately, and the OPL program can carry on with other statements. status% will always be set to -46, which means that the function is still pending.

When, at some later time, the function completes, status% is automatically changed. (For this reason, status% should usually be global since if the program is still running, status% must be available when the request completes, or the program will probably crash). If status%&gt;=0, the function completed without error. If status%&lt;0, the function completed with error. The return values and error codes  are specific to the device driver.

If an OPL program is ready to exit, it does not have to wait for any signals from pending IOC calls.

ret%=IOA(handle%,func%,var status%,var arg1,var arg2)The device driver opened with handle% (as returned by IOOPEN) performs the asynchronous I/O function func% with two further arguments. The size and structure of these two arguments is specified by the particular device drivers documentation.

This has the same form as IOC, but the return value cannot be ignored. IOC is effectively the same as:

    ret%=IOA(h%,f%,stat%,...)

    IF ret%&lt;0

          stat%=ret%

          IOSIGNAL

    ENDIF

IOC allows you to assume that the request started successfully - any error is always given in the status word stat%. If there was an error, stat% contains the error code and the IOSIGNAL causes the next IOWAIT to return immediately as if the error occurred after completion. There is seldom a requirement to know whether an error occurred on starting a function, and IOC should therefore nearly always be used in preference to IOA.

IOWAIT Wait for an asynchronous request (such as one requested by IOC, KEYA or GETEVENTA32) to complete. IOWAIT returns when any asynchronous I/O function completes. Check status% to find out which request has completed. Use IOWAITSTAT with the relevant status word to wait for a particular request to complete.

IOSIGNAL Replace a signal of an I/O functions completion.

As shown in A simple example using asynchronous I/O in the previous section, it is sometimes useful to construct a synchronous operation from two asynchronous operations by waiting for either to complete before returning. In that case it waited for either the serial write request or a timeout.  As noted there, the example assumed that there were no other outstanding asynchronous requests.  If there had been one or more asynchronous requests made before calling that procedure, such as a request to play a sound file, and if that request had completed before both the serial write and the timer request, the procedure would incorrectly assume that the timer had completed:

    IOWAIT

    IF serStat%=-46                  REM must have timed out

          IOCANCEL(serChan%)            REM cancel serial request

          IOWAITSTAT serStat%      REM use up the signal

          RAISE -54                        REM inactivity timeout

    ENDIF

    IOCANCEL(timChan%)                  REM cancel timer request

    IOWAITSTAT timStat%            REM use up the signal

To deal with this situation correctly, the procedure should instead check that one of the particular two requests it knows about has completed and re-signal to put back the signal consumed for the sound file completion:

    PROC strToSer:(inStr$)

          LOCAL str$(255)      REM local copy of inStr$ needed for ADDR

          LOCAL len%,timStat%,serStat%,timeout%,ret%,pStr&

          LOCAL signals%            REM count of external signals

          LOCAL err%

          str$=inStr$

          pStr&=ADDR(str$)+1

                            REM ptr to string skipping leading count byte

          len%=LEN(str$)

          IOC(serChan%,2,serStat%,#pStr&,len%)

          REM request asynchronous serial write

          timeout%=50                  REM 5 second timeout

          IOC(timChan%,1,timStat%,timeout%)

                                        REM relative timer - function 1

          WHILE 1                        REM forever loop

                IOWAIT                        REM wait for any completion

                IF timStat%&lt;&gt;-46            REM timed out

                      IOCANCEL(serChan%)            REM cancel serial request

                      IOWAITSTAT serStat%      REM use up the signal

                      err%=-54            REM inactivity timeout (raised

                                        REM below after re-signalling)

                      BREAK                  REM stop waiting and re-signal

                ELSEIF serStat%&lt;&gt;-46            REM serial write complete

                      IOCANCEL(timChan%)            REM cancel timer request

                      IOWAITSTAT timStat%      REM use up the signal

                      BREAK                  REM stop waiting and re-signal

                ELSE                                    REM other unknown request

                      signals%=signal%+1            REM count other signals

                                                    REM loop again for next

                ENDIF

          ENDWH

          WHILE signals%&gt;0

                REM now re-signal any consumed external signals

                IOSIGNAL

                signals%=signals%-1

          ENDWH

          IF err%

                RAISE err%

          ENDIF

    ENDP

IOSIGNAL is called only after exiting the IOWAIT loop, otherwise the signal would cause the IOWAIT to return immediately.

IOWAITSTAT var status% Wait for a particular asynchronous function, called with IOC, to complete.

IOWAITSTAT32 var stat&Similar to IOWAITSTAT but takes a 32-bit status word. IOWAITSTAT32 should be called only when you need to wait for completion of a request made using a 32-bit status word when calling an asynchronous OPX procedure. status& will be &80000001 while the function is still pending, and on completion will be set to the appropriate EPOC32 error code, listed in Appendix G. See also the Using OPXs on the Series 5 chapter and the Alphabetic listing chapter.

IOYIELD Ensures that any asynchronous function is given a chance to run. Some devices are unable to perform an asynchronous request if an OPL program becomes computationally intensive, using no I/O (screen, keyboard etc.) at all. In such cases, the OPL program should use IOYIELD before checking its status% variable. This is always the case on the Series 5. IOYIELD is the equivalent of IOSIGNAL followed by IOWAIT - the IOWAIT returns immediately with the signal from IOSIGNAL, but the IOWAIT causes any asynchronous handlers to run.

IOCANCEL(handle%)Cancels any outstanding asynchronous I/O request made using IOC (or IOA) on the specified channel, causing them to complete with the completion status word containing -48 (I/O cancelled). The return value is always zero and may be ignored.

The IOCANCEL function is harmless if no request is outstanding (e.g. if the function completed just before cancellation requested).

GETEVENTA32(var status%,var event&())This is an asynchronous window server read function. You must declare a long integer array with at least 16 elements. If a window server event occurs, the information is returned in event&() as described under GETEVENT32 in the alphabetic listing.

GETEVENTC(var status%)Cancels a GETEVENTA32. Note that IOWAITSTAT should not be called after GETEVENTC - OPL consumes the GETEVENTC signal.

err%=KEYA(var status%,key%())This is an asynchronous keyboard read function. You must declare an integer array with two elements here, key%(1) and key%(2) to receive the keypress information. If a key is pressed, the information is returned in this way:

key%(1) is assigned the character code of the key.

The least significant byte of key%(2) takes the key modifier, in the same way as KMOD 2 for Shift down, 4 for Control down and so on. KMOD cannot be used with KEYA.

The most significant byte of key%(2) takes the count of keys pressed (0 or 1).

KEYA needs an IOWAIT in the same way as IOC.

Some useful IOW functions

IOW has this specification:

    ret%=IOW(handle%,func%,var arg1,var arg2)

Here are some uses:

    LOCAL a%(6)

    IOW(-2,8,a%(),a%())             REM 2nd a% is ignored

senses the current text window (as set by the most recent SCREEN command) and the text cursor position ignoring any values already in the array a%(). This gives the same information as SCREENINFO, which should be used in preference (see the Alphabetic Listing chapter).

The first four elements are set to represent the offset of the current text window from the default text window.  a%(1) is set  to the x-offset of the top left corner of the current text window from the default text windows top left corner and a%(2) to the y-offset of the top left corner of current text window.  Similarly a%(3) and a%(4) give the offset of bottom right corner of text window from the bottom right corner of the default text window. For example, if the most recent SCREEN command was SCREEN 10,11,4,5 the first four elements of the array a%() would be set to (3,4,13,15). The x and y positions of the cursor relative to the current text window are written to a%(5) and a%(6) respectively. All positions and offsets take 0,0, not 1,1, as the point at the top left.

    LOCAL i%,a%(6)

    i%=2

    a%(1)=x1% :a%(2)=y1%

    a%(3)=x2% :a%(4)=y2%

    IOW(-2,7,i%,a%())

clears a rectangle at x1%,y1% (top left), x2%,y2% (bottom right). If y2% is one greater than y1%, this will clear part or all of a line.

Example of IOW screen functions

The final two procedures in this module call the two IOW screen functions described beforehand. The rest of the module lets you select the function and values to use. It uses the technique used in the Friendlier interaction chapter of handling menus and short-cut keys by calling procedures with string expressions.

    PROC iotest:

          GLOBAL x1%,x2%,y1%,y2%

          LOCAL i%,h$(2),a$(5)

          x1%=2 :y1%=2

          x2%=25 :y2%=5                         REM our test screensize

          SCREEN x2%-x1%,y2%-y1%,x1%,y1%

          AT 1,1

          PRINT "Text window IO test"

          PRINT "Control-Q quits"            REM should be "Psion-Q" on 3c

          h$="cr"                               REM our shortcut keys

          DO

                i%=GET

                IF i%=$122                  REM MENU key

                      mINIT

                      mCARD "Set","Rect",%r

                      mCARD "Sense","Cursor",%c

                      i%=MENU

                      IF i% AND INTF(LOC(h$,CHR$(i%)))

                            a$="proc"+chr$(i%)

                            @(a$):

                      ENDIF

                ELSEIF i% AND $200                  REM shortcut key                              i%=(i%-$200)

                      i%=LOC(h$,CHR$(i%))      REM One of ours?

                      IF i%

                            a$="proc"+MID$(h$,i%,1)

                            @(a$):

                      ENDIF            REM ignore other weird keypresses

                ELSE                        REM some other key, so return it

                      PRINT CHR$(i%);

                ENDIF

          UNTIL 0

    ENDP

    PROC procc:

          LOCAL a&

          a&=iocurs&:

          PRINT "x";1+(a& AND &ffff);

          PRINT "y";1+(a&/&10000)

    ENDP

    PROC procr:

          LOCAL xx1%,yy1%,xx2%,yy2%

          LOCAL xx1&,yy1&,xx2&,yy2&

          dINIT "Clear rectangle"

          dLONG xx1&,"Top left x",1,x2%-x1%

          dLONG yy1&,"Top left y",1,y2%-y1%

          dLONG xx2&,"Bottom left x",2,x2%-x1%

          dLONG yy2&,"Bottom left y",2,y2%-y1%

          IF DIALOG

                xx1%=xx1&-1 :xx2%=xx2&-1

                yy1%=yy1&-1 :yy2%=yy2&-1

                iorect:(xx1%,yy1%,xx2%,yy2%)

          ENDIF

    ENDP

    PROC iocurs&:

          LOCAL a%(4),a&

          REM dont change the order of these!

          a%(1)=x1% :a%(2)=y1%

          a%(3)=x2% :a%(4)=y2%

          IOW(-2,8,a%(),a%())       REM 2nd a% is ignored

          RETURN a&

    ENDP

    PROC iorect:(xx1%,yy1%,xx2%,yy2%)

          LOCAL i%,a%(6)

          i%=2 :REM "clear rect" option

          a%(1)=xx1% :a%(2)=yy1%

          a%(3)=xx2% :a%(4)=yy2%

          IOW(-2,7,i%,a%())

    ENDP

The following two examples could not be used on the Series 5.  The use of sound in OPL on the Series 5 is handled with the use of System OPX.  See the Using OPXs on the Series 5 chapter for further details of how to use this.

  Alarm example - IOC to ALM:

The ALM: device provides access to alarms. When writing to it with IOW, IOC or IOA, you can use these two functions:

function=1 - only the date (and no time) is shown on the screen when the alarm rings - e.g. Thu 5 Sep

function=2 - the day and time are shown - e.g. Thu 11:54

In either case, you must pass these two arguments:

An array of 2 long integers the first is the time for the alarm to go off, and the second is the time for which it is due. Both are given in seconds since midnight on 1/1/1970.

A message, as a zero-terminated string of up to 64 characters.

This procedure asks for the information for an alarm, and sets it (as type 2 day and time to be shown when the alarm rings). If you press the Time button, and this is the next alarm to ring, it is shown as a RunOpl alarm.

    PROC alm:

          LOCAL h%,a&(2),a$(64),b$(65),d&,t&,t2&,a%,r%,s%

          r%=IOOPEN(h%,"ALM:",0)

          IF r%&lt;0 :RAISE r% :ENDIF

          d&=DAYS(DAY,MONTH,YEAR)            REM today

          t&=DATETOSECS(1970,1,1,HOUR,MINUTE,0)

          dINIT "Set alarm"

          dTIME t&,"Time",0,0,DATETOSECS(1970,1,1,23,59,59)

          dDATE d&,"Date",d&,DAYS(31,12,2049)

          dTIME t2&,"Alarm advance time",2,0,86399

          dEDIT a$,"Message"

          IF DIALOG

                a&(2)=86400*(d&-25567)+t&

                a&(1)=a&(2)-t2&

                b$=a$+CHR$(0)                  REM zero-terminate the string

                IOC(h%,2,s%,a&(),#UADD(ADDR(b$),1))

          ENDIF

          IOCLOSE(h%)

    ENDP

At the moment the alarm rings, either s% must still be available to take the status word set by this ALM: function, or the program must have exited. Otherwise the status word will be written to a random area of memory. So in this example, no error-checking is done after the IOC - the program just ends. So you would have to use this as a whole program itself - you must not call this procedure as written here from another procedure.

  Dialling example - IOW to SND:

The SND: device provides sound services on the Series 3c. One function, number 10, provides access to DTMF dialling. It requires these two arguments:

The number to dial, as a zero-terminated string of up to 24 characters.

An array of 2 integers. The first is the tone length (*256) plus the delay length, and the second is the pause length. All of these are specified in 1/32 s of a second.

    PROC dtmf:

          LOCAL h%,a$(24),b$(25),z%,r%,a%(2)

          r%=IOOPEN(h%,"SND:",0)

          IF r%&lt;0 :RAISE r% :ENDIF

          dINIT

          dEDIT a$,"Dial"

          IF DIALOG

                a%(1)=8+(256*8)

                a%(2)=48

                b$=a$+CHR$(0)

                r%=IOW(h%,10,#UADD(ADDR(b$),1),a%())

                IF r%&lt;0 :RAISE r% :ENDIF

          ENDIF

          r%=IOCLOSE(h%)

          IF r%&lt;0 :RAISE r% :ENDIF

    ENDP

The following section does not apply to the Series 5.  The use of sound in OPL on the Series 5 is handled with the use of System OPX.  See the Using OPXs on the Series 5 chapter for further details of how to use this.


Recording and playing sounds on the Seri
Recording and playing sounds on the Series 3c and Siena

Section Contents

Sound file structure

How to record and play sounds

Alarms

Example of recording

This section explains how to write a program which records sounds to a sound file using the in-built Series 3c microphone, and which plays these or pre-recorded sound files back.

Sound file structure

Series 3c sound files are files with a .WVE extension that contain a 32-byte header and a byte stream of digital sound which is sampled and played back at 8000 bytes per second (12-bit sound is converted to 8-bit using A-Law encoding).

The file header has the following format:

offset in file	bytes	contents

0	16	zero-terminated ALawSoundFile**

16	2	version of this format

18	4	number of 8-bit samples

22	2	trailing silence in ticks

24	2	repeats

26	6	spare bytes reserved for future use

The number of samples is the number of bytes following the header and should always be size of file less 32 for the header.

The silence in ticks is the number of system ticks of silence appended to each repeat on playback (in practice, you get at least 2 ticks between repeats). A system tick is 1/32 seconds or 250 samples.

The repeats are the number of times to repeat the sound on playback (0 and 1 are treated as the same).

You can truncate the sound file, change the number of repeats and the trailing silence by changing the file length and header using the I/O binary file handling functions (IOOPEN, IOW, IOSEEK, IOWRITE etc.) described elsewhere in this chapter.

For example, you can truncate the file to length newLen& using:

    ret%=IOW(handle%,11,newLen&,#0)

How to record and play sounds

The following set of procedures perform asynchronous recording and playing of sounds.

    PROC recorda:(pstat%,inname$,size%)

          LOCAL name$(128)

          name$=inname$+chr$(0)

          CALL($2186,UADD(ADDR(name$),1),size%,0,0,pstat%)

    ENDP

    PROC recordc:

          CALL($2386)

    ENDP

    PROC recordw%:(inname$,size%)

          LOCAL name$(128),p%,ret%

          p%=PEEKW($1c)+6       REM address of saved flags after CALL

          name$=inname$+chr$(0)

          ret%=CALL($2286,UADD(ADDR(name$),1),size%)

          IF PEEKW(p%) AND 1               REM carry set for error

                RETURN ret% OR $FF00       REM return error

          ENDIF

    ENDP

    PROC playa:(pstat%,inname$,ticks%,vol%)

          LOCAL name$(128)

          name$=inname$+chr$(0)

          CALL($1E86,UADD(ADDR(name$),1),ticks%,vol%,0,pstat%)

    ENDP

    PROC playc:

          CALL($2086)

    ENDP

    PROC playw%:(inname$,ticks%,vol%)

          LOCAL name$(128),p%,ret%

          p%=PEEKW($1c)+6       REM address of saved flags after CALL

          name$=inname$+chr$(0)

          ret%=CALL($1F86,UADD(ADDR(name$),1),ticks%,vol%)

          IF PEEKW(p%) AND 1               REM carry set for error

                RETURN ret% OR $FF00       REM return error

          ENDIF

    ENDP

recorda:(pstat%,inname$,size%) and recordw%:(inname$,size%) respectively perform asynchronous and synchronous recording to file inname$. Any existing file is replaced. You can only record to the Internal disk or to a RAM SSD - you cannot record to a Flash SSD. (You can playback from a Flash SSD, however.)

size% specifies the maximum number of bytes to be recorded in units of 2048 bytes. To record for one second size%=4. This figure excludes the 32-byte header. Before recording, a file of length 32+size%*2048 bytes is created and there must actually be room on the disk for a file of that length.

pstat% is the address of the status word to take the completion code for asynchronous recording.

recordc: cancels recording and truncates the file to the actual length recorded before cancellation.

playa:(pstat%,inname$,ticks%,vol%) and playw%:(inname$,ticks%,vol%) respectively perform asynchronous and synchronous playing of inname$.

inname$ should either be the filename of the sound file to play or a * followed by just the name component of the sound file. If it is preceded by a *, the extension .WVE is assumed and the service automatically searches ROM:: and the \WVE directories of M: (Internal disk), A: and B: (in that order). The ROM:: .WVE files have names SYS$AL01, SYS$AL02 and SYS$AL03.

ticks% is the duration that the sound file will play back in system ticks. If it is shorter than the given sound then playback is truncated to that time. If ticks% is negative, in addition to truncating the playback of longer files, it pads out as necessary to that duration with silence. If duration is zero, it plays the file without truncation or padding. (Alarms use a parameter of -480 to truncate or pad out to 15 seconds.)

volume% is a number between 0 and 5 inclusive, with 0 being the loudest. On the Series 3c there are only 4 actual levels: 0/1, 2, 3, and 4/5. pstat% is the address of the status word to take the completion code for asynchronous playback. Playback will append periods of silence and repeat the sound as specified in the file header.

playc: cancels playing back a sound.

Alarms

The system dialogs that are used to set alarms detect the presence of any .WVE files in the \WVE directory of any local directory (in practice, A:, B: and Internal) and make these available (by file name) as the sound of the alarm.

When an alarm with a .WVE file rings, .WVE file playback (including any repeats) is clipped to 15 seconds. If the .WVE file plays for less than 15 seconds (including any repeats), the 15 seconds is padded out with silence.

Example of recording

The following asynchronously records time% seconds of sound to file file$ or cancels the recording when any key is pressed. The section I/O device handling and asynchronous requests also in this chapter discusses the principles involved. The recorda: and recordc: procedures, from above, are used.

    PROC record:(file$,time%)

          LOCAL sstat%,kstat%,key%(4),size%,ret%,signals%

          size%=time%*4

          recorda:(ADDR(sstat%),file$,size%)      REM async record

          IOC(-2,1,kstat%,key%())                       REM async key read

          WHILE 1

                IOWAIT       REM wait for recording to complete, or a key

                IF sstat%&lt;&gt;-46             REM if sound no longer pending

                      IOCANCEL(-2)           REM cancel key read

                      IOWAITSTAT kstat%      REM wait for cancellation

                      IF sstat%&lt;0

                            GIPRINT "Error recording:"+err$(sstat%)

                      ENDIF

                      BREAK

                ELSEIF kstat%&lt;&gt;-46            REM else if key pressed

                      recordc:               REM cancel record

                      IOWAITSTAT sstat%       REM wait for cancellation

                      GIPRINT "Cancelled"

                      BREAK

                ELSE       REM some async request made outside this PROC

                      signals%=signals%+1       REM save it for later

                ENDIF

          ENDWH

          WHILE signals%

                IOSIGNAL                       REM put back foreign signals

                signals%=signals%-1

          ENDWH

    ENDP

The following section does not apply to the Series 5.


Series 3c and Siena OPL database informa
Series 3c and Siena OPL database information

ODBINFO var info%() is provided for advanced use only and allows you to use OS and CALL to call DbfManager interrupt functions not accessible with other OPL keywords.

The description given here will be meaningful only to those who have access to full SDK documentation of the DbfManager services, which explains any new terms. Since that documentation is essential for use of ODBINFO, no attempt is made here to explain these terms.

ODBINFO returns info%(), which must have four elements containing pointers to four blocks of data; the first corresponds to the file with logical name A, the second to B and so on.

Take extreme care not to corrupt these blocks of memory, as they are the actual data structures used by the OPL runtime interpreter.

A data block which has no open file using it has zero in the first two bytes. Otherwise, the block of data for each file has the following structure, giving the offset to each component from the start of the block and with offset 0 for the 1st byte of the block:

Offset	Bytes	Description

0	2	DBF systems file control block (handle) or zero if file not open

2	2	offset in the record buffer to the current record

4	2	pointer to the field name buffer

6	2	number of fields

8	2	pointer to start of record buffer

10	2	length of a NULL record

12	1	non-zero if all fields are text

13	1	non-zero for read-only file

14	1	non-zero if record has been copied down

15	1	number of text fields

16	2	pointer to device name

Example

To copy the Descriptive Record of logical file B to logical file C:

    PROC dbfDesc:

          LOCAL ax%,bx%,cx%,dx%,si%,di%

          LOCAL info%(4),len%,psrc%,pdest%

          ODBINFO info%()

          bx%=PEEKW(info%(2))              REM handle of logical file B

          ax%=$1700                         REM DbfDescRecordRead

          IF OS($d8,ADDR(ax%)) and 1

                RETURN ax% OR $ff00       REM return the error

          ENDIF

          REM the descriptive record has length ax%

          REM and is at address peekW(uadd(info%(2),8))

          IF ax%=0

                RETURN 0                        REM no DescRecord

          ENDIF

          len%=ax%+2      REM length of the descriptive record read

                            REM + 2-byte header

          psrc%=PEEKW(uadd(info%(2),8))

          pdest%=PEEKW(uadd(info%(3),8))

          CALL($a1,0,len%,0,psrc%,pdest%)      REM copy to Cs buffer

          cx%=len%

          bx%=PEEKW(info%(3))            REM handle of logical file C

          ax%=$1800                         REM DbfDescRecordWrite

          IF OS($d8,ADDR(ax%)) and 1

                RETURN ax% OR $ff00

          ENDIF

          RETURN 0                         REM success

    ENDP

The following section does not apply to the Series 5. OPXs provide a different mechanism for calling language extensions and creating object instances. It uses language extensions provided in separate EPOC32 DLLs written especially for OPL support. These DLLs can be added to the language by anyone at any time and have the file extension .OPX. Unlike procedures written in OPL, these procedures are as fast to call as built-in keywords. OPXs enable OPL programs to perform virtually any operation in EPOC32 which is available to a C++ program. OPX procedures are called in a similar way to user-defined OPL procedures.


Series 3c and Siena DYL handling

Section Contents

var arguments

Loading a DYL

Unloading a DYL

Linking a DYL

Finding a category handle given its name

Converting a category number to a handle

Creating an object by category number

Creating an object by category handle

Sending a message to an object

Protected message sending

Protected message sending (returns zero on success)

This section contains a complete reference description of OPLs support for accessing previously created dynamic libraries (DYLs). These libraries have an object-oriented programming (OOP) user-interface and several have been built into the Series 3c ROM for use by the ROM applications. DYLs cannot be created using OPL.

Since a vast amount of documentation would need to be provided to describe the essential concepts of OOP and the services available in existing DYLs, no attempt is made to supply it here. This section simply introduces the syntax for all the OOP keywords supported in OPL with a brief description of each. Also, OOP terminology is used here without explanation, to cater for those who have previous experience of DYL handling in the C programming language.

var arguments

The use of var and # for arguments was discussed earlier in this chapter in the section I/O functions and commands. The DYL handling keywords use var and # in the same way, for example:

    ret%=SEND(pobj%,method%,var p1,var p2,var p3)

This is because many DYL methods need the address of a variable or of a structure to be passed to them.

When you use a LOCAL or GLOBAL variable as the var argument, the address of the variable is used. (You cannot use procedure parameters or field variables, for this reason.) If you use a # before a var argument, though, the argument/value is used directly, instead of its address being used.

If, for example, you need to call a method with p1 the address of a long variable a&, p2 the integer constant 3, and p3 the address of a zero terminated string "X", you could call it as follows:

    s$="X"+CHR$(0)                          REM zero terminate

    p%=UADD(ADDR(s$),1)                    REM skip leading count byte

    ret%=SEND(pobj%,method%,a&,#3,#p%)

The address of a& is passed because there is no #. 3 and the value in p% are passed directly (no address is taken) because they are preceded by #.

Loading a DYL

    ret%=LOADLIB(var cathand%,name$,link%)

loads and optionally links a DYL that is not in the ROM. If successful, writes the category handle to cathand% and returns zero. You would normally only set link% to zero if the DYL uses another DYL which you have yet to load in which case LINKLIB would subsequently be used. The DYL is shared in memory if already loaded by another process.

Unloading a DYL

    ret%=UNLOADLIB(cathand%)

unloads a DYL from memory. Returns zero if successful.

Linking a DYL

    LINKLIB cathand%

links any libraries that have been loaded using LOADLIB. LINKLIB is not likely to be used much in OPL - pass link% with a non-zero value to LOADLIB instead.

Finding a category handle given its name

    ret%=FINDLIB(var cathand%,name$)

finds DYL category name$ (including .DYL extension) in the ROM. On success returns zero and writes the category handle to cathand%. To get the handle of a RAM-based DYL, use LOADLIB which guarantees that the DYL remains loaded in RAM. FINDLIB will get the handle of a RAM-based DYL but does not keep it in RAM.

Converting a category number to a handle

    cathand%=GETLIBH(catnum%)

converts a category number catnum% to a handle. If catnum% is zero, this gets the handle for OPL.DYL.

Creating an object by category number

    pobj%=NEWOBJ(catnum%,clnum%)

creates a new object by category number catnum% belonging to the class clnum%, returning the object handle on success or zero if out of memory. This keyword simply converts the category number supplied to a category handle using GETLIBH and then calls NEWOBJH.

Creating an object by category handle

    pobj%=NEWOBJH(cathand%,clnum%)

creates a new object by category handle cathand% belonging to the class clnum%, returning the object handle on success or zero if out of memory.

Sending a message to an object

    ret%=SEND(pobj%,method%)

    ret%=SEND(pobj%,method%,var p1)

    ret%=SEND(pobj%,method%,var p1,var p2)

    ret%=SEND(pobj%,method%,var p1,var p2,var p3)

send a message to the object pobj% to call the method number method%, passing between zero and three arguments depending on the requirements of the method, and returning the value returned by the selected method.

Protected message sending

    ret%=ENTERSEND(pobj%,method%)

    ret%=ENTERSEND(pobj%,method%,var p1)

    ret%=ENTERSEND(pobj%,method%,var p1,var p2)

    ret%=ENTERSEND(pobj%,method%,var p1,var p2,var p3)

send a message to an object with protection.

Methods which return errors by leaving must be called with protection.

ENTERSEND is the same as SEND except that, if the method leaves, the error code is returned to the caller; otherwise the value returned is as returned by the method.

Use ENTERSEND0 (described next) for methods which leave but do not return a value explicitly on success.

Protected message sending (returns zero on success)

    ret%=ENTERSEND0(pobj%,method%)

    ret%=ENTERSEND0(pobj%,method%,var p1)

    ret%=ENTERSEND0(pobj%,method%,var p1,var p2)

    ret%=ENTERSEND0(pobj%,method%,var p1,var p2,var p3)

send a message to an object with protection and guarantee that the known value zero is returned on success. Otherwise ENTERSEND0 is the same as ENTERSEND.

Methods which return errors by leaving but return nothing (or NULL) on success must use ENTERSEND0. Besides providing protection, ENTERSEND0 also returns zero if the method did not leave, or the negative error code if it did.

If ENTERSEND were incorrectly used instead and the method completed successfully (i.e. without leaving), the return value would be random and could therefore be in the range of the error codes implying that the method failed.


Dynamic memory allocation

Section Contents

Maximum data size in a procedure

Series 5 32-bit addressing

Porting Series 3c programs to the Series 5

Overview of memory usage

The heap allocator

The heap structure

Growing and shrinking the heap

Lost cells

Internal fragmentation

The OPL runtime interpreter and the heap

Warning - peeking/poking the cell

Reasons for using the heap allocator

Using the heap allocator

  ALLOC and associated heap keywords

Allocating a cell

Freeing an allocated cell

Changing a cells size

Inserting or deleting data in cell

Finding out the cell length

Example using the allocator

For each running OPL program (or process) the operating system automatically allocates memory. On the Series 3c, this can grow up to a maximum of 32 bytes less than 64K. On the Series 5, there are no built-in memory limits and a module or application can use as much of the available memory as it requires. A corollary of this is that although the use of integers were sufficient for the storage of addresses on the Series 3c, this is no longer the case on the Series 5.  Hence long integers must be used instead. See the 32-bit addressing section below.

Maximum data size in a procedure

Although there is no built-in limit to the total amount of data in an OPL program on the Series 5, it is still not possible to declare variables in a procedure that use in total more than 65516 bytes. This allows the largest integer array in a procedure to have 32757 elements. This number decreases for any other variables, externals or for procedures called from the given procedure, as they all consume some of the procedures data space.

The maximum for the Series 3c is about 32758 bytes of data in total per procedure.

Series 5 32-bit addressing

As described above, memory addresses outside the 64K address space need to be stored in long integers. Thus all keywords which support addresses take and return long integers on the Series 5. Note also that where # is used to specify the address of a variable that is listed in the syntax using var, the address is a long integer, so you would use #address&. See the I/O functions and commands section above.

Porting Series 3c programs to the Series 5

To facilitate porting of OPL programs written on the Series 3c (and other earlier machines) to the Series 5, it is in fact possible to set a flag to emulate the Series 3c memory model using SETFLAGS 1. This will cause an Out of memory error if an attempt is made to obtain an address beyond the 64K limit.

If this flag is set, the Series 5 checks that the 64K limit is not exceeded when,

the variables for a procedure are allocated, on calling any procedure. If the address of any variable in the procedure would require more than 16 bits, the procedure will fail to be loaded and an Out of memory error will be raised. This is evidently preferable to having, for example, p%=ADDR(i%) giving an Overflow error, once the procedure has been loaded. The ideal solution is, of course, to port the program to use 32-bit addresses instead, but setting the flag is a quick solution for many applications that are known to require less than 64K.

the value returned by the heap allocation keywords requires more than 16 bits.

Some existing Series 3c programs may at times attempt to exceed the 64K limit and deal with the Out of memory error. To port such programs there are two choices. Either you can

change all integers that contain addresses to be 32-bit

or

set the flag that enforces the 64K limit using SETFLAGS.

Note that the use of the word "address" is in fact imprecise for the Series 5. Series 5 addresses are in fact offsets into OPLs variable-heap, so an address of 0 really means 0 offset relative to the base of OPLs heap. This allows OPL to perform the appropriate 64K limit tests. This method of addressing variables is referred to as "base-relative addressing".

Another potential problem may well concern you at this point. When, for example, displaying an OPL address in some diagnostic debugging code, if a variable i% or a heap cell has a base-relative offset  of between 32768 and 65535, then p%=ADDR(i%) would produce an overflow error unless special measures were taken. The reason for this is that 16-bit integers are signed in OPL, so the range is -32768 to 32767. Hence, although 32768 fits in an unsigned 16-bit integer (hex $8000) it doesnt fit into a signed integer. OPL gets around this problem by sign-extending the result of ADDR and the heap functions when 64K restriction is in force, as follows:

if the value returned is 32768 to 65535 (hex $8000 to $ffff), OPL treats the value as signed. So $8000 becomes &ffff8000 (or -32768), and $ffff becomes &ffffffff (or -1). These values can then be assigned to the signed p%.

if the value is greater than or equal to 65536 (hex &10000) then it is not sign-extended and so assigning it to p% raises an Overflow error as required.

As mentioned above, this conversion should always be totally transparent to your program. This is possible because the keywords that use these sign-extended addresses, all convert the value back to an unsigned 16-bit value before use.)

There is one case, however, where porting is required even with the flag set for a 64K limit. This is when the long integer value returned by ADDR or from a heap-allocating function is passed directly into a user-defined procedure. An OPL procedure from the Series 3c taking an address parameter will probably have been written to take a 16-bit address. As the translator cannot necessarily know the type taken by a user-defined procedure, it cannot coerce the type returned by these functions to match that taken by the user-defined procedure. This means that a type violation error can be caused.

Example:

If a user-defined procedure, userProc:(p%) is called as follows:

    userProc:(ADDR(i%))

the 32-bit integer returned by ADDR will cause a type violation, as it will not be coerced to a 16-bit integer.  The solution is either to define userProc: to take a 32-bit integer, or to declare the prototype of userProc: as userProc:(ADDR%).  Either of these would allow translator coercion.  See the Calling Procedures chapter and also EXTERNAL in the Alphabetic Lisiting for more information on procedure prototyping in OPL on the Series 5.

Porting Series 3c programs to the Series 5 without enforcing the 64K limit

If you are porting Series 3c OPL code (or code from earlier machines) to the Series 5 without using a SETFLAGS statement, it should be obvious that there are two points which you need to remember:

any address variables which were previously specified as integers must be changed to long integers.

as mentioned in the Safe pointer arithmetic section above, UADD and USUB should not be used when you are using 32-bit addressing without the flag set to restrict to 64K. Use of these functions should be replaced with ordinary long integer arithmetic.

Overview of memory usage

The actual memory used by an OPL program depends on the requirements of the process and is automatically grown or shrunk as necessary.

An OPL process contains several separate memory areas, but the only one of significant interest to the OPL programmer is the OPL heap, used to contain the OPL variables and dynamically allocated memory cells. The heaps of different processes are entirely separate - you need only concern yourself with the heap used in your own process.

This memory is called the process data segment and contains all the data used by the process as well as some fixed length data at low memory in the segment needed by the operating system to manage the process and for other system data.Although the data segment for an OPL process contains several components, the only component of significant interest to the OPL programmer is the process heap. This section describes several keywords for accessing the heap.The heap is essentially a block of memory at the highest addresses in a process data segment, so that the operating system can grow and shrink the heap simply by growing and shrinking the data segment and without having to move other blocks of memory at higher addresses in the data segment. The heaps of different processes are totally independent - you need concern yourself only with the heap used in your own data segment.

The heap allocator

The heap allocator keywords are used to allocate, resize and free variable length memory cells from the OPL heap (the process heap on the Series 3c). Cells typically range in size from tens of bytes to a few kilobytes. Allocated cells are referenced directly by their address; they do not move to compact free space left by freed cells.

Heap allocator keywords are:

ALLOC allocates a cell of specified size, returning its address.

FREEALLOC frees a previously allocated cell, which is returned to the heap.

REALLOC changes the size of a cell, returning its new address.

ADJUSTALLOC opens or closes a gap in the middle of a cell (useful for insertion or deletion of cell content), changing the size of the cell as appropriate.

LENALLOC returns the size of a cell.

The heap structure

Initially, the heap consists of a single free cell. After a number of calls to allocate and free cells, the heap typically consists of ranges of adjacent allocated cells separated by single free cells (which are linked). If a cell being freed is next to another free cell the two cells are automatically joined to make a single cell to prevent the free cell linked list from growing unnecessarily.

Writing beyond the end of a cell will corrupt the heaps integrity. Such errors are difficult to debug because there is no immediate effect - the corruption is a "time bomb". It will eventually be detected, resulting in the process exiting prematurely by a subsequent allocator call such as FREEALLOC.

Growing and shrinking the heap

The heap is not fixed in size. The operating system can grow the heap to satisfy allocation requests or shrink it to release memory back to the system.

Allocation of cells is based on "walking" the free space list to find the first free cell that is big enough to satisfy the request. If no free cell is big enough, the operating system will attempt to grow the data segment to add more free space at the end of the heap.

If there is no memory in the system to accommodate growth or, on the Series 3c and Siena, if the data segment has reached its maximum size of (approximately) 64K, the allocate request fails. There are few circumstances when an allocate request can be assumed to succeed and calls to ALLOC, REALLOC and ADJUSTALLOC should have error recovery code to handle a failure to allocate.

Lost cells

There are cases in which programs allocate a sequence of cells which must either exist as a whole or not at all. If during the allocate sequence one of the later allocations fails, the previously allocated cells must be freed. If this is not done, the heap will contain unreferenced cells that consume memory to no purpose.

When designing multi-cell sequences of this kind, you should be mindful of the recovery code that must be written to free partially built multi-cell structures. The fewer the cells in such a structure, the simpler the recovery code is.

Internal fragmentation

The free space in the heap is normally fragmented to some extent; the largest cell that can be allocated is substantially smaller than the total free space. Excessive fragmentation, where the free space is distributed over a large number of cells - and where, by implication, many of the free cells are small - should be avoided because it results in inefficient use of memory and reduces the speed with which cells are allocated and freed.

Practical design hints for limiting internal fragmentation are:

Avoid using the heap for small, highly transient data structures for which ordinary variables are adequate. High frequency cycling through allocate and free pairs, "churns" the heap and leads to a long free space list.

When you have a large number of variable length data structures - particularly when they are frequently resized, "granularise" them (i.e. round the allocation up to a multiple of some reasonable value) so that you decrease the chance of leaving small, unusable free space cells.

Although a small number of gaps are not too serious and should eventually disappear in most cases anyway, the new heap allocating keywords provide ample opportunity to fragment the heap. Provided that you create and free cells in a careful and structured way, where any task needing the allocator frees them tidily on completion, there should not be a problem.

The OPL runtime interpreter and the heap

The OPL runtime interpreter, which actually runs your program, uses the same data segment and heap as your program and makes extensive use of the heap. It is very important that you should understand the interpreters use of the heap - at least to a limited extent to avoid substantial internal fragmentation as described above.Whenever an OPL procedure is called, a cell is allocated to store data required by the interpreter to manage the procedure. The same cell contains all the variables that you have declared in the procedure. On the Series 3c, when cacheing is not being used, the same cell also contains the translated code for the procedure which is interpreted. When the procedure returns (or implicitly returns due to an error) the cell is freed again back to the heap. This use of the heap is very tidy - adjacent cells are allocated and freed with little opportunity for leaving gaps in the heap.Unfortunately, various other keywords also cause cells to be allocated and these can cause fragmentation. For example, LOADM, CREATE, OPEN etc. all allocate cells; UNLOADM, CLOSE etc. free those cells. If a procedure is called which uses CREATE to create a data file, the procedure cell is allocated, followed by the CREATE cell and the procedure cell is then freed when the procedure returns. The heap structure therefore contains a gap where the procedure cell was, which remains until all cells at higher addresses are freed.

On the Series 5, the OPL runtime interpreter uses two separate heaps: one for process management and one for user variables and allocated cells. LOADM, CREATE, OPEN, etc use the process management heap. This ensures that the use of these keywords will not cause fragmentation. It is therefore not necessary for you to understand the interpreters use of the heap.

Warning - peeking/poking the cell

Using the allocator is by no means simple in OPL since the data in an allocated cell usually has to be read or written in OPL using the PEEK and POKE set of keywords which are intrinsically subject to programming error. OPL does not provide good support for handling pointers (variables containing addresses), which are basic to heap usage, nor for complicated data structures, so that it is all too easy to make simple programming errors that have disastrous effects.

For these reasons, you are recommended to use the heap accessing keywords only when strictly necessary (which should not be very often) and to take extreme care when you do use them. On the other hand, for programmers with previous experience of dynamic memory allocation, the heap allocation keywords will often prove most useful.

Reasons for using the heap allocator

A few common instances where the allocator might be used are:

when the amount of data to be stored is variable or cannot be determined at the time of writing the program. Without using the allocator, you would have to declare a large array to hold the data always even when it turns out that only a few bytes are needed in a particular case. Using the allocator allows you to grow the cell containing your data as and when required.

the amount of data may be specified in a file or by the user of the program. Once again, you would need to declare a possibly unnecessarily large array to cope with all allowed cases.

a system of library procedures might use a common cell, usually called a control block, to store common data. You could have one procedure creating the cell and initialising data in it, other procedures in the system could be passed the address of the cell, using and possibly updating the data in it, and finally a further procedure could free the cell.

This concept will be familiar to you if you have used handles for the I/O keywords, where the handle references a cell used internally by the I/O system.

If you did not use the allocator in this case, you would probably need to declare a global array in the procedure calling the library procedures, with the disadvantages that the name and size of the array would need to be fixed for all time even when a better alternative mechanism has been devised for the library code with different data requirements.

ADJUSTALLOC allows you to insert or remove data at the start or in the middle of data that has previously been set up. With an array, you would need to copy each element to the next or previous element to make or close a gap.

Using the heap allocator

  ALLOC and associated heap keywords

On the Series 5, ALLOC, REALLOC and ADJUSTALLOC allocate cells that have lengths that are the smallest multiple of four greater than the size requested. All of these raise errors if the cell address argument is not in the range known by the heap. The same address checking is done for peeking and poking, but note that OPL on the Series 5 allows the addresses of the application-specific SIBO magic statics DatApp1 to DatApp7 (hex 28 to 34 inclusive) to be used for compatibility.

ALLOC, REALLOC and ADJUSTALLOC return a long integer value, so that a request can be made to allocate a cell of any length within memory constraints.

See also sections above for discussion of 32-bit addressing.

Note that in the sections which follow, Series 5 32-bit addressing is assumed.  If you are using a Series 3c, you can substitute integers for long integers, so for example the usage of ALLOC becomes, pcell%=ALLOC(size%), i.e. % rather than &. However, using long integers will make any porting to a Series 5 machine in the future easier.

Allocating a cell

Use pcell&=ALLOC(size&) to allocate a cell on the heap of a specified size returning the pointer to the cell or zero if there is not enough memory. The new cell is uninitialised - you cannot assume that it is zeroed.

Freeing an allocated cell

Use FREEALLOC pcell& to free a previously allocated cell at pcell& as returned, for example, by ALLOC. Does nothing if pcell& is zero.

Changing a cells size

Use pcelln&=REALLOC(pcell&,size&) to change the size of a previously allocated cell at address pcell& to size&, returning the new cell address or zero if there is not enough memory. If out of memory, the old cell at pcell& is left as it was.

If successful, pcelln& will not be the same as pcell& on return only if the size increases and there is no free cell following the cell being grown which is large enough to accommodate the extra amount.

Inserting or deleting data in cell

Use pcelln&=ADJUSTALLOC(pcell&,offset&,amount&) to open or close a gap at offset& within the allocated cell pcell& returning the new cell address or zero if there is not enough memory. offset& is 0 for the first byte in the cell. Opens a gap if amount& is positive and closes it if negative. The data in the cell is automatically copied to the new position.

If successful, pcelln& will not be the same as pcell& on return only if amount& is positive and there is no free cell following the cell being adjusted which is large enough to accommodate the extra amount.

Finding out the cell length

Use len&=LENALLOC(pcell&) to get the length of the previously allocated cell at pcell&.

Example using the allocator

This example illustrates the careful error checking which is essential when using the allocator. RAISE is used to jump to the error recovery code.

If you cannot understand this example it would be wise to avoid using the allocator altogether.

As above, note that Series 5 32-bit addressing is assumed.  If you are using a Series 3c, you could substitute integers for long integers, so for example the usage of ALLOC becomes, pcell%=ALLOC(size%), i.e. % rather than &.

    LOCAL pcell&                         REM pointer to cell

    LOCAL pcelln&                      REM new pointer to cell

    LOCAL p&                         REM general pointer

    LOCAL n%                         REM general integer

    ONERR e1

    pcell&=ALLOC(2+2*8)             REM holds an integer and 2

                                        REM 8-byte floats initially

    IF pcell&=0

          RAISE -10                   REM out of memory; go to e1::

    ENDIF

    POKEW pcell&,2                      REM store integer 2 at start of cell

                                        REM i.e. no. of floats

    POKEF UADD(pcell&,2),2.72               REM store float 2.72

    POKEF UADD(pcell&,10),3.14              REM store float 3.14 ...

    pcelln&=REALLOC(pcell&,2+3*8)       REM space for 3rd float

    IF pcelln&=0

          RAISE -10                           REM out of memory

    ENDIF

    pcell&=pcelln&                          REM use new cell address

    n%=PEEKW(pcell&)                         REM no. of floats in cell

    POKEF UADD(pcell&,2+n%*8),1.0       REM 1.0 after 3.14

    POKEW pcell&,n%+1                   REM one more float in cell ...

    pcelln&=ADJUSTALLOC(pcell&,2,8)       REM open gap before 2.72

    IF pcelln&=0

          RAISE -10                         REM out of memory

    ENDIF

    pcell&=pcelln&                        REM use new cell address

    POKEF UADD(pcell&,2),1.0             REM store 1.0 before 2.72

    POKEW pcell&,4                        REM 4 floats in cell now ...

    p&=UADD(pcell&,LENALLOC(pcell&))       REM byte after cell end

    p&=USUB(p&,8)                         REM address of final float

    POKEF p&,90000.1                        REM overwrite with 90000.1

    RAISE 0                                  REM clear ERR value

    e1::

    FREEALLOC pcell&                        REM free any cell created

    IF err&lt;&gt;0

          ...                                   REM display error message etc.

    ENDIF

    RETURN ERR


Series 5 Database Handling

Introduction

The Series 5 Database Model

Databases, tables, views, fields and field handles

Creating databases and tables

Logical names

Fields

Opening databases and tables

Transactions

Record position

Saving records

The number of records

Closing views and databases

Indexes

Compaction

Opening a database created by the Data application

Introduction

The Series 5 uses the relational database management system (DBMS) of EPOC32 which supports SQL (Standard Query Language).

Apart from the removed keywords RECSIZE, COMPRESS and ODBINFO, the Series 3c methods of database programming are completely understood by the Series 5 model and existing code will not have to change. However, it is very strongly recommended that you use INSERT, MODIFY, PUT and CANCEL along with bookmarks and transactions, rather than using APPEND, UPDATE, POS and POSITION.

See also the Alphabetic Listing chapter for some more detailed description of the use of new and changed database commands and the Database OPX section in the Using OPXs on the Series 5 chapter.

The Series 5 Database Model

As has been stressed previously, it is very strongly recommended that you use this Series 5 specifc model on the Series 5, despite the fact that the data file handling methods of the Series 3c may still be used on the Series 5.  The reasons for this are as follows:

the new keywords closely reflect the underlying EPOC32 database model supplied by DBMS. They are therefore more efficient than the Series 3c keywords on the Series 5.

to emulate the Series 3c behaviour, APPEND has to create an intermediate copy of the record which is erased on completion of the keyword. This ensures the rather strange requirement that the field values of the previous APPEND are used as the initial values for the current APPEND. This can make a database grow far larger than on the Series 3c. You can, however, use COMPACT or SETFLAGS to remove erased records from a database.

without transactions writing a large number of records to a database is far slower on the Series 5 than on the Series 3c. However, with transactions it is far faster.

the Series 5 model is superior.

Databases, tables, views, fields and field handles

To describe the new model it is necessary to expand upon the terminology that was used in the previous chapter.

A Series 3c data file corresponds more or less to a single table in a DBMS database file. A database can contain one or more tables. A table, like a data file on the Series 3c, contains records which are made up of fields. Unlike the Series 3c, however, the field names as well as the table names are stored in the database.

Creating databases and tables

Section Contents

Logical names

Fields

With the statement:

    CREATE "datafile",A,f1%,f2%

as described in the previous chapter, the Series 5 creates a database called datafile and a table with the default name Table1 would be added to it. The field names are derived from the f1% and f2% which are called field handles. The type of the field, as always, is defined by these handles.

With the Series 5 it is also possible to use, for example,

    CREATE "people FIELDS name, number TO phoneBook",A,n$,number$

This will create a table called phoneBook in the database called people, creating the database too if it does not exist.  The table will have fields name and number, whose respective types are specified by the field handles n$ and number$, both strings in this example.

Note that CREATE creates a table. An error is raised if the table already exists in the database. DBMS does not allow the database to be open when a table (or an index: see the Database OPX section in the Using OPXs on the Series 5 chapter) is created in it, so you should first close the database, i.e. close any tables previously opened in it, before using CREATE.

Logical names

You can have up to 26 views on tables open at a time on the Series 5. Each of these must have a logical name: A to Z (the Series 3c only supported 4 files open at one time).

Fields

On the Series 5, field names may be up to 32 characters long, including any qualifier like &.

Opening databases and tables

With the Series 3c OPEN statement,

    OPEN "datafile",A,f1%,f2%

the Series 5 would open the default table Table1 and provide access to as many fields as there are handles supplied.

On the Series 5, it is also possible to open multiple views on a table simultaneously and to specify which fields are to be available in a view, e.g.

    OPEN "people SELECT name FROM phoneBook",A,n$

This view gives you access to just the name field from the phoneBook table.

The string from SELECT onwards in the OPEN statement forms an SQL query which is passed straight on to the underlying EPOC32 DBMS. The SQL command-set is specified in Appendix F.

A more advanced view, ordered by an index (described later), would be opened as follows,

    OPEN "people SELECT name,number FROM phoneBook ORDER BY name

         ASC, number DESC",A,n$,num%

This would open a view with name fields in ascending alphabetical order and if any names were the same then the number field would be used to order these records in descending numerical order.

Transactions

A set of related records should be committed only on successfully PUTting the last one. Otherwise all new records may be discarded using ROLLBACK. This ensures the atomicity of the whole transaction.

Transactions allow changes to a database to be committed in stages. It is necessary to use transactions in database operations to achieve reasonable speeds.

Transactions are a truly fundamental part of the DBMS model, so much so that without the use of transactions you will find that writing to a DBMS database is in fact slower than the equivalent operations in on the Series 3c. With transactions however, the Series 5 database handling is far faster than that of the Series 3c.

A transaction is carried out using the following commands:

BEGINTRANS begins a transaction on the current database. Once a transaction has been started on a view (or table) then all database keywords will function as usual, but the changes to that view will not be made until COMMITTRANS is used.

COMMITTRANS commits the transaction of the current view.

ROLLBACK cancels the current transaction on the current view. Changes made to the database with respect to this particular view since BEGINTRANS was called will be discarded.

INTRANS finds out whether the current view is in a transaction.

Record position

In the DBMS model, as with most modern relational database models, absolute record position does not have much significance.

Bookmarks can be assigned to particular records to provide fast record access and should be used in preference to POS and POSITION when opening views using the Series 5 OPEN...SELECT.. or CREATE ... FIELDS... statements.  POS and POSITION can be used safely on tables opened or created using a Series 3c-style OPEN or CREATE statement.  However, POS and POSITION should not be used in conjunction with bookmarks as bookmarks can cause these keywords, kept mainly for Series 3c compatibility, to become inaccurate. Note that if bookmarks are used in conjunction with POS and POSTION accuracy can be restored by using FIRST or LAST on the current view.

The new commands provided for the use of bookmarks are as follows: BOOKMARK puts a bookmark at the current record of the current database view. The value returned can be passed to GOTOMARK to make the record current again and to KILLMARK to delete the bookmark.

Saving records

When using the Series 5 extensions to CREATE and OPEN, you should also use the new MODIFY, INSERT, PUT and CANCEL keywords in preference to the APPEND and UPDATE Series 3c commands. APPEND and UPDATE will still work as expected, but do not naturally fit in the DBMS model.

MODIFY allows records to be changed without being moved to the end of the set (as UPDATE still does).

Instead of copying the current record to the end of the set as APPEND does, INSERT appends a new record to the end of the set with numeric fields set to 0 and string fields empty if values have not been assigned to them.

PUT marks the end of a databases INSERT or MODIFY phase and makes the changes permanent.

CANCEL marks the end of a databases INSERT or MODIFY phase and discards the changes made during that phase.

The number of records

The COUNT function returns the number of records in the file. If you try to count the number of records between assignment and APPEND/UPDATE or between MODIFY/INSERT and PUT an Incompatible update mode error will be raised.

Closing views and databases

CLOSE closes the current view on a database. If there are no other views open on the database then the database itself will be closed.

Indexes

Indexes can be constructed on a table using several fields as keys. These indexes are subsequently used to provide major speed improvements when opening a table or views on them.

Further database functionality is provided in the Database OPX, discussed in the Using OPXs chapter.

Compaction

COMPACT replaces the COMPRESS command  on the Series 5. This compacts a database, rewriting the file in place without any removed or deleted data. All views on the database and the hence the file itself should be closed before calling this command. Compaction may also be done automatically on closing a file by setting the automatic compaction flag using SETFLAGS. See the Alphabetic Listing chapter for full details of this.

Opening a database created by the Data application

It is currently not possible to open an OPL database from the Data application. You can however open a file created by the Data application in an OPL program. The file is opened for reading only because if it were written to, OPL would have to discard all the formatting characters and prevent the Data application from reopening the file subsequently. An OPL program can create a new OPL database and copy the Data application records into it if necessary.

To open a Data application database that has one string field which you need to access, you could use:

    OPEN "file",a,a$

Types not supported by OPL will be ignored. Note that integer fields in the Data application correspond to long integer fields in OPL: the Data application does not support (16-bit) integer fields. The types and order of the OPL field handles must match the fields in the Data file. For example, if the data file Data2 contains:

long integer field

date/time field (ignored by OPL)

string field

floating-point number field

You could access the fields supported by OPL using:

    OPEN "Data2",A, f1&,f2$,f3

It would be better, however, to use the SQL SELECT clause to name the required Data file fields explicitly. For this to be possible it is necessary to use table name and the same field names as are used by Data. All Data files have a single table called Table1. The fields (referred to internally as columns in Data) are named ColA1, ColA2, etc.

So, with the field types from the previous  example, the Data file could be opened using:

    OPEN "Data2 SELECT ColA1,ColA3,ColA4 FROM Table1",a,f1&,f2$,f3


Importing an Existing Project

OPPDev creates a project from scratch but the author recognises that existing software should be imported into the development environment. The problem is that different authors utilise different standards when developing their code.

It follows, therefore, that to convert an existing project into OPPDev is a little laborious to start. However, the author has tried to make the conversion as simple as possible.

This tutorial assumes that you now have a working knowledge of OPPDev and the standards that are employed. If this is not the case then please read the Start Here topic before progressing further.

This section is broken into the logical steps that should be employed.

1.	Create a new project.

Create a new project, we will assume a project name of 'PROJECT' for the purposes of this tutorial.

This action will create a new directory called PROJECT under \OPP\Include, a project handling file, an OPP file called PROJECT.OPP and the Main.oph, Consts.oph, Structs.oph and Globals.oph files. All these files will be empty apart from Main.oph which will have the PROC main: procedure defined.

2.	Open Windows Explorer

Copy into the new \OPP\Include\PROJECT directory all your program files that contain code. The extension for each of these files must be .oph, not .opp.

2.1	PROC Main:

Open the module where you have your start procedure (PROC main: in OPPDev) in an editor like Notepad and cut the code between the PROC and ENDP keywords. Switch back to OPPDev, double click on the main.oph file and paste this code into the PROC main: procedure. Now delete the original procedure reference and save the original file.

2.2	Globals

If you have your global variables in one file open it with Notepad and cut  the global list. Then switch back to OPPDev, double click on the Globals.oph file and paste the list directly into the project file.

If the global variables are in different places then repeat the above procedure for each file.

2.3	Consts

If you have your constants in one file open it with Notepad and cut  the CONSTS list. Then switch back to OPPDev, double click on the Consts.oph file and paste the list directly into the project file.

If the constants are in different places then repeat the above procedure for each file.

2.4	Structs

If you have your structures in one file open it with Notepad and cut  the STRUCTS list. Then switch back to OPPDev, double click on the Structs.oph file and paste the list directly into the project file.

If the structures are in different places then repeat the above procedure for each file.

3.	Add each .oph file

Within OPPDev add each .oph file into the project with the Add a File button. This action will cause OPPDev to check if the oph file has a corresponding .hed file. If there is no .hed file then it will be created and the procedures within the .oph file will be parsed and created in the new .hed file.

Now you should be in a position to try an OPP pre-compile. Watch the output window carefully to check for any error messages that are produced. The author has found that the biggest problem is '#include-ing' files too many times within the source code.

Please contact the author if you find a better way to import your existing programs and suggestions will be included here.


Source Code Window

The toolbar shown at the top of the source code window is reproduced below.

Pass your mouse over the above graphic to jump to help on the area that you need.

Alternatively, choose an option from the following list:

Procedures in a Module

Add a Procedure

Source Swapping

Copy, Cut and Paste

Procedure Mapping in HED

Source Editing Area

OPL Keyword List


Procedures in Module
Procedures in a Module

At the top of the source code edit window is a drop-down list of all the procedures within the module.

You can fast navigate to the procedure you require by manipulating this list.


Add a Procedure

Press the Add a Procedure button to add a new procedure to the module.

It is suggested that this is done as a matter of course so that OPPDev can register the new procedure in the HED file. If you do not want the procedure registered within the HED file then manually type the PROC .... ENDP keywords.


Source Swapping

Pressing on the swap button will switch the editor to/from the oph/hed files.


Copy, Cut and Paste

These are the copy, cut and paste buttons.


Procedure Mapping in HED

Pressing this button will open a new window that maps the HED procedure names to the OPH procedure names. This is provided to enable you to track down errors reported by the executing OPL program: Error in P_xx.


Source Editing Area

This is the area in which you can enter your source code.


OPL Keyword List

The right hand side of the source code window has a list of all the OPL keywords.

Double click on a keyword to embed it within your source code at the current cursor position.

Single click, then right click on a keyword to open a popup menu that will allow you to either embed the keyword in your source code or access the OPL help file for that particular keyword.