Programming Guide: Part Persistency

# Programming Guide

## Part Persistency

The part persistency recipes are listed as follows:

- "Multiple Kind Support"
- "Part Storage Model"
- "Part Init and Externalizing"
- "Part Info 'Ternalization"
- "Display Frame 'Ternalization"
- "Lazy Frame Internalization"

### Multiple Kind Support

OpenDoc provides a structured storage model which enables part editors to persistently store multiple representations of their content. To present this feature in a simple and easy to use manner to users, part editors must follow a few rules in general, and be more particular in their implementation of a handful of their methods. This document contains the list of part editor responsibilities, and sample code which would allow a part editor to fulfill these responsibilities.

#### Terminologies

The terms you need to be familiar with are:

Kind | Specific data format or type
Category | Set of similar kinds. A kind can be in multiple categories.
Preferred kind of a part | The preferred kind of a part designates the data format which the editor running the part must use to 'ternalize the part's content. The preferred kind is stored in kODPropPreferredKind property of the part's storage unit. If the kODPropPreferredKind property does not exist, then the editor can presume that the preferred kind of the part is the value type of the first value in the contents property (kODPropContents) of the part's storage unit.
Registration | Registration of a part handler. See the "Dynamic Binding" recipes for more information.

#### Standard Kinds

Standard kinds are those data formats which, either through an official decree or by some de facto means, have become widely used and accepted. Here are some examples:

Industry Standard Kinds | ASCII, TIFF, JPEG, etc.

Supporting a standard kind has many advantages.

- It permits your part editor to support a large amount of the content already out there.

- It permits users to read old documents with your part editor.

Supporting a standard kind means there is a greater chance that your editor can read parts created by other editors. This enables users to share documents created by your part editor.

#### Coding Required

To correctly support multiple kinds, there are methods your editor needs to ensure proper behavior.

##### InitPart

This method is called when stationery is created. Your editor should make sure it does the following:

1. Set the preferred kind. Write the appropriate ISO string into the kODPropPreferredKind property of your part.

2. Create a value whose type is that kind in the contents property

3. Initialize the content value with valid initial contents. Often leaving the value empty is a valid; it depends on the particular data format. Alternatively, perhaps as an optimization, your editor may just set an internal dirty flag, and then perform the above three steps in its externalize method when it realizes there is no contents property.

##### InitPartFromStorage

This method is called when a part is read back in from an existing document. This happens whenever a user opens a document or creates a new document. Your editor should make sure it does the following:

1. Get the preferred kind. Read the value from the kODPropPreferredKind property of your part. If no property exists, then use the value type of the first value of the contents property as the preferred kind. Cache the preferred kind in an instance variable.

2. Focus your part's storage unit to the value of the contents property whose value type is the preferred kind.

3. Read the contents of that first value and create the in-memory data structures necessary to represent that content.

   Note:

   > It is possible that your editor was bound to a part which previously had a different editor. In this case, the binding subsystem will automatically notify the user. In this case, if your editor does not support the preferred kind, then use the highest fidelity kind in the contents property which your editor does support as the de facto preferred kind. Do not update the preferred kind property until externalize or ChangeKind is called on your part.

##### Externalize

This method may be called at any time. Depending on the Save model of the current document, and the idle-time optimizations which may or may not be present, your part may be told to externalize only when the user Saves a document, or as often as every minute. The point is, do not assume that when you are told to externalize, that it is for any one particular reason. As an optimization, your editor should probably keep an fDirty flag which is set whenever the user changes the part's content, and cleared whenever Externalization is completed. If your fDirty flag is clear then your externalize method should be a no op. Your editor should make sure it does the following:

1. Clean up the storage unit. You need to prepare the storage unit for clean externalization from your part editor. You should only have to do this the first time externalize is called on your part whenever it is brought into memory. Remove any values that are not being updated. Specifically, this means remove any values which have value types/kinds which your editor does not support, or are of lower fidelity than kinds which you wish externalize.

2. Add values if necessary. Use AddValue to create or recreate the value types you wish to externalize in proper fidelity order (first value, highest fidelity, to last value, lowest fidelity). Fidelity ordering is important, because OpenDoc binding and translation use the ordering of the values in the contents property to determine which value is probably of the highest fidelity to determine which editor would best edit any given part. These first two steps are called prepping your storage unit; there will be examples in other methods where you need to reprep your storage unit.

3. Externalize your content in the format of the preferred kind. Do not write a different kind. There are ways for a user to explicitly change the preferred kind of a part. These will be discussed later. For now keep in mind that only the user should be changing the preferred kind of a part, and only then through an explicit action.

4. OPTIONAL: Write out alternative kinds. As discussed above, your editor may want to write out one or more kinds in addition to the preferred kind. The typical part editor should by default only write out either the one preferred kind, or the preferred kind, and one interchange (or standard) kind. If you want your editor to write out many more representations than that, we recommend that the editor present a user interface in their settings or preferences dialog to allow the user to pick a set of kinds which your editor externalizes by default.

##### ExternalizeKinds

This method is called when the user does a Save a copy as multiple formats, or when a data interchange utility goes through a document asking all the parts to externalize themselves in a set of kinds extracted from a set of standard interchange kinds. Your editor should make sure it does the following:

1. Externalize the set of kinds specified. Make sure that the fidelity ordering of the values in your contents property is maintained by creating the values for these kinds in the right order. You may need to reprep your contents property and recreate the values in order to make sure they are in the appropriate fidelity ordering. Be sure to write out these kinds in addition to the preferred kind, not instead of the preferred kind.

2. Ignore unsupported kinds. Ignore any kinds in the set which you do not support.

##### ChangeKind

This method is called when the user changes the preferred kind of a part. This is usually done from the Part/Document Properties notebook, although do not assume it is the only user interface that can cause this method to be called. Your editor should make sure it does the following:

1. Externalize the part in the given kind. Make sure that the fidelity ordering of the values in your contents property is maintained by creating the values for these kinds in the right order. You may need to reprep your contents property and recreate the values in order to make sure they are in the appropriate fidelity ordering.

2. Set that kind to be the preferred kind. Write the given kind into the preferred kind property of the part.

##### CloneInto

This method is similar to externalize. It is usually called in response to a data interchange action such as using the clipboard or drag and drop. See the "Data Interchange" recipes for precise detail about implementing the CloneInto method. For the purposes of the multiple kind support however, make sure your editor does the following:

1. Write the same kinds as if you were Externalizing plus any standard kinds you support. It is more important to write out standard kinds during CloneInto than externalize because when your CloneInto method is called, it is more likely that your part is in the middle of a data interchange operation, and should therefore be trying extra hard to enable the user to move content to a different editor or application.

2. SetPromiseValue for each kind if you are using promises.

#### Resources Required

Your part editor tells OpenDoc what kinds it supports in response to its clsGetODPartKinds metaclass method. This method should return a sequence of PartKindInfo structures that contain the appropriate information for each part kind.

#### Multiple Kinds User Interface Description

Earlier we stated that the user should be the only thing which changes the preferred kind of a part. Here is how they do it. To change the preferred kind of an embedded part, the user does the following:

1. Select the part.
2. Choose Selection Properties from the Edit menu.
3. Select from the choices in the "Kind:" listbox of the Type page.
4. Optionally change the editor; some editors work better than others when it comes to certain kinds.

To change the preferred kind of the root part document, the user does the following:

1. Choose Document properties from the Document menu.
2. Select from the choices in the "Kind:" listbox of the Type page.
3. Optionally change the editor; some editors work better than others when it comes to certain kinds.

In any discussion of preferred kinds, it makes sense to also describe preferred editors. Using the OpenDoc Preferences notebook the user can set a particular editor to be the preferred editor for a particular kind. Whenever the user encounters a part of that kind, whose previous editor is unavailable, the user set preferred editor for that kind is used to run that part. Similarly, the user can set a particular editor to be the preferred editor for a whole category, such as Other Plain Text. This means that whenever the user encounters a part whose kind is in the Plain Text category, whose previous editor is unavailable, and there is no preferred editor for that kind, the user set preferred editor for that category (Plain Text) is used.

### Part Storage Model

Parts, like all other persistent objects have a storage unit in which they can store their content data and state persistently. However, parts are intimately involved in actions like binding, translation, data interchange (clipboard, drag and drop). As a result, parts need to satisfy many more requirements than the other persistent objects, and so they need to be more disciplined about how they use their storage units.

#### Rules and Requirements

1. Parts need to be able to store multiple representations of their content, ordered by fidelity.

2. Each representation of a part's content must be a full representation, that is it cannot just be a delta of another representation.

3. It must be possible to extract exactly one representation from the part's storage unit, without understanding the format of any of the representations in the part.

4. It must be possible to remove all but one representation from the part's storage unit, without understanding the format of any of the representations in the part.

5. It must be possible to distinguish annotations/meta-information on/about the part, and content of the part.

6. Annotations/meta-information on/about a part may go away at any time, independent of the part editor, and therefore cannot be depended on to store critical content. However, they can be used to store non-critical optimizations such as caches.

7. Property names are ISO strings which constrain the naming conventions we can place on properties to that of ISO strings. The only naming conventions we can apply to ISO strings is that of prefix ownership, that is TC corporation may decide and control the format and meaning of all ISO strings beginning with TC.

8. To achieve document interoperability across various platforms, in particular between machines with different endianess, parts have to adhere to the following guidelines:

   Values written out by parts must be in Little Endian format regardless of the endianess of the machine. OpenDoc provides a set of utility functions for writing standard type values to storage units. These functions will perform the data conversion to Little Endian format when necessary. The functions are:

     ODSetBooleanProp
     ODSetUShortProp
     ODSetSShortProp
     ODSetULongProp
     ODSetSLongProp
     ODSetISOStrProp
     ODSetTypeListProp
     ODSetITextProp
     ODSetTime_TProp
     ODSetPointProp
     ODSetRectProp
     ODSetStrongSURefProp
     ODSetWeakSURefProp
     ODSetPolygonProp
     ODSetMatrixProp

   Alternatively, if a part needs to write out values to the storage unit without using the functions listed above, OpenDoc provides a set of conversion macros. These macros have to be called BEFORE writing out the values to the storage unit. Following are the macros that have to be called:

     ConvertODUShortToStd
     ConvertODSShortToStd
     ConvertODULongToStd
     ConvertODSLongToStd
     ConvertODUShortArrayToStd
     ConvertODSShortArrayToStd
     ConvertODULongArrayToStd
     ConvertODSLongArrayToStd
     ConvertODSShortToStd
     ConvertODSShortToStd
   Following is an example of using the conversion macro to write data of signed short type to the current focus value of the storage unit:

       ODSShort value = 12;
       ODSUForceFocus(ev, storageUnit, property, valueType);
       value = ConvertODSLongToStd(value);
       StorageUnitSetValue(storageUnit, ev, sizeof(ODSShort), &value);

   Similarly, values extracted from the storage unit have to be converted on a Big Endian machine before they can be used. Again, OpenDoc provides both utility functions and conversion macros for parts to use.

   The utility functions read standard type values from storage units and will format the data in Big Endian format if necessary. They are:

     ODGetBooleanProp
     ODGetUShortProp
     ODGetSShortProp
     ODGetULongProp
     ODGetSLongProp
     ODGetISOStrProp
     ODGetTypeListProp
     ODGetITextProp
     ODGetTime_TProp
     ODGetPointProp
     ODGetRectProp
     ODGetStrongSURefProp
     ODGetWeakSURefProp
     ODGetPolygonProp
     ODGetMatrixProp

   If conversion macros are used, they should be called AFTER reading the values from the storage units. The list of conversion macros are:

     ConvertODUShortFromStd
     ConvertODSShortFromStd
     ConvertODULongFromStd
     ConvertODSLongFromStd
     ConvertODUShortArrayFromStd
     ConvertODSShortArrayFromStd
     ConvertODULongArrayFromStd
     ConvertODSLongArrayFromStd
     ConvertODSShortFromStd
     ConvertODSShortFromStd

   Below is an example of how to use the conversion macro:

        if (ODSUExistsThenFocus(ev, storageUnit, property, valueType);
        {
           ODSShort value;
           StorageUnitGetValue(storageUnit, ev, sizeof(ODSShort), &value);
           value = ConvertODSShortFromStd(value);
        }

#### Details of the Part Storage Model

As a result of the above rules/requirements, parts store their contents as follows:

- Every part creates a contents property, the constant for which (kODPropContents) is defined in StdProps.IDL.

- For every representation it wishes to store, the part creates a value, the value type of which is the kind of the representation.

  These values are ordered by fidelity, for example highest fidelity first, lowest fidelity last.

  Into each of these values, the part streams out its contents using the appropriate data format.

  One representation, one value.

No content should be stored outside of the contents property.

All other properties on a part's storage unit are for meta-information/annotations and should not contain content.

Example:

    property OpenDoc:Property:Contents
             value OSA:Kind:TestDrag
             value OSA:Kind:StyledText
             value OSA:Kind:PlainText

#### Questions and Answers

- Question:

  How do you tell the difference between annotations and content?

  Answer:

  There is often a misconception that there is a lot of overlap between annotations and content. In fact, it is difficult to come up with a precise definition which separates the two. However, more often than not, it is fairly easy to categorize any particular piece of information as annotation or content.

  Here are some questions to ask yourself about a piece of info X which you are unsure of whether it is annotation or content.

  - Would users be upset if they thought they had saved X and it disappeared?

  - Do you want X to be persistent across machines?

  - Do you want X to be persistent across editors?

  - Do you want X to be persistent across platforms (for cross-platform formats)?

  If you answered yes to any of the above, then X is content.

- Question:

  But cannot we just distinguish meta-information from content by using a property naming convention, and store them both as sets of top level properties?

  Answer:

  No, storing content outside the contents property would violate requirements 3 and 4 above. Also, placing a more restrictive naming convention on property names would violate requirement 7 above.

- Question:

  But what if a part wants to store a particular representation using more than one property in a structured way?

  Answer:

  Any part may store a particular representation in a structured fashion by requesting additional storage units from the draft and tying them together with references.

- Question:

  Would not it be more efficient to keep the multiple properties in the part's storage unit instead of requesting another storage unit?

  Answer:

  It is hard to be more efficient when it does not even work! That approach would violate requirements 3 and 4. It is not a question of efficiency, it is a question of functionality.

- Question:

  Usually when an application stores a document, it stores some data in the data fork of the document file and then a bunch of other information as resources in the resource fork. Isn't the part storage model more restrictive than this?

  Answer:

  No, the part storage model is only as restrictive as you want it to be. If, like most applications, your part uses only a single stream to store its contents, then your storage model fits right into the part storage model, you simply use a single value to store your contents. However, if you are using resources or some other form of structured storage, then you can ask your storage unit's draft object for additional storage units, and then store references to these storage units inside your primary content value. See the persistent reference document for more information about using storage unit references. It is important to note that because you can always ask for more storage units and then save references to them in any value, you can construct any kind of arbitrary graph structure for your storage representation. This is much more powerful than current day files and streams. Each additional storage unit can have multiple properties (you may think of these as multiple forks), and each property can have multiple values (again, different values within the same property should only be used for multiple representations of the same data).

  Also, note that:

  1. Most applications use only the data fork.
  2. On DOS, Windows, UNIX and any other major OS out there, the files have a single fork, and therefore their applications use only a single flat stream for storing document content. Cross platform applications which want document portability also use only the data fork on OS/2. This single flat stream to store one representation is the model we are leveraging. This the 90% (if not more) case of applications which are out there today.

### Part Init and Externalizing

This document contains some sample code showing how a part editor externalizes and internalizes itself.

The sample code shown here is for a very simple part editor. Its assumptions may not be valid for a part editor with more functionality:

- PartData is a Pascal string (Str255) which holds the part content or part Data.
- The sample code reads and writes the entire part Data from and to persistent storage.
- The sample code uses a fDirty flag which it sets to true when its contents change.
- The sample code uses a fSelf field to hang onto the part-wrapper object.

#### Proper Treatment of Permissions

The part is responsible for caching the draft permissions in InitPartFromStorage. The sample code does this by assigning a field:

    fPermissions = storageUnit->GetDraft(ev)->GetPermissions(ev);

in the InitPartFromStorage. Whenever the potential for changing user content exists, the part should check to make sure fPermissions >= kDPSharedWrite before changing any of the content. Also, as a side effect, if fPermissions < kDPSharedWrite then fDirty should never be able to become kODTrue, and as a result, the externalize method will never write to the contents property.

#### PartWrapper

In addition to a storage unit, InitPart and InitPartFromStorage methods on ODPart also takes an extra parameter, partWrapper. The partWrapper parameter represents a delegator object which OpenDoc uses to insulate the rest of OpenDoc from having to have a direct pointer to your ODPart object. This feature helps implement features such as being able to change the editor of part without closing and reopening the document. Plus, it makes an excellent bottleneck for platform developers. For the most part, all the part wrapper does is delegate methods of the part function to your ODPart object.

The part editor must save this parameter in the part's internal field; you will see this in the sample code below. Whenever an OpenDoc function requires the part editor to pass in an ODPart* representing the part, instead of passing in somSelf, the part editor must pass in partWrapper stored in the part's internal field. An example is when registering the part for idle time. A part editor should never pass somSelf in as a parameter to an OpenDoc function call.

#### Sample Code for Initialization

    // Includes the appropriate .XH files from OpenDoc
    #include <StorageU.xh>
    #include <StdProps.xh>
    #include <POUtils.h>
      ...

    // Define the part kind.
    // This should probably be in your part's ....def.H file which
    should
    // be #included  in your .r file for your nmap resources
    #define kMyPartKind "Sample:Kind:CMyKind"
      ...
    void MyPartInitPart(MyPart* somSelf,
                        Environment* ev,
                        ODStorageUnit* storageUnit,
                        ODPart* partWrapper)

    {
      ...
      // Call your parent's init routine
      parent_InitPart(somSelf, ev, storageUnit, partWrapper);

      // Set your dirty flag
      fDirty = kODTrue;

      // Save off the part-wrapper object
      fSelf = partWrapper;
    }

    void MyPartInitPartFromStorage(MyPart* somSelf,
                                   Environment* ev,
                                   ODStorageUnit* storageUnit,
                                   ODPartWrapper* partWrapper)

    {
      // Initialize the parent
      parent_InitPartFromStorage(somSelf, ev, storageUnit, partWrapper);

      // Focus to the value where the part Data is stored
      storageUnit->Focus(ev,
                         kODPropContents,
                         kODPosUndefined,
                         kMyPartKind,
                         0,
                         kODPosUndefined);

      // Get the size of the string
      fPartData[0] = storageUnit->GetSize(ev);

      // Get the string itself
      storageUnit->GetValue(ev, fPartData[0],&(fPartData[1]));

      // Save off the part-wrapper object
      fSelf = partWrapper;

      // Note the draft permissions, in order to avoid
      // writing to a read-only draft
      fPermissions = storageUnit->GetDraft(ev)->GetPermissions(ev);

      // Set the dirty flag to false
      fDirty = kODFalse;
    }

#### Sample Code for Externalization

    void MyPartExternalize(MyPart* somSelf,
    Environment* ev)
    {
      // Only externalize when the part has been updated.
      // Externalize the parent.
      parent_Externalize(somSelf, ev);

      if (fDirty)
      {
        // Get the part's storage unit
        ODStorageUnit* storageUnit = somSelf->GetStorageUnit(ev);
        if (storageUnit->Exists(ev, kODPropContents, kMyPartKind, 0))
        {
          // Focus to the desired value
          storageUnit->Focus(ev,
                             kODPropContents,
                             kODPosUndefined,
                             kMyPartKind,
                             0,
                             kODPosUndefined);
        }

        else
        {
          storageUnit->AddProperty(ev, kODPropContents);
          storageUnit->AddValue(ev, kMyPartKind);

          // Adding a property and value has the side effect of focusing
          // the storage unit to that value.

        }
        // Write out the part data
        storageUnit->SetValue(ev, fPartData[0],&(fPartData[1]));

        // Set the dirty flag to kODFalse to signify that the part is clean
        fDirty = kODFalse;
      }
    }

### Part Info 'Ternalization

Parts can attach part info data to their display frames. This data can be used to hold any information the part wants to associate with the display frame, and is stored persistently in the frame's storage unit. While the data is stored with the frame, the part editor is responsible for its 'ternalization, as the editor is the only entity that understands the structure and format of the part info.

#### Persistent Format

A frame's part info is stored in the frame's storage unit in the part info property. The standard name for this property is found in the constant kODPropPartInfo. Just as a part editor stores multiple representations of a part's contents in multiple values in the contents property, the editor should also store multiple representations of the part info which correspond to the representations of the contents. Some content formats may not have corresponding part info, especially interchange formats like JPEG or RTF. However, many editors will define formats with corresponding part info, and if your editor supports another editor's native format directly, it should also support the part info for that format.

The names of part info formats which are linked to a content format should be the same as the name of the content format, with the segment PartInfo appended to the end of the ISO name. For example, if your part content format was SurfSoft:SurfText 1.0 the part info should be SurfSoft:SurfText1.0:PartInfo.

For a part which externalized representations for the formats SurfText 1.0, RTF, ClarisWorks 3.0, and ASCII, its display frames should have part info with formats SurfSoft:SurfText1.0:PartInfo and Claris:ClarisWorks 3.0:PartInfo (assuming the interchange formats have no associated part info).

#### 'Ternalizing Part Info

There are three calls in the part function that deal with 'ternalization of part info:

- ReadPartInfo: internalizes the part info from persistent storage
- WritePartInfo: externalizes the part info into persistent storage
- ClonePartInfo: externalizes the part info as in WritePartInfo, but clones object references

In all of these calls, the frame passes a StorageUnitView which is focused to the frame's part info property, but not to any value within that property. Your part editor must get the view's storage unit, and focus it to value(s) in that property as necessary to read or write the formats it needs to.

#### When to Write

When externalizing a property of a persistent object, it is only necessary to write the data if it is the first time writing it, or if it has changed from the last time the object was externalized. As an optimization, you can keep an isDirty flag in a frame's part info, indicating whether it has changed from when it was internalized or last externalized. In WritePartInfo, if the flag is false, skip externalizing; if the flag is true, externalize the data and set the flag to false. Note that you must always externalize part info when cloning (in ClonePartInfo), as you are making a new copy.

Just as a part must mark the draft as dirty when its content needs to be externalized, it must also mark the draft dirty when part info needs to be externalized. The part should call draft->SetChangedFromPrev() when part info has changed and needs to be externalized. Note that changes to part info in a non-persistent frame should not mark the draft dirty.

#### Sample Code

This code 'ternalizes a simple part info structure. The only value in it is an RGB color describing the background color of the frame. The forDebuggingRGBColor intermediate value makes debugging easier.

    #include <ISOStr.h>
    #include <StorageU.xh>
    #include <SUView.xh>
    #include <StorUtil.h>
      ...
    ODInfoType CMyPart::ReadPartInfo(Environment* ev,
                                     ODFrame* frame,
                                     ODStorageUnitView* storageUnitView)

    {
      ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
      ODPropertyName propName = storageUnitView->GetProperty(ev);
      PartInfoRec* partInfo = kODNULL;

      if (ODSUExistsThenFocus(ev, su, propName, kKindSamplePartInfo))

      {
        partInfo = new PartInfoRec;

        RGBColor forDebuggingRGBColor ;
        StorageUnitGetValue(su,
                            ev,
                            sizeof(RGBColor),
                            (ODValue)&forDebuggingRGBColor);

        partInfo->bgColor = forDebuggingRGBColor;
        partInfo->isDirty = kODFalse;
      }
      SOMFree(propName);
      return (ODInfoType)partInfo;
    }

    void CMyPart::WritePartInfo(Environment *ev,
                                ODInfoType partInfo,
                                ODStorageUnitView* storageUnitView)
    {
      if (partInfo && partInfo->isDirty)
      {
        ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
        ODPropertyName propName = storageUnitView->GetProperty(ev);
        ODSUForceFocus(ev, su, propName, kKindTestDraw);

        RGBColor forDebuggingRGBColor ;
        forDebuggingRGBColor = ((PartInfoRec*)partInfo)->bgColor;
        StorageUnitSetValue(su,
                            ev,
                            sizeof(RGBColor),
                            (ODValue)&forDebuggingRGBColor);

        partInfo->isDirty = kODFalse;
        SOMFree(propName);
      }
    }

    void CMyPart::ClonePartInfo(Environment *ev,
    ODInfoType partInfo,
    ODStorageUnitView* storageUnitView)
    {
      if (partInfo)
      {
        ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
        ODPropertyName propName = storageUnitView->GetProperty(ev);
        ODSUForceFocus(ev, su, propName, kKindTestDraw);

        RGBColor forDebuggingRGBColor ;
        forDebuggingRGBColor = ((PartInfoRec*)partInfo)->bgColor;
        StorageUnitSetValue(su,
                            ev,
                            sizeof(RGBColor),
                            (ODValue)&forDebuggingRGBColor);

        SOMFree(propName);
      }
    }

### Display Frame 'Ternalization

A well behaved part editor needs to keep track of its display frames, and store and retrieve them from an annotation property on its storage unit. This document contains the list of methods affected, and a brief description of their responsibilities. The sample code documents step by step how to implement this functionality in those methods.

#### Caveats

The following sample code:

- Contains minimal proper error checking.

- Does not account for the case where a part may want to do AbstractTertiaryCase.

- Uses the CLink and CLinkedList classes, which are implemented by the part.

- Uses StorUtil.H and StdTypIO.H which are unsupported utilities of OpenDoc. It is recommended that you copy the sample code from these unsupported utilities into your project.

- Does not contain any optimizations such as lazily internalizing your display frames, or only rewriting your list of display frames when it changes.

- May not contain all the necessary #includes.

- Is written with the assumption that the rest of the implementation follows the SOM Wrapper/C++ implementation strategy.

- Does not contain full implementation for the methods listed.

#### Methods Affected

Constructor | Clear fDisplayFrames.
Destructor | Delete fDisplayFrames if it is not null, and set it to null.
InitPart | Create a new, empty collection (fDisplayFrames) in which to store a list of your display frames.
DisplayFrameAdded | Add the frame passed in to fDisplayFrames.
DisplayFrameConnected | Add the frame passed in to fDisplayFrames if it is not already in the list.
DisplayFrameRemoved | Remove the frame passed in from fDisplayFrames.
DisplayFrameClosed | Remove the frame passed in from fDisplayFrames.
Externalize | If necessary create a DisplayFrames annotation property and StorageUnitRefs value on your storage unit. Otherwise just focus to your DisplayFrames annotation property and its StorageUnitRefs value. Write out a list of references to your display frames from fDisplayFrames.
InitPartFromStorage | Focus to your DisplayFrames annotation property and its StorageUnitRefs value. Read in the list of your display frames into fDisplayFrames. See the "Lazy Frame Internalization" recipe for detail.
ReleaseAll | Assert that you fDisplayFrames collection is empty.

#### Sample Code

    void CMyPart::CMyPart()
    {
      ...
      fDisplayFrames = kODNULL;
      ...

    }

    void CMyPart::~CMyPart()
    {
      ...
      if (fDisplayFrames != kODNULL)
      {
        delete fDisplayFrames;
        fDisplayFrames = kODNULL;
      }
      ...
    }

    void CMyPart::InitPart(Environment* ev,
                           ODStorageUnit* storageUnit,
                           ODPart* partWrapper)
    {
      ...
      // Create a list to keep track of the frames we are being
      // displayed in
      fDisplayFrames = new CLinkedList;
      ...
    }

    void CMyPart::InitPartFromStorage(Environment* ev,
                                      ODStorageUnit* storageUnit,
                                      ODPart* partWrapper)
    {
      ...
      // Create a list to keep track of the frames we are being
      // displayed in
      fDisplayFrames = new CLinkedList;

      // Read in our list of display frames
      storageUnit->Focus(ev,
                         kODPropDisplayFrames,
                         kODPosUndefined,
                         kODWeakStorageUnitRefs,
                         0,
                         kODPosUndefined);
      ODULong size = storageUnit->GetSize(ev);
      storageUnit->SetOffset(ev, 0);

      for (ODULong offset = 0; offset < size; offset += sizeof(ODStorageUnitRef))
      {
        StorageUnitGetValue(storageUnit,
                            ev,
                            sizeof(ODStorageUnitRef),
                            weakRef);

        if (storageUnit->IsValidStorageUnitRef(ev, weakRef))
        {
          ODFrame *frame = storageUnit->
                           GetDraft(ev)->
                           AcquireFrame(ev,   // Calls my DisplayFrameConnected
                           storageUnit->
                           GetIDFromStorageUnitRef(ev, weakRef));
          frame->Release(ev);
        }
      }
      ...
    }

    void CMyPart::DisplayFrameAdded(Environment* ev,
                                    ODFrame* frame)
    {
      ...
      frame->IncrementRefCount(ev);
      fDisplayFrames->Add((ODPtr)frame);
      ...
    }
    void CMyPart::DisplayFrameConnected(Environment* ev,
                                        ODFrame* frame)

    {
      ...
      if (!fDisplayFrames->Contains((ODPtr)frame))
      {
        frame->IncrementRefCount(ev);
        fDisplayFrames->Add((ODPtr)frame);
      }
      ...
    }

    void CMyPart::DisplayFrameRemoved(Environment* ev,
                                      ODFrame* frame)
    {
      ...
      fDisplayFrames->Remove((ODPtr)frame);
      frame->Release(ev);
      ...
    }

    void CMyPart::DisplayFrameClosed(Environment* ev,
                                     ODFrame* frame)
    {
      ...
      fDisplayFrames->Remove((ODPtr)frame);
      frame->Release(ev);
      ...
    }

    void CMyPart::Externalize(Environment* ev)
    {
      ...
      ODStorageUnitRef weakRef;
      ODStorageUnit* su = this->GetStorageUnit(ev);
      ODSUForceFocus(su, kODPropDisplayFrames, kODWeakStorageUnitRefs);

      // Persistent object references are stored in a side table, rather than
      // in the property/value stream.
      // Thus, deleting the contents of a value will not delete the references
      // previously written to that value.
      // To completely delete all references written to the value, we must
      // remove the value and add it back.
      su->Remove(ev);
      su->AddValue(ev, kODWeakStorageUnitRefs);

      for (CLink* link = fDisplayFrames->First();
           link->Content();
           link = link->Next())
      {
        frame = (ODFrame*) link->Content();
        frameID = frame->GetID(ev);

        // Write out weak references to each of the part's display frames
        su->GetWeakStorageUnitRef(ev, frameID, weakRef);
        StorageUnitSetValue(su, ev, sizeof(ODStorageUnitRef), weakRef);
      }
      ...
    }

    void CMyPart::ReleaseAll(Environment* ev)
    {
      ...
      ASSERT(fDisplayFrames->First() == kODNULL);
      ...
    }

### Lazy Frame Internalization

There are situations in OpenDoc where eagerly doing work as soon as possible is not the best thing to do, and the work should be deferred until it really needs to be done.

#### Winning through laziness

Internalization is usually a good candidate for being lazy. In general, do not internalize data until it is needed, or you can predict it will be needed soon (for example you might pre-fetch the next page so scrolling will be fast). A particular case of lazy internalization involves your part's frames, both display frames and embedded frames.

Since OpenDoc places very few requirements on a part's internal representation, your part is free to organize its data however it pleases. The only requirements relevant to frames are that your part must be able to supply an EmbeddedFramesIterator for its embedded frames, and it should externalize a list of its display frames in a standard property of its storage unit.

For very simple containing parts, there will not be much advantage in lazily internalizing embedded frames. But for large containing parts, the advantages can be quite significant. For example, a containing part which embeds five thousand frames (think CD-ROM here) would not want to internalize all of those frames at the same time. There would be no way to display all the frames simultaneously, and a large amount of memory would effectively be wasted on keeping those persistent objects resident. All parts should consider lazy internalization of display frames, since your part has no control over the number of display frames that may be added to it.

#### How To Be Lazy

The main trick to lazy frame internalization is stopping before you go too far. In InitPartFromStorage, you should stop short of calling draft->GetFrame(). Instead, just hang on to the frame ID that you got from the storageUnitRef. (Remember that display frame references should be weak, so they may not actually have a frame or ID at the other end). The minimal way to deal with this ID is to create a structure that can hold a frame pointer and a frame ID. Anywhere in your code you would have stored a frame reference, store one of these structures instead. If the structure does not have a frame yet, use the ID to get it, then store it in the structure for next time (Remember to increment the frame's reference count.)

For slightly more effort up-front, you can have a much easier time of it later. You can create a class that stores a frame reference and a frame ID, and does the GetFrame for you when necessary. The class Futon below is an example of such. (Do not try to read any significance into the name. It is just a random word that was easy to type).

#### Getting Laziness to Work for You

Now that your part is more relaxed about internalizing its frames, here is how it can take advantage of being lazy.

- Don't get a frame unless you really need to. You have already fixed your part so that instead of direct frame references in its content, it has Futons instead. Now it would be a real waste if you went and made all those Futons get their frames right away. As a containing part, your part probably has a pretty good idea of where its embedded frames are located, and how big they are. You need this information to determine when to add a facet to a frame to make it visible. So be lazy and do not get the embedded frame from the Futon until you need to add a facet to it, or perform some other operation on it. Just do not get it for the sake of getting it.

- Purge frames when memory is low. When your part gets a Purge() call, it should attempt to free up memory. If you are set up for lazy internalization, it is easy to purge frames. This only works if you do not keep direct frame references anywhere. First off, only purge frames that are not currently visible. Start by removing a frame's facets. Then call Release() on the frame, and null it out in the Futon. If a frame's embedded and containing parts both do this, the reference count of the frame should drop to zero, and the object can be flushed from memory. Do not call Close() on an embedded frame to purge it from memory. It is an awful lot of work to Close and reconnect frames, and the technique described above works just as well if not better.

#### Sample Code

    #define  INCL_ODAPI
    #define  INCL_ODFRAME
    #define  INCL_ODDRAFT
    #include <ODos2.h>
    #include <ODUtils.h>
    class Futon

    {
    public:
      Futon(ODDraft* draft, ODID frameID);
      ~Futon();

      ODFrame* GetFrame(Environment* ev);
      void Purge(Environment* ev);

      ODDraft* fDraft;
      ODID fFrameID;
      ODFrame* fFrame;
    };

    Futon::Futon(ODDraft* draft, ODID frameID)
    {
      // Note:
      // I am not reference counting the draft, because the draft is
      // referenced from the part's storage unit, so it is
      // not going away while the Futon (and thus the part)
      // is still around.
      fDraft = draft;
      fFrameID = frameID;
      fFrame = kODNULL;
    }

    Futon::~Futon()
    {
      ODSafeReleaseObject(fFrame);
    }
    ODFrame*
    Futon::GetFrame(Environment* ev)
    {
      if (fFrame == kODNULL)
        fFrame = fDraft->GetFrame(ev, fFrameID);
        return fFrame;
    }

    void
    Futon::Purge(Environment* ev)
    {
      ODReleaseObject(ev, fFrame);
    }

[ Top | Previous | Next | Contents | Index | Documentation Homepage ]