Everyday OPL programs can use the same graphical interface seen throughout the Series 3a:
Menus provide a simple way for any reasonably complex OPL program to let you choose from its various options.
To display menus in OPL takes three steps:
The first argument to mCARD is the name of the menu. This will appear at the top of the menu; the names of all of the menus form a bar across the top of the screen.
From one to eight options on the menu may be defined, each specified by two arguments. The first is the option name, and the second the keycode for a hot-key. This specifies a key which, when pressed together with the Psion key, should select the option. (Your program must still handle hot-keys which are pressed without using the menu.) It is easiest to specify the hot-key with "%" eg "%a" gives the value for "a".
If an upper case character is used for the hot-key keycode, the Shift key must be pressed as well to select the option. If you supply a keycode for a lower case character, the option is selected only without the Shift key pressed. Both upper and lower case keycodes for the same character can be used in the same menu (or set of menus). This feature may be used to increase the total number of hot-keys available, and is also commonly used for related menu options eg. "%z" might be used for zooming to a larger font and "%Z" for zooming to a smaller font (as in the built-in applications).
For example,
"mCARD "Comms","Setup",%s,"Transfer",%t"
defines a menu with the title "Comms". When you move to this menu
using " LEFT RIGHT ", you'll see it has the two options "Setup"
and "Transfer", with hot-keys Psion-S and Psion-T respectively
(and no Shift key required). On the other hand,
"mCARD "Comms","Setup",%S,"Transfer",%T"
would give these options the hot-keys Shift-Psion-S and Shift-Psion-T.
The options on a large menu may be divided into logical groups (as seen in many of the menus for the built-in applications) by displaying a grey line under the final option in a group. To do this, you must pass the negative value corresponding to the hot-key keycode for the final option in the group. For example, "-%A" specifies hot-key Shift-Psion-A and displays a grey line under the associated option in the menu.
Each subsequent mCARD defines the next menu to the right. A large OPL application might use mCARD like this:
mCARD "File","New",%n,"Open",%o,"Save",%s mCARD "Edit","Copy",%c,"Insert",-%i,"Eval",%e mCARD "Search","First",%f,"Next",%g,"Previous",%p
The MENU function displays the menus defined by mINIT and mCARD, and waits for you to select an option. It returns the hot-key keycode of the option selected, in the case supplied by you, whether you used Enter or the hot-key itself to select it. If you supplied a negative hot-key keycode for an underlined option, it is converted to its positive equivalent.
If you cancel the menus by pressing Esc, MENU returns 0.
When a set of menus is displayed, the cursor is positioned to the menu and option that the user selected previously (or, if no menus have previously been displayed, to the first option in the first menu).
This works only if your program has only one set of menus. If you have another set of menus, the cursor is still set to the position of the menu and option selected in the first set of menus (if that position exists in the new menus). To get around this, use "m%=menu(init%)" and set "init%" to zero the first time a set of menus is displayed. The cursor will in this case be positioned to the first option in the first menu. "init%" is set to a value which specifies the menu and option selected, and should be passed to MENU the next time that same set of menus is called If your program has more than one set of menus, you should have a different "init%" variable for each set of menus.
When choosing hot-keys, do not use those such as the number keys which produce different characters when used with the Psion key. Unless you have a good reason not to, stick with "a" to "z" and "A" to "Z".
You must ensure that you do not use the same hot-key twice when defining the menus, as OPL does not check for this.
Each menu definition uses some memory, so `No system memory' errors are possible.
Don't forget to use mINIT before you begin defining the menus.
If the menu titles defined by mCARD are too wide in total to fit on the screen, MENU will raise an error.
This procedure allows you to press the Menu key and see a menu. You might instead be typing a number or some text into the program, or moving around in some way with the arrow keys, and this procedure returns any such keypresses. You could use this procedure instead of a simple GET whenever you want to allow a menu to be shown, and its hot-keys to work.
Each option in the menus has a corresponding procedure named "proc" plus the hot-key letter so for example, the option with hot-key Psion-N is handled by the procedure "procn".
This procedure uses the technique of calling procedures by strings, as described in the `Advanced Topics' chapter.
PROC kget%: LOCAL k%,h$(9),a$(5) h$="nosciefgd" REM our hot-keys WHILE 1 k%=GET IF k%=$122 REM Menu key? mINIT mCARD "File","New",%n,"Open",%o,"Save",%s mCARD "Edit","Copy",%c,"Insert",-%i,"Eval",%e mCARD "Search","First",%f,"Next",%g,"Previous",%d k%=MENU IF k% AND (LOC(h$,CHR$(k%))<>0) REM MENU CHECK a$="proc"+CHR$(k%) "@"(a$): REM procn:, proco:, ... ENDIF REM END OF MENU CHECK ELSEIF k% AND $200 REM hot-key pressed directly? k%=k%-$200 REM remove Psion key code IF LOC(h$,CHR$(k%)) REM DIRECT HOT-KEY CHECK a$="proc"+CHR$(k%) "@"(a$): REM procn:, proco:, ... ENDIF REM END OF DIRECT HOT-KEY CHECK ELSE REM some other key RETURN k% ENDIF ENDWH ENDP
PROC procn: ... ENDP
PROC proco: ... ENDP
...
Note: this procedure allows you to press a hot-key with or without the Shift key. So Shift-Psion-N would be treated the same as Psion-N.
Neither LOC nor the "@" operator (for calling procedures by strings) differentiate between upper and lower case. If you have Shifted hot-keys you will need to compare against two sets of hot-key lists. For example, with hot-keys "%A", "%C", "%a" and "%d", you would have upper/lowercase hot-key lists like "hu$="AC"" and "hl$="ad"", and the "MENU CHECK" section becomes:
IF k%<=%Z REM if upper case hot-key IF LOC(hu$,CHR$(k%)) a$="procu"+CHR$(k%) "@"(a$): REM procua:, procuc:, ... ENDIF ELSE REM else lower case hot-key IF LOC(hl$,CHR$(k%)) a$="procl"+CHR$(k%) "@"(a$): REM procla:, procld:, ... ENDIF ENDIF(This calls procedures "procua:", "procuc:", "procla:" and "procld:"). If a hot-key was pressed directly you cannot tell from "k%" whether Shift was used; so make the same change to the "DIRECT HOT-KEY CHECK" section, but use "IF KMOD AND 2" instead of "IF k%<=%Z".
In OPL, dialogs are constructed in a similar way to menus:
Each of the commands like dEDIT and dDATE specifies a variable to take the information you type in. If you press Enter to complete the dialog, the information is saved in those variables. The dialog is then removed, and the screen redrawn as it was.
You can press Esc to abandon the dialog without making any changes to the variables.
If you enter information which is not valid for the particular line of the dialog, you will be asked to re-enter different information.
Here is simple example. It assumes a global variable "name$" exists:
PROC getname: dINIT "Who are you?" dEDIT name$,"Name:" DIALOG ENDP
This procedure displays a dialog with "Who are you?" as its top-line title, and an edit box for typing in your name. If you end by pressing Enter, the name you have typed will be saved in "name$"; if you press Esc, "name$" is not changed.
When the dialog is first displayed, the existing contents of "name$" are used as the string to edit.
Note that the dialog is automatically created with a width suitable for the item(s) you defined, and is centred in the screen.
This section describes the various commands that can define a line of a dialog. In all cases:
Although examples are given using each group of commands, you can mix commands of any type to make up your dialog.
More details of the commands may be found in the alphabetic listing.
dEDIT var str$,prompt$,len%
defines a string edit box.
len% is an optional argument. If supplied, it gives the width of the edit box (allowing for the 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 be. (You may wish to set a suitably small "len%" to stop some dialogs being drawn all the way across the screen)
dXINPUT var str$,prompt$
defines a secret string edit box, such as for a password. A special
symbol will be displayed for each character you type, to preserve
the secrecy of the string.
dFILE var str$,prompt$,f%
defines a filename edit box.
Here is an example dialog using these three commands:
PROC info: dINIT "Your personal info" dEDIT n$,"Name:",15 dXINPUT pw$,"Password:" dFILE f$,"Log file:",0 RETURN DIALOG ENDP
This returns `True' if Enter was used, indicating that the GLOBAL variables "n$", "pw$" and "f$" have been updated.
dFILE automatically has a `Disk' selector on the line below it. The third argument to dFILE controls the type of file editor you see, and the kind of input allowed. See the `Alphabetic listing' for more details of dFILE.
dCHOICE var choice%,prompt$,list$
defines a choice list. "list$" should contain the possible
choices, separated by commas for example, ""Yes,No"".
The "choice%" variable specifies which choice should initially
be shown 1 for the first choice, 2 for the second, and so
on.
For example, here is a simple "choice" dialog:
PROC dcheck: LOCAL c% c%=2 REM default to "Internal" dINIT "Disk Check" dCHOICE c%,"Disk:","A,Internal,B" IF DIALOG REM returns 0 if cancelled ... REM disk-check code ENDIF ENDP
dLONG var long&,prompt$,min&,max&
and
dFLOAT var fp,prompt$,min,max
define edit boxes for long integers and floating-point numbers respectively.
Use dFLOAT to allow fractions, and dLONG to disallow
them. "min(&)" and "max(&)" give the minimum and maximum values
which are to be allowed. There is no separate command for ordinary
integers use dLONG with suitable "min&" and "max&" values.
dDATE var long&,prompt$,min&,max&
and
dTIME var long&,prompt$,type%,min&,max&
define edit boxes for dates and times. "min&" and "max&" give
the minimum and maximum values which are to be allowed.
For dDATE, "long&", "min&" and "max&" are specified in "days since 1/1/1900". The DAYS function is useful for converting to "days since 1/1/1900".
For dTIME, "long&", "min&" and "max&" are in "seconds since 00:00". The DATETOSECS and SECSTODATE functions are useful for converting to and from "seconds since midnight" (they actually use "seconds since 00:00 on 1/1/1970").
dTIME also has a "type%" argument. This specifies the type of display required:
"type%" time display 0 absolute time without seconds 1 absolute time with seconds 2 duration without seconds 3 duration with secondsFor example, "03:45" is an absolute time while 3 hours 45 minutes is a duration.
This procedure creates a dialog, using these commands:
PROC delivery: LOCAL d&,t&,num&,wt d&=DAYS(DAY,MONTH,YEAR) DO t&=secs&: UNTIL t&=secs&: num&=1 :wt=10 dINIT "Delivery" dLONG num&,"Boxes",1,1000 dFLOAT wt,"Weight (kg)",0,10000 dDATE d&,"Date",d&,DAYS(31,12,1999) dTIME t&,"Time",0,0,DATETOSECS(1970,1,1,23,59,59) IF DIALOG REM returns 0 if cancelled ... REM rest of code ENDIF ENDP
PROC secs&: RETURN HOUR*INT(3600)+MINUTE*60 ENDP
The secs&: procedure uses the HOUR and MINUTE functions, which return the time as kept by the Series 3a. It is called twice to guard against an incorrect result, in the (albeit rare) case where the time ticks past the hour between calling HOUR and calling MINUTE.
The INT function is used in secs&: to force OPL to use long integer arithmetic, avoiding the danger of an `Integer overflow' error.
d& and t& are set up to give the current date and time when the dialog is first displayed. The value in "d&" is also used as the minimum value for dDATE, so that in this example you cannot set a date before the current date.
DATETOSECS is used to give the number of seconds representing the time 23:59. The first three arguments, "1970", "1" and "1", represent the first day from which DATETOSECS begins calculating.
dDATE returns a value as a number of days. To convert this to a date:
PROC daytodat:(days&) LOCAL dyscent&(2),dateent%(4) LOCAL flags%,ax%,bx%,cx%,dx%,si%,di% dyscent&(1)=days& si%=ADDR(dyscent&()) :di%=ADDR(dateent%()) ax%=$0600 REM TimDaySecondsToDate fn. flags%=OS($89,ADDR(ax%)) REM TimManager int. IF flags% AND 1 RAISE (ax% OR $ff00) ELSE year%=PEEKB(di%)+1900 :month%=PEEKB(UADD(di%,1))+1 day%=PEEKB(UADD(di%,2))+1 :yrdy%=PEEKW(UADD(di%,6))+1 ENDIF ENDP
If you do use this procedure, be careful to type it exactly as shown here.
dTEXT prompt$,body$,type%
defines "prompt$" to be displayed on the left side of the line,
and "body$" on the right. There is no variable associated with
dTEXT. If you use a null string ("""") for "prompt$",
"body$" is displayed across the whole width of the dialog.
"type%" is an optional argument. If specified, it controls the alignment of "body$":
"type%" effect 0 left align "body$" 1 right align "body$" 2 centre "body$"In addition, you can add any or all of the following three values to "type%", for these effects:
"type%" effect $100 use bold text for "body$". $200 draw a line below this item. $400 make this line selectable. (It will also be bulleted if "prompt$" is not """".)dTEXT is not just for displaying information. Since DIALOG returns a number indicating the line you were on when you pressed Enter (or 0 if you pressed Esc), you can use dTEXT to offer a choice of options, rather like a menu:
PROC selact: dINIT "Select action" dTEXT "","Add",$402 dTEXT "","Copy",$402 dTEXT "","Review",$402 dTEXT "","Delete",$402 RETURN DIALOG ENDP
In each case "type%" is $402 ($400+2). The "$400" makes each text string selectable, allowing you to move the cursor onto it, while "2" makes each string centred.
Most dialogs are completed by pressing Enter to confirm the information typed, or Esc to cancel the dialog. These keys are not usually displayed as part of the dialog.
However, some Series 3a dialogs offer you a simple choice, by showing pictures of the keys you can press. A simple "Are you sure?" dialog might, for example, show the two keys `Y' and `N', and indicate the one you press.
If you want to display a message and offer Enter, Esc and/or Space as the exit keys, you can display the entire dialog with the ALERT function.
If you want to use other keys, such as "Y" and "N", or display the keys below other dialog items such as dEDIT, create the dialog as normal and use the dBUTTONS command to define the keys.
ALERT and dBUTTONS are explained in detail in the alphabetic listing.
If a dialog overwrites important information on the screen, you can position it with the dPOSITION command. 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.
The following general restrictions apply to all dialogs:
Pressing Psion-Menu when an OPL program is running will always display a temporary status window. This status window is in front of all the OPL windows, so your program can't write over it.
Use "STATUSWIN ON" or "STATUSWIN ON,type%" to display a permanent status window. It will be displayed until you use "STATUSWIN OFF". "type%" specifies the status window type. The small status window is displayed for "type%=1" and the large status window either when "type%" is not supplied or when "type%=2".
You might use "STATUSWIN ON" when Control-Menu is pressed, for consistency with the rest of the Series 3a.
The status window is displayed on the right-hand side of the screen.
Important: The permanent status window is behind all other OPL windows. In order to see it, you must use either FONT or both SCREEN and gSETWIN, to reduce the size of the text window and the default graphics window. You should ensure that your program does not create windows over the top of it.
FONT automatically resizes these windows to the maximum size excluding any status window. It should be called after creating the status window because the new size of the text and graphics windows depends on the size of the status window. Note that "FONT -$3fff,0" leaves the current font and style it just changes the window sizes and clears them.
If you use SCREEN and gSETWIN instead of FONT, you should use the STATWININFO keyword (described next) to find out the size of the status window.
curtype%=STATWININFO(type%,extent%()) sets the four element array extent%() as follows: extent%(1) = pixels from left of screen to status window extent%(2) = pixels from top of screen to status window extent%(3) = status window width in pixels extent%(4) = status window height in pixels for status window type%
"type%=3" specifies the compatibility mode status window and "type%=-1" specifies whichever type of status window is currently shown. Otherwise, use the same values of "type%" as for STATUSWIN.
STATWININFO returns the type of the current status window. The values are as for "type%", or zero if there is no current status window.
Alternatively you could simply use "FONT -$3fff,0" as described under STATUSWIN above, which also sets the height to full screen height and sets the text window size to fit inside it.
The status window always displays the OPL program name, a clock and, by default, an icon. This will be the default OPL icon, unless your program is an OPA with its own icon. (OPAs are described in the `Advanced topics' chapter.) In addition, the settings selected in the `Status window' menu option of the System screen are automatically used in OPL status windows. The status window will therefore also display all the indicators required, and a digital or analog clock as selected there.
The status window is inaccessible to, and does not affect, the OPL keywords gORDER and gRANK.
You can set or change the name displayed in the status window with SETNAME for example, "SETNAME "ABCD"" or "SETNAME a$".
Your program may have several distinct modes/views/screens between which you would like the key to switch. The built-in applications use the key extensively Agenda uses it it to switch to the different views, while Word switches between `Normal' and `Outine' view.
The list the list of modes/views/screens which the key goes between is displayed in the status window.
OPL programs can set up a list. Use
"DIAMINIT pos%,str1$,str2$,..."
to initialise the list (discarding any existing list). The list can
be initialised before or after a status window is displayed.
"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 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%=0", or if DIAMINIT is used on its own with no arguments, no bar is defined.
If "pos%=-1" the list is replaced by the icon instead in the large status window.
If "pos%>=1" you must supply at least this many strings.
Defining a list uses some memory, so `No system memory' errors are possible.
"DIAMPOS pos%" positions the indicator in a list. You might move the indicator to the next item when the key is pressed and to the previous item when Shift- is pressed. The key has keycode value 292 and KMOD returns 2 when the Shift key is pressed.
Positioning outside the range of the items wraps around in the appropriate way if there are three items in the list, "DIAMPOS 4" positions to the first.
"DIAMPOS 0" causes the symbol to disappear.
Use "chr$(4)" to display a key in a menu. If you use it as a hot-key, a Shift will be added automatically.
GIPRINT displays an information message for 2 seconds, in the bottom right corner of the screen. For example, "GIPRINT "Not Found"" displays "Not Found". The string you specify can be up to 63 characters. If a string is too long for the screen, it will be clipped.
You can add an integer argument to control the corner in which the message appears:
value corner 0 top left 1 bottom left 2 top right 3 bottom rightFor example, "GIPRINT "Who?",0" prints "Who?" in the top left corner.
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 """.
Messages which say a program is temporarily busy, or cannot respond for some reason, are by convention shown in the bottom left corner. The BUSY command lets you display your own messages of this sort. Use "BUSY OFF" to remove it.
"BUSY "Paused..."", for example, displays ""Paused..."" in the bottom left corner. This remains shown until "BUSY OFF" is used.
You can control the corner used in the same way as for GIPRINT. You can also add a third argument, to specify 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.
For example, "BUSY "Wait:",1,4" will display "Wait:" in the bottom left corner after a delay of 2 seconds. As soon as your program becomes responsive to the keyboard, it should use "BUSY OFF". If this occurs within two seconds of the original BUSY, no message is seen.
Only one message can be shown at a time. The string to display can be up to 19 characters long.