Writing OpenDoc Software
This section briefly summarizes some high-level aspects of the design and implementation of a part editor. It discusses
- issues related to developing with the System Object Model that underlies all OpenDoc classes
- protocols that your part can participate in to accomplish its tasks
- several development scenarios for creating OpenDoc software
Developing With SOMobjects and IDL
OpenDoc is implemented as a shared library consisting of a set of classes constructed using SOMobjects™ for the Mac OS, the implementation of the System Object Model (SOM) for the Mac OS platform. The interfaces to SOM classes must be written in the SOM Interface Definition Language (IDL) and compiled by the SOM compiler, usually separately from the implementations of the classes. The implementation of OpenDoc objects as SOM objects has several advantages to the use of OpenDoc as a shared library:
Because OpenDoc consists of SOM classes, the class
- SOM objects are dynamically bound. Dynamic binding is essential to the nature of OpenDoc, in which new parts can be added to existing documents at any time.
- All SOM objects, whether their implementations were compiled under different compilers in the same programming language or in different languages, communicate consistently and pass parameters consistently.
- Unlike with other object-oriented architectures, the modification and recompilation of a SOM class do not necessarily require the subsequent recompilation of all of its subclasses and clients.
ODPart
is naturally a SOM class. If you want your part editor--which is a subclass ofODPart
plus any other classes that you define--to also consist of SOM classes, then you must write your interfaces in IDL, separate from your implementations, and you must compile them with the SOM compiler. The result of the compilation is a set of header and stub implementation source files, in one of the procedural or object-oriented programming languages, such as C or C++, supported by the SOM compiler. You complete your development by writing your implementa-
tions into the stub implementation files and compiling them, along with the headers, using a standard compiler for your programming language.If you write your part-editor interfaces in IDL, you will notice that the IDL syntax is very similar to that of C and C++. It includes essentially the same character set, whitespace rules, comment styles, preprocessing capabilities, identifier-naming rules, and rules for literals. But there are a few differences in source-code appearance to note when declaring or calling methods of SOM-based objects:
Advantages of making all your classes SOM classes include a greater ability to develop portions of your part editor (or set of editors) using different programming languages and compilers, and a lesser need for recompilation of all of your code when you change portions of it. These advantages may be compelling only if your libraries are very large, however, because they must be balanced against the disadvantages of working in both IDL and a separate programming language.
- In IDL method declarations (function definitions), each parameter declaration is preceded by a
directional attribute
("in
", "out
", or "inout
") that notes whether the parameter is used as an input, or as a result, or as both. See, for example, the declaration for theODPartƒlayout.::FacetAdded
method on page 108.- The C++ interface to any method of a SOM object includes an extra initial parameter, the environment parameter (
ev
), used by all SOM methods to pass exceptions. See "SOM Exception Handling" for more information.- The C interface to any SOM method includes another extra parameter, before the environment parameter, specifying the object to which the method call is directed.
You are not required to make your part-editor classes SOM classes. If you are developing in C++, for example, you can use C++ classes instead. The generally preferred procedure is to create only one SOM class, a subclass of
ODPart
whose interface contains nothing but your public methods (overrides of the methods ofODPart
and its superclasses). That SOM class delegates all of its method calls to a C++ wrapper class, which contains the functionality for the public methods as well as any private fields and methods. Additional classes can be subclasses of your C++ wrapper class.Advantages of developing in C++ with a single wrapper object include a lesser need to work with interfaces in two languages (IDL and C++), a smaller memory and calling overhead for your objects, and the availability of C++ features (such as templates) that are not supported by SOM.
For more information on SOM and using the SOM compiler on the Mac OS platform, see the OpenDoc Cookbook for the Mac OS. For a more detailed description of the Interface Definition Language and instructions on program-
- SOM class ID and editor ID
- A SOM class ID is an ISO string whose format is "module::className". A part editor's editor ID is a SOM class ID that uniquely defines the editor; you need to specify your editor ID in your editor's IDL interfaces. For example, the editor ID for AcmeGraph 1.0 might be "Acme::AcmeGraph". Editor IDs are used for binding; see "Information Used for Binding" for more information.
![]()
ming with SOM, see, for example, SOMObjects Developer Toolkit Users Guide and SOMObjects Developer Toolkit Programmers Reference Manual from IBM.OpenDoc Protocols
OpenDoc imposes very few restrictions on how your part editor does its job. The inner workings of your editor's core data engine are of little concern to OpenDoc. The engine should be able to free storage when requested, it should adequately handle cases where the part can be only partially read into memory, and it should handle any multiprocessing issues that arise. Other than that, it simply needs to provide an interface to OpenDoc and use the OpenDoc interface to accomplish OpenDoc-related tasks.The programming interfaces that your part editor uses (and provides) to perform specific tasks are called protocols. The methods of
ODPart
, for example, participate in approximately 12 protocols, such as part activation and undo. This section briefly describes the protocols; Table 2-1 lists the methods ofODPart
that you have to override to participate in each protocol.Which Protocols to Participate In
To implement the simplest possible part, you need to participate in only some OpenDoc protocols, and you need to override only some methods ofODPart
. As a minimum, your part editor must be able to
Unless it creates extremely simple parts, your part editor must also provide some sort of command interface to the user. It must then be able to
- draw its part
- retrieve its part's data
- handle events sent to its part
- store its part's data, if it permits editing
If you wish your parts to be able to contain other parts, your part editor must be able to
- activate its part
- handle menus
- handle windows, dialog boxes, and palettes
Beyond these capabilities, you may want your parts to have additional capabilities, such as drawing themselves asynchronously, providing data-
- embed frames and manipulate them
- create facets for visible frames
- store frames
transfer capability, supporting scripting, or others. You can add those capabilities by overriding other methods ofODPart
.Overriding the Methods of ODPart
Your fundamental programming task in creating an OpenDoc part editor is to subclass the classODPart
and override its methods. The following list summarizes the OpenDoc protocols that your part editor can use and lists the sections in this book that describe each protocol more fully. To create and edit full-featured container parts, your editor must support all of these protocols.The methods of
ODPart
involved in each protocol are shown in the table that follows this list.
Table 2-1 lists the methods of
- Layout. The layout and imaging protocols together make up the drawing process. Your part uses the layout protocol for frame manipulation and for facet manipulation.
- Frame manipulation includes adding and removing display frames, setting frame characteristics and order, opening frames into windows, and performing frame negotiation (modifying embedded-frame shapes on request). Your part interacts with its containing part and with its draft object to modify your display frames.
- Facet manipulation is the adding, altering, or removing of facets of frames that are modified or scrolled into or out of view. You interact with existing facet objects to add or manipulate embedded facets. OpenDoc uses the facet hierarchy constructed by the layout protocol for passing geometric events like mouse clicks to the appropriate part editor.
All parts that are visible must participate in this protocol. The layout protocol is described, along with the embedding protocol, in the section "Frame and Facet Hierarchies".
- Imaging. Your part uses the imaging protocol after the layout protocol, to draw itself appropriately in each of its display frames. Drawing may occur asynchronously, it may occur in response to update events, and it may occur when activation or deactivation affects highlighting. Drawing can be to a video display or to a printer. You interact with frames, facets, canvases, transforms, and shapes during the imaging process.
All parts that are visible must participate in this protocol. The imaging protocol is described in the sections "Canvases", "Transforms and Shapes", "Drawing", and "Printing".
.
- Activation. Through this protocol your part activates and deactivates itself (and its frames and facets). Your part interacts with the arbitrator to change ownership of the selection, menu, and keystroke foci, and to notify the parts and frames involved of the changes.
Your part must participate in this protocol if it ever needs to be active or if it needs any of the other focus types. The activation protocol is described in the section "Focus Transfer".
- User events. OpenDoc uses this protocol to distribute events such as keystrokes and mouse clicks to the appropriate part editors. The document shell, the arbitrator, and the dispatcher use focus-ownership information and the facet hierarchy to deliver these events to your part.
Your part must participate in this protocol if it handles events. The user-
events protocol is introduced in the section "About Event Handling in OpenDoc" and is described in more detail in other parts of Chapter 5.- Storage. Your part uses the storage protocol to write its content and its state information persistently in its document, and subsequently to retrieve it from the document. You interact with your draft object and your part's storage unit when reading and writing your part, and you may also interact with other drafts and storage units when using the clipboard, drag-and-drop, and linking protocols.
All parts must participate in this protocol. The part-storage protocol is described in the sections "Storage Units, Properties, and Values" and "Documents, Drafts, and Parts".
- Binding. The OpenDoc document shell controls the runtime binding of your part editor to the parts in a document that it can edit. Your part's methods are not called during binding. The binding protocol is discussed in the section "Binding".
- Linking. Your part can use the linking protocol to export updatable data to another part or to incorporate or embed an updatable copy of another part's data into your part. If your part is the source of a link, you interact with a link-source object; if your part is the destination of a link, you interact with a link object.
Your part must participate in this protocol if it supports linking. The linking protocol is described in the section "Linking".
- Embedding. Your part uses this protocol to embed other parts within itself and to interact with those parts.
Your part participates in this protocol if it is a container part--that is, if it is capable of embedding parts within itself. The embedding protocol is described in the section "Frame and Facet Hierarchies".
- Clipboard. The clipboard protocol allows the user to use menu commands to move content elements and embedded parts into or out of your part, from or to a system-maintained buffer. Your part interacts with the clipboard object both when copying to the clipboard and when pasting from it.
Your part must participate in this protocol if it supports clipboard transfer. The clipboard protocol is described in the section "Clipboard Transfer".
- Drag and drop. The drag-and-drop protocol allows the use of direct manipulation to move content elements and embedded parts into or out of your part or within your part. Your part interacts with the drag-and-drop object.
Your part must participate in this protocol if it supports drag and drop. The drag-and-drop protocol is described in the section "Drag and Drop".
- Undo. Your part uses this protocol to give the user the capability of reversing the effects of recently executed commands. Your part interacts with the undo object to perform the reversals.
Your part must participate in this protocol if it has an undo capability. The undo protocol is described in the section "Undo".
- Extensions. The extension protocol is a very general mechanism for extending your part's capabilities; it consists of an interface in the form of a specialized extension object that other part editors can access.
Your part participates in this protocol if it provides OpenDoc extensions. The extensions protocol is described in the section "The OpenDoc Extension Interface".
- Semantic events. Your part uses the semantic events protocol to make itself scriptable. It interacts with the semantic interface object when receiving scripting messages (semantic events); it interacts with the message interface object when sending scripting messages.
Your part must participate in this protocol if it is scriptable. The semantic events protocol is described in the section "Scripting and OpenDoc".
- Memory management. OpenDoc manages the memory needed for the document containing your part.
In general, your part editor does not need to be concerned with memory management except to make sure that it deletes or releases objects that it has created or obtained. The memory-management protocol and the use of reference counting are further discussed in the section "Creating and Releasing Objects"
ODPart
that you must override to have a function-
ing part editor, as well as those that you can optionally override to participate in specific protocols. Note that some protocols, such as layout, imaging, and activation, are required of all part editors, and you must override some or all of the methods associated with them. Other protocols, such as embedding or undo, are not required, and you need not override any of their methods if your parts do not participate. It is, of course, strongly recommended that your parts participate in all protocols that are appropriate to their content model.
Table 2-1 Required and optional ODPart overrides Protocol Required overrides Optional overrides Layout AttachSourceFrame
ContainingPartPropertiesUpdated
DisplayFrameAdded
DisplayFrameClosed
DisplayFrameConnected
DisplayFrameRemoved
FacetAdded
FacetRemoved
FrameShapeChanged
GeometryChanged
Open
SequenceChangedAcquireContainingPartProperties[1]
RevealFrame[1]Imaging CanvasChanged
Draw
GetPrintResolution
HighlightChanged
PresentationChanged
ViewTypeChangedAdjustBorderShape[1]
CanvasUpdated[1]Activation AbortRelinquishFocus
BeginRelinquishFocus
CommitRelinquishFocus
FocusAcquired
FocusLost
User events AdjustMenus
HandleEvent
Storage CloneInto[2]
ClonePartInfo
Externalize[2]
ExternalizeKinds
InitPart
InitPartFromStorage
ReadPartInfo
WritePartInfosomInit[2]
somUninit[2]Binding ChangeKind
Memory
ManagementReleaseAll[2]
Acquire[2]
Purge[2]
Release[2]Linking LinkStatusChanged
CreateLink
EditInLinkAttempted[1]
FulfillPromise[3]
LinkUpdated
RevealLinkEmbedding
CreateEmbeddedFramesIterator[1]
EmbeddedFrameUpdated[1]
RemoveEmbeddedFrame[1]
RequestEmbeddedFrame[1]
RequestFrameShape[1]
UsedShapeChanged[1]Clipboard
FulfillPromise[3]
Drag
and drop
DragEnter
DragLeave
DragWithin
Drop
DropCompleted
FulfillPromise[3]Undo DisposeActionState
ReadActionState[3]
RedoAction
UndoAction
WriteActionState[3]Extensions
AcquireExtension[2]
HasExtension[2]
ReleaseExtension[2]Semantic
events
EmbeddedFrameSpec[1]
Generally, you must override all of the optional methods listed for a given protocol (other than those marked with [3] in Table 2-1) if you are to participate in that protocol. For example, to participate in the extensions protocol, you must override all three methods AcquireExtension, HasExtension, and ReleaseExtension. The embedding protocol has even further requirements; to support embedding, you must not only override all the optional methods listed for that protocol, but you must override several methods associated with other protocols (marked with [1] in Table 2-1).
Development Scenarios
This section contains a high-level discussion of several possible OpenDoc development scenarios. Reading the scenarios may help you to decide what kinds of OpenDoc component software you are most interested in developing. Specifically, it discusses
- creating a part editor for noncontainer parts
- creating a part editor for container parts
- converting a conventional application into a part editor
- converting a conventional application into a container application
- developing OpenDoc components that are not part editors
Writing an Editor for Noncontainer Parts
Writing a part editor that does not support embedding is somewhat simpler than writing an editor that does. Furthermore, if you are starting from scratch (not modifying an existing application), you are free to consider all aspects of the design of your part editor before you implement anything.
Once your part editor is complete, you typically create one or more stationery documents, which are empty versions or other kinds of templates for creating parts with your part kind. Stationery documents are commonly blank, but they may have any content you wish, including other embedded parts.
- Content model. Create a content model that defines the functioning of your part editor and the OpenDoc protocols that it participates in. If your parts are to be scriptable (see step 6), your part's content objects and operations must reflect that content model.
- Core engine. Design and implement your core data engine, the set of data structures and behaviors that manifest the basic purpose of your editor.
- Storage. Implement persistent storage in these situations:
- Use the OpenDoc storage system to store your part editor's data. Your part editor must implement code to initialize a part and to write it back to storage.
- Implement clipboard and drag-and-drop capabilities to transfer informa-
tion between parts, using the same OpenDoc storage concepts for data transfer as for persistent storage.- Implement linking support, using a combination of event-handling code and storage code (similar to support for document storage, clipboard, and drag and drop).
- Drawing. Give your part the capability of drawing its content, properly transformed and clipped, in whatever combination of facets and frames the content appears, onscreen or offscreen, and for screen display or for printing.
- Event handling. Give your part editor the capability of responding to user events such as mouse clicks and keystrokes. It must also respond properly to activation and deactivation events, and use the OpenDoc menu bar object to manipulate the contents of the menu bar.
- Scripting. To support scripting, you must first define the content model of your parts, as noted earlier (step 1). Then you must implement accessor functions to resolve object specifiers (external references to a part's content objects) as well as semantic-event handlers to perform your part's content operations.
- Extensions. If you plan to extend the capabilities of your parts to communicate with other parts or process information fast, create and attach an extension interface to your part editor. To obtain existing public extension interface designs or to propose a new public interface, contact CI Labs.
- Packaging and shipping. Once your part editor is complete, package one or more stationery documents with your part editor, as templates of your part kind. Provide information (in the form of name-mapping resources on the Mac OS) for the OpenDoc binding process to use, indicating what part kinds are handled, what semantic events are handled, and what extension interfaces are provided.
You ship your product as one or more part editors, plus one or more stationery documents, plus accompanying documentation. Users install your part editor into their systems and then, using the stationery, create new documents of your part kind or insert new parts with your part kind into their documents. Rules and conventions for installing part editors and storing documents vary among platforms. For Mac OS platform rules, see Appendix C, "Installing OpenDoc Software and Parts."
Users themselves can create additional, custom stationery documents. Users should be able to exchange documents, including stationery, freely.
Writing an Editor for Container Parts
If your part editor is to support embedding--that is, if its parts are to be container parts--you need to include the following additional steps:
For a summary of the issues to consider in creating a part editor for container parts, see Appendix A, "Embedding Checklist."
- Embedding. You need to add embedding support to your content model and to storage.
- Your content model needs to include a type of content element that represents an embedded part. If your part editor supports semantic events, embedded parts must be content objects to which you can pass events.
- Make sure your parts can store embedded parts, both in their documents and during data transfer. This capability is relatively simple to implement; OpenDoc takes care of most of it.
- Layout management. You need to add support for layout management during event handling and during frame negotiation.
- Your part editor must be able to maintain updated information on embedded frames and facets, and notify embedded parts of such changes. It must add facets when embedded frames become visible. It must modify or delete facets when embedded frames move or become no longer visible.
- Your part editor must include support for layout negotiation, including updating the shapes and transforms associated with each visible embedded frame and facet.
Converting a Conventional Application to a Part Editor
Creating a part editor from an existing conventional application involves maintaining its core features but repackaging them for the OpenDoc environment.
If your converted application is to be a container part, you need to follow these additional steps:
- Content model and core data engine. You should have your content model and core data engine already in place. You may need to separate your core data engine from other facilities, such as user interface and storage, if it is not already sufficiently separated.
- Storage. You must refit all file I/O and clipboard data transfers into OpenDoc terms, as described for part editors in step 3 on page 93.
- Event handling. Because your part editor will receive its event information from the document shell, you need to remove your event loop and event-
handling code.- Scripting. You can add scripting support, as described for part editors in step 6 on page 94.
- Extensions. You can extend the capabilities of your parts, as described for part editors in step 7 on page 94.
- Packaging and shipping. Package and ship your part editor, as described for part editors in step 8 on page 94.
- Embedding. Add embedding support, as described for part editors in step 9 on page 95.
- Layout management. Add layout-management code, as described for part editors in step 10 on page 95.
Converting a Conventional Application to a Container Application
If you have an existing application, the simplest way to give it some OpenDoc capabilities may be to convert it into an embedding application (also called a container application), so that it can embed parts. A container application's documents still belong to it but may also contain embedded frames and data from embedded parts.A container application performs some tasks of a container part, although it never has to act as an embedded part. It also handles some tasks normally performed by the OpenDoc document shell; the document shell is not executing when a container application's document opens. The container application initializes the session object at startup and disposes of it at shutdown. The container application accepts platform-specific user events, converts them to OpenDoc user events, passes them to the dispatcher, and takes care of events that the dispatcher does not handle.
Here are some general considerations to keep in mind if you develop a container application:
To make development of a container application even easier, OpenDoc is distributed with a container-application library (CALib) that contains helpful code. CALib includes support for proxy objects and has other useful features.
- Initialization. Because the document shell is not available, your container application needs code to initialize the OpenDoc class library.
- Storage. Because all OpenDoc parts in a given document share persistent storage, you must refit all file I/O and clipboard data transfers into OpenDoc terms, as described for part editors in step 3 on page 93. You can use OpenDoc calls to store and retrieve your application's intrinsic data without changing the data format, as long as it is stream-oriented. (The OpenDoc container-application library contains code that helps with reads and writes.) Embedded parts can then also use the regular OpenDoc calls for reading and writing their own data.
Your container application cannot support OpenDoc drafts of its documents (if it uses the container-application library).
- Data transfer. Your container application need do nothing to support clipboard transfer, drag and drop, or linking of content to or from embedded parts. However, for transfers that involve your application's intrinsic content mixed with embedded parts, your application will need to translate between the platform-specific facilities and OpenDoc facilities.
- Drawing. Your basic drawing routines need little modification, except that you need to set up your drawing so that it is properly transformed and clipped in your embedded parts' facets and frames. When your container application receives an update event for synchronous drawing, it draws its own content and then passes the event to the OpenDoc dispatcher so that embedded parts can also redraw.
- Event handling. Because your application takes the place of the document shell, it needs to pass all user events to the OpenDoc dispatcher first--and handle them only if OpenDoc does not.
To make this work, you can have a proxy root part, with a frame and facet that correspond to your document window. Events not handled by actual embedded parts are then passed to the proxy part and therefore back to your application for handling. Other parts of OpenDoc can also communicate with those proxies in a normal fashion, and your application can convert those communications into actions that make sense for your application.
Although OpenDoc supports multiple levels of undo, only one undoable action is preserved when the user switches from an embedded part to your native content. Your container application needs to manage the Undo and Redo menu items itself.
- Embedding. You need to add embedding support, as described for part editors in step 9 on page 95.
- Layout management. You need to add layout-management code, as described for part editors in step 10 on page 95. Your container application does not necessarily have to support frame negotiation.
- Scripting. You can add scripting support, as described for part editors in step 6 on page 94.
Writing Other Types of Component Software
This book primarily describes how to create a part editor. However, you are not limited to that alone. Development of container applications is discussed briefly in the previous section. Using the OpenDoc class libraries, you can also create other kinds of OpenDoc component software:
- Part viewers are simply part editors with their editing and saving capabilities removed or disabled. A part viewer should be able to read, display, and print its parts.
Development issues for part viewers are a subset of those for fully functional part editors. A part viewer can be much smaller than its corresponding editor, and it can be developed from the same code base.
To encourage data interchange, you should always create a part viewer for every part editor you develop, and you should distribute the viewer widely and without cost or obligation to the user.
- Part extensions are objects with which you can extend the programming interface of your part editor. Extensions work in conjunction with part editors, allowing their parts to communicate with and directly manipulate other parts. Extensions are described in Chapter 10, "Extending OpenDoc."
- Document-shell plug-ins are extensions to the capabilities of the OpenDoc shell. They are shared libraries that you can write and install. Shell plug-ins are described in the section "Shell Plug-Ins".
- Part or document services are OpenDoc components that, instead of editing and saving parts in a document, provide specialized services to those parts and documents. Spelling checkers, database-retrieval engines, and network connection services are all examples.
You develop an OpenDoc service much as you develop a part editor. Like part editors, services are subclasses of
ODPart
. However, services commonly use less of the embedding, layout, and imaging protocols of OpenDoc, and they usually communicate with the parts they serve through an extension interface (a subclass ofODExtension
). The extension interface is described in Chapter 10, "Extending OpenDoc."
[1] Required of all parts that support embedding
[2] Defined in a superclass ofODPart
[3] Optional even if you implement this protocol
Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help