If this guide is distributed with software that includes an end user agreement, this guide, as well as the software described in it, is furnished under license and may be
us ed o r co pied onl y in acc ord anc e with the ter ms of s uch licen se. Exc ept as per mit ted b y an y su ch l ice nse , no pa rt o f this gui de may be reproduced, stored in a retrieval
system, or transmitted, in any form or by any means, electronic, mechanical, recording, or otherwise, without the prior written permission of Adobe Systems
Incorporated. Please note that the content in this guide is protected under copyright law even if it is not distributed with software that includes an end user license
agreement.
The content of this guide is furnished for informational use only, is subject to change without notice, and should not be construed as a commitment by Adobe Systems
Incorporated. Adobe Systems Incorporated assumes no responsibility or liability for any errors or inaccuracies that may appear in the informational content
contained in this guide.
Pl ea se r eme mb er t hat ex ist ing ar two rk o r i mag es t ha t yo u m ay w ant to inc lud e i n yo ur p ro jec t ma y b e pr ote ct ed u nde r c opy ri ght law. The unauthorized incorporation
of such material into your new work could be a violation of the rights of the copyright owner. Please be sure to obtain any permission required from the copyright
owner.
Any references to company names in sample templates are for demonstration purposes only and are not intended to refer to any actual organization.
Adobe, the Adobe logo, Acrobat, Bridge, Creative Suite, Illustrator, InCopy, InDesign, Photoshop, and Reader are either registered trademarks or trademarks of
Adobe Systems Incorporated in the United States and/or other countries. Microsoft and Windows are either registered trademarks or trademarks of Microsoft
Corporation in the United States and/or other countries. Apple and Mac OS are trademarks of Apple Computer, Incorporated, registered in the United States and
other countries. Java is a trademark of Sun Microsystems, Incorporated in the United States and other countries.All other trademarks are the property of their
respective owners.
Adobe Systems Incorporated, 345 Park Avenue, San Jose, California 95110, USA. Notice to U.S. Government End Users. The Software and Documentation are
“Commercial Items,” as that term is defined at 48 C.F.R. §2.101, consisting of “Commercial Computer Software” and “Commercial Computer Software
Documentation,” as such terms are used in 48 C.F.R. §12.212 or 48 C.F.R. §227.7202, as applicable. Consistent with 48 C.F.R. §12.212 or 48 C.F.R. §§227.7202-1
through 227.7202-4, as applicable, the Commercial Computer Software and Commercial Computer Software Documentation are being license d to U.S. Gover nment
end users (a) only as Commercial Items and (b) with only those rights as are granted to all other end users pursuant to the terms and conditions herein.
Unpublished-rights reserved under the copyright laws of the United States. Adobe Systems Incorporated, 345 Park Avenue, San Jose, CA 95110-2704, USA. For U.S.
Government End Users, Adobe agrees to comply with all applicable equal opportunity laws including, if appropriate, the provisions of Executive Order 11246, as
amended, Section 402 of the Vietnam Era Veterans Readjustment Assistance Act of 1974 (38 USC 4212), and Section 503 of the Rehabilitation Act of 1973, as
amended, and the regulations at 41 CFR Parts 60-1 through 60-60, 60-250, and 60-741. The affirmative action clause and regulations contained in the preceding
sentence shall be incorporated by reference.
This guide provides detailed information on the Adobe® InDesign® CS4 plug-in architecture.
This C++-based SDK can be used for creating plug-ins compatible with the CS4 versions of
InDesign, InDesign Server, and Adobe InCopy®.
This guide contains the most detailed information on plug-in development for InDesign products. It is not designed to be a starting point. It picks up where Learning Adobe InDesign CS4Plug-in Development leaves off, and it is more commonly used to understand particular subjects deeply.
For experienced InDesign developers
If you are an experienced InDesign plug-in developer, we recommend starting with Adobe
InDesign CS4 Porting Guide.
Introduction
For experienced InDesign developers
For new InDesign developers
If you are new to InDesign development, we recommend approaching the documentation as
follows:
1. Getting Started With the Adobe InDesign CS4 Products SDK provides an overview of the
SDK, as well as a tutorial that takes you through the tools and steps to build your first plugin.
2. Learning Adobe InDesign CS4 Plug-in Development introduces the most common programming constructs for InDesign development. This includes an introduction to the InDesign
object model and basic information on user-interface options, scripting, localization, and
best practices for structuring your plug-in.
3. The SDK itself includes several sample projects. All samples are described in the “Samples”
section of the API reference. This is a great opportunity to find sample code that does something similar to what you want to do, and study it.
4. Adobe InDesign CS4 Solutions provides step-by-step instructions (or “recipes”) for accom-
plishing various tasks. If your particular task is covered by the Solutions guide, reading it
can save you a lot of time.
5. This manual provides the most complete, in-depth information on plug-in development for
InDesign CS4 products.
Introduction27
Introduction
For new InDesign developers
28
Persistent Data and Data Conversion
Persistent Data and Data Conversion
This chapter describes how an application stores and refers to persistent data as objects and
streams. The chapter also provides background information and implementation guidelines
related to data conversion.
This chapter has the following objectives:
z Describe how Adobe InDesign® stores data.
z Explain how an object is made persistent.
z Show how to refer to a persistent object.
z Define a stream and explain how to create a new stream.
z Identify when data conversion is used.
z Describe the types of conversion providers.
z Show how conversion providers are defined.
z Describe schemas and their use.
Concepts
For common procedures and troubleshooting related to converting persistent data, see the
“Versioning Persistent Data” chapter of Adobe InDesign CS4 Solutions.
Concepts
Persistence
A persistent object can be removed from main memory and returned again, unchanged. A persistent object can be part of a document (like a spread or page item) or part of the application
(like a dialog box, menu item, or default setting). Persistent objects can be stored in a database,
to last beyond the end of a session. Non-persistent objects last only until memory is freed,
when the destructor for the object is called. Only persistent objects are stored in databases.
Databases
The application uses lightweight databases to store persistent objects. The host creates a database for each document created. The host also creates a database for the clipboard storage
space, the object model information (the InDesign SavedData or Adobe InCopy® SavedData
file), and the workspace information (the InDesign Defaults or InCopy Defaults file).
Each document is contained in its own database. Each persistent object in the database has a
UID, a ClassID, and a stream of persistent data.
The stream with the persistent object data contains a series of records that correspond to the
persistent object. The object’s UID is stored with the object, as a key for the record. The vari-
Persistent Data and Data Conversion29
Persistent Data and Data Conversion
ImplIDLengthDataImplIDLengthDataImplIDLength
k<Foo>Boss object persistent data record
UID
Persistent objects
able-length records have one segment for each persistent interface. Every segment has the same
structure:
ImplementationID tag
int32 length
<data>
Figure 1 is a conceptual diagram of this structure. The figure is a conceptual diagram of the
database contents, and does not represent the actual contents of any database. The format and
content of the data are determined by the implementation of the interface. See “Reading and
writing persistent data” on page 34.
FIGURE 1Conceptual diagram of an object’s data
For each object, the ClassID value is stored in the table, and the ImplementationID values are
stored in the stream, but the InterfaceID value is not stored with either. Adding an existing
implementation (i.e., an implementation supplied by the SDK) to an existing class can cause
problems if another software developer adds the same implementation to the class: one of the
two plug-ins will fail on start-up. To avoid this collision, create a new implementation for any
persistent interface to be added to a class, using ImplementationAlias. For an example, see
<SDK>/source/sdksamples/dynamicpanel/DynPn.fr.
Persistent objects
This section discusses how persistent objects are created, deleted, and manipulated.
The application stores persistent objects in a database, and each object has a unique identifier
within the database. The methods for creating, instantiating, and using a persistent object are
different from the methods for non-persistent objects.
Using persistent objects
When a persistent object is created, its record exists in memory. Certain events, like a user’s
request to save a document, trigger the writing of the record to storage. (See Figure 2) A count
is maintained of the number of references to each persistent object. Any object with a reference
count greater than zero remains active in memory. If the reference count for a persistent object
reaches zero, the object may be asked to write itself to its database and be moved to the instance
cache, which makes the object a candidate for deletion from memory. Events that require
access to the object, like drawing or manipulating it, trigger the host to read the object back
30
Persistent Data and Data Conversion
HostMain memoryStorage
Instantiate
(no storage)
Dirty
ReadWrite
Instantiate
(with storage)
NewUID
Reference count
reaches zero
Persistent objects
into memory. Because individual objects can be saved and loaded, the host does not have to
load the entire document into memory at once.
FIGURE 2Creating and storing persistent objects
Creating a persistent object
Creating a new instance of a persistent object differs from creating a non-persistent object,
because it requires a unique identifier to associate the object with its record in the database. For
the CreateObject and CreateObject2 methods used to create persistent objects, see
<SDK>/source/public/includes/CreateObject.h.
For examples of the creation of persistent objects, see the CreateWidgetForNode method in
<SDK>/source/sdksamples/paneltreeview/PnlTrvTVWidgetMgr.cpp or the StartupSomePal-
Persistent Data and Data Conversion31
ette method in <SDK>/source/sdksamples/dynamicpanel/DynPnPanelManager.cpp.
You also can call IDataBase::NewUID method to create a new UID in the database. It adds an
entry to the database relating the UID to the class of the object being created; however, the
object is not instantiated yet, and no other information about it exists in the database.
Instantiating a persistent object
Before you instantiate a new object, use the InterfacePtr template to retrieve one of the object’s
interfaces. Pass the returned InterfacePtr to the IDatabase::Instantiate method, which calls the
database to instantiate the object.
There is only one object in memory for any UID and database. This method checks whether
the object already is in memory and, if so, returns a reference to that object. If the object is
Persistent Data and Data Conversion
Persistent objects
marked as changed, it is written to the database. For more information, see “Reading and writ-
ing persistent data” on page 34. If the object is not in memory, the method checks for previ-
ously stored data in the database. If data is found, the method instantiates the object from this
data. Otherwise, the method instantiates the object from the object’s constructor. Each implementation has a constructor, so the boss and all its implementations are instantiated.
An object is stored to the database only if it is changed, making the default object data invalid.
A single object could exist and be used in several user sessions without ever being written to
the database. A persistent object whose data is not stored in the database is constructed from
the object’s constructor.
Whether it instantiates a new object or returns a reference to an object previously instantiated,
IDatabase::Instantiate increments the reference count on the object and returns an IPMUnknown* to the requested interface, if the interface is available.
Using commands and wrappers
There are commands for creating new objects of many of the existing classes (e.g., kNewDocCmdBoss, kNewPageItemCmdBoss, kNewStoryCmdBoss, and kNewUIDCmdBoss). When you
need to create an object, first look for a suite interface, utility interface, or facade interface to
make creating your object safe and easy. If no such interface exists, use a command if one is
available, rather than creating the object with general functions.
Using a command to create an object protects the database. Commands are transaction based;
if you use a command when the application already is in an error state, the command performs
a protective shut-down, which quits the application rather than permitting a potentially corrupting change to be made to the document. Commands also provide notification for changes
to the model, allowing observers to be updated when the change is made, including changes
made with undo and redo operations.
When you implement a new type of persistent object, also implement a command to create
objects of that type, using methods outlined in “Implementing persistent objects” on page 33.
Types of references to objects
There are four types of reference to a persistent object: UID, UIDRef, InterfacePtr, and
UIDList. Each type of reference serves a different purpose. Understanding these reference
types makes working with persistent objects easier.
z UID is the type used for a unique identifier within the scope of a database. The UID value
by itself is not sufficient to identify the object outside the scope of the database. Like a
record number, a UID value has meaning only within a given database. UID values are useful for storing, passing, and otherwise referring to boss objects, because UID values have no
run-time dependencies, and there is an instance cache ensuring fast access to the instantiated objects. kInvalidUID is a value used to identify a UID that does not point at a valid
object. Any time a UID is retrieved and needs to be tested to see if it points at a valid object,
the UID should be compared to kInvalidUID.
32
z A UIDRef object contains two pieces of information: a pointer to a database and the UID of
an object within this database. A UIDRef is useful for referring to objects, because it identifies both the database and the object. Using a UIDRef object is a common means of referring to a persistent object, especially when the persistent object is to be passed around or
Persistent Data and Data Conversion
Persistent objects
stored, since a UIDRef does not require the referenced object to be instantiated. A UIDRef
object cannot itself be persistent data, because it has a run-time dependency, the database
pointer. An empty or invalid UIDRef object has kInvalidUID as its UID and a nil pointer as
its database pointer.
z A InterfacePtr object contains a pointer to an interface (on any type of boss object) and
identify an instantiated object in main memory. While an InterfacePtr object on the boss is
necessary for working with the boss, it should not be used to track a reference to a persistent
object, because this forces the object to stay in memory. In many cases, a nil pointer
returned from InterfacePtr does not indicate an error state but simply means the requested
interface does not exist on the specified boss.
z A UIDList object contains a list of UIDs and a single pointer to a database. This means all
objects identified by a UIDList must be within the same database. A UIDList is a class
object and should be used any time a list of objects is needed for a selection or a command.
Destroying an object
There are commands for deleting persistent objects. Use the command rather than calling the
DeleteUID method directly, to be sure of cleaning up all references to the object or item references contained in the object.
When you implement a command to delete a persistent object, after you remove the references
to the object, use DeleteUID to delete the object from the database, as follows:
To make a boss object persistent, add the IPMPersist interface. Any boss with this interface is
persistent and, when an object of the boss is created, it is assigned a UID. Even though the
object has a UID, and the UID has an entry in the (ClassID, UID) pairings in a database, the
object does not automatically store data. It is up to the interface to store the data it needs. To
implement this, add the ReadWrite method to the interface (see “Reading and writing persis-
tent data” on page 34), and make sure the PreDirty method is called before information is
changed (see “Marking changed data” on page 34).
NOTE: If you add an interface to an existing persistent boss, the interface also may be made
persistent. If so, you must obey the following implementation rules for it.
Adding the IPMPersist interface to a boss
All instances of IPMPersist must use the kPMPersistImpl implementation. For an example, see
the kPstLstDataBoss boss class definition in <SDK>/source/sdksamples/persistentlist/PstLst.fr
Creating an interface factory for a persistent interface
For a persistent implementation, use the CREATE_PERSIST_PMINTERFACE macro.
Persistent Data and Data Conversion33
Persistent Data and Data Conversion
Streams
Reading and writing persistent data
To store data, your interface must support the ReadWrite method. This method does the actual
reading and writing of persistent data in the database. The method takes a stream argument
containing the data to be transferred. Read and write stream methods are generalized, so one
ReadWrite method handles transfers in both directions. For example, XferBool reads a boolean
value for a read stream and writes a boolean value for a write stream. For an example, see the
BPIDataPersist::ReadWrite method in <SDK>/source/sdksamples/basicpersistinterface/BPIDataPersist.cpp.
Marking changed data
When data changes for a persistent object that resides in memory, there is a difference between
the current version of the object and the object as it exists in the database’s storage. When this
happens, the object in memory is said to be dirty, meaning it does not match the version in
storage. Before a persistent object is modified, you must call the PreDirty method to mark the
object as being changed, so it is written to the database. For an example, see the BPIDataPersist::Set method in <SDK>/source/sdksamples/basicpersistinterface/BPIDataPersist.cpp.
The PreDirty method called from within BPIDataPersist::Set is implementation-independent,
so you can rely on the version provided by HELPER_METHODS macros defined in HelperInterface.h (DECLARE_HELPER_METHODS, DEFINE_HELPER_METHODS, and
HELPER_METHODS_INIT).
Streams
This section discusses how streams are used to move information into and out of a document.
Streams are used by persistent objects to store their information to a database. Streams also are
used by the host application, to move data like placed images, information copied to the clipboard, and objects stored in the database. IPMStream is the public interface to streams. Implementations of IPMStream typically use the IXferBytes interface to move data.
Stream utility methods (in StreamUtil.h) are helpers for creating all the common types of
streams used to move information within or between InDesign databases. The stream utility
methods and general read, write, and copy methods are needed any time you work with a
stream.
IPMStream methods
IPMStream is a generalized class for both reading and writing streams. Any particular stream
implementation is either a reading stream or a writing stream, and the type of stream can be
determined with the IPMStream::IsReading and IPMStream::IsWriting methods.
Any persistent implementation has a ReadWrite method, which uses a set of data-transferring
methods on the stream to read and write its data. (See “Reading and writing persistent data” on
page 34.) The IPMStream methods starting with the Xfer prefix are used for transferring the
34
data type identified in the method name. For example, XferByte transfers a byte, XferInt16
transfers a 16-bit integer, XferBool transfers a boolean value, and so on. All transferring methods are overloaded, so they can take a single item or an array of items. (The XferByte(uchar,
int32) version typically is used for buffers.) Streams also handle byte swapping, if required. If
swapping is not set (SetSwapping(bool16)), the default is to not do byte-order swapping.
Additional IPMStream methods, XferObject and XferReference, transfer boss objects and references to objects. XferObject transfers an owned object, and XferReference transfers a reference to an object not owned by the object using the stream. To decide which method to use,
think about what should happen to the object if the owning object were deleted. If the object
should still be available (as, for example, the color a page item refers to), use XferReference. If
the item is owned by the object and should be deleted with the owner (as, for example, the page
a document refers to), use XferObject.
Implementing a new stream
If you must read from or write to a location the host application does not recognize, you must
create a new type of stream. For example, you might need to create a new stream type to import
and export files stored on an FTP site or in a database.
Persistent Data and Data Conversion
Streams
Stream boss
The first step in implementing a new stream is to define the boss. Typically, a stream boss contains IPMStream and any interface required to identify the type of information in the stream,
the target or source of the stream, or both. Example 1creates a pointer-based read-stream boss:
EXAMPLE 1 kExtLinkPointerStreamWriteBoss, a pointer based stream boss
IPMStream is the only interface all stream bosses have in common. In Example 1, the IPointer-
StreamData controls a stream that writes out to memory; it contains a buffer and a length.
Persistent Data and Data Conversion35
Persistent Data and Data Conversion
Missing plug-ins
Example 2 is another example.
EXAMPLE 2 kFileStreamReadBoss, a stream commonly used in importing
When implementing your own stream, take advantage of the default implementations of IPMStream, CStreamRead, and CStreamWrite. These default implementations use an abstract base
class, IXferBytes, to do the actual reading and writing. To implement a stream for a new data
source, you must create an IXferBytes subclass that can read and write to that data source.
Missing plug-ins
This section discusses how to open a document that contains data saved by a plug-in that is no
longer available.
Plug-ins you create can add data to the document. When your plug-in is present and loaded, it
can open and interpret the data; however, if the user removes the plug-in and then opens the
document, or gives the document to someone who does not have the plug-in, the plug-in is not
available to interpret the data.
You have two ways to handle such situations:
z Control what warning is shown when the document is opened without the plug-in.
z Implement code to update the data the next time the document is opened with the plug-in.
The rest of this section describes these options.
Warning levels
The application can give a warning when it opens a document that contains data created by a
plug-in that is not available. There are three warning levels: critical, default, and ignore. By setting the warning level, the plug-in can specify the relative importance of its data. Data created
by the plug-in has the “default” warning level unless you override the setting and identify the
data as more important (critical) or less important (ignored). This importance settings can be
modified by adding resources to the plug-in’s boss definition file:
36
z CriticalTags — A “critical” warning tells the user the document contains data from missing
plug-ins and strongly advises the user not to open the document. If the user continues the
Persistent Data and Data Conversion
Missing plug-ins
open operation, the application opens an untitled document that is a copy of the original, to
preserve the original document. Use this level when the data is visible in the document or
contributes objects owned by another object in the database, like text attributes, owned by
the text model.
z DefaultTags — A “default” warning tells the user the document contains data from missing
plug-ins and asks whether to continue the open operation. If the user continues the open
operation, the application opens the original document. Use this level when the data is selfcontained and invisible to the user, but the user might encounter missing function that
would have been provided by the plug-in.
z IgnoreTags — An “ignore” warning provides no warning message at all; the application pro-
ceeds with the open operation as if there were no missing plug-ins. Use this level when the
data is invisible to the user and completely self-contained. In this case, the user does not
need to know the plug-in was involved in the construction of this document. If the plug-in
stored data in the document, but that data is used only by this plug-in and does not reference objects supplied by other plug-ins, the user sees no difference in the document when
the plug-in is missing. For example, the plug-in might store preferences information in
every document for its own use.
You can set these warnings to use ClassID (when the plug-in creates new bosses) or ImplementationID (when the plug-in adds interfaces to existing bosses) values as triggers. Use kImplementationIDSpace to specify a list of ImplementationID values, and kClassIDSpace for
ClassID values. You can put any number of IDs in the list, but all the IDs must be of the same
type. Use a second resource to mark IDs of another type. Example 3 and Example 4 set the
warning level to ignore data stored by the PersistentList plug-in in the SDK by adding two
resources to PstLst.fr:
You do not need to mark any IDs that do not appear in the document (for example, data that
was written out to saved data) or implementations that are not persistent.
You do not need to mark IDs if you want the default behavior.
Persistent Data and Data Conversion37
Persistent Data and Data Conversion
Missing plug-ins
Missing plug-in alert
This alert is activated when a document is opened and contains data from one or more missing
plug-ins that cannot be ignored. The document contains a list of the plug-ins that added data to
it. Each piece of data added has an importance attached to it; this may be critical, default, or
ignorable. Data marked as ignorable does not cause the alert to be activated. Data marked as
critical or default causes the alert to be activated. In the case of critical data, the alert works
more strongly; this is the only difference between critical and default data.
The alert tells the user data is missing, presents a list of missing plug-ins, and allows the user to
continue or cancel the open operation. Each missing plug-in has the chance to add a string to
the alert that specifies additional useful information (e.g., a URL for purchasing or downloading the plug-in). The alert is modeled on the missing-font alert.
The “Don’t Warn Again For These Plug-ins” option is de-selected by default. If this option is
selected, the alert is not activated the next time a document is opened and any subset of the
listed plug-ins is missing (and no other plug-ins are missing). This allows users accustomed to
seeing (and ignoring) alerts concerning specific plug-ins to automatically bypass the alert,
while still getting warned about data from any plug-ins newly found to be missing. The alert is
activated again if a document is opened that uses other missing plug-ins. The alert is activated
again if the “Don’t Warn Again For These Plug-ins” option is de-selected.
Guidelines for handling a missing plug-in
If a plug-in creates persistent data in a document, these guidelines ensure the document
behaves gracefully if a user tries to open it when the plug-in is missing or if the document is
edited by a user who did not have the plug-in:
If your plug-in does not store data in documents, you do not need to take any special precautions.
If the data stored by your plug-in does not reference other data in the document and does not
appear visually in the document, mark the data as ignorable.
If editing the document without your plug-in could corrupt the document, mark the data as
critical. You can specify a string that is displayed when the plug-in is missing and the user
opens a document that contains data added by the plug-in. See the ExtraPluginInfo resource,
which may provide information like the URL of a site from which the missing plug-in can be
obtained. See an example of the use of this resource in <SDK>/source/sdksamples/transparen-
cyeffect/TranFx.fr.
If there are checks your plug-in can do to restore the data’s integrity on opening a document
edited without the plug-in, supply a FixUpData method. For an example, see
<SDK>/source/sdksamples/persistentlist/PstLstPlugIn.cpp.
If you want your plug-in to handle the storage of its own data, use the application-supplied
mechanism that treats the plug-in’s data like a black box. (See “Black-box data storage” on
page 39.)
38
Data handling for missing plug-ins
If a document contains data placed there by a plug-in that is not available, the user can choose
to open the document anyway. If the data is completely self-contained, there may be no problem; however, if the plug-in’s data depends on anything else in the document, undesirable
things can happen.
Missing data not copied
InDesign maintains most data in a centrally managed model in which the core-content manager keeps track of what information is added to the document, handles conversion of the data,
and provides a convenient mechanism for instantiating objects based on the data. This
approach does not allow the data to be copied when the plug-in is missing, however, because
the content manager would not be able to provide these services for the missing plug-in’s copied data, and that potentially can leave the document in an invalid state. With the exception of
those objects that hold onto only ClassID values, InDesign blocks copying data associated with
missing plug-ins.
This means no attribute is copied if the plug-in that supplies the attribute is missing. This
applies to all attributes: text, graphics, table, cjk, etc. Furthermore, if a plug-in attached a data
interface to an existing attribute, and the plug-in is missing, the attribute is copied but the addin data interface is not. This is consistent with how InDesign handles UID-based objects.
Persistent Data and Data Conversion
Missing plug-ins
Likewise, if a data interface is added to an XML element, the data interface is not copied if the
plug-in that supplied it is missing.
There are several features based on an object from a required plug-in holding a ClassID from
an optional plug-in, including adornments, text composer, pair-kern algorithm, section numbers, and unit defaults. In these cases, the consequences of losing track of the plug-in that supplied the data is much less severe. Conversion of these ClassIDs is quite unusual and could be
handled if necessary by issuing new ClassIDs. Error handling in the user interface when the
plug-in is missing is much more graceful.
Black-box data storage
A second, simpler data-model storage mechanism was added for software developers requiring
that data (like text attributes) is copied with the text, and for developers who want to attach
data to other ID objects, such that it gets copied even when the source plug-in is missing. This
mechanism is black-box data storage.
In the simplest case, the black box is just a new persistent interface that sits on the object. A
plug-in can store data in the box or fetch data out of the box. The data is keyed by a ClassID,
which is supplied by the plug-in. Multiple plug-ins can store their data in the same black box,
and each plug-in gets its own, unique, streamed access to its data. The black box just keeps
track of the key, the length of the data, and the data stream. The software developer is responsible for handling everything else—conversion, swapping, etc. Users do not get a missing plug-in
alert for data placed in a black box.
Any UID-based object could have a black box. In addition, attributes and small bosses (used
for XML elements) also can have black boxes.
Persistent Data and Data Conversion39
Persistent Data and Data Conversion
Conversion of persistent data
The following objects support black boxes:
z kDocBoss — The root object of the document does not get copied.
z kPageItemBoss — This includes all page item objects, including spreads, master pages,
splines, frames, images, and text frames.
z Attributes — This includes text, graphic, table, cjk, etc. (i.e., everything that appears in an
AttributeBossList).
For more information on the black-box mechanism, see IBlackBoxCommands, IBlackBoxCmdData, and IBlackBoxData in the API reference documentation.
FixUpData
Suppose a hyperlink attribute is linked to another frame, and the user can double-click the link
to go to the frame. A plug-in supplies an observer, so if the frame is deleted, the link is severed.
Now suppose you give the document containing the hyperlinks to someone who does not have
the hyperlink plug-in. This person edits the document, deletes the frame, saves the document,
then returns the document to you. The document is now corrupted, because your plug-in was
unable to delete the associated link, which now points to an undefined frame.
To restore the integrity of a document in this case, the plug-in can override the IPlugIn::FixUpData method. This method is called when the document is opened, if the plug-in was used to
store data in the document and the document was edited and saved without the plug-in. In this
case, the hyperlinks plug-in could override FixUpData to scan all its objects, checking whether
the linked frame UIDs were deleted; when the document is opened with the plug-in, the
method correctly severs the links.
40
Conversion of persistent data
This section describes types of conversion providers and the advantages of each type.
Converting persistent data from an old document to a new one is complex, because each plugin can store data independently in the document. When the host opens a document created
with an older version of the application or an older version of any plug-in used in the document, you must convert and update the older data to match the new format.
Versioning persistent data is the process of converting persistent data in a database from one
format to another. The data in different formats usually resides in different databases; for
example, data in a database (document) from a previous version of InDesign versus that in a
database (document) from a newer version of InDesign. Just as each plug-in having a persistent
data implementation is responsible for the order of reading and writing its own data (thus
implicitly defining a data format), each plug-in also is responsible for converting its own data
from one format to another. Whether data in a database requires conversion usually is determined when the database is opened.
There are two approaches to converting persistent data:
z You can use the host’s conversion manager to manage the conversion process. This
approach is the most common. See “Converting data with the conversion manager” on
page 42.
z The plug-in that owns the data can manage when and how data is converted, using version
information or other data embedded with the object data. See “Converting data without the
conversion manager” on page 52.
When to convert persistent data
As a plug-in developer, you want to ensure your users can open documents with persistent data
from an older version of your plug-in. To do this, your plug-in must provide data conversion
function. The best time to consider your data-conversion strategy is when you realize your
plug-in will store some data to a database. You need to implement data-conversion utilities in
the following cases:
z You change the order of IPMStream::Xfer* calls in the ReadWrite method.
z You change a persistent object’s definition.
Persistent Data and Data Conversion
Conversion of persistent data
z You re-number (change the value of) an ImplementationID or ClassID identifier.
z You remove a plug-in and data from the removed plug-in might be in a document a user
wants to open.
In any of these cases, the conversion manager needs to be notified how to convert the persistent
data for use by the loaded plug-in.
Specifying how the persistent data format changed is somewhat different from adding persistent data to or removing it from a document. Besides telling the conversion manager to add or
delete any obsolete data, the conversion provider has to be able to tell the conversion manager
about every implementation in the plug-in that was ever removed, to keep the content manager
up to date about the various persistent data formats.
NOTE: To provide a document for use with an earlier version of InDesign, use the InDesign
Interchange file format.
At the very least, the resources required to support data conversion can help you keep a log of
how your persistent data format has changed. Such a log can be useful.
Sample conversion scenario
Consider a plug-in with two released versions, 1 and 2, which use the same data format; in this
case, both versions of the plug-in have the same format version number, 1. The new release of
the plug-in, version 3, stores additional data, such as a time stamp. You must update the format
version number to match the current plug-in version number; so, for plug-in version 3, the format version also would be 3. Because you changed the format version number, you must create
a converter that converts from version 1 to version 3. See Tab le 1.
Persistent Data and Data Conversion41
Persistent Data and Data Conversion
Conversion of persistent data
TABLE 1 Version changes example
Plug-in versionFormat changeFormat version
1N/A (new plug-in)1.0
2No1.1
3Ye s3
4Yes4
For a fourth version of the plug-in, you again change the format, allowing a date stamp to be
signed. Change the plug-in and format version numbers to 4, and add an additional converter
to convert from version 3 to version 4. Conversions from version 1 to version 4 are done by the
conversion manager, which chains the converters together; the first converts from format version 1 to format version 3, and the second converts from format version 3 to format version 4).
Converting data with the conversion manager
Each document contains header information about the content, which includes a list of all
plug-ins that wrote data to the document and the version number of the plug-in last used to
write that data. When a document is opened, the application checks whether any plug-ins are
missing or out of date. If a plug-in is missing, it might provide an alert embedded in the document. (See “Missing plug-ins” on page 36.)
If a plug-in is out of date, data written by the old plug-in must be updated to match the format
required by the loaded plug-in. A plug-in can register a conversion service to do the update.
The InDesign conversion manager (IConversionMgr) determines which conversions are
required for opening the document and calls the appropriate plug-in to do the conversion.
When the persistent data for any plug-in changes, this is a document format change. Any of the
following can change the document format:
z Changes to the ReadWrite method — If the ReadWrite method is used to stream data to the
document, changing the ReadWrite method might change the document format; however,
an implementation might have a ReadWrite method that works with some other database
or other data source, not with the document itself. For example, a widget has a ReadWrite
method used for streaming to and from resources and to and from the SavedData file.
Changes to a method that does not work with the document database do not require any
special conversion.
z Changes to an object’s definition — If you add an implementation to (or remove an imple-
mentation from) the definition of a persistent boss in the framework resource (.fr) file, you
change how the object is streamed. If you add a new implementation, an old version of the
object will stream, but it will not contain the data normally appearing for the implementation you added. This is fine if the data can be initialized adequately from the implementation’s constructor; otherwise, you may need to add a converter. If you change the
implementation of an interface from one ImplementationID to another, you must convert
the data. If you remove an ImplementationID from a class, you should add a converter to
42
Persistent Data and Data Conversion
Conversion of persistent data
strip the old data from the object; otherwise, the obsolete data is carried around with the
object indefinitely.
z Re-numbering an ImplementationID or ClassID — If an ImplementationID or ClassID
changes, you must register a converter so occurrences of the ImplementationID or ClassID
in old documents can be updated. In practice, re-numbering a ClassID or ImplementationID is a source of many bugs and typically leads to corrupt documents, so we strongly
recommend you not re-number an ImplementationID or ClassID.
When you make a format change, you must do two things to maintain backward compatibility:
z Update the version number of the plug-in whose data format you changed.
z Provide a converter that can convert between the previous format and the new format.
Updating version numbers
Each plug-in has a plug-in version resource of type PluginVersion. The PluginVersion resource
appears in the boss definition file. The first entry of this resource describes the application's
build number; on the release build, this entry is the final-release version number. The second
entry of the resource is the plug-in's ID, followed by three sets of version numbers, followed by
an array of feature-set IDs. If any of the IDs in this list matches the current feature-set ID, the
plug-in is loaded. To see an example, open any example .fr file in the SDK.
Each version number has a major number and a minor number. The first version number is the
version of the plug-in, which gets updated for every release of the plug-in. The second version
number is the version of the application with which the plug-in expects to run. This ensures
the user does not drop a plug-in compiled for one version of the application into an installation
of another version of the application. The last number is the format version number, which
indicates the version of the plug-in that last changed the file format; this is the version number
written into the document, and it is the number the conversion manager checks to see whether
conversion is required.
The format version number does not always match the plug-in’s version number. The format
version number does not generally change as often as the plug-in version number. The plug-in
version number changes for every release of a plug-in, but the format version number changes
only if the format for the data the plug-in stores in the document has changed and the conversion manager is required to convert the data.
Adding a converter
Converters can be implemented as conversion services. InDesign supports two types of service-provider-based data conversion:
z Schema-based provider — Schema-based converters are configured through resources, are
easier to use than code-based converters, and cover most format-change needs. Use this
type of converter unless it cannot handle the special needs of your plug-in.
z Code-based provider — If your implementation uses a special data-compression algorithm
or other storage optimizations, involves data of variable length, or uses virtual object store
(VOS) objects, schema-based converters cannot handle the necessary data format conversions. In this case, you must implement your own custom conversion provider.
Persistent Data and Data Conversion43
Persistent Data and Data Conversion
Conversion of persistent data
A conversion service is responsible for all conversions done by the plug-in. A converter might
at first handle only a single conversion, from the first format to the second. Later, if you change
the format again, you can add another conversion to the converter, to convert from the second
format to the third.
Suppose you market a plug-in that supplies a date stamp. You had released two versions (version 1.0 and version 2.0) of the plug-in without changing the persistent data created by the
plug-in; so both releases have the same format version number (1.0). For the third released version of the plug-in, you add a time stamp. You must update the format version number to
match the current plug-in version number; i.e., for plug-in version 3.0, the format version also
must be 3.0.
Suppose for the fourth released version of the plug-in (version 4.0), you again change the format, allowing a date stamp to be signed. You then change the format version number to 4.0 and
add an additional converter, capable of converting from format version 3.0 to format version
4.0. Conversions from format version 1.0 to format version 4.0 can be done by the conversion
manager, which chains the two converters together, using the first converter to convert from
format version 1.0 to format version 3.0, and using the second converter to convert format version 3.0 to format version 4.0.)
Adding a converter (either schema-based or code-based) to your plug-in means adding a new
boss with two interfaces, IK2ServiceProvider and IConversionProvider. The
IK2ServiceProvider implementation, kConversionServiceImpl, is provided by the application.
You need to supply the IConversionProvider implementation. Here is a sample boss:
/**
This boss provides a conversion service to the conversion manager
to use the schema-based implementation.
*/
Class
{
kMyConversionProviderBoss,
kInvalidClass,
{
IID_ICONVERSIONPROVIDER, kMySchemaBasedConversionImpl,
IID_IK2SERVICEPROVIDER, kConversionServiceImpl,
}
},
44
Most of the work is done in the conversion provider supplied with the SDK.
The default implementation of IConversionMgr calls IConversionProvider::CountConversions
and IConversionProvider::GetNthConversion to determine which conversions are supported
by the converter. When you implement a new converter, CountConversions returns 1, and GetNthConversion returns fromVersion set to the version number before your change and toVersion set to the version number having your change in it. VersionID is a data type in the Public
library; it consists of the PluginID value, the major format version number, and the minor format version number.
For a new converter, fromVersion should be VersionID(yourPluginID, kOldPersistMajorVersionNumber, kOldPersistMinorVersionNumber). The toVersion should be the new format version number.
Persistent Data and Data Conversion
Conversion of persistent data
Your new conversion is added as conversion index 0. For a new converter, it looks like this:
When adding another change after a changed version already exists, the change numbers
should chain together so the conversion manager can do changes across multiple formats. So, if
your plug-in used three formats—starting with version 1, then changed in version 3, and
changed again in version 4—your plug-in should register one converter that handles the conversion from format version 1 to format version 3 and another converter that handles the conversion from format version 3 to format version 4. If necessary, the conversion manager can
chain them together to convert a document from version 1 to version 4. The methods would
look like this:
void TextConversionProvider::GetNthConversion(
int32 i, VersionID* fromVersion, VersionID* toVersion) const
{
if (i == kFirstChange)
{
*fromVersion = VersionID(kTextPluginID, kMajorVersionNumber,
kFirstPersistMinorVersionNumber);
*toVersion = VersionID(kTextPluginID, kMajorVersionNumber,
kSecondPersistMinorVersionNumber);
}
else if (i == kNewChange)
{
*fromVersion = VersionID(kTextPluginID, kMajorVersionNumber,
kSecondPersistMinorVersionNumber);
*toVersion = VersionID(kTextPluginID, kMajorVersionNumber,
kThirdPersistMinorVersionNumber);
}
}
Persistent Data and Data Conversion45
Persistent Data and Data Conversion
Conversion of persistent data
Next, tell the conversion manager which information you changed. The default implementation of IConversionMgr calls IConversionProvider::ShouldConvertImplementation with each
implementation in the document supplied by the plug-in. Depending on the data status of the
implementation, the plug-in must return kMustConvert when the content must be modified,
kMustRemove when the data is obsolete and must be removed, kMustStrip when the content
must be stripped, or kNothingToConvert for all other cases.
Using kTextFrameImpl as an example, define a method for IConversionProvider::ShouldConvertImplementation, returning kMustConvert when the tag is kTextFrameImpl and kNothingToConvert in all other cases. The plug-in should do this when the conversion manager is
converting that plug-in’s data and not performing another conversion, so check the conversion
index and make sure it is the one that you added. It might look like the following:
switch (conversionIndex)
{
case 0:
if (tag == kTextFrameImpl)
status = IConversionProvider::kMustConvert;
break;
default:
break;
}
return status;
}
Next, implement ConvertTag to do the actual conversion. Suppose the TextFrame ReadWrite
method writes an integer value and a real value, and you are adding a boolean. The ConvertTag
implementation might look like the following, which illustrates what most converters look like:
If the converter needs to convert a class, it implements ShouldConvertClass and ConvertClass.
This is necessary only if the class is being deleted or is a container for some other data. (See
“Containers and embedded data” on page 48.)
Removing classes or implementations
If you remove a ClassID or an ImplementationID from a version of your plug-in, the identifier
also must be removed from documents as they are converted. The conversion manager
removes an identifier for you if you implement your converter to return invalid status. For
example, to remove a tag, implement ConvertTag as follows:
Deleting a class is similar: implement ConvertClass to return kInvalidClass.
Changing an implementation in one class
Suppose a boss is defined with an IBoolData interface, implemented as kPersistBoolTrue,
meaning it defaults to true. Suppose you change this to kPersistBoolFalse. When you read an
old document with the new boss, you want it to use the new implementation. You would then
write a converter to take the data stored as kPersistBoolTrue and switch it to be stored as kPersistBoolFalse. When you first access the boss, it has the old value. If you do not make a converter, the boss will not read the old data, because there is no interface in the new boss using
the kPersistBoolTrue ImplementationID. Instead, the boss looks for kPersistBoolFalse but does
not find it, because the old boss did not have it; therefore, the value is false, because it is the
default value instead of the old value.
To change the ImplementationID of the data in the document, make a conversion method to
catch the old ImplementationID and change it to the new one. Do this only for your boss; do
not change other bosses using kPersistBoolTrue. Your method might look like this:
forClass is the boss in which the data was found. The conversion should be done only if
forClass is the one you want changed.
Also implement ShouldConvertImplementation. forClass is either the class in which the data
was found or kInvalidClass if any class should match. The implementation of ShouldConvertImplementation looks like this:
switch (conversionIndex){
case 0:
if (tag == kTextFrameImpl &&
(forClass == kMyBoss || forClass == kInvalidClass))
status = IConversionProvider::kMustConvert;
break;
default:
break;
}
return status;
}
Containers and embedded data
InDesign does not have many instances of containers. One example is in the text attribute code.
A text style is a UID-based boss object containing an implementation, TextAttributeList, that
contains a list of attribute bosses (Bold, Point Size, Leading, and so on). These attribute bosses
are not UID-based bosses; instead, TextAttributeList streams the ClassID for a boss, followed
by the boss’s data, then streams the next boss in the list. TextAttributeList, therefore, must call
the stream’s content tracker to notify the content manager that a new class was added to the
document.
48
Persistent Data and Data Conversion
Conversion of persistent data
The following example illustrates why this is important.
Suppose a new Red-Line plug-in adds an attribute to the style and the text does not compose
correctly if the Red-Line plug-in is removed. Red-Line can list the attribute as critical, so the
host provides a strongly worded warning when the user tries to open the document without the
Red-Line plug-in. However, if TextAttributeList does not register the attribute boss with the
content manager, the application never knows the attribute is in the document and cannot
warn the user. Suppose the Red-Line plug-in is updated, and the attribute boss in particular is
updated to store its data differently. The Red-Line plug-in registers a converter handling the
format change. If the host does not know the attribute appears in the document, the Red-Line
converter is never called to perform the conversion. The document appears to open correctly,
but it crashes on the attribute’s ReadWrite method the first time the style is read.
For the Red-Line attribute to be converted, TextAttributeList must register a converter. If the
content manager was notified when the attribute was streamed, the conversion manager knows
the attribute needs to be converted. It even knows the attribute was streamed by TextAttributeList. But the conversion manager has no way of knowing where the attribute is inside the
data streamed by TextAttributeList; therefore, TextAttributeList should register a converter that
calls back to the conversion manager to convert each piece of embedded data. Otherwise, the
embedded data is not converted.
Content manager
The host has a content manager that tracks what data is stored in the document, which plug-in
stored it, and the format version number of the plug-in last used to write the data.
Think of this as a table attached to the root of the document: every ImplementationID part of
the document appears in the table. This table also includes all ClassID values in the document.
Ta bl e 2 and Tab le 3 are an example.
TABLE 2 Version information
ImplementationIDPluginIDFormat version number (only
minor number)
kSpreadImplkSpreadPluginID0
kSpreadLayerImplkLayerPluginID0
kTextAttrAlignJustImplkTextAttrPluginID1
kCMSProfileListImplkColorMgmtPluginID3
...
Persistent Data and Data Conversion49
Persistent Data and Data Conversion
Conversion of persistent data
TABLE 3 Class IDs in the document
ClassIDPlug-in IDFormat version number (only
kSpreadBosskSpreadPluginID1
kSpreadLayerBosskLayerPluginID1
kTextAttrAlignBodyBosskTextAttrPluginID3
kDocBosskDocFrameworkPluginID1
When an object is streamed to a document, the document receives the ClassID of the object
and the data streamed by each of the object’s ReadWrite methods. The data written by a ReadWrite method is marked with the ImplementationID of the ReadWrite implementation; this
way, when the data is read later, the system knows which C++ class to instantiate to read the
data. IContentMgr maintains an overall list of the ClassID and ImplementationID values used
in the document. When a new ClassID or ImplementationID is added to the document, IContentMgr checks which plug-in supplied the ClassID or ImplementationID, notes the plug-in ID
and the format version number, and adds the new entry to the table.
minor number)
The plug-in supplying a ClassID is the one supplying the class definition. Only the plug-in supplying the original class definitions is considered; add-in classes do not count. The plug-in supplying an ImplementationID is the one that registered a factory for the ImplementationID.
This data is used to detect missing plug-ins and manage document conversion. When the user
opens a document containing data supplied by a missing plug-in, the host alerts the user that
the document contains data from a missing plug-in. The host detects missing plug-ins by looking in the content manager to see which plug-ins supplied data to the document and checking
this list against the list of loaded plug-ins to see whether any are missing.
This data also is used for document conversion. When the user opens a document, the default
implementation of IConversionMgr checks the format version number of the plug-ins supplying data to the document and compares it with the format version number of loaded plug-ins.
Any mismatch means a conversion is required before the document can be opened. If there is a
format version change without a supplied data converter, the document will not open.
Conversion manager
When a document is opened, the conversion manager is called to check whether any data in
the document requires conversion. If the data requires conversion and a converter is available,
the document is converted and opens as an untitled document. If a converter cannot be found,
the host displays an alert message that warns the user the document cannot be opened because
it cannot be converted.
This initial check, implemented in IConversionMgr::GetStatus, happens very early in the process of trying to open the document, before any objects in the document are accessed (i.e.,
before any of their ReadWrite methods are called). This is critical, because a ReadWrite
method will not succeed if the object needs to be converted. The conversion manager accesses
the content manager (converting it if necessary), and uses the content manager to find out what
50
Persistent Data and Data Conversion
Conversion of persistent data
plug-ins supplied data to the document. If any plug-ins used in the document have different
formats than the formats of the loaded plug-ins, conversion is necessary. Next, the conversion
manager sees whether there is a converter to convert from the format in the document to the
format associated with the loaded plug-in. If such a converter is available, conversion is possible; the document may be closed and opened again as an untitled document.
If the GetStatus method returns with a result indicating conversion is not necessary, the open
operation proceeds without calling the conversion manager again. If the GetStatus method
returns with a result indicating conversion is necessary but impossible, the open operation is
aborted. If the GetStatus method returns with a result indicating conversion is required and a
converter is available, the conversion manager’s ConvertDocument method is called to perform the conversion.
The first thing ConvertDocument does is compile a list of the classes and implementations in
the document that must be converted. A plug-in may have many different implementations
that wrote data to the document, but only one of these implementations might require conversion. The conversion manager uses the content manager to iterate over classes and implementations supplied by the plug-in, and the conversion manager calls the converter to find out
which classes and implementations require conversion. Each class or implementation in the
document that the converter says must be converted is added to the list of classes or list of
implementations to be converted. Other data written by the plug-in is ignored by the converter;
it is not converted.
The next step is to iterate over the UIDs in the document. For each UID, the conversion manager gets the class of the UID. If the class is on the list of classes to be converted, the conversion
manager calls a converter for the class. If, as is more common, the class contains an implementation needing to be converted, the conversion manager opens an input stream on the UID and
iterates through the implementations in the UID. If any implementation in the UID requires
conversion, an output stream is opened on the UID. Any implementation not requiring conversion is copied to the output stream. If an implementation requires conversion, its converter
is called. The converter gets passed the input stream and the output stream. The converter
reads from the input stream in the old format and writes to the output stream in the new format.
After the UID iteration is completed, the content manager is called to update the format numbers for all plug-ins that were outdated, to show their data was converted and is up to date.
After this, it is safe for ReadWrite methods to be called on objects in the document.
If any converter requires access to another object to do its conversion, it is called in the last
phase of conversion. For example, suppose there was one document-wide preference setting for
whether text in frames has smart quotes turned on, but you want to make this a per-frame setting. The text frame must store a new flag that indicates whether smart quotes are on or off.
The converter adds a new flag in the first phase of conversion, but the converter cannot set the
correct value for the flag until the preferences are converted. So, the converter needs to be
called once again, after the first phase is complete and it is safe to instantiate the preferences, to
get the value of the document-wide smart-quotes setting so the converter can set the frame’s
new smart-quotes setting accordingly.
Persistent Data and Data Conversion51
Persistent Data and Data Conversion
IDLengthIDLength
Data
k<Foo>Boss persistent data record
Vers. | Other
Data
Vers. | Other
Conversion of persistent data
Converting data without the conversion manager
To perform format conversion without the conversion manager, the persistent data itself must
identify the version of the plug-in that originally wrote the information. To use this method,
add a version number to the implementation classes, and implement the ReadWrite method to
write the version number first whenever the method writes data, as shown in Figure 3. This
method has the advantage of forward compatibility.
FIGURE 3Persistent data with version number
For example, suppose the first version of your plug-in stores two-byte values, fOne and fTwo,
and uses a byte value to store the data’s version number. The ReadWrite method for this implementation would be something like Example 5:
EXAMPLE 5 ReadWrite method supporting multiple versions (version 1 of the plug-in)
The second version of this plug-in modifies the data format by adding a 16-bit integer value,
fThree. In this version, your ReadWrite method must be able to read data in either format, but
it always must write data back in the new format. See Example 6:
EXAMPLE 6 ReadWrite method supporting multiple versions (version 2 of the plug-in)
FooStuff::ReadWrite(IPMStream* stream, ImplementationID implementation)
{
uchar version = 0x02; //Always *write* version 2 data
stream->XferByte(version);
if (version == 0x01)
{
stream->XferByte(fOne);
stream->XferByte(fTwo);
}
else
{
stream->XferByte(fOne);
stream->XferByte(fTwo);
stream->XferInt16(fThree);
}
}
52
This code both reads and writes all data for this implementation. It could check whether the
stream is a reading or writing stream and perform only the needed operation, but the dual-purpose code actually is simpler and accomplishes the same thing. If the stream is a ReadStream,
the version is initialized as 0x02 but immediately replaced by the contents of the first byte in
the stream, and the rest of the stream is processed according to the version number found. If
this plug-in encounters data claiming a version number greater than 2, only the data it understands (processed by the else clause) is read. This method allows the version 2 plug-in to work
with data from both earlier and later versions. Each new version of the plug-in using this
method must preserve the portion of the stream previous versions created and add new information only to the end.
When using this approach, the plug-in’s data version number must not change between versions of the plug-in, but only when a data converter is being supplied to convert the data from
one version to another.
Resources
Persistent Data and Data Conversion
Resources
This section explains the fundamental elements and ODFRez resources you need when incorporating a converter in your plug-in.
PluginVersion resource, format numbers, and their macros
PluginVersion is a resource included in every InDesign and InCopy plug-in. Part of the
resource is the declaration of a persistent data format number, like the following:
For an example, see the resource definition in <SDK>/source/sdksamples/basicdia-
log/BscDlg.fr.
Each plug-in specifies a different format number. These format numbers are stored in documents, so when a document is opened, the content manager can determine whether data conversion is needed. This determination is made by comparing the format numbers stored in the
document for each plug-in with the format numbers declared by loaded plug-ins. If the format
numbers are different, the conversion manager is called upon to do a data conversion.
Format number values must meet the following criteria:
z Be greater than or equal to zero.
z Increase with each format change.
z Increment if the data format of any persistent class in a plug-in changes.
NOTE: If multiple persistent classes in a plug-in change their data formats at the same time, the
data-format version number needs to increment only once. How much you increment
data format version numbers is up to you.
Format-number values are just numbers; however, you might find it easier to use #define macros for format numbers instead of using their values directly.
Persistent Data and Data Conversion53
Persistent Data and Data Conversion
Resources
Ta bl e 4 lists format-number macros and their values from previous InDesign SDKs. See
<SDK>/source/sdksamples/common/SDKDef.h.
TABLE 4 Format number macros from prior InDesign SDKs
With the first format change for a plug-in, you must add a converter (either schema-based or
code-based) to your plug-in (only one converter per plug-in). See “Adding a converter” on
page 43.
Schemas
The schema resource defines which formats of which implementations the converter should
handle. The schema resource is defined in your .fr file and compiled using the ODFRC (Open
Document Framework Resource Compiler). See Example 7 and <SDK>/pub-
lic/includes/Schema.fh.
EXAMPLE 7 Schema resource
resource Schema(uniqueResourceID)
{
ImplementationID,
{ schemaFormatMajorNumber, schemaFormatMinorNumber },
{ // [0..n] SchemaFields go here (see SchemaFields.fh) }
};
54
Examples
When you define a schema resource, you explicitly state which format number of which implementation it defines. It knows which plug-in contains the implementation, because it is defined
implicitly to be the plug-in that contains the schema resource. In other words, all schemas
defined in plug-in A are for implementations provided by plug-in A. The schema-based converter uses the information in this resource (or the SchemaList resource) to determine how to
map persistent data from one format to another.
Persistent Data and Data Conversion
Resources
In Example 8, (1) identifies the schema resource ID. The value does not matter, as long as it is
unique among all schema resources compiled into the plug-in. The first item in the schema
specifies the implementation ID, and the second item specifies the format number as a tuple {1,
0}. Next comes the schema field list. Curly brackets ( { } ) delimit the list, and commas separate
the individual fields. Each field has a type, name, and default value. It is extremely important
each field name is unique and these values are not re-used when a field is deleted. Field names
must be unique among the schemas that describe different formats of the same implementation, because this is what allows data mapping between different format numbers.
NOTE: Although it is not done in the example below, we recommend you use #define macros
for each field name, to enhance readability.
EXAMPLE 8 Schema resource from <SDK>/sdksamples/snapshot/Snap.fr
In Example 9 (based on the preceding Snap.fr example), there is a new schema resource ID (2).
Inside the schema, the following changes occurred:
z The implementation ID has not changed, but the format number is specified as a tuple {1,
1}.
z schemaFormatMinorNumber changed from 0 to 1.
z In the schema field list, the fifth field (0x0005, fMinimumResolution) was deleted. During
conversion, the fifth field (Real) is stripped from the existing data.
z The seventh field changed its type. It uses the same name but is now of type Bool8. This
requires an implicit conversion. (For a table of valid and invalid type conversions, see the
“Versioning Persistent Data” chapter of Adobe InDesign CS4 Solutions.)
z A new field (0x000d, fMaximumResolution) was added. On conversion, an element of type
Int32 (with a value of 3) is appended to the end of the existing data.
Default values in a schema are used only when the associated field is added to the data stream
by conversion. For example, if an implementation originally contained only one int32 value but
now also needs a bool16 value, the first schema lists only the int32 and the second schema lists
both the int32 and the bool16. When the conversion runs, the schema converter writes a
bool16 into the output stream, using the value specified as the default. Because the int32
already existed, the default value specified by the schema is not used.
Suppose you have an implementation with two int32 values. In your constructor, you give them
values of 11 and 52. The schema should reflect this. If you later decide 53 is a better default
value for the second one, change the schema to match. In this case, however, you do not need a
new schema.
SchemaList
The SchemaList resource allows you to specify several schemas in one resource, for convenience. Example 10 shows the two previous schema resources combined in one SchemaList.
Even if initially you have only one Schema inside your SchemaList, it is a good idea to create
this resource because, over the course of development, this SchemaList resource acts as a persistent data format log.
The possible types for schemaTypeIdentifier (see Example 10) are listed below. See Schema.fh
for their definitions.
z ClassSchema — Schema for classes in the current plug-in.
z OtherClassSchema — Like ClassSchema, but you can specify it for another plug-in by an
additional PluginID field.
z ImplementationSchema — Schema for implementations in the current plug-in.
Persistent Data and Data Conversion57
Persistent Data and Data Conversion
Resources
z Schema — Another name for ImplementationSchema, because this is the most common
type.
z OtherImplementationSchema — Like ImplementationSchema, but you can specify it for
another plug-in by an additional PluginID field.
DirectiveList
To instruct the schema-based converter to add or remove implementations or an entire boss,
specify a DirectiveList resource to your resource file, as shown in Example 11. (The Direc-
tiveList resource formerly was known as BossDirective.) The DirectiveList resource is defined
in your .fr file and compiled using ODFRC.
EXAMPLE 11 DirectiveList syntax
resource DirectiveList(uniqueResourceID)
{
{ // [0..n] Directives go here }
};
A DirectiveList resource is required each time you do any of the following:
z Add a new boss or remove an existing one.
z Add an implementation to a boss or remove one from a boss.
z Change the ID of a boss or implementation.
The DirectiveList resource serves several purposes:
z Helps the conversion manager delete unnecessary data from a document when a boss or
implementation is removed.
z Keeps the content manager up to date regarding a document’s contents.
z Allows the conversion manager to do its job correctly, even when a boss or implementation
moves to a different plug-in.
The possible list of directives is in Tab l e 5.
TABLE 5 Possible list of directives
DirectiveDescription
IgnorePluginMark a plug-in as ignorable.
MoveClassMoves a boss class from one plug-in to another.
MoveClassToPluginA boss was moved from one plug-in to another.
MoveImplementationMoves an implementation from one plug-in to another.
58
MoveImplementationToPluginAn implementation was moved from one plug-in to another.
RemoveAllImplementationRemoves an implementation from all boss classes.
RemoveAllOtherImplementationRemoves an implementation from all boss classes.
Persistent Data and Data Conversion
Advanced schema topics
DirectiveDescription
RemoveClassRemoves a boss class from a plug-in.
RemoveImplementationRemoves an implementation from a boss class.
RemoveOtherClassRemoves a boss class from another plug-in.
RemoveOtherImplementationRemoves an implementation from a boss class.
RemovePluginRemoves an entire plug-in.
RenumberPluginRe-numbers an entire plug-in.
ReplaceAllImplementationReplaces one implementation with another in all plug-ins.
ReplaceClassReplaces one boss class with another.
ReplaceImplementationReplaces one implementation with another in a specific plug-in.
NOTE: See Schema.fh. Fields vary by type of directive.
Advanced schema topics
Arrays of values
Suppose an implementation contains three boolean flags followed by four uint32 values. The
schema could contain seven separate fields, or it could define two array fields, as in
The number of default values statically determines the number of elements in each array. Note
that the set of default values is enclosed in braces.
Persistent Data and Data Conversion59
Persistent Data and Data Conversion
Advanced schema topics
FieldArray
If the quantity of array elements is dynamic or an array consists of structures rather than single
elements, use a FieldArray, which is a type of field, like Bool16, Uint32Array, or any of the
other types in Schema.fh.
In the following example, the Bar implementation differs slightly from the previous example.
Instead of having three flags followed by four values, it has a value associated with each flag,
and the number of (flag, value) pairs is dynamic:
The syntax looks slightly complicated because of ODFRC limitations, but it is fairly straightforward. The schema contains only one field; its type is FieldArray, and its name is kBarPairs.
Following the field's name is its iteration count. Because this is an attribute of the FieldArray, it
has a type but not a name. It also has a default value, which usually is zero. The counter might
be a signed or unsigned integer that is 8, 16, or 32 bits wide.
NOTE: The iteration count immediately precedes the iterated values. (If your implementation's
persistent data requires the count be elsewhere, you must write your own conversion
provider.)
Following the iteration count is the list of iterated fields. Each has a type, name, and default
value, like any other field. The default value is not used unless the iteration count has a nonzero
default. In the preceding example, the output stream would simply be “0.” If the default iteration count were 2, the output would be “2, kFalse, 0, kFalse, 0.”
FieldArrays can be nested up to three levels deep.
Conditional-field inclusion
An important variant of the FieldArray type is the FieldIf type. Use this construct to include a
block of fields zero times if a condition is not met or once if the condition is met. The conditional value can be of type Bool8, Bool16, ClassID, or ImplementationID. If the conditional
value is of type ClassID or ImplementationID, the fields are included if the ID is valid.
In this case, kBarOption and kBarValue are included zero or one times, depending on the
Bool16 value.
FieldIf constructs can be nested up to three levels deep.
Persistent Data and Data Conversion61
Persistent Data and Data Conversion
Advanced schema topics
62
Commands
ClientInvokerCommand
+ Execute() : void
Receiver
+ Action() : void
ConcreteCommand
+ Execute() : void
receiver->Action();
This chapter describes InDesign’s command architecture. It also outlines how to find and use
the commands provided by the InDesign API, how to implement new commands of your own,
and other extension patterns associated with commands.
Concepts
This section introduces the generic command pattern, databases that provide persistence to the
application’s objects, and models that represent the application’s objects in memory.
Command pattern
The intent of the command pattern is as follows: “Encapsulate a request as an object, thereby
letting you parameterize clients with different requests, queue or log requests and support
undoable operations.” (This is described in Gamma, Helm, Johnson, and Vlissides, Design Pat-terns, Addison-Wesley, 1995.) The structure of this pattern is shown in Figure 4.
Commands
Concepts
FIGURE 4Command Pattern Structure
This pattern decouples the object that invokes an operation from the object that implements it.
The client wants an operation to be performed, the receiver knows how to perform the operation, and the two are decoupled by this pattern. The command declares an interface for executing an operation. The client does not send messages directly to the receiver; rather, the client
creates a concrete command object and sets its receiver. The concrete command implements
execute by calling the corresponding operation on the receiver. The invoker asks the command
to carry out the request, which causes the receiver to perform the operation.
Within the InDesign API, an implementation of the command pattern is used in situations
where preventing data corruption is paramount and users must be able to undo or redo
changes. See “Commands” on page 68.
Commands63
Commands
Concepts
Databases and undoability
A persistent object has its state maintained outside the application runtime. It can be removed
from main memory and returned again, unchanged. The application maintains persistent
objects in a database. A database on disk is an opaque binary file in the operating system’s file
system. This database file stores the serialized state of a collection of objects. The set of objects
stored in a particular database is collectively known as a model. For example, an InDesign document file like Untitled-1.indd is a database file that represents the state of an instance of a document model. See “Models” on page 64.
Objects are stored in a database in the following format:
z Objects that persist are stored as a tree of objects, each of which is associated with and per-
sisted by a database.
z Each persistent object has a identifier, the UID, that identifies it uniquely within its data-
base.
z Each persistent object (except the root object) is owned and referred to by another object,
the parent. A persistent object also can be referenced by other objects, though this does not
indicate any form of ownership.
Models
The class that represents a database is IDataBase.
For more detail, see the “Persistent Data and Data Conversion” chapter.
Databases must be consistent and stable. Some databases also need to support undoability—the
ability for a user to undo or redo changes. For example, the ability to undo changes made to a
document is required; however, there is no requirement to undo changes made to the database
that persists the user interface state.
Ta bl e 6 shows the databases that exist and their support for undoability. If a database supports
undoability, that support cannot be turned off. All changes to objects that persist in the database must be undo-able. Turning off undo support is not allowed, because it would disable
error handling and increase the risk of document corruption.
NOTE: To change objects that persist in a database that supports undo, we recommend using
commands. If, however, you need to change data without showing something in the Edit >
Undo menu, you can bypass commands and wrap your changes in calls to
CmdUtils::BeginAutoUndoSequence and CmdUtils::EndAutoUndoSequence.
A model is a collection of objects backed by a database for persistent storage. A model is a treestructured graph of objects. The ownership relationships between objects in a model defines
this tree structure. Ownership relationships are just parent-child relationships within the tree.
64
Document model
UID 75 :
kDocWorkspaceBoss
IWorkspace
UID 91 :
kPMColorBoss
IColorData
UID 125 :kStyleBoss
IStyleInfo
ITextAttributes
UID 1 :kDocBoss
IDocument
UID 60 :kSpreadBoss
ISpread
UID 41 :
kTextStoryBoss
ITextModel
Documen t co ntent
such as layout and
text.
Document
preference settings
such as styles and
swatches.
IStyleGroupManager
IDocument::GetDocWorkSpace
ISwatchList
ISpreadListIStoryList
Documents are represented by the document model. Figure 5 shows an example.
FIGURE 5Objects in a document model (instance diagram)
Commands
Concepts
The document (kDocBoss) is the root object in the document model. It owns a collection of
spreads (kSpreadBoss), stories (kTextStoryBoss), a workspace (kDocWorkspaceBoss), and so
on. These objects may own further objects specific to their domain. The document’s workspace
owns objects like styles and swatches, which can be used throughout the document.
The database file that provides persistence for a document model is an end-user document file;
for example, a file Untitled-1.indd saved from InDesign.
Commands65
Commands
UID 1 :
kWorkspaceBoss
IWorkspace
UID 27 :kStyleBoss
IStyleInfo
ITextAttributes
UID 42 :
kPMColorBoss
IColorData
Global default preference
settings such as styles and
swatches.
ISwatchList
IStyleGroupManager
Concepts
Defaults model
Global preference settings are represented by the defaults model. Figure 6 shows an example.
FIGURE 6Objects in the defaults model (instance diagram)
The workspace (kWorkspaceBoss) is the root object in a defaults model. It owns the objects
that represent default preference settings like styles, swatches and so on. The defaults model is
global and is accessed from the session (kSessionBoss) via the ISession interface. When a new
document is created, preference settings can be copied from the defaults model into the document’s workspace (kDocWorkspaceBoss).
The database file that provides persistence for this model is an application defaults file. For
example, the file named InDesign Defaults is the database file used by InDesign to persist the
defaults model.
66
Commands
UID 2 :kAppBoss
UID 3 :
kActionManagerBoss
UID 4 :
kPanelManagerBoss
UID 5 :
kToolManagerBoss
UID 6 :
kDocumentListBoss
UID 1 :kSessionBoss
ISession
IApplication
Concepts
Session model
A session of an application is represented by the session model. Figure 7 shows an example.
FIGURE 7Objects in the session model (instance diagram)
The session (kSessionBoss) is the root object in the session model. This model owns application-related objects that must persist from session to session. For instance, user interface
objects are found here, together with other objects that store the application’s type system (the
boss classes provided by all registered plug-ins).
The database file that persists objects in this model is the application’s saved data file. For
example, the file named InDesign SavedData is the database file used by InDesign to persist the
session model.
NOTE: Objects in the session model can be modified by calling mutator methods on their
interfaces directly. There is no need to modify them using commands. The database that
persists this model does not support undo.
Books, asset libraries, and other models
Several other features in an application have their own dedicated model, together with a database that provides that model with persistence. Prominent examples are books, asset libraries,
and the scrap. See Ta b le 6 for the level of undo support provided by their databases.
Commands67
Commands
Commands
Commands
This section describes how commands are used in the application. It includes sections on the
CmdUtils class, which is fundamental to the processing of commands, and how to use command sequences to group multiple commands into one logical unit.
Within the application, the most prevalent use of commands by client code is to modify persistent objects in the document model or defaults model. For example, plug-ins use commands to
create frames in a document or modify the default text style.
Commands encapsulate the changes made to persistent objects into a database transaction.
Encapsulating changes in this way helps prevent corruption. Furthermore, any change to the
state of persistent objects that needs to support undo must be made using a command.
Commands change persistent objects by doing the following:
z Calling mutator methods on interfaces that exist on persistent objects.
z Processing other commands (that call mutator methods on interfaces that exist on persis-
tent objects).
z Calling utilities that process commands (that call mutator methods on interfaces that exist
on persistent objects).
z A mixture of the above.
A command is processed by the application; this means an instance of the command is passed
to the application to be executed. A command can change persistent data within only one database each time it is processed. The processing of a command moves the database from one consistent state to another.
When a command is used to modify objects that persist in a database that supports undo, that
command can manifest on the undo and redo menu items, and the database automatically
reverts the state of affected objects on undo and restore the changed state on redo.
NOTE: Before InDesign CS4, commands were directly responsible for reverting or restoring
the changes they made to persistent objects on undo and redo. The application has
taken over this responsibility.
The InDesign API provides commands for plug-ins to use. See “Command processing and the
CmdUtils class” on page 70 and “Key client APIs” on page 84.
Plug-ins also can introduce new commands using the command extension pattern. See “Com-
mand” on page 86. This is required when a plug-in adds custom persistent data to a document,
defaults, or other objects that persist in a database that supports undo. The extension patterns
named persistent interface and persistent boss are ways in which custom persistent data can be
added to a database. See “Persistent interface” on page 89 and “Persistent boss” on page 91.
Commands implement the protocol used to modify objects that persist in a database that sup-
ports undo. A client initiates this protocol by instantiating a command and passing it off to the
application for processing. The client also is responsible for initializing the state of the command, including the setting of parameters. In general, the command pattern supports two
mechanisms for parameter passing:
1. The UIDList in the ICommand interface (the “item list”), which identifies a set of persistent
objects; see ICommand::SetItemList. On input, this might identify the set of page items on
which the command operates. For example, a page item move command (kMoveAbsoluteCmdBoss) would use this to identify the set of items to be moved. On output, the item
list might identify the set of object manipulated by a command. For example, a page item
create command (kNewPageItemCmdBoss) would provide the UIDs of the objects created
as a result of the command being processed. The use of the command’s item list is specific to
each command; a command is not required to use the item list.
2. Data-carrying interfaces on the command’s boss class. For example, the command used to
apply a particular text style aggregates an IBoolData interface to define whether character
styles should be overridden, and an IRangeData interface to indicate the range of text that
should be updated by the command.
Commands
Commands
These two approaches for passing parameters are shown in Figure 8. There is a command boss
class showing two aggregated interfaces, ICommand and a data-carrying interface (ISomeData). Parameters can be passed through the item list in ICommand, the data-carrying interface, or both.
FIGURE 8Passing parameters to commands
Commands69
Commands
Commands
Command undoability
Each command has a property called undoability (see ICommand::Undoability), which determines whether the command name can appear in undo and redo menu items (i.e., whether the
changes made by the command can be undone or redone in a distinct step).
z An undoability of kRegularUndo is used by commands whose changes need to appear as a
separate named step in undo and redo menu items. This is the default behavior.
z An undoability of kAutoUndo is used by commands whose changes do not appear as a sep-
arate named step; for example, commands whose changes should be undone or redone with
an existing step.
Changes made to objects that persist in a database that supports undo must be undoable. To
achieve this, the various extension patterns involved, such as commands and persistent interfaces, must be implemented following the rules described in this chapter. The database then
automatically reverts the state of affected objects on undo and restores the changed state on
redo. The undoability of a command (kRegularUndo / kAutoUndo) has no effect on this behavior.
It affects only whether the command appears as a distinct step in undo and redo menu items.
Command processing and the CmdUtils class
Client code uses commands when changing objects that persist state in any database that supports undo. For example, commands are used to change objects in the document model or the
defaults model. The level of support for undo of each database is given in Ta bl e 6 .
To change the objects, client code creates instances of one or more commands and submits
these instances to the application for execution. For example, to create frames in a document, a
plug-in either creates commands to be processed and passes them to the application or calls a
helper class that encapsulates both the creation and request for processing. The helper classes
provided by the InDesign API are introduced in “Command facades and utilities” on page 84.
Client code that must create commands uses the CmdUtils class. CmdUtils::CreateCommand
creates an instance of a specific command. The client code parameterizes the command, using
the command’s item list and data interfaces. The client code submits the command to the application for execution, by calling CmdUtils::ProcessCommand. Client code can group the commands it processes into command sequences, using methods and classes provided by
CmdUtils. Guidance on processing commands and command sequences is in “Command-pro-
cessing APIs” on page 86 and the “Commands” chapter of Adobe InDesign CS4 Solutions.
The sequence of calls involved in processing a command is shown in Figure 9. The client code
creates the command, populates the item list and other data interfaces, then passes the command off for processing.
70
FIGURE 9Client processing a command
Client codeCmdUtilsA CommandA Persistent
Interface
Some client code creates an
instance of a command using
CmdUtils::CreateCommand
The client sets the command
parameters, both through the
item list (shown here) or
through data carrying
interfaces.
The processing of the
command is initiated through
the client calling
CmdUtils::ProcessCommand.
Client code
creates
command, sets
parameters and
passes it for
processing. The
client should
also test error
state on return,
before
processing
further
commands.
CmdUtils should
be used for all
client
interactions with
the commands
sub-system. The
rest of the
internals are not
shown in this
diagram.
The update to some
interface on a (UID
based) boss object.
The call to PreDirty
allows the
application to track
the changes made
so that the
application can
undo or redo the
changes
automatically.
CreateCommand
construct
SetItemList
ProcessCommand
Do
Set
PreDirty
Commands
Commands
Command sequences
Commands71
A command sequence (see ICommandSequence) groups a set of commands into a single
undoable step that changes the model from one consistent state into another. The command
sequence manifests on the undo and redo menu items as a single item.
Commands
ClientcodeCmdUtilsICommandSequence
Theclientcreatesanewcommandsequence.
Theclientgivesthesequence
a name, otherwise the
sequence uses the name of
the first command processed.
Multiple commands are
processed within the scope of
a sequence.
The client indicates the
sequence is complete.
BeginCommandSequence
ICommandSequence*=
SetName
ProcessCommand
ProcessCommand
EndCommandSequence
Commands
Command sequences can be nested. For example, say you begin a sequence in one method,
then call a second method that begins a second sequence. The second sequence is said to be
nested within the first. When sequences are nested, only one sequence—the outermost one—
appears on undo and redo menu items.
Command sequences assimilate all commands are processed within their scope, whether the
command is processed directly from within the sequence or indirectly through subcommands
(commands processed by a command). See the “Commands” chapter of Adobe InDesign CS4Solutions for how to write code that uses command sequences.
A typical scenario for a command sequence is shown in Figure 10. Between the BeginCommandSequence and EndCommandSequence calls, all commands processed act as a single set of
changes; only one element will appear on undo and redo menu items.
FIGURE 10Calls made for a command sequence
A command sequence can change persistent objects in more that one database. (A command,
on the other hand, can change objects only in one database each time it is processed.) Operations that change objects in two or more documents can be implemented using a command
sequence. Such a set of modifications manifests in undo and redo menu items for all affected
documents.
72
Command managers, databases, and undo support
A command sequence has limited support for error handling: the sequence either succeeds
entirely or fails. An abortable command sequence (see IAbortableCmdSeq) allows more
sophisticated error handling, to allow for fail/retry semantics. Abortable command sequences
incur a significant performance overhead and should be used only where absolutely necessary.
Guidance on using these types of sequence is in the “Commands” chapter of Adobe InDesignCS4 Solutions.
Command managers, databases, and undo support
This section describes how command managers relate to the application’s databases.
The application object model realizes a tree-structured graph of boss objects. Within this tree
are several distinct models, notably the session model, the defaults model, and one or more
document models (see “Models” on page 64). Each model has a distinct database that provides
persistence for its objects (see “Databases and undoability” on page 64). Each database has an
associated boss, called a command manager. When commands are used to change objects in a
model, the command manager is responsible for executing the commands.
Commands
Figure 11 shows that the application object model has several distinct databases that provide
persistence for its objects. The diagram shows some of the objects in an InDesign session (with
two open documents) and the databases with which these objects are associated. Each UIDbased object refers to its associated database through its IPMPersist interface. The defaults
model, represented by an instance of kWorkspaceBoss, persists in the InDesign Defaults database file. Each document model, represented by an instance of kDocBoss, persists in an InDesign document database file. The session model, represented by the instance of kSessionBoss
and the child objects that do not belong to any other model, persists in the InDesign SavedData
database file.
Each database has an associated command manager (see objects in Figure 11 that have an
ICommandMgr interface). The command manager is responsible for managing database
transactions and executing the commands that change the objects that persist in the database.
If an object has an ICommandMgr interface, it is the root object of its associated database. This is
shown in Figure 11. Key objects that are command managers are listed in Ta bl e 6 ; for the com-
plete list, see ICommandMgr in the API Reference.
NOTE: The class that represents a database is IDataBase. This class is an abstract C++ class, but
it is not an IPMUnknown interface.
Commands73
Commands
UID 1 :kSessionBoss
ICommandMgr
UID 2 :kAppBoss
UID 3 :kDocumentListBossUID 1 :kWorkspaceBoss
ICommandMgr
UID 1 :kDocBoss
ICommandMgr
UID 1 :kDocBoss
ICommandMgr
InDesign SavedData :
IDataBase
Untitled-1.indd :
IDataBase
Untitled-2.indd :
IDataBase
InDesign Defaults :
IDataBase
IPMPersist
IPMPersist
IPMPersist
IPMPersist
IPMPersistIPMPersist
IDocumentList::GetNthDoc(1)
IDocumentList::GetNthDoc(0)
ISession::QueryWorkspace
IApplication::QueryDocumentList
ISession::QueryApplication
Command managers, databases, and undo support
FIGURE 11Models and databases in an indesign session (object diagram)
74
Command managers, databases, and undo support
TABLE 6 Frequently Used Databases and their Support for Undoability
Commands
Command manager /
root boss class
kSessionBossApplication SavedDataNoneInDesign SavedData User interface objects like
kWorkspaceBossApplication DefaultsFullInDesign Defaults Default resources like
kDocBossInDesign DocumentFullYour.indd Document content like spreads,
kBookBossInDesign BookPartialYour.indb A collection of InDesign documents
kCatalogBossInDesign Asset LibraryPartialYour.indl A collection of page items.
Clipboard/scrapApplication ClipboardScrapPartial
DatabaseUndo
support
Example filename and use
tools, menus, panels, dialogs, controls, and
string translations. Objects that define the
application object model, like plug-ins and boss
classes.
swatches, fonts, and styles inherited by new
documents.
pages, page items, and text.
and associated resources.
Each database has a level of support for undo which is fixed when the database is created and
can be one of the following:
z Full — The database fully supports undoable operations. The changes made by a transac-
tion can be reversed automatically on undo and restored on redo. Undo and redo menu
items are fully supported.
z Partial — The database can undo only the most recent operation. If an error occurs, the
database is rolled back to its state before the transaction began; however, once a transaction
is committed, it is irreversible. There is no support for undo and redo menu items.
z None — The database does not support undoable operations. There is no support for error
handling, abortable command sequences, or undo and redo menu items.
NOTE: Undo support varies by product. In InDesign and InCopy, full undo support is
provided for documents and defaults. In InDesign Server, only partial undo support is
provided for these databases; the server has no concept of user undo and redo.
Objects that persist in a database with full or partial support for undo must be modified using
commands. For example, a plug-in must process commands to change document content such
as spreads, pages, or page items.
Objects that persist in a database without support for undo can be modified by calling mutator
methods on persistent interfaces directly. Commands are not required. For example, a plug-in
can call interfaces on user interface objects, such as widgets, that set state directly.
Commands75
Commands
«boss»
kSessionBoss
«boss»
kCommandProcessorBoss
ICommandProcessor
«boss»
kSomeCommandBoss
ICommand
«boss»
kSomeModelRootBoss
ICommandMgr
IDataBase
IDataBase::GetRootUID1
IPMPersist::GetDataBase1
changes objects that persist in
1
1..*
1
ISession::QueryCommandProcessor
1
The command processor
The command processor
This section describes how a command is passed through the application until it is processed.
The session (see the ISession interface) has a singleton instance of the command processor (see
kCommandProcessorBoss and the ICommandProcessor interface). The command processor is
the core application component to which all commands are submitted via CmdUtils.
The command processor’s structure is shown in Figure 12. A command targets one or more
objects for change, and these objects persist in a particular database. Normally, client code creates a command instance and sets this target by passing parameters to the command (see
“Command parameters” on page 69). The client code submits the command instance for pro-
cessing using CmdUtils. Figure 13 shows the internal collaboration between the objects
involved.
FIGURE 12Command processor (class diagram)
76
The command processor examines the command and determines which database contains the
objects the command changes. Each database has an associated command manager boss (see
ICommandMgr interface), shown in Figure 12 as the boss class named kSomeModelRootBoss.
For example, the root of the document model is kDocBoss, and kDocBoss is a command manager. The command processor calls the associated command manager to execute the command.
Commands
A CommandCmdUtilsA Command
Manager
CommandProcessorA Persistent
Interface
The command is
passed to the
session wide
command
processor which
determines the
target database
and calls
ICmdManager::Do
for that database.
The command
manager
invokes the
command.
The command
updates the
persistent
interface.
Generally the
command will
derive from the
Command class,
providing an
implementation
of Do().
The persistent
interface calls
PreDirty before
making any
update to the
data.
ProcessCommand
ProcessCommand
Do
DoImmediate
Set
PreDirty
The command processor
Figure 13 shows the internal processing of a command. The call to process a command is
passed to the session-wide command processor. This determines which database is targeted by
the command, and the command is passed to the command manager for the specified database. The command manager processes the command, and the command updates an interface
on a persistent (UID based) boss object. On completion, control returns to the client that initiated the process.
FIGURE 13Internals of command processing (sequence diagram)
NOTE: Third party plug-ins should not interact directly with the command processor or
command manager interfaces. CmdUtils provides all methods needed by third parties
for processing commands.
Commands77
Commands
A CommandA CmdManagerCommandProcessorCmdUtilsA Persistent
Interface
Some client code schedules the
command. The command is
processed as part of the main
event loop after all other
processing has completed, and
before idle tasks are processed.
Processing of a scheduled command is identical to that of a non-scheduled
command. The only difference is with a non-scheduled command, client code is
responsible for the initiation, upon completion, control returns to the client. With
a scheduled command the
event loop initiates processing, upon completion
control returns to the main event loop. If a scheduled command returns an error,
it is reported to the user, the error condition is cleared and processing resumes.
ScheduleCommand
ProcessScheduledCmds
ProcessCommand
Do
DoImmediate
Set
PreDirty
Scheduled commands
Scheduled commands
Commands can be scheduled for later processing, using CmdUtils::ScheduleCommand. The
command is placed in a queue and processed when the application is idle as part of the main
event loop, before idle tasks are processed. The sequence of calls involved in processing a
scheduled command is shown in Figure 14. Compare this with the more normal case of immediately processing a command, shown in Figure 13. In Figure 14, the scheduled command is
passed to the command processor (through the CmdUtils::ScheduleCommand call). At some
later time, after all other processing is complete and control is returned to the main application
event loop, all scheduled commands are processed.
Guidance on using scheduled commands is in the “Commands” chapter of Adobe InDesign CS4Solutions.
FIGURE 14Processing a scheduled command
78
Snapshots and interface implementation types
Snapshots and interface implementation types
A snapshot is a copy of the state of an interface at a particular time. Undo and redo are achieved
automatically by the application, which keeps snapshots of the interfaces changed within an
undoable transaction. The snapshots are kept in the command history (see “Command his-
tory” on page 80) of the database with which the interface is associated.
The way in which an IPMUnknown interface is implemented determines whether its data is
persistent and/or needs a snapshot to support undo or redo:
z Regular interfaces store transient data that is not maintained on undo or redo. They are
declared to the object model using CREATE_PMINTERFACE, and their state is lost if they
are removed from memory. Data in a regular interface is not persistent, and no snapshot is
taken.
z Persistent interfaces are declared to the object model using the
CREATE_PERSIST_PMINTERFACE macro. They serialize their state to their associated
database via the ReadWrite method and can be removed from memory and returned again
unchanged. Persistent interfaces that are part of a database that supports undo also are
called to serialize their state (again via their ReadWrite method) to a snapshot. This mechanism is required to support undo and redo. See “Persistent interface” on page 89.
Commands
z Snapshot interfaces store transient data that must be maintained on undo and redo. They
are declared to the object model using the CREATE_SNAPSHOT_PMINTERFACE macro,
and they serialize their state in a snapshot via the SnapshotReadWrite method. Snapshot
interfaces are aggregated on a boss that persists in a database that supports undo; however,
their data is not persistent. Data maintained by a snapshot interface is lost whenever the
associated database is closed or when the command history for the database is cleared. See
“Snapshot interface” on page 92.
z Snapshot view interfaces are similar to snapshot interfaces, which also store transient data
that must be maintained on undo and redo. The distinction is that a snapshot view interface
depends on model objects that persist in another database. A snapshot view interface is part
of a user interface object and must explicitly identify the database containing the model of
interest. Also, the database on which a snapshot view interface depends can change. For
example, a widget that tracks some state in the front document must change the database on
which its snapshot view interface depends when the front document changes. Snapshot
view interfaces are declared to the object model using the
CREATE_VIEW_PMINTERFACE macro. See “Snapshot view interface” on page 99.
z It also is possible to create a hybrid interface to allow the data that persists in the database to
differ from the data of which a snapshot is taken for undo and redo. This can be used by
complex data structures, like collections that need to optimize performance. Such an interface is declared to the object model using the
CREATE_PERSIST_SNAPSHOT_PMINTERFACE macro. Data is serialized to the database via the ReadWrite method and to the snapshot via the SnapshotReadWrite method.
For example, consider this approach if you have a collection of some sort and want to serialize only those objects that changed relative to the snapshot (rather than the entire collection).
Commands79
Commands
Command history
Command history
Consider a database to be the state of the persistent object model. For example, a document
database is the state of the document object model (kDocBoss and all its dependents) at a particular time. Persistent interfaces (on dependent objects) are called to serialize their state to the
database when necessary, via their ReadWrite method.
The command history (ICmdHistory) provides a history of the undoable operations that were
performed on each database. It records the state changes made by a particular command or
command sequence, for the purposes of undo and redo. The name of the first command or
command sequence processed to perform an operation on the model appears as a named step
in the command history. These steps manifest as undo and redo menu items. The command
history is used to automatically revert the state of affected objects on undo and to restore the
changed state on redo.
The database and its command history are related but distinct states.
To maintain the command history, the database monitors which of its objects change when
commands are processed. It tracks the UIDs that are deleted, created, or un-deleted. It tracks
persistent interfaces, snapshot interfaces and snapshot view interfaces that are modified, and it
takes snapshots of them. This information is kept in the command history as a set of CmdStackItem objects. (CmdStackItem is opaque on the SDK; third parties cannot access its data.)
When undo is invoked, the application automatically reverts the database state to its previous
revision, using this information. When redo is invoked, the application automatically restores
the database state to its next revision.
See Figure 15. The command processor (kCommandProcessorBoss) aggregates the command
history (interface ICmdHistory), which is responsible for maintaining the state changes made
by a particular command or command sequence (which manifests on the undo and redo
menu). This state, represented as CmdStackItems, is used to move the model to previous and
next states on undo and redo. The CmdStackItem encapsulates all data required for an undo
and redo that occurred while processing a command or command sequence.
80
FIGURE 15Command history
«boss»
kCommandProcessorBoss
«interface»
ICmdHistory
CmdStackItem
The command
processor
aggregates a
command history
interface.
The command history maintains a set of
CmdStackItems (opaque in the SDK). These items
represent the set of UIDs added, deleted and modified.
For the modified persistent interfaces, the changes are
captured through a call to ReadWrite.
Any UID based object that has an associated snapshot
interface
that has been modified, will have its state
captured through a call to its SnapshotReadWrite.
On undo/redo, the state that is maintained in the
CmdStackItem (opaque in the SDK), which is used to
reset the UID based objects (through calls to
ReadWrite and
SnapshotReadWrite).
0..*
1
1
1
Commands
Undo and redo
Merging changes with an existing step in the command history
Sometimes it is desirable to extend the scope of an existing step shown in the undo/redo menu
to include functionality that occurs after the step finished. This can be done using either of the
following:
z A command with undoability of kAutoUndo.
z A command sequence with undoability of kAutoUndo.
Undo and redo
To enable a user to undo or redo a change, the objects changed must:
z Persist in a database that supports undo (see Tab l e 6).
z Be modified by processing commands.
The application maintains the state required for undo/redo of changes made to persistent
objects, as commands are processed. Each database that supports undo has a command history
in which the necessary information is recorded, as described in “Command history” on
page 80. Each persistent interface on a UID-based boss object has its ReadWrite method called
to add the state to the command history (for undo and redo) and to the database (to persist its
Commands81
Commands
CmdHistoryInternals
«interface»
ICmdHistory
DB1:
CmdStackItem1
DB1:
CmdStackItem2
DB1:
CmdStackItemN
DB2:
CmdStackItem1
DB2:
CmdStackItem2
DB2:
CmdStackItemN
DBN:
CmdStackItem1
DBN:
CmdStackItem2
DBN:
CmdStackItemN
kCommandProcessorBoss
1
1
Undo and redo
state). On undo, the ReadWrite method is called to reset the state back to that from before the
action, and on Redo, the ReadWrite method is called again to set the state back to that from the
initial action. The sequence of calls to a persistent interface is shown in Figure 17. Snapshot
interfaces are called on their SnapshotReadWrite method using a similar approach; see
Figure 18.
The command processor records in the command history the set of undoable operations it has
performed (see the ICmdHistory interface on kCommandProcessorBoss). The steps in the
command history appear in undo and redo menu items and are modelled internally using
CmdStackItems. (These are internal only. Discussion is provided here to give some notion of
how the command processor works.) This is shown in Figure 16.
FIGURE 16Command history internals (instance diagram)
82
The command history maintains a stack of CmdStackItem objects for each database that supports undo; see Figure 16. A CmdStackItem (opaque in the SDK) represents the set of changes
made by the sequence or command that manifests on the undo/redo menu. It contains data
gathered during a database transaction for the purposes of undo and redo. On undo/redo, the
“current” CmdStackItem is used to revert the state of the model. The CmdStackItem encapsulates all data required for an undo/redo that occurred while processing a command or command sequence. This includes all changes to persistent interface and snapshot interface data
that were pre-dirtied. The CmdStackItem also maintains any inval handler cookies (see “Inval
handler” on page 94) created as part of the sequence.
Each step has a name of either a command or a command sequence that performed the operation. On undo, the application automatically reverts the database to its state before the operation was performed. On redo, the application automatically restores the database to its state
after the operation was performed.
Extension patterns involved in undo and redo
The following extension patterns are called at undo or redo:
z Persistent interfaces; see “Persistent interface” on page 89.
z Snapshot interfaces; see “Snapshot interface” on page 92 (introduced in InDesign CS4).
z Snapshot view interfaces; see “Snapshot view interface” on page 99 (introduced in InDesign
CS4).
z Inval cookies; see “Inval handler” on page 94 (introduced in InDesign CS4).
z Observers, via their LazyUpdate method (introduced in InDesign CS4); see the “Notifica-
tion” chapter.
Commands
Notification within commands
Notification within commands
A command can initiate notification, so observers interested in changes to the objects it modifies are notified. See the “Notification” chapter.
Commands also can be processed within observers and responders. Take care with commands
that modify the model in this way. See the “Notification” chapter for further discussion of
model changes within the context of notification.
If you are implementing your own command, see “Command” on page 86 for additional information on notification in the command extension pattern.
Error handling
In response to a user gesture or another event, the application calls a plug-in to carry out an
action. For example, the user creates a text frame. In this case, the plug-in creates and processes
the commands that perform the action. On detecting an error, a plug-in normally sets the global error code (see ErrorUtils) to something other than kSuccess and returns. When control
returns to the application, the global error code is checked. If it is set, the database is reverted to
the state it had before the modifications began. Extension patterns that participated in the
transaction being rolled back are called as follows:
Commands83
Commands
Key client APIs
1. Persistent interfaces, snapshot interfaces, and snapshot view interfaces modified by the
transaction are called to revert their state. See “Persistent interface” on page 89, “Snapshot
interface” on page 92, and “Snapshot view interface” on page 99.
2. Inval cookies created during the transaction are called to undo. See “Inval handler” on
page 94).
When the application returns to its main event loop, it informs the user through an alert, using
the string associated with the error code that is set (see “Error string service” on page 88). The
global error code is then cleared, and the application continues.
Plug-in code that processes commands or command sequences must check for errors. CmdUtils::ProcessCommand returns an error code that should be checked for kSuccess before continuing. Alternatively, you can check the global error code using ErrorUtils. Details are in the
“Commands” chapter of Adobe InDesign CS4 Solutions. If a plug-in tries to process a command
while the global error code is set, protective shutdown occurs to protect the integrity of the
document or defaults databases (see “Protective shutdown” on page 84).
Command implementation code also must check for errors. If a command encounters an error
condition within the scope of its Command::Do method, it should set the global error code
using ErrorUtils and return. Details on error handling is in the command extension pattern;
see “Command” on page 86.
Plug-in code that requires more sophisticated error handling, to allow for fail/retry semantics,
must use an abortable command sequence. For information on using abortable command
sequences, see the “Commands” chapter of Adobe InDesign CS4 Solutions.
Protective shutdown
Protective shutdown is a mechanism that helps prevent document corruption. Any attempt to
process a command when the global error code is set (to something other than kSuccess)
causes a protective shutdown. The application creates a log file describing the problem it
encountered and then exits.
Key client APIs
This section summarizes the APIs a plug-in can use to interact with commands.
Command facades and utilities
There are many command-related boss classes named k<whatever>CmdBoss, but in many
cases you do not need to instantiate these commands, as there are command facades and utilities in the API that encapsulate parameterizing and processing these commands.
84
Interfaces like those in Ta bl e 7 are examples of command facades and utilities. These encapsulate processing of many commands required by plug-in code. These interfaces are aggregated
Commands
Key client APIs
on kUtilsBoss; the smart pointer class Utils makes it straightforward to acquire and call methods on these interfaces. You can call their methods by writing code that follows this pattern:
Utils<IDocumentCommands>()->MethodName(...)
TABLE 7 Useful command facades and utilities
APIUsed for manipulating...
IDocumentCommandsDocuments.
IPathUtilsFrames and splines. See the “Layout Fundamentals” chapter
for other APIs that help with layout.
IGraphicAttributeUtilsGraphic attributes. See the “Graphics Fundamentals”
chapter for other APIs that help with graphics.
ITextModelCmdsText. See the “Text Fundamentals” chapter for other APIs
that help with text.
ITableCommandsTables. See the “Tables” chapter for other APIs that help
with tables.
IXMLUtilsXML. See the “XML Fundamentals” chapter for other APIs
that help with XML.
kUtilsBossSee kUtilsBoss in the API Reference for a complete list of
command facades and utilities.
These utility classes abstract over low-level commands. Before using commands, always look
for such a utility to see if there is a method that serves your purpose on one of these interfaces.
By doing so, you avoid the increased chance of confusion and error that comes with processing
commands. Your use case may require you to process some commands, if the utilities do not
provide all of the functionality you require.
As used in the application, command “facade” and “utility” are just different names for the
same thing, a class that reduces and simplifies the amount of client code you need to write to
change objects in the model. They decouple client code from command creation, initialization,
and processing.
When implementing custom commands, it is advisable to provide your own command facade
or utility. If you want to share your class with other plug-ins, implement it as an add-in interface on kUtilsBoss; for example, see XDocBkFacade. If the class is used only by one plug-in, a
C++ class is sufficient; for example, see BPIHelper.
Commands85
Commands
Extension patterns
Command-processing APIs
The classes used when writing client code to process commands are listed in Tab le 8.
TABLE 8 Useful classes for processing commands
APIDescription
CmdUtilsProvides methods that create, process, or schedule commands and
manage command sequences.
PersistUtilsProvides methods like GetDataBase, GetUID, and GetUIDRef, which
are used to identify the objects to be operated on by a command.
ErrorUtilsProvides access to the global error code.
NOTE: You must use CmdUtils to process commands. Do not use the ICommandProcessor or
ICommandMgr interfaces for this. Misuse of these interfaces can easily cause
document corruption.
For information on finding and processing the commands provided by the API, see the “Commands” chapter of Adobe InDesign CS4 Solutions.
Extension patterns
This section summarizes the mechanisms a plug-in can use to extend the command subsystem.
Command
Description
Suppose:
z You added a new persistent boss to a document or a persistent interface to an object in a
document, and you need to set custom data in these objects.
z The API does not provide a command that sets the model data you need to change.
z You want do some processing, and you concluded that a command provides the best pattern
in which to encapsulate it.
Architecture
To implement a command, follow these steps:
z Define a new boss class in your plug-in that aggregates IID_ICOMMAND. If you require
input parameters over and above the command’s item list, aggregate further data interfaces
to the boss through which the parameters can be passed.
z Provide an implementation of ICommand using Command as a base class.
86
Commands
Extension patterns
z Add client code that processes your new command. See the “Commands” chapter of Adobe
InDesign CS4 Solutions.
z For more guidance, see the Command page in the API Reference and “Best Practice” below.
Best practices
z The Do method contains the code that modifies the model. The model is changed by calling
mutator methods on model interfaces, processing commands, processing commands in a
command sequence, or calling utilities that process commands for you.
z If you encounter an error condition within your Do method, set the global error code
(ErrorUtils::PMSetGlobalErrorCode) and return. The application is responsible for reverting the model back to its state before the Do method was called and informing the user of
the error.
z If you need more sophisticated flow control that allows for fail/retry semantics, use an
abortable command sequence (see IAbortableCmdSeq).
z The Do method can change objects in only one database each time it is called. To ch ang e
objects in more than one database in one undoable step, use a command sequence (ICommandSequence).
z Normally, the database containing the objects to be changed is passed using the command’s
item list. Alternatively, a command can target a predetermined database, by calling Command::SetTarget in its constructor. It also can override Command::SetUpTarget and determine the database containing the objects to be changed when the command is processed.
z Normally, the DoNotify method contains the code that initiates notification, should you
require it. Initiate notification by calling ISubject::ModelChange (not ISubject::Change).
Calling ISubject::ModelChange when model objects are changed broadcasts regular and
lazy notification (see the “Notification” chapter). The application calls DoNotify after the
Do method of the command is called; however, notification need not be restricted to this
method.
z Notification can be performed for each object that was modified using the ISubject inter-
face of affected objects. Notification also can be performed centrally. For example, many
commands that modify the document model notify change using the ISubject interface of
the document (kDocBoss). Sometimes commands use both these approaches. The benefit
of using a centralized approach is that an observer needs to attach to only one subject to
receive the notification.
z If you need to pre-notify observers before the model is changed, call your DoNotify method
from your Do method before making any changes to the model.
z Notification can result in the processing of further commands, which can result in global
error code being set. If further commands are processed, application shutdown occurs. If
multiple subjects are notified by a command, the global error code should be tested between
notifications. If the error code is set, the DoNotify method should return without calling
further subjects, or it should consume the error and reset the global error code.
z The undoability of a command should be fixed at command construction (see ICom-
mand::GetUndoability). Its default value is kRegularUndo. If a different undoability is
Commands87
Commands
Extension patterns
z A command must have a name (see Command::CreateName), if it has an undoability of
z Commands with undoability of kAutoUndo do not need a name, because their changes
z The destructor of a command normally is empty. Do not change the model within the
See also
z For documentation on commands: Command and ICommand in the API Reference.
required, it should be set in the command’s constructor. See ICommand::Undoability in the
API Reference.
kRegularUndo and it returns kTrue for IsNameRequired. Such commands can appear in
undo and redo menu items. The name must have a translation, so it can be displayed in
undo and redo menu items in a localized form. Commands with an undoability of kRegularUndo that override IsNameRequired to return kFalse must pick up their name from a
subcommand. Some commands create and process other commands (subcommands) to
make their changes and want to pick up the name of the first subcommand called within
their scope instead of providing their own name.
merge with an existing step in the undo and redo menu items.
destructor.
z For documentation on notification: ISubject and IObserver in the API Reference.
z If you need error codes set by your command to map onto strings that describe the error
condition: “Error string service” on page 88.
z For sample code: kBPISetDataCmdBoss, BPISetDataCmd, and BPIHelper from the
BasicPersistInterface plug-in.
Error string service
Description
Suppose error conditions can occur in your plug-in, and you need to inform the user of these
errors.
Architecture
Sometimes, error conditions can occur in your plug-in, often within commands or command
sequences. When control is returned to the application by your plug-in, and the global error
code is set to something other than kSuccess, the user is informed and the model is reverted
back to the last consistent state. An error string service provides the ability to map error codes
onto error strings. To handle this, follow these steps:
z You Provide an error string service. This service allows ODFRez resources in your plug-in
to map error codes onto strings that describe the error. Detailed documentation on how to
define the error codes, resources, and implementations involved is the API Reference for
IErrorStringService.
88
z On detecting the error condition, call ErrorUtils::PMSetGlobalErrorCode to set your error
and return control to the application. The application informs the user of the error.
See also
For sample code: BPIErrorStringService from the BasicPersistInterface plug-in.
Persistent interface
Description
Suppose you need to add custom data to an existing object in the model and have that persist.
For example, you want frames in a document to have a set of custom properties that you control.
Architecture
The state of a persistent interface that is associated with a database that supports undo is captured by the application before it is changed; its state is reverted on undo and restored on redo.
The state is saved in the command history (see “Command history” on page 80) for undo and
redo, and in the database for persistence. Figure 17 shows the typical sequence of calls made to
modify persistent interface, and when that modification subsequently is undone or redone.
Some entity
processes a
command which
results in a
persistent interface
being modified. The
persistent interface
calls PreDirty before
mutating the data,
which allows the
application core to
snapshot the state of
the interface for the
command history
before it is changed
(to allow the
modification
to be
undone), and to
require it to be
streamed to the
database afterwards.
The application calls on
the persistent interface to
stream its data to the
database.
On undo, the command
history reverts the
persistent interface to the
state it had before the
modification was made.
On redo, the command
history restores the
persistent interface to the
state it had after the
modification was made.
ProcessCommand
DoImmediate
Set
PreDirty
PreDirty
ReadWrite
Push
Modify
ReadWrite
Undo
ReadWrite
Redo
ReadWrite
Extension patterns
FIGURE 17Sequence of calls to a persistent interface
90
Commands
Extension patterns
To implement a persistent interface, follow these steps:
1. Aggregate your interface on the boss class in the model to which you want to add custom
data that persists. Use an add-in interface (an ODFRez AddIn statement) if you are adding
the interface to an existing boss class. To define a new type of persistent boss, see “Persistent
boss” on page 91.
2. Provide an abstract interface that uses IPMUnknown as a base class.
3. Provide an implementation of this abstract interface.
4. Use CREATE_PERSIST_PMINTERFACE to make your implementation class available to
the application (instead of CREATE_PMINTERFACE).
5. Define a ReadWrite method for your implementation class. It gets called to serialize your
data when needed.
6. Call PreDirty within mutator methods before changing member variables that need to be
serialized. This gives the application the ability to recognize that this interface is about to
change.
7. Call mutator methods to update the state of your interface. The application calls your ReadWrite method to serialize your data when needed.
NOTE: If your interface persists in a document, you must consider what should happen when
users who do not have your plug-in open documents that contain your plug-in’s data.
See the section on missing plug-ins in the “Persistent Data and Data Conversion”
chapter.
See also
z “Command” on page 86.
z For sample code: IBPIData, BPIDataPersist and BPISetDataCmd from the BasicPersistInt-
erface plug-in.
z For documentation on persistence: the “Persistent Data and Data Conversion” chapter.
Persistent boss
Description
Suppose you need to add a new type of persistent object to the model. For example, you need to
add a list of custom style objects to defaults.
Architecture
To implement a persistent boss that has a UID, follow these steps:
1. Define a new boss class.
2. Aggregate IID_IPMPERSIST, and use the kPMPersistImpl implementation provided by the
API.
Commands91
Commands
Extension patterns
3. Add persistent interfaces to the boss to store your custom data. See “Persistent interface” on
4. Choose a boss class to own the instances of your new persistent boss. Add a persistent inter-
NOTE: If your boss persists within a database that supports undo, such as a document or
See also
z For sample code: kPstLstDataBoss and PstLstUIDList from the PersistentList plug-in.
z For documentation on persistence: the “Persistent Data and Data Conversion” chapter.
z “Command” on page 86.
page 89.
face into the boss class you have chosen that stores the UIDs of the new persistent boss. For
example, to add custom styles to defaults, add this interface into kWorkspaceBoss.
defaults, you must create, modify, and delete the persistent boss using commands.
Implement a create command to allocate a new UID for each new instance of the
peristent boss, and store this UID in an interface on boss that owns it. The delete
command deletes all child UIDs owned by the persistent boss, removes all references to
the persistent boss from the model, then deletes the UID.
Snapshot interface
Description
Suppose you have an object containing transient data, which depends on model objects that
persist in a database that supports undo, and:
z Your object needs to be updated when the model objects are modified initially and on any
subsequent undo or redo.
z You cannot use lazy notification to update your object, because it must be kept up to date
with changes to the model at all times.
z Your object must be updated on undo or redo, before observers get called.
For example, the text state (see the ITextState interface) caches the text attributes that are
applied to the “text caret”; the next text insertion uses these attributes. This cache is implemented as a snapshot interface and maintained on undo and redo.
Architecture
The state of an snapshot interface is captured by the application before it is changed; its state is
reverted on undo and restored on redo. The state is saved in the command history (see “Com-
mand history” on page 80) for undo and redo. Figure 18 shows a typical sequence of calls for a
snapshot interface when it is modified, and when that modification is subsequently undone or
redone.
92
FIGURE 18Sequence of calls for a snapshot interface
Application coreICmdHistoryAn ObserverA Snapshot
Interface
Some change causes an observer to
be called which results in a snapshot
interface being modified. The
snapshot interface calls PreDirty
before mutating the data, which
allows the application core to
capture the state of the interface for
the command history before it is
changed
(to allow the modification
to be undone).
On undo, the command history is
invoked. This leads to a
SnapshotReadWrite call on the
snapshot interface, to revert the state
of the interface back to the was it
was before the modification.
Similarly, on redo, the snapshot
interface has the SnapshotReadWrite
method called to restore the state to
the way it was after the modification.
Update
Set
PreDirty
PreDirty
SnapshotReadWrite
Push
Modify
Undo
Undo
SnapshotReadWrite
Redo
Redo
SnapshotReadWrite
Commands
Extension patterns
Often, an observer or responder is used to modify a snapshot interface, as shown in Figure 18.
Before changing any data, the snapshot interface implementation calls PreDirty, indicating to
the application that the SnapshotReadWrite method should be called to capture the state of the
interface (this state is associated with the CmdStackItem for the current sequence)). On undo
Commands93
Commands
Extension patterns
or redo, the SnapshotReadWrite method is invoked with the data associated with that particular sequence on the undo/redo menu.
To implement a snapshot interface, follow these steps:
1. Add your interface to a persistent boss class that is part of the model on whose state your
2. Provide an abstract interface that uses IPMUnknown as a base class.
3. Provide an implementation of this abstract interface.
4. Use CREATE_SNAPSHOT_PMINTERFACE to make your implementation class available
5. Define a SnapshotReadWrite method for your implementation class. It gets called to serial-
6. Call PreDirty within mutator methods before changing member variables you serialize in
interface depends. Check that this class aggregates IPMPersist, since the boss must have a
UID to support a snapshot interface. For example, if your object depends on objects in a
document, you might choose kDocBoss.
to the application (instead of CREATE_PMINTERFACE).
ize snapshots of your data when needed.
SnapshotReadWrite.
7. When the model objects you depend on are changed, call mutator methods to modify the
state of your interface. You might need to track the change to the object using regular notification. The application takes a snapshot of your interface, reverts the state on undo, and
restores it on redo.
NOTE: Even though this extension pattern is very similar in its implementation to a persistent
See also
z For sample code: LnkWtchCache in the LinkWatcher plug-in and GTTxtEdtSnapshotInter-
fa ce in th e G oTo L as tTe xt Ed it plu g- in .
z The “Notification” chapter.
Inval handler
Description
Suppose you have an object that depends on model objects that persist in a database that supports undo, and:
z Your object must be updated at undo and at redo when the model objects it depends on
change.
interface, its effect is very different. The information in your snapshot interface is
transient and not saved persistently with a document, defaults, or whatever other model
it is associated.
94
z You cannot use lazy notification (see the “Notification” chapter) to update your object,
because you need it to be kept up to date with changes to the model at all times.
Commands
Extension patterns
z You cannot use a snapshot interface (see “Snapshot interface” on page 92) because you need
to do more than restore the state of an object within the application.
z You need to program behavior that runs at undo and redo.
NOTE: This extension pattern is very rare. This is an advanced pattern and should be used only
if absolutely necessary.
For example, an observer might watch a subject that can be deleted from the model. Typically,
such observers are attached to the subject when it is created and detached just before it gets
deleted. If an object is created, the observer is attached. If there is an undo at this point, the
observer should be detached; a subsequent redo should result in the observer being reattached. Similarly, if an object is deleted, the observer is detached. At this point, an undo
should result in the observer being attached; a subsequent redo should result in the observer
being detached.
Consider an observer that attaches to a spread (see kSpreadBoss). When a spread is created, the
observer gets attached to the new spread. If the creation of the spread is undone, this observer
must be detached before the undo takes place. If the creation of the spread is redone, the
observer must be re-attached after the redo takes place. The inval handler extension pattern
allows for this. The GoToLastTextExit plug-in provides sample code that shows how to use an
inval handler to manage the attachment and detachment of an observer that watches stories in
a document.
Architecture
Inval handlers allow plug-in code to be called at undo and redo. There are two parts to the
mechanism:
z An inval handler (see IInvalHandler) that registers interest in a database that supports
undo.
z An inval cookie that gets called on undo and redo. The inval cookie is created by the inval
handler at the end of a transaction that contained changes in which the plug-in was interested.
Inval handlers are used in situations where a plug-in needs to achieve more than the restoration
of the state (through persistent interfaces and snapshot interfaces), and the lazy notification
broadcast occurs too late to meet this need. The plug-in has code that must be called immediately at undo or redo.
To implement an inval handler, follow these steps:
z Provide an implementation of an inval handler (see the IInvalHandler class in the API Ref-
erence for details. See GTTxtEdtInvalHandler for sample code).
z Provide an implementation of an inval cookie (see the IInvalCookie class in the API Refer-
ence for details. See GTTxtEdtInvalCookie for sample code).
z Create an instance of the inval handler, and call DBUtils::AttachInvalHandler to associate
this instance with the database that persists the objects in which you are interested. The
scope of this association can be defined by the lifetime of the database or the enabling of
particular functionality. For example, an inval handler might be attached to a database
when a document is opened.
Commands95
Commands
Extension patterns
z Once an inval handler is attached to a database, call DBUtils::StartCollectingInvals to indi-
z The plug-in code records the semantics of the change. Where this is recorded is implemen-
z Further changes of interest can occur within the same transaction. The plug-in code should
z At the completion of the transaction, the IInvalHandler::EndCollectingInvals method is
z The inval cookie is called on undo and redo of the transaction.
cate the inval handler is interested in participating in undo and redo. This usually occurs on
some event (through an observer or some other means). The StartCollectingInvals call
informs the database that the inval handler should be called at the end of the current transaction. If an undoable transaction is ongoing, the inval handler is said to be “collecting
invals.”
tation-dependent: it could be in the state associated with the inval handler (recommended),
an inval handler cookie, or elsewhere.
record the semantics of the changes; for example, by adding state to a list within the inval
handler.
called. The inval handler can return an instance of an inval cookie representing the
change(s) of interest that occurred. This inval cookie is kept in the command history for
this database transaction. The inval handler is now said to be “not collecting invals.”
z When the lifetime of an inval handler is over, call DBUtils::DetachInvalHandler to detach
the inval handler from the database. For example, an inval handler might be detached from
a document database when a document is closed.
Figure 19 shows the lifetime of a typical inval handler. Figure 20 shows the typical sequence of
calls made to an inval cookie on undo. On undo, any inval cookies that were previously associated with the CmdStackItem are called. The inval cookie setting the error state aborts the undo
(the model is reset to the state from before the undo).
An Inval HandlerApplication coreAn Inval CookieICmdHistory
New events in the model of interest for undo / redo cause the above sequence to be repeated and a new inval
cookie to be associated with the transaction.
An inval handler is created and associated
with the database that persists the objects
of interest. For example, an inval handler
might be attached when a document is
opened.
More events of interest for undo / redo can occur within the same transaction. The inval handler must be called to
record each event. At the end of the transaction, EndCollectingInvals is called. The inval handler returns the cookie
to be
called on undo and redo of the transaction. See sequence diagram showing inval cookie calls.
When the lifetime of an inval handler is over,
DetachInvalHandler is called to detach the
inval handler from the database. For
example, an inval handler might be
detached when a document is closed.
StartCollectingInvals is called to indicate
that the inval handler is interested in
participating in undo and redo of a
transaction. This usually occurs on some
event (through an observer, or some other
means).
Event that causes attach to the model
new
DBUtils::AttachInvalHandler
Event in the model of interest for undo / redo
AddInvalInfo(info)
bool16= DBUtils::StartCollectingInvals
IInvalCookie*= EndCollectingInvals
new
AddInvalInfo(vector of info)
Push
Event that causes detach from the model
DBUtils::DetachInvalHandler
delete
Commands
Extension patterns
Commands97
Commands
Application coreICmdHistoryAn Inval CookieModel
Prior to the model state being
reverted, all inval cookies are
called.
After all inval cookies are
called, the model is reverted
(assuming no error).
After the model has been
reverted, all inval cookies are
called. Setting error here will
undo the "Undo".
Undo
ErrorCode= InvalBeforeUndo
ReadWrite / SnapshotReadWrite
ErrorCode= InvalAfterUndo
Extension patterns
FIGURE 20Inval cookie calls on undo
There are times where the lifetime of an inval handler does not match that of the command history. Inval handlers can be attached and detached at any time, asynchronously with anything
else happening in the model. This means there are entries in the command history that do not
have corresponding inval cookies for a particular inval handler. In this situation, the inval handler’s BeforeRevert_InvalAll and AfterRevert_InvalAll methods are used to provide the opportunity to rebuild the required state directly.
There are times when an inval cookie instance can be asked to merge another instance. This
causes the IInvalCookie::Merge method to be called, to merge cookies that were returned by
each call to IInvalHander::EndCollectingInvals within one undoable transaction. For example,
this can happen at the end of an abortable command sequence.
See also
z IInvalHandler, IInvalCookie, DBUtils::AttachInvalHandler, DBUtils::StartCollectingInvals,
and DBUtils::DetachInvalHandler in the API Reference.
z For sample code: GTTxtEdtInvalHandler in the GoToLastTextEdit plug-in.
z For documentation on how to detect change in other objects using observers and respond-
ers: the “Notification” chapter.
98
Snapshot view interface
Description
Suppose you have a user interface object like a widget, which has data that depends on model
objects that persist in a database that supports undo, and:
z Your data needs to be updated when the model objects it depends on are modified or when
modifications are undone or redone.
z You cannot use lazy notification to update your data, because it must be kept up to date with
changes to the model at all times.
NOTE: Use of this pattern is extremely rare in the application codebase. For example, it is used
by interface ILayoutControlData on the layout widget. The data in this interface is
depended on by many other objects and always must be kept in tight synchronzsation
with the database. This is an advanced pattern and should be used only if absolutely
necessary.
Architecture
A snapshot is taken of the state of a snapshot view interface, before it is changed, reverted on
undo, and restored on redo. It is very similar to a snapshot interface (see “Snapshot interface”
on page 92). The distinction is that the database with which a snapshot interface is associated is
fixed and implicitly defined by the persistent boss on which it is aggregated. A snapshot view
interface, on the other hand, is part of a user interface object and must explicitly identify the
database containing the model objects on which it depends. Also, it is not one specific database. For example, a widget that tracks some state in the front document must change the database on which its snapshot view interface depends, when the front document changes.
Commands
Extension patterns
To implement a snapshot view interface, follow the steps described under CViewInterface in
the API Reference.
See also
The CViewInterface template class in the API Reference.
Commands99
Commands
Extension patterns
100
Loading...
+ hidden pages
You need points to download manuals.
1 point = 1 manual.
You can buy points or you can get point for every manual you upload.