Extend OpenDoc with SOM
Volume Number: 13
Issue Number: 4
Column Tag: Programming Techniques
Using SOM to Extend Your OpenDoc Parts
by Gerry Kenner, University of Utah, David Kenner, Phone Directories, Deborah
Grits, Apple Computer
An introduction to SOM and OpenDoc extensions with an
overview of Apple's ScriptRunner sample code
With the release of OpenDoc, version 1 (DR4) in January 1996, Apple provided the
developer community with a powerful tool for creating interchangeable software
modules. The good news is that component parts for use inside of applications such as
Microsoft Excel and ClarisWorks can be produced quickly using the tools provided.
Further, the OpenDoc Frameworks Library (ODF) is definitely a step in the right
direction - providing tools to take care of routine graphic interfacing.
Unfortunately, OpenDoc parts are optimized for responding to menu and user events
and cannot easily communicate with each other directly. The problem is summarized
on p.69 of the OpenDoc Programmer's Guide. "The basic architecture of OpenDoc is
primarily geared toward mediating the geometric interrelationships among parts,
their sharing of user events, and their sharing of storage. Direct communications
among parts for other purposes than frame negotiation is mostly beyond the basic
intention of OpenDoc. If separate parts in a document (or across documents) need to
share information, or if one part needs to manipulate the contents or behavior of
another part, you need to extend the capabilities of OpenDoc in some way.
Translated, this says that you cannot conveniently pass parameters to and from a part
without adding extension subclasses. To do this, you must write and compile SOM idl
files. Fortunately writing, modifying and compiling idl files is not difficult as we will
show in this article.
In this article we approach the subject of OpenDoc extensions in the following way.
1. Explain what extensions are.
2. Provide a glossary of some of the common terms we will be using in this article.
3. Show how extensions work by providing a detailed description of Apple's
ScriptRunner demo. This will also show how to use lists, shared resources and shell
plug-ins.
4. Outline and build a simple example which will show how to write the necessary
code for implementing a minimal extension.
5. Provide instructions for compiling the files by executing the SOM compiler from
within Apple's MPW Shell program or the ToolServer.
What are these magic things called extensions? First, we consulted Chapter 10 of the
OpenDoc Programmer's Guide and determined that OpenDoc uses the extension
mechanism for the following:
• Expanding the object interface.
• Creating custom event types.
• Creating custom focus types.
• Supporting scripting.
• Customizing information returned by the Part Info dialog box.
Also, extensions can be used for graphical transformations, creating shell plug-ins
and writing OpenDoc patches.
These things are possible because all subclasses of class ODObject can be extended
using subclasses of the ODExtension class.
Extensions operate by providing an interface to a part which can be accessed by other
parts. The extension has methods which when called will relay the call to the
corresponding methods of the parent classes.
Figure 1 shows how this works for the ScriptRunner example.
Figure 1. The HandleMenuEvent method of the TextEditor object can access the Show
method of the ScriptRunner object thru the PaletteExt object, which is a subclass of
ODExtension.
Some OpenDoc Terminology
Here are some terms used in the material that follows:
Name space
Object (or list) which maps data types to values. If published, name spaces can be used
globally.
Plug-in module
Shared libraries which must be installed when a document which has parts that use
them is first opened. They modify or extend the functions of the document shell. They
are not ODExtension subclasses.
Document shell or document shell program
Managing the environment of the parts in a document.
Extension
Mechanism for expanding the functionality of the OpenDoc classes.
Agent
Code for performing tasks such as instantiating an object.
ScriptRunner Project
Apple included a project in the version 1.0 release of OpenDoc (Developer Release 4)
named ScriptRunner. The function of ScriptRunner was to demonstrate how to program
and use extensions and plug-ins. ScriptRunner was designed to enhance the capabilities
of another Apple Demo part, TextEdit, by adding a palette with scripting functions.
ScriptRunner has several components, an OSA plug-in, a data transfer extension, a
scripting palette extension and the ScriptRunner part. The interrelationship between
the components and the TextEdit part is shown in Figure 2.
Figure 2. Relationships of the various components of the ScriptRunner system.
Modified from illustration in Apple's ScriptRunner Read Me document entitled
"ScriptRunner Mechanics.
ScriptRunner operates as follows. The TextEdit part is opened and the user either
types or pastes an Apple script into the active text window. The menu item "Show
ScriptRunner" of the Tools menu is then selected (Figure 3). The ScriptRunner part is
instantiated and the ScriptRunner palette appears in the document window. Selection of
the Run item of the palette results in execution of the script contained in the text
window. At the end of execution, a dialog box appears with the results returned by the
AppleScript. It will read "No results" if nothing is returned. As of DR4, the record
function had not been implemented.
Figure 3. Window of TextEditor at the completion of an execution cycle of the Run
command of ScriptRunner.
Now that the reader understands how ScriptRunner is called and what it does, we will
show the details of the operation. For clarity, we will present this in several
segments.
OSA Plug-in
Plug-ins are shared libraries which are called by the document shell when a document
is created. When a document is opened, the document shell is launched by OpenDoc. The
document shell sets up the document after which it calls the install methods of
associated plug-ins. Once this is done, the shell finishes setting up the document by
opening its window.
Each shell plug-in has a single exported entry point, its install function. The OSA
plug-in's install function is named ODShellPlugInInstall. This function creates a name
space for storing an object reference to an object of the class ScriptRunnerAgent. It
then instantiates the object and puts its address in the name space (Figure 4).
Figure 4. Creation of name space and ScriptRunnerAgent objects, followed by storage
of an object reference to the ScriptRunnerAgent object in the name space object.
Accessing ScriptRunnerAgent by TextEdit
ScriptRunner is accessed by selecting the "Run ScriptRunner" item from the Tools
menu. The first time this occurs, the program checks to see if ScriptRunner is
available and then installs it (Figure 5). This is done by retrieving the reference to
the ScriptRunnerAgent from the name space and using its methods to instantiate the
ScriptRunner part.
Figure 5. Sequence of events following selection of "Open ScriptRunner" item of tools
menu.
Accessing the palette extension
For a client part to use another part's extension, it calls the method AcquireExtension
to get a reference to it. AcquireExtension will create a new extension object if one does
not already exist
Using the palette extension
At this point, the palette menu is showing. Selection of the Run item results in the
sequence of events shown in Figure 6.
Figure 6. Program flow when the Run item of the Palette is selected.
Extension Project
Planning Stage
We are going to design a simple project to demonstrate how to write an extension
which has minimal functionality.
Business Sublevel
Our starting point is the Image project that was described in the February, 1996
issue of MacTech Magazine. This project involved the creation of objects of two parts
named ImagePart and SelectPart. Following instantiation, the ImagePart object
displayed a read only text screen and had a menu option for selecting the name of a text
file. Selection of the menu option resulted in the instantiation of the SelectPart object
which automatically displayed an SFGetFile dialog box that asked for a file name. The
SelectPart object then placed the name in global storage from which it was retrieved
by the ImagePart object. The name was then displayed in the read-only window.
Solution Sublevel
ImagePart will be simplified by having it call the MyOpenPict method from its
InitializeFromStorage method thus bypassing the menu operations. This is bad practice
but it does enable getting a program running quickly. The MyOpenPict method will
instantiate a SelectPart object and call one its methods which was added via the
extension mechanism.
The ODExtension subclass GetNameExt will add a method named GetName to SelectPart.
The extension will be accessed by ImagePart which will then call the method. The
method GetName will display a dialog box asking for the name of a file. This is to
provide visual evidence that calling the method was successful.
To simplify our demo, we are not going to pass or return any parameters. Adding this
functionality is moderately complicated and will make a good subject for a future
article.
Prototyping Level
Figures 7 and 8 give the general layout for running the modified image program. After
instantiating SelectPart, ImagePart will interrogate it to determine if it has the
desired extension for getting a file name. If it is available, a call will be made to obtain
the file name. The SelectPart object will then be disposed of and the ImagePart object
will be displayed (Figure 7).
Figure 7. Outline of activities of the ImagePart part.
An overall outline of the operations of the SelectPart object is shown in Figure 8.
Figure 8. Outline of operations of SelectPart.
Flow Diagramming Level
Figure 9 shows the flow of the MyOpenPict method. The SelectPart part is instantiated
followed by the acquisition of a pointer to the GetNameExt extension (AcquireExtension
method). A call is then made to the GetName method of the extension. Figure 10 shows
the details of how the GetName method of the SelectPart part is called from the
GetName method of the extension.
Figure 9. Program flow of ImagePart/SelectPart interaction.
Figure 10. Execution of the GetName method.
SOM Interface
To get the advantages of a cross-platform development system, it is necessary to
isolate the program code from the interface code. This is done in OpenDoc by writing
SOM interface code using the IDL (Interface Definition Language) programming
language. The interface code bridges the gap between SOM and high level programming
languages such as C++ which are used to write the program proper.
SOM has several components, the most important of which are the SOM kernel, the
SOM class libraries and the SOM compiler. The kernel implements the basic runtime
behavior while the class libraries augment the runtime environment. The compiler
translates the IDL code into a target language such as C++.
The first step towards creating a part is to write a definition file using IDL. The file
will be identified with an .IDL suffix. The format of a typical class definition taken
from Apple's SamplePart project is as follows:
module SampleCode
interface som_SamplePart : ODPart
majorversion = 1; minorversion = 0;
functionprefix = som_SamplePart__;
override:
somInit,
somUninit,
AcquireExtension,
HasExtension,