Gavin Crawford

Writing Ovation Pro Applets

Gavin Crawford presents a beginner's guide to scripting with Ovation Pro.

An approachable Ovation Pro Script Language Primer is provided for those readers who are unfamiliar with the C-style syntax used by the script language.

The example code and applet from this article is available to download as a zip file.

Ovation Pro, the desktop publishing application by David Pilling, has a built-in script language that allows you to extend the application's features and add extra functionality.

In this article I aim to describe how to use the script language to do some simple tasks, followed by an explanation of how to construct Ovation Pro applets. I will work through an example applet that can be used as a basis for your own applets, and then polish it off into what I hope will be a useful applet in its own right.

Ovation Pro's script language is based on the C programming language, so for many people it will be quick and easy to learn, but for those people who are unfamiliar with C, the companion Script Language Primer article will explain the basics in order to get you started.


A working environment

Before we start any coding, let me describe my working environment and the tools I use when I'm writing applets.

First of all you will need a text editor. My personal preference is StrongED, but others may favour Zap. There is also Edit, the text editor built into RISC OS. You could even use Ovation Pro itself as an editor, but because you will need to run and quit Ovation Pro regularly while you are developing your applets, a separate text editor is better.

Next on the list is StrongHelp. This, together with the StrongHelp version of the Ovation Pro Script Language Reference Guide, will save you a lot of time when you need to look up Ovation Pro's functions. It is also beneficial to use a StrongHelp-aware text editor such as StrongED or Zap, as you can look up the word at the caret, and you will know immediately if the function is listed in the Script Language Reference Guide. (Press <F1> in StrongED or <Ctrl-H> in Zap.)

A template editor will be required if your applet is going to have its own windows. TemplEd and WinEd are popular alternatives.

A final very helpful piece of freeware is Reporter. This provides a convenient way of adding debugging information to your applet while you are developing it: by using a simple *-command you can send text that will appear in Reporter's window.

Ovation Pro is compatible with throwback error handling. If Ovation Pro finds an error in your applet code when it is first loaded, it will display the error in a normal RISC OS-style error box. If, however, throwback is enabled, the error message will be displayed in a throwback window showing the line number on which the error occurred. A click on the throwback window will open the file that contained the error, and the caret will be placed in the offending line. To enable the use of throwback you need the DDEUtils module and a throwback-compliant editor (both StrongED and Zap comply). The DDEUtils module that provides throwback was originally part of Acorn's C/C++ application development environment package, but is now included in ROM from RISC OS 4 onwards.

My final suggestion is to create a new directory for your applet development work, and in this directory make a separate copy of Ovation Pro. Use this copy to try out your applets while you are developing them; this will keep your normal working copy safe and avoid your making changes to your good version that will prevent you from continuing with your normal work. While you are creating your applet you will regularly be loading and quitting Ovation Pro each time you make changes to your applet code, so removing any applets that are not needed from your development version of Ovation Pro will make it load much more quickly.


Writing simple script files

Before we start looking at writing an applet we'll begin with the very basics of using the script language itself and see how we can make Ovation Pro do some simple things.

You don't have to write a complete applet in order to utilise Ovation Pro's script language; functions can be used in macro definitions, and a simple script file can be used to perform a variety of actions.

Example script 1

Our first example is a script file. Script files are created in a text editor and saved with a filetype setting of &b24 (OvPScrpt). Double-clicking the file will cause it to be processed by Ovation Pro if Ovation Pro is already loaded. If it isn't, then Ovation Pro will be loaded first, as long as it has been seen by the filer. A script may also be executed by dragging it to Ovation Pro's icon on the icon bar.

    // Ovation Pro Script Language Tutorial
    // Foundation RISC User
    // File 1-TypeText
    void main(void)
      type("Foundation RISC User");

Looking at the code from this file, we can see that the first three lines are comments and will be ignored by Ovation Pro. The next part of the example forms our main() function. As explained in the Primer, every script file must have a function called main() if you want the script to actually perform any useful tasks. In this example, the function main() contains just one line of code: type("Foundation RISC User");. When Ovation Pro executes this line, it will enter the words Foundation RISC User at the current caret position in an open Ovation Pro document, as though you had typed the text at the keyboard. If there is no Ovation Pro document open with a caret in it then you will not see anything happen, so be sure to open a document and click to place the caret in a text frame before you run the example file. Figure 1 shows the result you should expect.

Figure 1
Figure 1: The result of executing example script 1

So, what is actually happening in our function? The words Foundation RISC User are passed to the 'command' called type(), which does the job of entering the text. type() is in fact a function which is built into Ovation Pro's script language. It needs to be passed a string of characters which will be entered into the document, and we do this by wrapping up our text in double quotation marks.

After the type() function has been called, and the text is inserted into the current document, Ovation Pro continues to work through our script file from where it left off, but as there is nothing else in our function, Ovation Pro reaches the end of the script file and finishes processing it. As this example script is a separate, external file which was run from the magazine disc, all its functions and variables are discarded, after execution.


Example Script 2

Let's now try something a little more complex. The next script introduces many new concepts: we will see the use of variables, if statements, while loops, and another function in our file that requires a parameter to be passed to it and which then returns a value.

This script is an example of how we can insert some text in a different font into a document. As a practical demonstration it will insert a phone number with a telephone symbol preceding the numbers. The symbol will be picked from the Selwyn font, if it is available. If Selwyn is not installed, the script will instead type the word Tel:. The text will be inserted at Ovation Pro's current caret position, so to test this script you should again make sure that you have a document open with the red caret visible in a text frame when you run the script file. Figure 2 shows what you can expect to see.

Figure 2
Figure 2: The result of executing example script 2

Those of you who are proficient in programming in C will, no doubt, spot the areas where this script could be rewritten to make it more compact and efficient, but I wanted to keep the programming simple and logical for those who are just beginning with C-style commands.

     1.  // Ovation Pro Script Language Tutorial
     2.  // Foundation RISC User
     3.  // File 02-TypeFnt
     5.  // Insert a telephone number.
     6.  // Uses a telephone symbol from the font Selwyn,
     7.  // if the font is installed.
     9.  int typefnt_getfonthandle(string fontname)
    10.  /* return the font handle for the supplied fontname */
    11.  {
    12.    int handle=0;
    13.    string name;
    14.    while(getfontname(handle,name)){
    16.      if(name==fontname){
    17.        /* return from function with the font handle */
    18.        return handle;
    19.      }
    21.      handle++;
    22.    }
    23.    return -1; /* return -1 if no match */
    24.  }
    27.  void main(void)
    28.  {
    29.    int existingfont;
    30.    int newfont;
    31.    caretcontext();
    32.    existingfont=getfont();
    33.    newfont=typefnt_getfonthandle("Selwyn");
    35.    if(newfont != -1){
    36.      /* is not -1 so must have found the font */
    37.      setfont(newfont);
    38.      type("a");
    39.      setfont(existingfont);
    40.      type(" 01234 567890");
    42.    }else{
    43.      /* we got -1 so font didn't match */
    44.      type("Tel: 01234 567890");
    45.    }
    46.  }

To make it easier to describe the code, I have numbered each line. If you are copying this directly from this page then the numbers should not be included.

Working through the code from the top, we can see that the first eight lines don't do anything other than provide a few comments. Then, at lines 9 to 24, we have a definition of a function called typefnt_getfonthandle which we'll come back to in a moment. Next, at line 27, we have the start of our function main() which is where Ovation Pro will start to execute our script's code. First we declare two integer variables on lines 29 and 30. At this stage they have not been assigned a value, although Ovation Pro will have set them to zero. As these integer variables are declared inside the function's definition, they become local to the function, and will be discarded when the execution reaches the end of the function's code. To create local variables inside a function, they must be declared, so they are the first things to be executed directly after the opening brace and before any other statements. (However, you can have any number of comments before them if you wish.)

Line 31 calls caretcontext(), which instructs Ovation Pro to set the internal cached values of style parameters to those at the caret. This helps Ovation Pro to read the font values from the correct open document.

Line 32 calls getfont(), which is a function built into Ovation Pro that returns the handle of the font at the caret, or throughout the selection, as an integer. The value it returns will be stored in our existingfont integer that we declared earlier.

At line 33 we call a function of our own that was defined earlier in our script: between lines 9 and 24. Line 9 defines the start of the function with int typefnt_getfonthandle(string fontname). Take note of the text between the parentheses: this means that the function requires a value to be passed to it when we call it. In this case, the function requires a string value which it will store in a variable called fontname. The function will also return a value when it finishes, as can be seen by the int type at the start of the line signifying that this function will return an integer value. When we call this function on line 33, we pass the font name of "Selwyn" as this is the font for which we want to find the font handle.

So let's now have a look at what this function does. Lines 12 and 13 declare a couple of local variables. The first is an integer called handle, which is assigned the value of 0, and the second is a string called name. Next, we have a while loop which will evaluate the part in parentheses. If it evaluates to non-zero then the lines of code between the braces will be executed, after which it repeats the whole process until the part in parentheses evaluates to zero. In our while loop's expression, we have, in parentheses, getfontname(handle,name), which calls the Ovation Pro function getfontname. This returns in name the name of the font for the handle number passed in handle. This function returns a non-zero value if a font with that handle exists, or zero if no font exists with that handle, which is very handy as it gives us a simple way of checking through all the font handles until we find the one we want to match.

So, as we start the loop, we have the handle number set to 0. After the call to getfontname, handle will refer to font 0. If there is actually a font with this handle then the part inside the braces is executed, and this checks to see whether the name of font 0 matches the name we are searching for. This is done with the if command, and, should we get a match, the current font handle is returned to the code that called our function (line 33). If it didn't match then we increment handle by 1 with handle++ and the loop will repeat through the process again. Should getfonthandle return zero to show that the font handle doesn't exist, then the section in the braces won't be executed and we will drop through to line 23, where we will return -1 to signify that we didn't match our required font. The return keyword allows the function to return a value, but the type of the value returned must match the type that was specified at the start of the function definition. So, for example, if the function was an int type, we couldn't pass back a string. Of course, many functions don't pass back any values at all, and these are typed as void, but we can still use the return keyword to jump out of the function at any time and back to the point at which it was called. In this case, the return keyword is similar to ENDPROC in Basic.

After calling our function typefnt_getfonthandle(), it should run though its code and return an integer, either a valid font handle or -1 if it didn't find the font we asked for. Back in our main() function we check that the returned value is not -1 by using if(newfont != -1). If it is not -1 then, at line 37, the current font is changed to Selwyn and the character 'a' is typed into the document, which produces a telephone symbol in this particular typeface. At line 39 we switch back to the original font and type our telephone number. If the Selwyn font wasn't found then -1 would have been returned, in which case we just type the telephone number preceded by the word "Tel: ". For this example script to be of any use, you will of course need to change the phone number to your own.

A final point worth noting is that, during the testing of this script, it was discovered that in some circumstances the Selwyn font was not being applied correctly in the typed text. This was despite the fact that the script detected that the Selwyn font was available and inserted the correct character. This happened when a new document was opened but had not received a mouse-click or keypress somewhere in the document. Ovation Pro detects these events and sets its internal references to use that document, and that particular view of the document, for any scripts that are being run. Without these references being set, our script doesn't read the font settings correctly. The use of caretcontext(); helps to alleviate the problem, so hopefully you won't see the problem manifest itself here. Nevertheless, it is a good idea in general to have clicked or typed in a document before double-clicking on a script file to launch it from the filer, or, in this specific instance, before clicking the 'Run' icon above to launch example script 2.


Writing an Ovation Pro applet

So far we've seen how a script file can be used to perform some simple tasks, but let's now expand on this further and look at how to construct an applet.


The structure of an applet

An applet is created as a directory within Ovation Pro's Applets directory. Your applet's directory should be named so that it starts with a ! character like normal RISC OS applications. You should also be aware that your applet's directory name must also allow for a - character to be added at the end, so in reality it's better to keep your applet's directory name to eight characters or fewer, so that you don't have more than ten characters in total when the ! and - are added. This will avoid potential problems should Ovation Pro be running on a filing system that doesn't support long filenames.

Within your newly-created applet directory you will need a number of files. Figure 3 shows a common selection for a typical applet.

Figure 3
Figure 3: An example of the contents of an an existing applet

First is an Obey file named !ARun. It's common for this file to be empty in many applets, but it has an important function when required: this file is obeyed by Ovation Pro when it is first loaded, as it works its way through each of the applets in turn. This makes this file an ideal place in which to add any special setup requirements that may be needed by the applet, such as checking for particular modules or setting system variables, in a similar way as a !Run file is used in a standard application.

!Help is a text file that should explain what your applet does.

!Info is a text file that follows a particular format. Your applet's current version number is stored in this file, along with a brief description of what your applet does, and of course your name as the applet's author. This file should also contain the details of the minimum version of Ovation Pro that this applet will work with. But note that this number isn't actually checked in any way by Ovation Pro; it's just there for reference.

!Run is a special utility file that does the job of enabling or disabling the applet when the applet is double-clicked. When the applet is disabled, its name will end with a - and, commonly, a cross will appear over its icon (if it has one). For your own applet, just copy the !Run utility into the directory from another existing applet.

AResources is a directory that Ovation Pro will look in to find the resources used by your applet, such as a sprite file called Buttons that holds the button bar buttons, a Help text file containing the interactive help messages for the any defined windows, a Messages file, a Sprites file, and the Templates file for the window definitions for the applet.

Library is a directory that holds the applet's script file. Any script files inside this directory will be executed by Ovation Pro during its loading sequence.

A !Sprites file is optional, and allows you to give your applet a nice fancy icon when it is visible in the Applets directory, in the same way as any other RISC OS application. I personally like to include an icon to help distinguish the applet from all the others in the directory, and I also include an icon that clearly shows when the applet has been disabled. Whenever I add a !Sprites file, I also add a !Boot Obey file that includes the line IconSprites <Obey$Dir>.!Sprites to make the applet directory display the sprites when the filer display is opened.


Creating an applet

So, let's get down to actually creating an applet. If you are following my earlier suggestion, you will be building this applet inside a second copy of Ovation Pro that you have copied to another directory on your hard disc, to avoid any problems that may occur as a result of messing with your current working copy. <Shift>-double-click to open the !OvnPro application directory, then open the Applets directory situated within. I'm going to call this example applet GrabText so create a new directory called !GrabText. Into this directory we need to copy some files from an existing applet; in this instance I'm copying them from the ColSupp applet. Copy the files !ARun, !Help, !Info and !Run. The !ARun file is an empty Obey file, so we don't need to do anything with that, but we should edit the !Help file to describe what the applet does, and change the details in !Info.

The next task is to create a directory called Library to hold our script file. To avoid the rather long and boring process of creating a full script file for this example applet, I've supplied one ready to use, with the hope that it includes a number of useful functions that can be used for your own applet ideas, either by adding to this applet or by adapting it for your own purposes.

Within the supplied !GrabText example applet is a script file named GrabText, housed in the Library directory. I'm not going to explain all the code in this script, as this would take up too much space, but there are plenty of comments throughout the code to explain what it does in detail. I will, however, explain what the supplied functions do and how you can use them.

The basic idea of this applet is to select the word under the caret and then allow that word to be dealt with by the applet. This could include such things as replacing the word by another one, or passing the word on to another external application for further processing.


The supplied functions

The functions supplied in GrabText are broken down in a number of operations to make it easier for you to use just the ones you need for your particular requirements. The first of these is grabtext_selectword(), which will select the text under the caret and return a string to the part of the script that called it. This function uses three bookmarks to read the word one character at a time. When it has finished reading the word it leaves bookmark number one at the start of the word and bookmark number two at the end so that we can find the word position later if we require it. The third bookmark is used as a temporary counter; it is no longer needed at the end of the function, so it is deleted.

Bookmarks are invisible markers that may be positioned and moved around within text stories. Typically they are used by scripts which need to scan through stories, locating and replacing text strings. Bookmarks are retained in the document when saved, so they should be deleted when we have finished with them, and for this purpose we have the function grabtext_deletebookmarks(). Before we delete the bookmarks, however, we may want to replace the text with some new text, and grabtext_replaceword() will allow us to do that by passing a string to the function, where it will be typed into the document to replace the original selected word. Once we have replaced the original word with our new one, we may want to have the new word selected, to highlight to the user what has changed. This can be achieved with grabtext_selectbookmarked_text(), which will select the text between the two bookmarks (which will have been moved to either end of the newly replaced text).

Let's now look at how we use these functions within the script. We start by adding an entry to the Applets menu so that we can activate the process when we are working in a document. To do that we use the following code in the function main():

    addentry_menu(script_handle,"grabtext_example","","","","Grab Text");

The first line is there to initialise and create the Applets menu if no other applet has done so before ours. The second line is the one that actually adds our entry onto the Applets menu. If you look up the definition of the addentry_menu() function, you will see that it requires six parameters. The first is the handle of the menu to which we wish to add the entry: in this case it's the Applets menu, whose handle is stored in the variable script_handle. Next is the name of the function that is executed when the entry is chosen: here we are going to call grabtext_example(), which is described below. The following parameters are not needed in this instance, except for the last one which is the text that will appear as the menu's entry. For this example, "Grab"Text" will be displayed.

We now have a menu item in the Applets menu that will call a function when chosen, so let's look at the code that will make up that function:

     1.  int grabtext_example(int n, int sub)
     2.  {
     3.     string word;
     4.     word=grabtext_selectword();
     5.     messagebox("'"+word+"'");
     6.     grabtext_replaceword("New Replaced Text");
     7.     grabtext_selectbookmarked_text();
     8.     grabtext_deletebookmarks();
     9.     return sub;
    10.   }

The first thing to note about this function definition is that the function requires two integer parameters when called. In reality, our function won't use these parameters, and they are only there because the mechanism used by addentry_menu() to call our function, when the menu item is chosen, needs to pass two parameters. Also note that our function is expected to return the integer sub that has been passed to it, so the function definition reflects this on line 1, showing that the function returns an int type. Line 9 does the job of actually returning the variable sub.

Now we can take at look at the code in the function that does the things we require it to do.

Line 3 defines a string variable called word which will hold the text that is returned from the call to function grabtext_selectword() on line 4. To show that we have the correct text stored in word, we will display it on-screen in a message box (on line 5). Line 6 makes a call to our function grabtext_replaceword() and passes to it a string of characters that will be inserted into the document to replace the original word. To show the user that the word has been changed, we can select the new text that has been inserted by calling grabtext_selectbookmarked_text(). Finally, on line 8 we tidy up after ourselves by calling grabtext_deletebookmarks(), which removes the bookmarks from the document so that they won't be saved in the file.

So, looking at our grabtext_example() function, we can see that it's not really all that useful in itself; who in their right mind would want to go through a document replacing words with the text "New Replaced Text"? But it does show how easy it is to use these four handy functions to select a word and, if we wish, to replace it with another.

Using these functions, we can select a word and pass it on to another application if we so wish, and to serve as an example, the GrabText applet has another function to demonstrate this. This time it selects the word and attempts to perform a StrongHelp lookup on the word. I won't describe the function in this article, but it is fully commented in the code and describes what is going on.

To allow this function to be used, we'll have to add another menu item to the Applets menu. We do this using the following:

    addentry_menu(script_handle,"grabtext_stronghelplookup","","","S_F1","StrongHelp lookup");

Notice that, this time, we've included S_F1 as parameter five. This adds a keyboard shortcut to the menu item. Key names are the same as those used for macro definitions, so in this example <Shift-F1> will call our function. Figure 4 shows what we have just added to the Applets menu.

Figure 4
Figure 4: The GrabText applet's additions to the Applets menu


Over to you

It's now up to you to think of other ideas and put this applet to good use. I've used the basis of this applet to create another, called URLlink, which selects the text under the caret and broadcasts it as an URL/URI, allowing such things as web site and email addresses that are present in an Ovation Pro document to be launched in a Web browser or email client. The selection code in URLlink is slightly changed so that it can select the appropriate text that makes up an URL. As for other ideas, how about an applet that looks up the word in an on-line dictionary, thesaurus or translation site? The mechanism within URLlink for broadcasting the URLs, which is a simple little utility called URLcast, could be used to find the word on the required Internet site.

Hopefully this article will have provided some insight into the creation of Ovation Pro scripts and applets, and will inspire you to have a go at creating your own. There are numerous applets available, many written by the author of Ovation Pro, David Pilling, and plenty of third party applets, which serve as good examples of the further techniques that are available to applet writers, such as creating windows and icons etc. Many of my own applets, which are supplied here on the Foundation RISC User disc, include plenty of remarks to assist others to understand the code and techniques being used.

This article was originally published in Foundation RISC User magazine, and is reproduced here with the permission of Richard Hallas.
All text and code Copyright © Gavin Crawford, 2006