NOTE This is the most technical chapter in the manual and can safely be ignored by anyone who wishes to use only the built-in features of Powerbase as described in previous chapters.
Powerbase has many powerful and useful features but, as with any software, some users will have special requirements which aren't catered for. This is why many organisations produce their software in-house as the only way of ensuring that their specific needs are met. I first came across this situation in the F.E. college for which Powerbase was originally written and in which it was (and still is) used to maintain student records. I was asked if the program could be enabled to print students' personal timetables and, since Powerbase was used by so many other people for diverse purposes, was unwilling to include code for this very specialised function in the main program. The solution developed at that time was to provide the special feature in a subsidiary program file which was loaded as a library and to give Powerbase the "hooks" to enable it to recognise the library and create the controls which gave the user access to the extra facilities. This method of adding features to Powerbase has been progressively refined to the point where it can, with qualifications, be offered to users.
Although you can experiment quite safely with the demonstration Customise directory supplied, it must be emphasised that writing your own customisation library is a far from easy task and is recommended only to users who have considerable experience in programming with BBC BASIC V. Errors in coding are bound to occur. If you are lucky these will be met by an error message describing the nature of the error and the line-number at which it occurs. The latter might be a line in your own library code or a line in Powerbase's !RunImage file. In the worst cases Powerbase will crash and the computer might lock up completely and require a reset.
The directory Customise is intended for use with the Elements sample database and is provided for you to try out database customisation easily and painlessly. Use it only with the copy of Elements supplied with it since this contains additional data which is used by the demonstration. If you examine Customise you will find that it contains several objects, the most important of which is the BASIC program file Demo.
Open the application directory !Elements with Shift-double-click and copy Customise into it. Now run Powerbase and open the database in the usual way. Non-standard behaviour is seen as soon as the database is open. A window displaying the Periodic Table of the elements appears. This window forms no part of Powerbase itself but is obtained from the Templates file inside Customise. If you examine this Templates file with a template editor you will see that it supplies only a blank Periodic Table; the symbols and atomic numbers are filled in from data in the database itself when it is opened. One element (Actinium) is highlighted in white on black. If you use the browsing and searching buttons on the tool-pane the table display reflects what you are doing by always highlighting the element you access. Moreover, things work both ways; clicking SELECT over any element on the Periodic Table window brings up the same element in the record window.
Click the Report button, enter the search formula GP=1 and type Return. The expected list of names of the Group 1 elements is displayed, but look at the Periodic Table window! The matching elements are highlighted there in blue and grey. Try a number of other queries. There's no need even to have a report produced; selecting the Count function on the Match window will do just as well. When the table becomes too cluttered with highlighting you will want to clean things up. Look near the bottom of the tool-pane and you'll see a pop-up menu button which isn't normally there. Click this and choose Clear from the menu. Next to the menu button is another unfamiliar button resembling a house window. This will re-open the Periodic Table if the window has been closed. The pop-up menu has other choices to try which highlight certain sections of the table in colour. Clicking with MENU over the Periodic Table window displays a different menu and you can try the choices there too.
The thing which does the work is the BASIC file called Demo, all of whose FN/PROCs become accessible, when required, as if they were part of !Powerbase.!RunImage itself. It's now time to load Demo into your favourite editor (Zap or StrongEd but not Edit, I hope!) and have a good look at it.
Note first of all the frequent occurrence of the string "Demo" in the names of variables, functions and procedures. When assigning names to FN/PROCs and to variables specific to the library it is important to avoid clashes with names which are used within Powerbase itself and also with names in other libraries. You may have as many databases as you like, each with its own customisation library tailored to add specific features to that database, but no two libraries may contain identically named functions or procedures. Having identically-named variables is likely to be less serious (and is no problem at all if they're LOCAL variables) but care is still needed. Achieving the necessary uniqueness in naming isn't difficult if you start out with the Demo library and do the following:
Note that although you should not use the same name for different libraries (you're likely to get program crashes and strange error messages if you do) it is possible to use the same library in more than one database, which is useful where a library provides general facilities not geared to one specific database.
All the objects in certain designated directories are examined to see whether they are databases containing a Customise sub-directory. If any such are found the pathnames of the BASIC libraries they contain are placed in a string array which is then used with the OVERLAY command. When any function is called which is not found in the main program each library is loaded in turn until the required function is found and executed. Since all the libraries use the same area of memory, only one being paged in at any given time, this system makes economical use of RAM and, more importantly, allows several customised databases to be opened successively without the need to shut down and re-run Powerbase. The memory area used must be large enough to accommodate the biggest library, which is why it is advisable to have all your customised databases in directories which are known to Powerbase when it is first loaded; that's what is meant by the "designated" directories referred to above. That makes it possible for the maximum size of library to be found before any database is opened.
You can obtain a list of the libraries recognised by Powerbase at run time by choosing Libraries from the iconbar menu. Like all windowed reports this may be saved or printed (see 3.1.1). Double-clicking on a line of the displayed list will open the relevant Customise directory for examination and editing.
The directories designated for Powerbase to search are specified in !Powerbase.Resources.Config by means of the entry CustDir (see 14.8). As supplied CustDir has the value "Default", which means the same directory as that which contains Powerbase itself, but you may replace "Default" with the pathname of any directory you wish or by several directories whose pathnames are separated by commas. If a database is not located in any of the directories specified by Custdir that won't prevent you from using it but, if its library is larger than any already recognised, a new bigger slot will be claimed by OVERLAY and the old memory will be wasted.Another relevant Config entry is MaxLibs; the maximum number of libraries which can be handled. The default setting is 10 but you may increase this if necessary.
If the database has a Customise directory, Powerbase looks at the list of libraries which it knows about to see whether the database's library is one of them. If not the new library is added to the list, allowing you to use the customised features of databases outside the directory specified by CustDir but, if the new library is larger than any of the known ones, some memory will be wasted as explained above. For this reason you will see a message advising you to move the database into the same directory as the others. The message only appears the first time you open the database during a given session. Once the correct library has been identified Powerbase calls the function FN_<name>_setup where <name> is either "Demo" or whatever you renamed Demo to in 15.2. This, the first function in the library, is only called once while the database remains open and does the following:
Your customisation library will almost certainly require some or all of these features.
The Templates file should be stored in the Customise directory and the window definitions read from it in FN_<name>_setup. Demo shows how this is done by opening the the file, making calls to the Powerbase function FNnew_window passing the window template name as a parameter, and finally closing the file. Be careful what variable names you choose as window handles. If you incorporate the library name in them they are very unlikely to clash with any used by Powerbase itself but simple and obvious names like saveW% will clash.
wi% | The handle of the window on which the button is to be created. This must be either mainW% (for the record window) or keypadW% (even if the tool-pane, rather than the keypad, is being used). |
S$ | The text which is to appear on the button (if an action button) or adjoining it (if a radio or option button). It may also be used in "menu" and "window" buttons on the record screen (but not on the keypad/tool-pane) to supply a descriptor for the button. |
type$ | This may be "menu", "window", "option" or "radio". Any other value is understood as an action button. |
grey% | A boolean variable which determines whether the button is shaded (TRUE) or not (FALSE) on first appearance. |
x%,y% | Co-ordinates specifying the position of a button on the record window. They refer to the lower left corner of the button icon, the origin being the upper left corner of the window. y%, therefore, is always negative. Buttons on the keypad/tool-pane are positioned by Powerbase and any values supplied by the user are ignored. |
W% | The width of the button icon in OS units. It does not apply to "window" or "menu" buttons on the keypad/tool-pane (these are always 44 OS units square) but is relevant for all other types. For action buttons it is the width of the button itself, for all others it is the width of the button plus accompanying text. A value of W%=0 causes Powerbase to set the button width for you. You should normally do this for buttons on the keypad/tool-pane. These should be 144 OS-units wide and you should adjust the text so that it can be displayed within this width using the current desktop font. Buttons on the record window may be wider than this and, if W%=0, Powerbase will increase the width to accommodate the text. You can override this action by supplying your own value to make the button either wider than the automatic setting or narrower than the normal minimum of 144 OS units. This is useful if you wish to standardise the width of a group of record window buttons. |
will create the menu structure on the right (returning "Menu" in title$) and display it when the button called Demomenu% on the tool-pane/keypad is clicked with SELECT. If window% is the handle of a custom window the menu will appear at the pointer when MENU is clicked over the window background. Embedding to a maximum depth of four is supported and quite complex structures can be built up subject to the maximum string length of 255 characters. Transient dialogue boxes can be attached by placing the relevant window handle (which should already have been determined as in 15.5.1) in parentheses at the appropriate place in structure$. The string title$ will subsequently be needed if more than one menu tree is defined so that FN_<name>_select (see below) can determine which menu is being chosen from.
It will be seen that most of the setting-up in FN_<name>_setup occurs in the body of the IF...THEN structure, i.e. in lines 22-31 incl. Anything which must/should be done only once during a given Powerbase session with a particular database goes in here. Obvious examples are DIMensioning global arrays and reading window definitions from a Templates file. Don't put calls to FNadd_button in here because the custom buttons are removed from the tool-pane when the database is closed and all icons (including buttons) on the record window are created from scratch on the empty window on opening.
Section 15.2 listed eight functions within a customisation library which are called from the main Powerbase code. FN_<name>_setup has already been described. The others are called under the following circumstances:
FN_<name>_button(wi%,ic%,b%) This is called whenever the user clicks on one of the custom buttons. wi% contains the handle of the window which owns the button. This will either be mainW% (button on record window) or keypadW% (button on tool-pane or keypad).
b% gives the state of the mouse buttons and the user can test the bits to see which button was pressed and whether double-clicked or not. ic% is the icon number of the button. and all such numbers will be known to the programmer because s/he will have set them up using FNadd_button.
There will usually be at least one CASE statement to determine which button was pressed and take the appropriate action, such as opening a menu or window.
FN_<name>_select(menu%) Called when the user chooses from a menu which is not recognised by Powerbase and must therefore be one created by the customisation library. menu% is the handle of the menu and the item chosen is present, in textual form, in choice$(1). If the choice was from a 1st-level submenu the submenu choice will be in choice$(2) and so on down to choice$(4). The parameter menu% comes from Powerbase and should not be set by the library. If more than one custom menu is to be serviced by this function the coding needs to use the menu's title string returned by FN_<name>_menu (see above) to find out which menu is in use.
FN_<name>_click(wi%,ic%,b%) Called when a button is clicked in a window which Powerbase doesn't recognise and which must therefore be a window created within the customisation library.
FN_<name>_press(wi%,ic%) Called when a keypress occurs and the window having the input focus is not known to Powerbase. The key code is in pressed%.
FN_<name>_drop(wi%,ic%) Called when a file is dropped on a window not known to Powerbase. You should not delete this function definition, even if your application doesn't call for file drops on a custom window. If a file is inadvertantly dropped on the window and the function has been wholly deleted you will get a "No such FN/PROC" error. Leave at least the DEF FN header line and the =TRUE at the end.
FN_<name>_help(wi%,ic%)
Called when the pointer moves over a custom button when Interactive Help is active. Help messages may be supplied for the buttons on the keypad/tool-pane (handle=keypadW%) and record window (mainW%) and also for any windows defined in the customisation library itself, such as the Periodic Table window (DemoW%).
FN_<name>_function(func%) The functions described above are designed to let Powerbase know about events occurring in the customisation library. This function performs the opposite task, informing the library about certain common events taking place in Powerbase. These events make use of a global variable libflags% which is treated as an array of 32 bit-flags. Events are only reported if the appropriate bit of libflags% has been previously set by the customisation library in FN_<name>_setup. Only the first eighteen of these flags are used by current versions of Powerbase and their meaning is explained below.
0. Database opening has been completed. Demo uses this event to tell the customisation library to fill in and display the Periodic Table.
1. A record has just been read from the database and displayed in the record window. The array Rf%() holds pointers to the icon text-buffers for the fields. A copy of the record is held in field$(). Scrollable lists and External fields have been read and all keys are now present in key$(). Demo uses this event to highlight the element on the Periodic Table whose record has been displayed.
2. A record has been read into array F$(). This is the array used when generating reports and for some other operations, so that the displayed record is not affected. Only "normal" text and numeric fields are read: where field n is a scrollable list, external or button, F$(n)="".
3. The displayed record has been written, including the writing of Scrollable lists and Externals and the insertion of keys into indexes.
4. The record held in F$() has been written back to Database.
5. A "tree-walk" or file-scan of the current key is about to begin. The keys will be accessed in order until the end of the subfile is reached. If more than one subfile is involved the subfiles are or scanned in sequence.
6. The aforementioned file-scan has finished.
7. A record has been matched to a search formula in the course of a file-scan. Demo uses this event to highlight the matched element on the Periodic Table.
8. A key has been pressed when the caret is in either an ordinary field or a scrollable list. Key number is in pressed%
9. A mouse-click has occurred on the record window.
10. Database is about to close. Demo uses this event to close the Periodic Table window. The global variable Postpone% can be set to TRUE if you want to put off closing.
11. A file has been dropped on the record window.
12. An Impulse GetField command (see App.A) has been received.
13. The record held in F$() is about to be written back to Database. (This gives you an opportunity to make changes before the record is saved.)
14. The displayed record is about to be written. As with event 13 you can intervene.
15. Selected subfile has been changed
16. A batch Move/Delete operation has been started
17. A record is about to be moved to another subfile
16 and 17 are useful if your database references objects (e.g. photographs) in an external directory. A customisation library could be written which moves (i.e. renames) the referenced files to correspond to the movement of the records. 18 enables you to intervene and change the pathname of the backup directory and the number of generations of backup from those specified in the database's BackupDir file. Very useful if you're using the database on two different machines.
The most trouble-free way to set these flags is with the aid of Basic's bit-shifting function <<. For example, to enable events 0, 1, 7 and 10 used by Demo as mentioned in the table above you would use the statement:
near the end of FN_<name>_setup, but not inside the IF...ENDIF block. "(1<<0)" is, of course, merely 1 and is only written in this way for conformity with the other bits set.
Powerbase frequently displays text in a window from which a menu allows it to be saved as a text file. The library procedure PROC_<name>_screen(pos$,line$,options%) enables you to do this to produce simple lists. line$ contains a line of text to be displayed. pos$ is a null string for all except the first and last calls to the procedure. Before text can be stored it is necessary to set up an area of memory to hold it and this is done by settimg pos$="begin" when storing the first line. After the last line has been stored we want the text to be displayed in a window so we set pos$="end" when storing the last line.
Parts of a line can be "tabbed" by embedding the required character position in square brackets immediately before the part of the string to be tabbed. For example:
would place "This" immediately after the margin indent, "is" at position 10, "a" at 20, "tabbed" at 30 and "line" at position 40. Although the contents of the variable line$ is subject to the usual string limit of 255 characters the actual displayed line-length is not so limited and could be made much longer by the use of tabs as described.
There's a further twist to this tabbing business; if line$ comprises only a number in square brackets, i.e. not embedded in any text, then it is treated as a "vertical tab" and the relevant number of blank lines is printed. This is especially useful in the first call to PROC_<name>_screen if you want a deep top margin, but can also be used to separate sections of the report by one or more blank lines.
Using PROC_<name>_screen as described above, calling the procedure with options%=0 every time, might be all you require. options% does, however, allow you to exercise a little more control. The least significant byte (byte 0) of this variable specifies the line length. If you leave options% at zero the line length defaults to 100 characters, which might be too short, or unnecessarily long, for the text you want to display. Placing your own value in options% lets you control this feature but if the value is too small the text will be corrupted and you'll need to increase the line length and try again. The line length for the entire report is determined on the first call to the procedure, i.e. the one where pos$="begin". Thereafter any value placed in byte 0 is ignored.
The next byte (byte 1) of options% determines the width of the left margin. If this byte is zero the margin will be 1 character. Unlike the line-length byte the margin is read for each line so indentation is possible. You do not, however, need to specify the margin for every line; the value passed in the first call to the procedure (i.e. at the same time as the line-length is determined) is the default margin and all subsequent calls which don't specifiy a margin will use the default.
The four lowest bits of byte 2 have the following actions when set:
0. Draw a horizontal rule before the text line
1. Draw a horizontal rule after the text line
2. Print the text in the header colour
3. Store record number
Bits 0 and 1 may both be set to produce rules before and after the text. The colour of these rules is determined by a setting in Powerbase's Report options window (see 3.2.1). The header colour is also specified there. You may force any line to appear in the header colour by setting bit 2 (whereas standard Powerbase reports use this colour for header and footer lines only). Placing 7 in this byte (i.e. setting bits 0, 1 and 2) will display the line in the header colour preceded and followed by horizontal rules.
If bit 2 is clear but bit 3 is set the record number will be stored with the text line. Double-clicking the line in the report will then retrieve the associated record, just as it does with standard Powerbase reports. To use this facility, which is only meaningful if what you are printing has some connection with a specific database record, the record number should be placed in the variable <name>_REC%. (This variable is declared in FN_<name>_setup to avoid 'No such variable' errors later.) Use of the header colour and record-retrieval by this method are mutually exclusive; if you set bits 2 and 3 you will obtain the former effect but not the latter.
When only the line length is being specified it is conveniently expressed as a decimal number but if more than one option is being set it is easier to use hexadecimal. Fig.1 shows how to produce a list with a line length of 48 (&30) characters and a left margin of 4 with a heading which is both coloured as such and underlined:
Fig.1 |
PROC_<name>_screen("begin","This is the heading",&00060430) PROC_<name>_screen("","Line of text",&00000400) PROC_<name>_screen("","Line of text",&00000400) PROC_<name>_screen("","Line of text",&00000400) etc. PROC_<name>_screen("end","Last line",&00000400) |
This section describes how to retrieve the keys of all records in a chosen subfile in an order determined by a specific index.
The subfile number (subfile%) must be in the range 0-5; many databases use subfile 0 only. The index, or key, number is passed in index%. A value of 0 refers to the primary key index and this may be the only one present. The function returns an integer which is the address of the first node in the index. This value will be needed in order to access the next node (A node consists of two pointers, a key, and a record number but you probably won't need to extract the individual bits of the node yourself.)
subfile% and index% must be the same values as you passed to FN_<name>_start_scan whilst address% is the value which was returned by that function. How will we know when there are no more nodes to scan? When FN_<name>_next_node returns a value of –1.
The code for scanning subfile 0 using index 0 is therefore:
Fig.2 |
address%=FN_<name>_start_scan(0,0) REPEAT address%=FN_<name>_next_node(0,0,address%) UNTIL address%=–1 |
The subfile number isn't required here but the index number is needed as before, together with the most recently-returned value of address%. The result of this procedure call is to read the fields of the record into an array F$(). The string returned by the function is the key, which you might or might not require. Another item which might be useful is the record number which is returned in the variable record%. When the function is called you must pass a numeric (preferably integer) variable as the third parameter, whether you actually want to know the record number or not.
The function returns the contents of the field specified by tag$, which is useful since you are more likely to be familiar with tags than with field numbers. So what's this parameter field% for? It enables you to obtain the field-number associated with tag$ if you need it. Simply place any convenient integer variable (a LOCAL variable should be used) in the field% position and it will contain the required field number after the function call.
An example occurs in Demo where the variable AtNum% is assigned to the field whose tag is Z
The complete procedure in Fig.3 will scan a specified subfile using a specified index, accessing each record:
Fig.3 |
DEF PROC_<name>_scan_file(subfile%,index%) LOCAL key$,record% address%=FN_<name>_start_scan(subfile%,index%) REPEAT address%=FN_<name>_next_node(0,0,address%) key$=FN_<name>_read_record(index%,address%,record%) Do whatever you want to do with the record in F$() UNTIL address%=–1 ENDPROC |
The section between the record-reading function and the UNTIL statement is where you place your own code for dealing with the records. In the Demo library a third parameter, do_this$, is passed to the procedure to determine what is to be done with each record. Note how this saves on coding; seven different outcomes from a subfile scan are achieved but the actual scanning and reading code only occurs once. Since all the outcomes in this example make use of the Atomic number field (Z), the contents of this field are obtained by calling FN_<name>_field() immediately after reading the record. The numeric value of the field is then passed as a parameter to each of the procedures which performs specific tasks on the records.
A record can be located using:
The index number and the key of the record in that index are given as parameters. If the key exists the function returns its address in the index. If the parameter display% is FALSE then that's all the function does, but if display% is TRUE the record will be retrieved and displayed in the record window, even if it is in a different subfile from the current one. The array Rf%() holds pointers to the field data icons so, if access to the contents of individual fields is required, they can be read (for most field types) using:
where field% is the number of the required field. (see 15.9.5 for the method of finding the fieldnumber.) Please note that the contents of External and Scrollable list fields cannot be read in this way and no method of reading these types of fields is provided in the library. Don't confuse this procedure with FN_<name>_read_record (see 15.9.3) which reads a record into the array F$() without affecting the display.
You might not have occasion to do this but Demo makes extensive use of the feature so it is documented here for your benefit. To set the colours of an icon you would use:
where window% and icon% are the window and icon handles of an icon on one of your custom windows (although you could use this procedure to do strange things to icons within Powerbase itself!). fore$ and back$ are the names of the required foreground and background colours. The recognised colours are as follows and the names are not case-sensitive:
white, black, blue, yellow, green, red, cream, olive, orange and cyan and also six progressively darker shades of grey, grey1-grey6
The very fact that customised databases present the user with features which are non-standard, and therefore not covered by the Powerbase documentation, makes it all the more important to provide some guidance for users. Implementing Acorn interactive help messages on custom buttons and windows has already been described (see 15.7) but something more than this is recommended.
At the very least authors should place a text file !Help inside the database directory explaining what the special features do. A longer document, perhaps in HTML format with or without illustrations, could be included in the Customise sub-directory and launched by means of a suitably named button on the tool-pane/keypad (as is done in the case of Demo) or on the record window.