Custom Statements

Panorama comes with nearly a thousand statements that are built-in and ready to use, out of the box. That’s just a starting point though, because Panorama is extensible and allows you to create your own statements written in Panorama code. Once set up, these statements work exactly the same as built-in statements written in Objective-C, there’s no difference. In fact, over half of the “built-in” functions that come built-in to Panorama are actually written in Panorama code, not Objective-C.

### Custom Statements Theory of Operation

When Panorama starts up, it looks for databases that contain custom statements. There are about two dozen such databases built into Panorama itself. You can see these library databases in the View Organizer window if you check the Include Libraries option and then click on the pop-up menu.

As Panorama starts up, it scans each library database looking for procedures with all upper case names (for example `REMOVEALLSUMMARIES`, `FIELDNAME` or `OPENTEXTFILE`). Any procedure with an all upper case name becomes a new custom statement, and can be used in code just like any other statement (notice that when used in code, the statement name can be upper case, lower case, or any mix).

    removeallsummaries

A custom statement is really a just a shorthand way to invoke a subroutine. The code above is exactly equivalent to:

    farcall "_DatabaseLib","REMOVEALLSUMMARIES"

I think you’ll agree that `removeallsummaries` is a lot easier to type!

Note: For more detail about how Panorama initializes any library databases it finds, see Custom Library Initialization at the bottom of this page.

#### Viewing Built In Custom Statement Code

There are four categories of statements in Panorama.

1) Closed source statements written by ProVUE Development in Objective-C. 2) Open source statements written by ProVUE Development in Panorama code. 3) Closed source statements written by ProVUE Development in Panorama code. 4) Your own custom statements writtin in Panorama code.

There are several hundred open source statements included in Panorama that were written by ProVUE Development (or in a few cases, by Panorama customers) but that are fully accessible to you. You can’t readily modify the code for these statements, but you can easily view the code, and use it as a model for developing your own code and custom statements.

The easiest way to look at an open source statement is to use the Open View command in the View menu. For example, suppose you are curious about how the rundialog statement works. To view the code, choose View>Open View, make sure the +Libraries option is enabled, then type in rundialog. Double click on the procedure name in the search results.

If you search for a statement and it doesn’t appear on the list, that means that it’s a closed source statement written in Objective-C, so the source code isn’t available. You may also encounter closed source statements written in Panorama code. These will show up in the search results, but when you double click on one of these you’ll be warned that you don’t have permission to view the code. For example, many of the statements that are part of Panorama X Server fall into this category.

Of course you should be very careful about editing Panorama’s built-in libraries. But if you want to see how things work “under the hood”, it can be valuable to take a peek.

#### Creating Your Own Custom Statement Library Database

You can’t modify the library databases that come with Panorama, but you can create your own library database. There is a special folder on your system for library databases.

    ~/Library/Application Support/PanoramaX/Libraries

When Panorama starts up, it will scan this folder to see if it contains any databases. If any databases are found, each database is automatically opened and loaded into memory (but opened secretly, without opening any windows). Once opened as a library database, the database can’t be closed - it will remain open until Panorama quits.

Panorama doesn’t just open each library database, it also scans the procedures in the database and registers any custom statements it finds (procedures with all upper case names) in the database. These newly registered custom statements become part of the Panorama programming language, and can be used in code just like any built-in statement.

You can open the `~/Library/Application Support/PanoramaX/Libraries` folder using the Finder, but the View Organizer gives you a shortcut to do this:

The View Organizer can also create a library database for you. This menu command will automatically create an empty library database with your user name in the correct spot. In most cases this one library database is all you’ll need, you can put all of your custom statements, custom functions, and default hotkey definitions into this database.

Once the database has already been created, you can use the same menu command to access your library database again later. Even if all of this database’s windows have been closed, you can easily access it again from the View Organizer. You can also easily access any custom statements you have created with the Open View command, as was described earlier on this page.

Alternate Method – You can also automatically load any database as a custom library database using the Preferences–>Favorites panel, even if the database isn’t in the PanoramaX/Libraries folder. See Automatic Startup Database to learn more about this technique.

#### Creating a New Custom Statement

Once you’ve create a library database, you can create a new custom statement simply by using the View menu to create a new procedure in the library database. The name of the new procedure must be all upper case letters.

To avoid conflicts with future versions of Panorama it’s best to choose statement names that are unlikely to be used by Panorama itself. For example, `MYCHIME` or `BIGCHIME` might be a safer choice than `CHIME`. If a future version of Panorama includes a statement with the same name as your statement then your statement will stop working (you’ll have to rename your statement and locate and change every place where you have used it).

Once you’ve written your custom statement, go back to the View Organizer and choose Register Funtions and Libraries from the Libraries menu. This allows you to start using your new custom statement immediately, without having to quit and relaunch Panorama. In this case, the new chime statement can now be used anywhere on your code (of course you must make sure the specified sound file is placed in the specified folder).

    chime

To learn more about the View Organizer see View Organizer.

#### Writing Custom Statement Code

There’s nothing special about the code you write in a custom statement procedure – it’s just regular Panorama code, like any other subroutine (see Subroutines).

One thing you do need to keep in mind is that your custom library database is not the active database while the code is running. So you should not reference any fields in the custom library database, and you cannot use the call statement to call other subroutines within that database (you’ll either need to use farcall or set up the procedure you want to call as a custom statement and call it that way.)

#### Custom Statement Parameters

When you call a subroutine, you can supply one or more parameters to be passed to the subroutine (each parameter must be separated by a comma). Custom statements can also have parameters, which are listed after the statement name, like this:

    statement parameter1,parameter2,parameter3, ... parameterN

Within the statement procedure’s source code, you can access the parameters with the parameter( function, or change the value of a parameter with the setparameter statement. For example, you could create a custom statement called ADDMULTIPLERECORDS to add several records at once:

Once this is defined, you can easily add five records to a database (or any arbritrary number of records).

    addmultiplerecords 5

This is a somewhat silly example, because Panorama already has an addlines statement, but it does illustrate how custom statements can use parameters.

You can also use the setparameter statement to modify the value of a parameter passed to the custom statement.

##### Setting Up a Procedure Information Block

Although it’s not required, it’s a good idea to include a procedure information block at the top of each procedure that is to become a custom statement. This block contains descriptive information about the statement and its parameters. It makes the statement self-documenting and allows Panorama to perform some error checking on the parameters whenever the statement is used in a procedure. Here is a revised version of our `ADDMULTIPLERECORDS` custom statement that includes a procedure information block which illustrates the items of information you might want to include:

    /*
    <PROCEDUREINFO>
    <description>Adds multiple records.</description>
    <parameter NAME=COUNT TYPE=INTEGER>Number of records to add.</parameter>
    <body>This statement adds one or more records to the current database. For example:

        addmultiplerecords 7

    </body>
    </PROCEDUREINFO>
    */
    for i,1,parameter(1)
        addrecord
    endloop

When Panorama registers a custom statement, it checks to see if there is an information block. If there is, the number of parameters is counted and registered. If you write code with the wrong number of parameters, Panorama will complain with a compile error:

Note: The parameter information in the procedure information block also includes the data type of the parameter, in this case INTEGER. However, the data type is not checked, Panorama will not check the data type at compilation time. You can add checks in the code itself, see the datatype( function.

IMPORTANT: If you set up a procedure information block, you must notify Panorama every time you change the number of parameters in the information block. To do this, open the View Wizard and choose Register Functions and Libraries from the Libraries menu. This action must be performed every time you add or remove a parameter from a procedure information block – the custom statement

The procedure information block can contain other information. The example above includes a description and body. If you look at the statements defined in the libraries that come with Panorama, they contain quite a bit of information. Panorama uses this information to build the help pages for each statement. You can also add extra information to the procedure information block – this information won’t be added to the help documentation, but it can be useful for your own reference. Or you can simply leave it out and only include the parameters. A minimal information block like this will work fine as far as Panorama is concerned.

    /*
    <PROCEDUREINFO>
    <parameter NAME=COUNT TYPE=INTEGER></parameter>
    </PROCEDUREINFO>
    */
    for i,1,parameter(1)
        addrecord
    endloop

Or, you can leave the information block out entirely – the only downside is that Panorama won’t be able to check the number of parameters at compile time.

#### Optional Parameters

You may want a custom statement to have a parameter that is optional. If you have a procedure information block, you must add the word `OPTIONAL` to the parameter specification, like this:

    /*
    <PROCEDUREINFO>
    <description>Adds multiple records to any open database.</description>
    <parameter NAME=COUNT TYPE=INTEGER>Number of records to add. </parameter>
    <parameter NAME=DATABASE OPTIONAL TYPE=TEXT>Database to add records to (if omitted add to current database).</parameter>
    </PROCEDUREINFO>
    */
    let targetDatabase = catcherror(parameter(2))
    topdatawindow targetDatabase
    for i,1,parameter(1)
        addrecord
    endloop

Since the parameter is optional, it may be missing. In that case the parameter( function will return an error. The code above handles this by converting the error into empty text (`""`), which the topdatwindow statement interprets as the current database. You could also use if error or try/catch to handle the error, see Error Handling to learn about these methods.

Repeating Parameters - Sometimes you may not know in advance how many parameters are needed. If placed at the end of the parameter list, an optional parameter can repeat over and over again, as in this example"

    /*
    <PROCEDUREINFO>
    <description>Adds one or more words to the end of a sentence.</description>
    <parameter NAME=SENTENCE TYPE=TEXT>sentence to be modified.</parameter>
    <parameter NAME=WORD OPTIONAL TYPE=TEXT>words to be added on the end.</parameter>
    </PROCEDUREINFO>
    */
    let sentence = parameter(1)
    let endingPunctuation = sentence[-1,-1]
    let sentence = sentence[1,-2]
    for n,2,info("parametercount")
        let nthWord = parameter(n)
        sentence = sentence+" "+nthWord
    endloop
    setparameter 1,sentence+ endingPunctuation

Here’s how this custom statement could be used:

    let message = "Jack and Jill."
    appendwords message,"went","up","the","hill"

The final contents of the variable message will be:

    Jack and Jill went up the hill.

You can add as few or as many words as you like, there is no limit.

    let message = "Jack and Jill."
    appendwords message,"went","up","the","hill","quickly"

#### Raw Parameters

The parameter( function allows a procedure to determine the value of a parameter. Sometimes, however, a procedure needs to access the text of the parameter itself, rather than the value. This information is available in the `«_RawParameters»` local variable. This variable is a tab separated array with the raw text of all of the parameters passed to the subroutine. If a statement needs to access the raw text of a parameter you must add the keyword RAW to the procedure information block for that parameter, like this:

    <PARAMETER NAME=ASSIGNMENT RAW TYPE=TEXT>Assignment statement. </PARAMETER>

The `RAW` option tells Panorama not to display an error message if it can’t compute a value for the parameter (for example if a variable hasn’t been defined yet). However, each parameter must still be a syntactically correct formula. In other words, it’s OK to have a parameter of `X` even if X hasn’t been created yet, but `X+` can never be a legal parameter because that is a syntax error.

Here’s a modified version of the custom statement that adds words to the end of a sentence.

    /*
    <PROCEDUREINFO>
    <description>Adds one or more words to the end of a sentence.</description>
    <parameter NAME=SENTENCE RAW TYPE=TEXT>sentence to be modified.</parameter>
    <parameter NAME=WORD OPTIONAL TYPE=TEXT>words to be added on the end.</parameter>
    </PROCEDUREINFO>
    */
    let sentence = defaulttext(".",catcherror("",parameter(1)))
    let endingPunctuation = sentence[-1,-1]
    let sentence = sentence[1,-2]
    for n,2,info("parametercount")
        let nthWord = parameter(n)
        sentence = sentence+" "+nthWord
    endloop
    setparameter 1,sentence+ endingPunctuation

This version allows the original sentence to be left undefined, like this:

    local message
    appendwords message,"went","up","the","hill"

The final contents of the variable message will be:

    went up the hill.

Here is a different example that makes a sentence from scratch. When it’s done, it creates a local variable and fills it with the new sentence.

    /*
    <PROCEDUREINFO>
    <description>Makes a sentence from one or more words.</description>
    <parameter NAME=VARIABLE RAW TYPE=TEXT>name of variable to place sentence into.</parameter>
    <parameter NAME=WORD OPTIONAL TYPE=TEXT>words for the sentence.</parameter>
    </PROCEDUREINFO>
    */
    let resultVariableName = strip(array(«_RawParameters»,1,¬))
    let sentence = ""
    for n,2,info("parametercount")
        let nthWord = parameter(n)
        sentence = sentence+" "+nthWord
    endloop
    setcallerslocal resultVariableName,sentence+"."

Notice that we can use this statement without setting up a local variable in advance.

    makesentence mywords,"Jack","and","Jill"

The end result is that a new local variable named mywords has been created. Essentially this is a very complicated way to get the same result as:

    let mywords = "Jack and Jill"

This example isn’t very practical, but it illustrates the use of a raw parameter.

#### Custom Library Initialization

If you are interested, this section provides detailed information about how Panorama initializes each library database. During startup, Panorama uses a four step process to open each library database it finds. (These steps are also followed when you open the View Organizer and choose the the Libraries>Register Functions and Libraries command – except that the database aren’t opened since they are already open.)

Step one is opening the database, loading it into memory (but secretly, without opening any windows).

Step two is to check to see if the database contains a procedure named `.InitializeFunctions`, and if it does, call the code in that procedure. You can use this code to initialize custom functions (see registercustomfunction), and to perform any other special tasks that you want Panorama to perform when it starts up. However, this code should never display an alert or dialog. Also, this code cannot use any of the custom statements in your library, because they haven’t been registered yet.

Step three is to register any custom statements in the library database. Any procedure that has an all upper case name will become a custom statement.

The final step is to check if the database contains a procedure named `.InitializeHotKeys`. If it does, that procedure is called. You can use this code to define global hotkeys with the definehotkey statement. Your custom statements are now available, so you can define hotkeys using these statements. You can also perform any remaining initialization tasks that require your custom statements, since they are now registered. However, you should still definitely not display any alert or dialog.

If you’re not careful, your code in `.InitializeFunctions` or `.InitializeHotKeys` could contain an error. Instead of displaying an alert and aborting the launch of Panorama, a notification will be displayed and Panorama’s initialization process will continue. This means an error in your code will not prevent Panorama from launching – a good thing since otherwise you might not be able to fix the problem! If you see a notification appear when Panorama launches, check it out to make sure it’s not telling you about an error in your code. Also, you should be sure that notifications for Panorama are enabled in the System Preferences application. If they are not, you won’t even know that an error has occurred.

Note: The library initialization process is itself in an open source custom statement called SCANLIBRARY, you can use the Open View command to open this statement and view the code if you want to see exactly how it works.

See Also

- call -- allows a procedure to call a separate procedure within the current database as a subroutine.
- call( -- allows a procedure to be called as a subroutine within a formula and return a result.
- calledby( -- returns true if this function is in code called by the specified database, the specified procedure, or both.
- callingdatabase( -- returns the name of the database that called this procedure as a subroutine, if any.
- callingprocedure( -- returns the name of the procedure that called this procedure as a subroutine, if any.
- callwithin -- allows a procedure to call a mini-procedure within a separate procedure within the current database as a subroutine.
- callwithin( -- allows a "mini-procedure" to be called as a subroutine within a formula and return a result.
- callwithindatabase( -- returns true if the current procedure was called by another procedure in the same database, false if it was called by a procedure in another database.
- customstatementdatabase( -- returns the name of the library database that defines a custom statement.
- execute -- allows a procedure to call a sequence of statements within the current procedure as a "mini-subroutine".
- execute( -- allows a formula to execute a sequence of statements and return a result.
- executeasap -- executes the specified code at the first possible opportunity, non-atomically.
- executecatcherrors -- is the same as the execute statement, except for the fact that if an error occurs while running, it can be trapped by an if error statement immediately following the executecatcherrors statement.
- executelocal -- is the same as the execute statement, but it shares local variables with the procedure that called it.
- farcall -- allows a procedure to call a separate procedure within a different database as a subroutine.
- farcallwithin -- allows a procedure to call a mini-procedure within a separate procedure in a different database as a subroutine.
- getproceduretext( -- gets the contents (source code) of a procedure.
- info("customfunctions") -- returns a list of all available custom functions.
- info("customstatements") -- returns a list of all available custom statements.
- info("parameters") -- returns the number of parameters passed to a subroutine.
- info("procedurestack") -- returns the contents of Panorama's procedure call stack.
- loadcustomfunctions -- reloads all ProVUE defined custom functions.
- parameter( -- is used to transfer data between a main procedure and a subroutine.
- parameterentity( -- returns the entity (field or variable) associated with a subroutine parameter, if any.
- registercustomfunction -- defines a new custom function.
- return -- ends a subroutine.
- returnerror -- passes an error back to the current subroutines calling procedure.
- setparameter -- is used to transfer data from a subroutine back to the main procedure that called it.
- shortcall -- allows a procedure to call a sequence of statements within the current procedure as a "mini-subroutine".

History

Version | Status | Notes
10.0 | Updated | Carried over from Panorama 6.0, with some updates.