Getting custom statements to work in Panorama X - much improved


Jim Rea has provided an interim method of setting up custom statements in Panorama X but there are a few precautions to take and a few rules to follow to get them working properly. This is my second attempt to provide a definitive guide to doing so. It differs so substantially from the earlier attempt that it warrants a new thread.

#####Setting up the host database#

Create a dedicated database to hold some or all of your custom statements - I’ll refer to it as CustomStats from here on. There are no limitations on the nature of the database name. However, the name of each procedure which you want to be a custom statement may contain only the upper-case letters A to Z, numbers and underscores. The presence of any other character in the name will prevent the procedure from being recognised as a Library procedure. CustomStats may contain other procedures but it’s probably better that it doesn’t.

#####Registering the custom procedures#

CustomStats must be open and its custom statements must be registered as Library procedures before any of them can be invoked. Registration is achieved via a ScanLibrary "CustomStats" command. This is the interim Panorama X equivalent of choosing the menu item, Statements>Register New/Modified Statements in the Custom Statements wizard in Panorama 6.0. The ScanLibrary statement can be in the .Initialize procedure of CustomStats. You should always set it up that way so you need only open the database to have all of its custom statements registered. The obvious next questions are where to store the database and how and when to open it.

Where to store it? I have mine in a folder called PanoramaX Customs in the Applications folder. The full file path is then “/Applications/PanoramaX Customs/CustomStats”. It really doesn’t matter where you put it as long as it’s readily accessible. It would be prudent to have a backup copy of CustomStats, preferably with the makesecret statements commented out.

How to open it? You can choose to open it automatically or manually and it can be visible or secret. Your choice will be driven by two factors. Firstly, you must to be able to access the content of the database to make changes from time to time and secondly, if you quit and restart Panorama and again require your custom statements, you must to be able to open the database manually and register its contents.

Opening automatically as a secret file is arguably the neatest way to do it - that way, you don’t have an extraneous file cluttering up your screen. Set up the automatic opening in System Preferences>Users & Groups>Login Items and include both ScanLibrary "CustomStats" and the makesecret statement in the .Initialize procedure. This also answers the question of when to open it.

If you subsequently quit and reboot Panorama and then attempt to run a procedure which uses a (currently unregistered) custom statement, the procedure will not compile. Just open the CustomStats database manually and recompile your procedure by clicking on the Check button in the Toolbar.

If you want to access your custom statements in CustomStats, you will want to open it without making it secret. At the time of writing, there are two ways to do this. You can execute the openplain statement in another database and select CustomStats in the dialog box or you can choose File>Find & Open, right-click on the database name and select Open Data Sheet Only.

If you prefer to have CustomStats visible, you could skip the makesecret statement, have the database open automatically and include in its .Initialize procedure a command like zoomwindow 5,5,50,50 to shrink its window and tuck it away in the top left corner of the screen. The zoomwindow statement must precede all other statements in the procedure except variable declarations and message statements - ideally, make it the first entry. You could make the window much smaller to the point of being invisible but that makes it impossible to drag it to a larger size, thus defeating the purpose of opening it as a visible window.

There is one more option. You could open CustomStats and register its statements in the .Initialize procedure of every database which might want to use a custom statement. This is likely to be a fairly small set but it doesn’t have a lot going for it in my opinion.

#####When custom statements are dependent on other custom statements#

The situation becomes more complex if you have one or more custom statements which draw on other custom statements (referred to here as “parent” statements). I have not been able to find a way in which parent statements can co-exist with dependent statements and successfully be registered, so I’ve resorted to using two databases.

My parent database is called CustomParents. It contains only those statements on which others are dependent. Its .Initialize procedure contains the following code:

ScanLibrary "CustomParents"
if info("files") notcontains "CustomStats"
    	opensecret "/Applications/PanoramaX Customs/CustomStats"
ScanLibrary "CustomStats"

This code registers the parent statements before registering the dependent statements. The .Initialize procedure in the CustomStats database contains the following code:

if info("files") notcontains "CustomParents"
	openfile "/Applications/PanoramaX Customs/CustomParents"
ScanLibrary "CustomParents"
ScanLibrary "CustomStats"

This code ensures that, if you happen to open CustomStats manually, CustomParents will also open and its custom statements will be registered before registering the dependent statements in CustomStats. You need open only CustomParents automatically when your Mac boots. It will be made secret and it will open CustomStats as a secret file. It would be prudent to have backup copies of CustomParents and CustomStats, preferably with their makesecret statements commented out

#####A sample Procedure Info block

Here is a complete ProcedureInfo block as an example of what to do if you want your custom statement to be incorporated as a mainstream statement. If a custom statement is for your use only, you can dispense with part or all of the ProcedureInfo block as you wish.

The GCDLCM statement calculates both the greatest common divisor (GCD, also known as the 
highest common factor, HCF), and the lowest common multiplier (LCM) of a set of two or more 
numbers.  Both values are calculated because it is necessary to calculate the GCD in order 
to obtain the LCM.
<parameter NAME=NUMBERS TYPE=TEXT> The set of numbers whose GCD or LCM is required.  The 
set must be a comma-delimited array. </parameter>
<parameter NAME=RESULTS TYPE=TEXT> The greatest common divisor of the number set and the 
lowest common multiplier of the number set, presented as a two-element semi-colon-delimited text 
array. </parameter>
The statement is based on the following identities:  
GCD(a,b,c) = GCD(GCD(a,b),c),  
LCM(a,b,c) = LCM(LCM(a,b),c)  
LCM(a,b) = (a*b)/GCD(a,b)

The procedure body is largely self-documenting.

GCDLCM "10,14,16,18",Result gives the result:


If any number in the input set is zero, both GCD and LCM of the 
set will be zero.  If the set contains only one number, it is 
deemed to have a zero second number and both results will be zero. 
All elements in the set must be positive numerics.
<error message="GCDLCM statement: The input set includes a non-numeric character.">
Non-numeric characters are not permitted as input.
<error message="GCDLCM statement:  The input set includes a negative number.">
Negative numbers are not permitted as input.
<revision version="1.0" status="No Change">New in this version</revision>
<author>Michael Kellock</author>

#####Next step#

Jim advises that he is planning on creating a mechanism that would allow users to place databases in a custom spot in the /Library folder and have them load automatically when Panorama launches, just like Panorama does for its own libraries but this may not have a high priority. In the interim, the above process should work well. I welcome suggestions for its improvement.

Ultimately, there should be a Custom Statements entry in the Help file.


There is a major correction to this document (not likely to be the last) in that it is possible for parent statements to co-exist with dependent statements. Hence, the entire section headed “When custom statements are dependent on other custom statements” can be dispensed with. This makes the whole operation much simpler.