OpenDoc Framework
Volume Number: 11
Issue Number: 11
Column Tag: APPLE TECHNOLOGY
The OpenDoc Development Framework 
A modern framework for OpenDoc development.
By Jim Lloyd, jim@melongem.com
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
Developing for OpenDoc
The OpenDoc Development Framework (ODF) is a modern object-oriented framework
for developing OpenDoc components for Macintosh and 32-bit Windows platforms. It
significantly reduces the work required to create an OpenDoc component editor,
especially if you’re developing the part editor for both Macintosh and Windows.
In this article, I’ll provide you with an overview of ODF. Because ODF is too
large to address in one short article, I’m forced to condense and highlight. My goal is to
provide you with an understanding of the problems ODF solves for you and how it
solves them, and to give you a taste of what it is like to develop an OpenDoc component
using ODF. I assume some level of understanding of OpenDoc, but not much, so if this is
your first exposure to OpenDoc, I suggest you dive in anyway.
Why Another Framework?
Macintosh developers today have their choice of several frameworks. Some of the
better known frameworks include Apple’s MacApp, Symantec’s TCL, Metrowerks’
PowerPlant, MacTech’s Sprocket, and Paul Dubois’ TransSkel. If you’ve used any of
these frameworks, then you probably appreciate the time and aggravation they can
save you by providing a solid foundation on which to build your application. These
frameworks not only provide you with code common to all Macintosh applications, they
provide you with an architectural framework that makes it easier to design your own
application. If all goes well, you simply “fill in the colors and connect the dots,” so to
speak.
The degree that “all goes well” depends on the framework you choose and your
application’s problem domain. The truth is, any framework is designed to work well
with a specific range of application domains. If your application doesn’t fit within that
range, then the framework will at best get in your way from time to time, and at worst
be totally useless. You are not likely to have much success trying to write a 2K INIT
using TCL or MacApp. I don’t consider this to be a shortcoming of those frameworks;
common sense dictates that one shouldn’t assume a given tool is the right tool for all
jobs, big and small.
Of course, a framework shouldn’t be a single tool; it should be a collection of tools
that work well together. If the framework is done well, some of these tools will be
applicable to any problem domain. Modern frameworks are designed with this in mind.
PowerPlant, for example, is well suited for both simple “dashboard” applications and
large-scale applications that use multiple document types, and pieces of the
framework can be used in programs that don’t fit these domains.
Why ODF?
Why ODF? Because even a flexible framework like PowerPlant isn’t appropriate for
all applications. First, developers need to deliver their applications on multiple
platforms, including Microsoft Windows. Second, OpenDoc introduces a new set of
requirements that may be very difficult to fulfill unless the requirements are
considered in the initial design of the framework. ODF addresses both of these issues.
ODF is a modern framework designed to be flexible and robust, to be cross-platform,
and to provide complete support for OpenDoc.
ODF is a cross-platform framework because its APIs abstract away the
particulars of each operating systems’ specific data structures and APIs. ODF defines
platform-independent APIs, and implements these APIs on multiple operating systems.
Developers who program to the framework’s APIs exclusively should be able to move
their applications to every operating system supported by the framework by simply
recompiling their code. ODF currently supports the Macintosh OS for both 68K and
PowerPC, and supports Microsoft Windows for its 32-bit operating systems, Windows
95 and Windows NT.
ODF includes a rich foundation of subsystems organized into two layers: the
Foundation layer and the OS layer. Subsystems in the Foundation layer have no
dependencies on the subsystems in the OS layer, and are generally useful for a very
wide range of application domains. Subsystems in the OS layer provide operating
system services such as files, resources, and graphics using platform-independent
interfaces.
On top of these two layers we add a third layer, known as the Framework layer.
The ODF Framework layer is specific to building OpenDoc component editors, and is
designed to solve the problems of that particular domain as well as possible. ODF
provides full coverage of the OpenDoc APIs, and correctly implements the human
interface standards. If you use ODF to develop your OpenDoc part, you’ll need to do less
work to make your part functional. You’ll also save yourself much of the additional
effort required to make your part interoperate correctly with other parts, as well as
the effort required to provide the correct human interface behavior.
An extra benefit to using ODF is that it provides some of the best sample code for
OpenDoc parts that you can find. The ODF examples range from the minimalist
ODFHello part, which is our interpretation of “Hello World”, implemented as a part,
to ODFDraw, a cross-platform drawing part with floating palettes for drawing tools,
pattern selection, and color selection. Of greater significance, ODFDraw is a robust
container part for embedding other parts. ODFDraw has been the container of choice
for OpenDoc demonstrations for much of the last year.
Also included among the ODF examples are a bitmap part, a clock part, a movie
part, a table part, and finally the “beeper” part, which will play a big role in this
article.
Extending The Beeper’s Functionality
ODFBeeper is a simple button part that can play a sound resource. ODFBeeper, as
delivered on previous releases of ODF, supports drag-and-drop so that you can change
the sound that is played by dropping a sound file onto the button. In this article, we’ll
extend the Beeper part to also execute any compiled AppleScript dropped onto it.
Thanks to Eric Jackson for coming up with this idea and drafting me to work on it with
him at a recent ODF Coding Retreat. Eric and I did a quick hack so that the Beeper
would execute scripts instead of playing a sound. It occurred to me later that with only
a little more effort, the Beeper could do either sounds or scripts. The code presented
in this article does both, and uses an architecture that would make it easy to add other
kinds of actions besides sounds and scripts.
In order to see where we are going, let’s first look at a screen snapshot of the
final product:
ODFBeeper User Interface (such as there is)
This screen shot shows an ODFDraw document being used as a generic container.
It contains two ODFBeeper parts embedded inside it. Conveniently located nearby are
some sound files and some compiled AppleScript files. The user can drag any of these
files onto either of the buttons. Clicking on the button will perform the action
corresponding to the last file dropped onto the button. Changes to the whole document
are persistent. The user can use the capabilities of the container application to
rearrange the buttons, and can change the action of a button by dropping a new script
or sound file onto the button. Saving the document saves all of the state, so the buttons
will be in the same locations and perform the same actions the next time the document
is opened.
Developing A Part Editor
Developing a part with ODF is much like developing an application with an application
framework like PowerPlant, TCL, or MacApp. You can use your favorite C++
programming tools, including Apple’s, Metrowerks’ and Symantec’s development
environments, and other tools such as Object Master from ACIUS and The Debugger
from Steve Jasik. The ODF view system is still evolving, so currently there are no
visual tools for laying out your part’s views, but visual tools for ODF are under
development.
For the purposes of this article, I started with the source for the ODFBeeper
example part included with the 1.0d9 release of ODF, which was provided on the
OpenDoc DR3 CD. If you have a source code disk subscription, the project and source
for this extended example will be on your source code disk, but you’ll still need the
OpenDoc DR3 CD if you want to further enhance the example. If you don’t have a source
code subscription, keep an eye out for the OpenDoc DR4 release; ODF 1.0d11 will be on
that CD and will include the extended ODFBeeper example.
By the way, there’s no way that MacTech could publish all the source code
necessary for this sample (and you wouldn’t want to try to type it all in!), so I won’t
be showing complete source code listings in this article. Instead, I’ll show the relevant
snippets of code as they are discussed. In these snippets, I’ll sometimes omit code not
directly relevant to the discussion by replacing several lines of code with a single line
containing an ellipsis and optionally a comment, like this:
... // irrelevant stuff omitted
ODF parts can be developed for either PowerPC or 68K using several different
environments. For this article I used the PowerPC compiler in the CW6 release of
Metrowerks CodeWarrior Gold and haven’t yet tested the part with other
environments, but you can expect the part to be buildable with all supported
development environments on the DR4 CD. The following is a snapshot of the
ODFBeeper CodeWarrior project:
ODFBeeper Project
The ODFBeeper example uses four source files. BeeperPart.cpp implements the classes CBeeperPart and CBeeperFrame, which are subclasses of the ODF classes
FW_CPart and FW_CFrame respectively. BeeperSel.cpp implements the
CBeeperSelection class, which is a subclass of the ODF class FW_CSelection.
Actions.cpp implements the classes CAction, CSoundAction, and CScriptAction,
all of which are completely specific to ODFBeeper; ODF itself knows nothing about
actions. SOMBeeper.cpp contains the C++ bindings for the required SOM subclass of
ODPart, which won’t be discussed in this article. In addition, ODFBeeper uses the
resource file BeeperPart.rsrc that contains a typical assortment of icons, strings,
etc. It won’t be discussed further in this article either.
I’ll now proceed to describe the source code in the three source files
Actions.cpp, BeeperPart.cpp, and BeeperSel.cpp, in that order. However, before
discussing the Action classes, I think it would be worthwhile to partially discuss the
CBeeperPart class. If you are familiar with the Model-View-Controller architecture
from Smalltalk that has been adapted in various incarnations in most frameworks,
then you can consider the Action classes to be the Model, CBeeperFrame to be the View,
and CBeeperPart to be the Controller. Since the CBeeperPart is in control, it is
worthwhile starting with its relationship to the Action classes.
CBeeperPart
Here is a partial definition of the class CBeeperPart:
class CAction; // forward declaration
class FW_CLASS_ATTR CBeeperPart : public FW_CPart
... // constructors, methods inherited from FW_CPart
//----------------------------------------------------------
// New API
//
public:
CAction* GetAction() { return fAction; }
void SetAction(CAction* action);
public:
void DoAction();
//----------------------------------------------------------
// Data Members
//
private:
CAction* fAction;
In ODF 1.0d9, CBeeperPart had a single data member that was a handle to the sound
resource to play when the button was clicked. Since we want our new part to perform
different kinds of actions, we’ve changed the data member to be a pointer to an abstract
class CAction. We’ll use the Action abstraction to minimize the dependencies of
CBeeperPart on the details of implementing sound and script actions. CBeeperPart
knows about the existence of abstract actions, and has a very simple protocol for
actions which include the ability to get and set the current action, and to forward a
request to apply the action to the current CAction object.
CAction
Let’s now look at the definition of the abstract class CAction:
class FW_CLASS_ATTR CAction
//----------------------------------------------------------
// Initialization/Destruction
//
public:
CAction();
virtual ~CAction();
//----------------------------------------------------------
// Action protocol
//
public:
virtual void Internalize(Environment* ev,
ODStorageUnit* storage) = 0;
virtual void Externalize(Environment* ev,
ODStorageUnit* storage) = 0;
virtual void DoIt() = 0;
Action objects assume a two-step initialization; they are created in a default state
and then initialized from a storage unit with the Internalize method. They also know
how to save themselves to a storage unit via the Externalize method, and to do their
action via the DoIt method. In this abstract base class, the three methods other than
the constructor and destructor are pure virtual methods. Subclasses of CAction must
provide a default constructor, a destructor, and implementations of the three virtual
methods Internalize, Externalize, and DoIt.
CSoundAction
Let’s now look at the implementation of the two Action subclasses. First, the
definition for the class CSoundAction:
class FW_CLASS_ATTR CSoundAction : public CAction
//----------------------------------------------------------
// Initialization/Destruction
//
public:
CSoundAction();
virtual ~CSoundAction();
static FW_Boolean IsInStorage(
Environment* ev, ODStorageUnit* storage);
//----------------------------------------------------------
// Action protocol
//
public:
virtual void Internalize(Environment* ev,
ODStorageUnit* storage);