Issue
How do I support my custom preferences for an InDesign document?
Solution
Adobe(R) InDesign(TM) stores plug-in preferences (dialog settings, etc.) with either the application workspace (in a special application preference file) or a specific document (with the object affected). Your plug-in uses saved preferences to revert to default settings if the application preference file or a document becomes damaged. When Adobe InDesign first launches and goes through its initialization (registering all installed plug-ins, import/export filters, etc.), the default settings for your plug-in are stored in the application preference file. Then, after the user has worked with your plug-in and customized settings for a particular object (for example, a document itself), Adobe InDesign records the changes and saves them with the affected object, as well in the application preference file.
If the Adobe InDesign preference file is damaged, the application repeats its initialization process at the next startup. When the user opens the changed object for the first time, the changed preferences are restored and rewritten to the Adobe InDesign preference file.
Adding preferences to your plug-in is a special case of adding persistent data. The InDesign 1.x SDK includes an XYGuides example plug-in that adds preferences to both the application workspace and a document workspace. The InDesign 2.x and CS SDKs include the CustomPrefs example plug-in that does something similar.
Here is a quick overview of the process to use in supporting preferences in your plug-in.
1. Create bosses supporting preference data (persistent).
2. Create a preference data implementation that has accessor methods and the required read/write method.
3. Create a set of commands to manipulate the preference data. Remember that all persistent data is part of the object model. Thus a view (dialog or panel) will need commands to manipulate the data.
4. Create the UI elements to get the preference data and fire the commands to change the data (step 3).
5. Create a resource to tell Adobe InDesign how to handle warnings if the preferences are in a file but the plug-in is not installed.
6. Create a facility for handling data conversion as you add new preferences to new versions of your plug-in.
7. (Optional) Make some observers to watch the data and take action, based on the modified preferences.
Step 1
Add a new data interface to the application and document workspace bosses.These bosses already aggregate the IPMPersist and ISubject interfaces required for persistent data. Define your resources as:
/* if you are using InDesign 1.x - from SimplePrefs */
ResourceAddInClas (10, "", purgeable)
BeginClas(kWorkspaceBoss),
kInvalidClass,
OpenArray
IID_ISIMPLEPREFS, kSpPrefsImpl,
CloseArray
EndClas
ResourceAddInClas (11, "", purgeable)
BeginClas(kDocWorkspaceBoss),
kInvalidClass,
OpenArray
IID_ISIMPLEPREFS, kSpPrefsImpl,
CloseArray
EndClas
/* if you are using InDesign 2.x and CS - from CustomPrefs - */
AddIn
{
kWorkspaceBoss,
kInvalidClass,
{
IID_ICUSTOMPREFS, kCstPrfImpl,
}
},
AddIn
{
kDocWorkspaceBoss,
kInvalidClass,
{
IID_ICUSTOMPREFS, kCstPrfImpl,
}
}
Step 2
The implementation for the preference data is basically like any other persistent data implementation. Let's look at a simple example with only a few member variables to hold the preference data, accessor functions, and a read/write method.
/* code taken from SimplePrefs sample in InDesign 1.x */
class SpPrefs : public ISpPrefs
{
public:
SpPrefs(IPMUnknown *boss);
virtual ~SpPrefs();
virtual void SetValue(const bool16& newVal );
{
if ( fSpPrefState != newVal )
{
fSpPrefState = newVal;
Dirty();
}
}
virtual const bool16& GetValue()
{ return fSpPrefState; }
virtual void ReadWrite(IPMStream *s, ImplementationID prop)
{ s->XferBool(fSpPrefState); }
DECLARE_HELPER_METHODS()
private:
bool16 fSpPrefState;
};
One useful thing to do in the constructor is to copy application preferences into the document preferences:
CREATE_PERSIST_PMINTERFACE(SpPrefs, kSpPrefsImpl)
DEFINE_HELPER_METHODS(SpPrefs)
SpPrefs::SpPrefs(IPMUnknown *boss) :
HELPER_METHODS_INIT(boss)
{
// Default to a value for the InDesign workspace
fSpPrefState = kFalse;
// What boss is this impl part of?
ClassID idOfThisObj = GetClass( this );
// Initialize the pref variable depending on whether
// this impl is for the InDesign workspace or a document workspace
if ( idOfThisObj == kDocWorkspaceBoss )
{
// This preference is for a document, init from InDesign workspace
InterfacePtr<IWorkspace> wrkSpc (gSession->QueryWorkspace());
InterfacePtr<ISpPrefs> wrkSpcPrefs (wrkSpc, IID_ISIMPLEPREFS);
if ( wrkSpcPrefs == nil )
{
// Can't get the InDesign workspace prefs, assert and go on
PMAssertFail("Invalid workspace prefs in SpPrefs::SpPrefs()");
}
else
{
// Set the doc pref value to the InDesign workspace pref value
fSpPrefState = wrkSpcPrefs->GetValue();
}
}
}
Step 3
A command would have a boss resource definition such as:
/* if you are using InDesign 1.x */
ResourceClas (12, "ModSimplePrefsCmdBoss", purgeable)
BeginClas(kModSimplePrefsCmdBoss),
kInvalidClass,
OpenArray
IID_ICOMMAND, kModSimplePrefsCmdImpl,
IID_IUIDDATA, kUIDDataImpl,
IID_IBOOLDATA, kBoolDataImpl,
CloseArray
EndClas
/* if you are using InDesign 2.x and CS */
Class
{
kModSimplePrefsCmdBoss,
kInvalidClass,
{
IID_ICOMMAND, kModSimplePrefsCmdImpl,
IID_IUIDDATA, kUIDDataImpl,
IID_IBOOLDATA, kBoolDataImpl,
}
}
The IUIDData interface carries the UID reference (UIDRef.h) information describing the preferences interface. Using this reference allows one command to work on both the application workspace boss and the document workspace boss. The IBoolData interface holds the new value for a preference.
As illustrated below, the Do() method for the command implementation acquires the interfaces, saves an old preference value in the command member variable for undo, and sets a new value. The Undo() and Redo() methods use very similar code.
// Get a pointer to the command's (boolean) data interface:
InterfacePtr<IBoolData> cmdBoolData (this, IID_IBOOLDATA);
// Get a pointer to the command's UIDData interface that describes
// the preferences interface:
InterfacePtr<IUIDData> cmdUIDData (this, IID_IUIDDATA);
// Use the UIDRef to get the simple prefs interface for the workspace we're changing:
InterfacePtr<ISpPrefs> simplePrefs(cmdUIDData->GetRef(), IID_ISIMPLEPREFS);
// Copy the old preferences value into the command's member variable for undo:
fspOldPref = simplePrefs->GetValue();
// Set the new preference value:
simplePrefs->SetValue(cmdBoolData->GetBool());
Step 4
As an example of a UI element, let's use a dialog that reads the preferences directly. Use the preference data (ISpPrefs) accessor methods in an implementation of IDialogController::InitializeFields(), and fire the preference modifier commands in IDialogController::ApplyFields(). If using two commands for modifying preferences for both application and the document workspaces, be sure to use a command sequence to represent both commands. (See IDialogController.h.)
NOTE: For preferences, you can add a customized panel to the Adobe InDesign selectable dialog for preferences.
Step 5
If the user opens a document that contains data created by a plug-in that is not installed, Adobe InDesign gives a warning. Your plug-in is automatically set to a default priority indicating that it writes default warnings only. You can override this setting to provide critical or ignored warnings to the user. Do this by adding a resource to the boss definition file for the plug-in. For example, to make data in the preferences plug-in critical, add the following resource. If you want the plug-in to write ignore warnings, use the same resource format but with the ResourceIgnoreTags (InDesign 1.x) or IgnoreTags (InDesign 2.0 and above) resource.
/* InDesign 1.x */
ResourceCriticalTags (1)
BeginTags
kImplementationIDSpace,
OpenArray
kSpPrefsImpl,
CloseArray
EndTags
/* InDesign 2.x and CS */
resource CriticalTags (1)
{
kImplementationIDSpace,
{
kSpPrefsImpl,
}
};
NOTE: The use of kImplementationIDSpace specifies that the IDs that follow are implementation IDs, not class IDs. If you want to set warning type for class IDs, use kClassIDSpace instead of kImplementationIDSpace. You can put any number of IDs in the list following the k*IDSpace statement, but all the IDs must be of the same type.
Please also be sure to read the WARNING on KB Document 50054.
Step 6
Data conversion is described in the Adobe InDesign Programming Guide, Persistence chapter. This chapter includes information on persistence, criticality, and data conversion.
Step 7
Set up observers on the preference data and have the rest of your plug-in take appropriate action. You might want to use a responder (IResponder) to attach document preference observers to each new/opened document.