Programming Guide


Data Interchange

   

This section provides a roadmap for those who want to learn how to implement data interchange (clipboard, drag and drop, and linking) in OpenDoc using the recipes.

The reader is assumed to have a basic knowledge of OpenDoc. The reader may also want to familiarize himself with the OpenDoc storage system and its recipes before proceeding.

The data interchange recipes are comprised of the following:

General data interchange recipes:

Drag and drop specific recipes:

Linking specific recipes:

Data interchange recipes which can be used for both drag and drop and clipboard:

Data Interchange Basics

         

This section contains recipes for transferring data between OpenDoc parts and data interchange objects. These basic recipes are referenced by the specific recipes for each data interchange object: Clipboard recipes, Drag and Drop recipes, and Linking recipes. The recipes here assume familiarity with the OpenDoc Storage recipes. For descriptions of individual methods used in the recipes, see the OpenDoc Programming Reference.

Note:

Code appearing in these recipes has not been tested, and may contain errors. They are provided to help you get started with data interchange, but need to be customized for your particular content model.

About Coding Examples

 

This section contains a number of coding examples in addition to descriptive text. The coding examples cover:
MyCloneStrongReference How to clone objects referenced by your part.
MyCloneWeakReference How to clone objects referenced by your part.
MyWriteToContentSU Writing intrinsic content to a data interchange object.
MyCloneEmbeddedFrameToContentSU Writing a single embedded frame to a data interchange object.
CloneInto Implementing your part's CloneInto method.
MyReadFromContentSU Incorporating content from a data interchange object.
MyEmbedContentSU Embedding content from a data interchange object.
MyMakeEmbeddedFrame How your part might make a new frame for embedded content.

The coding examples appearing in this section make use of utility routines in addition to public OpenDoc functions. The reader is referred to the utilities documentation for information about specific utilities.

For simplicity, the coding examples use ODByteArrays to represent the content to be read or written. If your part does not maintain its content as an ODByteArray, you can either copy it into an ODByteArray, or, more likely, read and write data in chunks using the StorageUnitSetValue or StorageUnitGetValue utility functions.

The examples use exception handling in the form of TRY...CATCH_ALL...ENDTRY blocks to catch exceptions, and assumes that errors returned by SOM methods are thrown upon return.

To ensure reference-counted objects are released when exceptions are raised, some of these examples employ temporary objects which encapsulate reference-counted objects and release them when their scope is exited.

Data Interchange Mechanisms

 

The exchange of content within and between parts in OpenDoc involves the use of three mechanisms: the Clipboard, Drag and Drop, and Linking. While each mechanism has its own characteristics, much of the low-level details are the same. This section discusses those common details, and provides examples of how your part can be organized to leverage this commonality.

The routines shown here are the workhorses of data interchange. They do the work of moving content to and from a data interchange object. Once your part has these in place, implementing Clipboard support in your part, for example, becomes the simpler problem of gaining access at the appropriate time to the clipboard content storage unit. The same for Drag and Drop and Linking, although with Linking you need to merge the persistent Linking objects into your content model as well.

Content Storage Units
   

At the lowest level, data interchange involves writing content to, and reading content from, one of these data interchange objects: the Clipboard object, the Drag and Drop Object, a Link Source object, or a Link object. LinkSource and Link objects also happen to be persistent objects. These four objects have similar interfaces, but these similarities are not based on class inheritance.

Each data interchange object has a distinguished storage unit, called the content storage unit, used to exchange content. Access to this storage unit is provided by a GetContentStorageUnit method defined by all data interchange objects. The content storage unit serves as the root of the storage units that collectively define the content of the interchange object. Parts can use the same code to write to the content storage unit of any data interchange object.

It is important to distinguish between a content storage unit and a persistent object storage unit. Links and LinkSource objects, for example, are data interchange objects that also happen to be persistent objects. All persistent objects have storage units which store their persistent state; Links and LinkSource objects have a storage unit containing their persistent state. This is NOT the content storage unit, and your part should never read from or write to this storage unit. Your part must use a content storage unit to exchange data.

Content Storage Units are Part Storage Units
       

Any content storage unit can be considered a part storage unit (although content storage units are never bound to a part editor). The content storage unit of the clipboard is always a part. The content storage unit of a Drag and Drop object is always a part. The content storage unit of a link is always a part. All content storage units have the only property required for a part: a kODPropContents property. (Exception: The content storage unit of a link may be missing the kODPropContents property if updating the link failed; see "Linking Recipes" for more information about this situation if your part supports linking. Likewise, the content storage unit of the Clipboard may also be missing the kODPropContents property if nothing has be cut or copied to the clipboard. Before cloning a content storage unit from the Clipboard, check to ensure the kODPropContents property is present.) Parts writing to a content storage unit must ensure they follow the same rules as for any part storage unit, for example, writing all data in the kODPropContents property. This enables a part to embed a content storage unit by simply cloning it; the destination does not have to "construct" a part storage unit.

Data Interchange Properties

These properties are relevant to data interchange and may appear in a content storage unit. Except for the kODPropContents property, these properties must be read directly from a content storage unit because they are not copied when a content storage unit is cloned.

kODPropContents

This property contains the data being transferred. It is in the same format as in a part's storage unit. We recommend that your part write a promise for data interchange, so only data used by a destination is actually transferred.  

kODPropProxyContent

This optional property may appear when a single embedded frame is written to the content storage unit. This property also contains content, but content that the part initiating the data transfer associates with the embedded frame (like a drop shadow or positional information). Like kODPropContents, this may be promised, too.  

Proxy content is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations must read proxy content from a content storage unit, not from the storage unit cloned from the content storage unit.

kODPropCloneKindUsed

This property may be present in a content storage unit to indicate the clone kind to use in fulfilling a promise in the storage unit. This property is created by a part cloning a single embedded frame to a data interchange object. It may also be created by any part writing a promise to a data interchange object.       This property should contain the value kODCloneCut, kODCloneCopy, or kODCloneToLink.

The clone kind used property is not copied when a content storage unit is cloned.  

kODPropContentFrame

This property references a frame storage unit for the data interchange content. This property should only appear when a single embedded frame is written to the content storage unit.

The content frame property is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations must read proxy content from a content storage unit, not from the storage unit cloned from the content storage unit.

kODPropSuggestedFrameShape

This property contains a suggested frame shape for use when the content is embedded rather than incorporated at the destination. This should be written by parts initiating a data transfer that write intrinsic content. A frame shape should not be written in addition to a kODPropContentFrame property. The shape applies to all representations in the kODPropContents property. If neither the kODPropSuggestedFrameShape nor the kODPropContentFrame property exists, the destination part will have to use a default shape for the embedded frame.

The frame shape written to the kODPropSuggestedFrameShape property does not specify the location of the shape in the original content. Positional information, in general, is relevant only when the content is pasted into a part of the same category. In this case, positional information is included as part of the content representation. If the content is embedded, its location should be determined by the destination part.

The suggested frame shape property is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations must read proxy content from a content storage unit, not from the storage unit cloned from the content storage unit.  

kODPropLinkSpec

This optional property is present when a link may be created to the source of the content in the kODPropContents property. See "Linking Recipes" for information on linking in OpenDoc and on link specifications.

The link specification is not copied when a destination part clones a content storage unit to embed the content in its draft. Destinations that wish to create a link must read the link spec from a content storage unit, not from the storage unit cloned from the content storage unit.  

kODPropMouseDownOffset

In a drag and drop operation, the part initiating the drag can add this property to the content storage unit to indicate the offset from the mouse down point to the beginning of the selection, so the part receiving the drop can align the result of the drop with the dragged image, if desired.

The mouse down offset is NOT copied when a destination part clones a content storage unit into its draft.  

All Data in the Contents Property

All data maintained by your part should be stored in values in the contents property of your storage unit. Similarly, when writing data to a content storage unit, you should only be writing to the contents property. OpenDoc does not expect you to store data in other properties, and such properties are subject to removal. That said, your part is free to create as many auxiliary storage units as you like, with whatever properties and kinds you like. However, you must access these auxiliary storage units from a reference stored in one or more values in your contents property.

Moreover, each value in a contents property should be a self-sufficient representation of your data. Some values may be lower fidelity representations, such as a plain text representation of a styled text part. Values may reference common auxiliary storage units, but it should not be necessary to read multiple values to assemble a representation of your data.

You are expected to order the values in a contents property in order of decreasing fidelity. Ordering is determined when the value type is added to the property. Add or promise the highest fidelity value type first.

How Many Kinds Are Enough?

The number of content kinds your part writes to a content storage unit is entirely up to your part. In general, your part should write the kind that is the current part kind (not necessarily the highest fidelity kind your part supports), plus one or more standard interchange formats depending on your category. This is in general a good compromise between size and utility. Most destinations will be able to accept the data, and you will not be writing too many redundant copies of your data.

Promise What You Have - Deliver What Is Needed

Code your part so that it promises content whenever possible when writing to a data interchange object. That way, only the kinds actually used need to be transferred. The recipes here demonstrate writing promises. This also helps clarify the recipes, since the delivery of part-specific content is pushed into your part's FulfillPromise method.

When writing a promise in your part's CloneInto method (as described later on in a recipe), a part should determine which of its embedded frames are included in the frame scope, and record those frames as part of the promise data it writes. The part's FulfillPromise method can then supply the correct content. A part must fulfill a promise with the content present when the promise was written, even if the original content is changed before the promise is fulfilled.

It is up to your part to ensure you can fulfill outstanding promises it has written. For example, if your part writes promises to the clipboard, you must be able to fulfill those promises for an indefinite period of time. Your part is not informed when promises are removed. Your part may need to preemptively fulfill clipboard promises should it become impossible for your part to fulfill them in the future. For example, if your part fulfills promises using undo action data, be aware that your part's DisposeActionState may be called while a promise still exists on the Clipboard, so your part may need to fulfill the promise at that time.

When writing a promise, make sure your FulfillPromise method will have the following information:

    Your part may write multiple promises for cut content (one promise for each value type). Your part's FulfillPromise method does not need to behave differently the first time it fulfills a promise for the cut content, verses fulfilling a second promise for the same content. OpenDoc will ensure that the first time cut data is pasted, the operation is a move, while each subsequent paste behaves like a copy. Your FulfillPromise can clone using the ODCloneKind value in the kODPropCloneKindUsed property each time. Any special action required by the cut must be carried out when the promise was written or in the course of handling an Undo action for the cut.

Exchanging Intrinsic Versus Embedded Content

Most data transfers involve writing or reading intrinsic content. Intrinsic content is content managed by the part doing the data transfer; for example, a Text part putting selected text on the clipboard is writing intrinsic content. If the selection includes an embedded picture as well as text, that too is considered writing intrinsic content. In both cases, the part initiating the data transfer is writing content to a content storage unit.

The one exception to transferring intrinsic content occurs when a single embedded frame is involved. In this case, the content of primary importance is that in the embedded frame, not in the containing part initiating the transfer. When a single embedded frame is selected, parts follow different recipes during data interchange.

Making the Embed Versus Incorporate Decision at a Destination

As long as it has a kODPropContents property, a content storage unit is a part, and can be either incorporated or embedded at a destination. When a part handles a Paste or Paste As command, or receives a drop, it must decide whether to embed or incorporate the data (simple parts that do not support embedding will just incorporate).

  If the content storage unit contains a kODPropContentFrame property, the data was cut, copied, or dragged as a single embedded frame. It should be embedded at the destination, even if the data could be incorporated.

Otherwise, on paste or drop the part should incorporate the data if it makes sense according to its content model. The user can override the part's default behavior by using the Paste As command.

Choosing the Content Kind to Incorporate

When incorporating data during a paste or drop, a part must decide which data format to use. When incorporating content, your part is free to use the highest fidelity kind in the content storage unit that it supports. It is not necessary to incorporate the preferred kind specified by a kODPropPreferredKind property, if present.

Cloning
 

Persistent objects are transferred by a process called cloning. Your part usually references other persistent objects like embedded frames or link objects. If your part initiates a transfer of intrinsic content that references other objects, your part transfers these objects by cloning them. Similarly, your part's CloneInto method is called when your part is involved in a data transfer operation initiated by another part. Cloning is described in more detail in the Cloning Overview section.

Clone Kinds Used To Initiate a Clone Transaction

              Cloning is performed within a transaction started by calling ODDraft::BeginClone and completed by calling ODDraft::EndClone or ODDraft::AbortClone. The BeginClone method takes as one of its parameters an ODCloneKind value. When a part initiates a cloning transaction, it is important to specify the clone kind constant appropriate for the semantics of the operation. The value kODCloneCopy informs OpenDoc that the clone is into an intermediate draft (like the clipboard or a drag and drop container) with copy semantics; kODCloneCut implies cut semantics. The value kODClonePaste informs OpenDoc that the clone is from an intermediate draft into a destination draft. Other ODCloneKind values are used when cloning to and from links; see "Linking Recipes" for more information.

Cloning Referenced Objects
 

Your part moves a referenced object to and from a data interchange object by cloning the object. You clone an object by specifying its object ID; if the object has been internalized, the object will clone itself via its CloneInto method, otherwise, the object's storage unit will perform the clone.

Your part distinguishes between strong and weak references. Strong references are those to objects that must also be cloned. Weak references are backward links that your part uses but which may be broken during data interchange. Weak references are important because they prevent irrelevant objects from being copied during a clone operation. When your part clones a strongly or weakly referenced object, its your part's responsibility to use Clone or WeakClone as appropriate.

The recipes below show how your part should implement cloning an object from a reference. Important points to remember:


SOM_Scope ODID  SOMLINK MyPartMyCloneStrongReference(MyPart *somSelf,
                                                     Environment *ev,
  ODStorageUnit* su,
  ODStorageUnitRef suRef,
  ODDraftKey draftKey)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyCloneStrongReference");

  ODID clonedID = kODNULLID;

  if (su->IsValidStorageUnitRef(ev, suRef))
  {
    SOM_TRY

      ODID storageUnitID = su->GetIDFromStorageUnitRef(ev, suRef);
      clonedID = su->GetDraft(ev)->Clone(ev,
                                         draftKey,
                                         storageUnitID,
                                         kODNULLID,
                                         kODNULLID);

    SOM_CATCH_ALL

    SOM_ENDTRY
  }

  return clonedID;
}

SOM_Scope ODID SOMLINK MyPartMyCloneWeakReference(MyPart *somSelf,
                                                  Environment *ev,
  ODStorageUnit* su,
  ODStorageUnitRef suRef,
  ODDraftKey draftKey)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyCloneWeakReference");

  ODID clonedID = kODNULLID;

  if (su->IsValidStorageUnitRef(ev, suRef))
  {
    SOM_TRY

      ODID storageUnitID = su->GetIDFromStorageUnitRef(ev, suRef);
      clonedID = su->GetDraft(ev)->WeakClone(ev, draftKey, storageUnitID,
                                             kODNULLID, kODNULLID);

    SOM_CATCH_ALL
    SOM_ENDTRY
  }
  return clonedID;
}

Specifying Platform Kinds

      If your part supports a standard platform data format, such as 'TEXT' or 'PICT' data, your part can determine the ODValueType designating that kind using the GetISOTypeFromPlatformType method of the translation object. This example shows how to specify the ODValueType for plain text. Supporting a platform data type allows your application to interchange data with non-OpenDoc applications supporting that type.

ODTranslation* translation = mySession->GetTranslation(ev);
ODValueType isoTypeTEXT =
    translation->GetISOTypeFromPlatformType(ev,
                                            CF_TEXT,
                                            kODPlatformDataType);

Promising Intrinsic Content

   

This example demonstrates a method that can be used to write the content storage unit of a link, the clipboard, or drag and drop object. This routine can be used only to promise intrinsic data; if a single embedded frame is selected, you should follow "Writing an Embedded Frame".

      When promising intrinsic content, your part typically writes two properties, kODPropContents and kODPropSuggestedFrameShape, and optionally a kODPropPartName. This is all the content storage unit needs to be either incorporated or embedded at the destination.

Your part may write a kODPropPartName property if your content model associates a name with the content being written, and you want that name associated with the part should the content be embedded at the destination.

The contentSU argument is the content storage unit of a data interchange object. The cloneKind parameter specifies the semantics of the operation and is necessary for cloning. The promiseData will be written into each promised value created by this method in contentSU. The contentShape parameter will be written into the kODPropSuggestedShape property. The partName parameter will be written into the kODPropPartName property.

Note that as shown here, MyWriteToContentSU:


void MyWriteToContentSU (Environment *ev,
  ODStorageUnit* contentSU,
  ODCloneKind cloneKind,
  ODByteArray* promiseData,
  ODShape* contentShape,
  ODIText* partName)
{
  // Begin a clone transaction
  ODDraft* myDraft = fSOMSelf->GetStorageUnit(ev)->GetDraft(ev);
  ODDraftKey draftKey = 0;

  ODVolatile(myDraft);
  ODVolatile(draftKey);

  draftKey =
    myDraft->BeginClone(ev, contentSU->GetDraft(ev), kODNULL, cloneKind);

  TRY
    contentSU->AddProperty(ev, kODPropContents);

    // Write out the contents, preferred representation first.
    // The type of the best representation is usually
    // the part's own proprietary format.
    // Rather that write the actual content, write a promise.
    // This part will deliver the data in its FulfillPromise method.

    contentSU->SetPromiseValue(ev,
        kMyContentKind,
        0,
        promiseData,
        fSOMThis->fPartWrapper);

    // Promise standard versions of the content, too.
    //   Note that the very same promise data can be used; FulfillPromise can
    //   get the requested type from its ODStorageUnitView* parameter.

    contentSU->SetPromiseValue(ev,
        kODKindOS2Text;
        0,
        promiseData,
        fSOMThis->fPartWrapper);

    // Add or replace the name property (for embedding at the destination)
    if (partName)
      ODSetITextProp(ev, contentSU, kODPropName, kODOS2IText, partName);

    // Add or replace the suggested frame shape
    // (for embedding at the destination)
    if (contentShape)
    {
      ODSUForceFocus(ev, contentSU, kODPropSuggestedFrameShape, kODNULL);
      contentShape->WriteShape(ev, contentSU);
    }

  CATCH_ALL

    myDraft->AbortClone(ev, draftKey);
    RERAISE;

  ENDTRY

  myDraft->EndClone(ev, draftKey);
}

Writing an Embedded Frame

When cloning an embedded frame, your part does not add the kODPropContents, kODPropName, or kODPropSuggestedFrameShape properties to the content storage unit. The kODPropContents property is written by the embedded part in its CloneInto method. If the embedded part is cooperative, it can be cloned into the content storage unit such that it will promise its content. The kODPropName property is added by OpenDoc.

Instead, your part typically writes three properties: kODPropContentFrame, kODPropProxyContent, and kODPropCloneKindUsed. Proxy content is optional; if your part does not associate any special information with the embedded frame, you do not need to provide it.

The recipe below must be followed carefully. Create a kODPropContentFrame property in the content storage unit, but DO NOT CLONE THE EMBEDDED FRAME YET. First clone the embedded part, specifying the content storage unit as the destination (this may be the only situation where your part must clone into a specific storage unit). Then clone the embedded frame, and store a WEAK reference to it into a kODWeakStorageUnitRef value type of the kODPropContentFrame property. The embedded part must be cloned first; if the embedded frame is cloned first, it will force cloning of the embedded part into a storage unit other than the content storage unit. The embedded frame should be weakly referenced so it must be explicitly cloned by the destination.

To enable the embedded part to fulfill a promise for its content, your part must add a kODPropProxyContent property to the content storage unit. This property should contain the clone kind specified in the call to BeginClone.

Although only an embedded frame is selected, the containing part may associate some intrinsic content with that frame, such as the addition of a drop shadow. After the embedded part is cloned into the content storage unit, the containing part can add a kODPropProxyContent property to the content storage unit to contain whatever intrinsic data it chooses. If the destination understands the proxy content, the characteristics of the frame will be preserved.

When a single embedded frame is written, its important that the content storage unit contain a kODPropContentFrame property. The presence of this property tells the destination part that the content came from an embedded frame. Content cut or copied as an embedded frame should be embedded at the destination, even if the content could be incorporated. Without the kODPropContentFrame property, the destination part cannot know the content came from an embedded frame.

The contentSU argument is the content storage unit of a data interchange object. The cloneKind parameter specifies the semantics of the operation and is necessary for cloning. The embeddedFrame parameter is the frame being transferred. The optional promiseProxyData will be written into each proxy value created by this method in contentSU.

void MyCloneEmbeddedFrameToContentSU (Environment *ev,
  ODStorageUnit* contentSU,
  ODCloneKind cloneKind,
  ODFrame* embeddedFrame,
  ODByteArray* promiseProxyData)
{
  // Begin a clone transaction
  ODDraft* myDraft = fSOMSelf->GetStorageUnit(ev)->GetDraft(ev);
  ODDraftKey draftKey = 0;

  ODVolatile(myDraft);
  ODVolatile(draftKey);

  draftKey = myDraft->BeginClone(ev,
                                 contentSU->GetDraft(ev),
                                 kODNULL,
                                 cloneKind);

  TRY

    // When transferring one frame, add the content frame property
    // now but write the value after the embedded part has been cloned.

    contentSU->AddProperty(ev, kODPropContentFrame);

    // Also add a clone kind used property in case the embedded part promises
    // its content and thus must perform a clone transaction in its
    // FulfillPromise method.

    ODSetULongProp(ev, contentSU, kODPropCloneKindUsed, kODCloneKind,
                                                        cloneKind);

    // Clone the embedded part into the content storage unit.
    // If the embedded part is savy, it will notice the kODPropContentFrame
    // property and promise its content.

    ODPart* embeddedPart = embeddedFrame->AcquirePart(ev);
    myDraft->Clone(ev,
         draftKey,
         embeddedPart->GetID(ev),
         contentSU->GetID(ev),
         embeddedFrame->GetID(ev));
    ODReleaseObject(ev, embeddedPart);

    // Clone the embedded frame to the data transfer draft;
    // must be done after cloning the part because the
    // embedded frame strongly references the part.

    ODID toFrameID = myDraft->Clone(ev,
         draftKey,
         embeddedFrame->GetID(ev),
         kODNULLID,
         kODNULLID); // scope is not relevant for cloning a frame

    // Weakly reference the frame so it must be explicitly cloned
    //   into the receiving draft if desired

    ODSUForceFocus(ev, contentSU, kODPropContentFrame,
                                  kODWeakStorageUnitRef);
    ODStorageUnitRef aSURef =
                     contentSU->GetWeakStorageUnitRef(ev, toFrameID);
    StorageUnitSetValue(contentSU, ev, sizeof(ODStorageUnitRef),
                                       &aSURef);

    // (Optional) Add proxy content describing the embedded frame
    if (promiseData)
    {
      ODSUForceFocus(ev, contentSU, kODPropProxyContents, kODNULL);
      contentSU->SetPromiseValue(ev,
          kMyContentKind,
          0,
          promiseProxyData,
          fSOMThis->fPartWrapper);
    }

  CATCH_ALL

    myDraft->AbortClone(ev, draftKey);
    RERAISE;

  ENDTRY

  myDraft->EndClone(ev, draftKey);
}

Implementing CloneInto

       

Your part may participate in a data interchange operation initiated by some other part. Specifically, your part may be requested to clone itself into a specified storage unit. This storage unit may be on the clipboard, in a drag and drop object, or in a link. Fortunately, it does not matter to your part, as long as your part clones itself correctly.

First, your part should not implement CloneInto by calling its Externalize method and then cloning its storage unit into the specified storage unit. Externalizing your part may require writing to disk; if every part did this in response to CloneInto performance would suffer. Also, your Externalize method does not take the scope frame into account. For example, a display frame of your part may have been cut but not yet removed, and your part should not include this as a display frame when it clones itself into a storage unit.

Your part's CloneInto method should only add and write to the kODPropContents property. OpenDoc will copy the part name and other annotations.

Your part's CloneInto method also should not start a clone transaction by calling BeginClone. One will have already been started by the part initiating the operation.

When CloneInto Should Write a Promise
   

To enable quick response when the user initiates dragging a frame, all parts should be able to promise their content when their CloneInto method is called. CloneInto should check for the presence of the kODPropContentFrame property. This property will only be present if the part is being cloned into a content storage unit by a containing part. In this situation only, a part can promise its content instead of actually copying data. In other situations, your part must write actual data. Your part's CloneInto method may be called when another part is fulfilling a promise, and your part must not promise content in this case.

    The promise data written should identify the display frame of this part specified in the scopeFrame parameter to CloneInto, to allow the part's FulfillPromise method to deliver the correct content. CloneInto may be called multiple times, possibly with different scopeFrame parameters.

SOM_Scope void  SOMLINK MyPartCloneInto(MyPart *somSelf, Environment *ev,
  ODDraftKey key,
  ODStorageUnit* storageUnit,
  ODFrame* scopeFrame)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","CloneInto");

  // First check for the existence of my content kind; if the value
  // is present, this part has already been cloned (but respect
  // scopeFrame which could include more that was previously cloned;
  // for simple parts this will not make a difference)
  if (!storageUnit->Exists(ev, kODPropContents, kMyContentKind, 0))
  {
    SOM_TRY

      parent_CloneInto(somSelf, ev, key, storageUnit, scopeFrame);

      // Focus to the contents property, adding if necessary
      ODSUForceFocus(ev, storageUnit, kODPropContents, kODNULL);

      // Optional - Check to see if the content can be promised
      if (storageUnit->Exists(ev, kODPropContentFrame, kODNULL, 0))
      {
        // Placeholders to be replaced by part-specific data
        ODByteArray promiseData = CreateByteArrayStruct(kODNULL,0);

        // Write a promise for each content kind
        storageUnit->SetPromiseValue(ev, kMyContentKind, 0,
                                     &promiseData, somThis->fPartWrapper);

        // Promise other content kinds
        storageUnit->SetPromiseValue(ev, kODKindOS2Text, 0,
                                     &promiseData, somThis->fPartWrapper);

        DisposeByteArrayStruct(promiseData);
      }
      else
      {
        // Placeholders to be replaced by part-specific data
        ODByteArray myContentKindData = CreateByteArrayStruct(kODNULL,0);
        ODByteArray textContentKindData = CreateByteArrayStruct(kODNULL,0);

        // Add content kinds in this part's fidelity order
        ODSUForceFocus(ev, storageUnit, kODPropContents, kMyContentKind);
        storageUnit->SetValue(ev, &myContentKindData);

        ODSUForceFocus(ev, storageUnit, kODPropContents, kODKindOS2Text);
        storageUnit->SetValue(ev, &textContentKindData);

        DisposeByteArrayStruct(myContentKindData);
        DisposeByteArrayStruct(textContentKindData);
      }

    SOM_CATCH_ALL

    SOM_ENDTRY
  }
}

Incorporating from a Content Storage Unit

This recipe demonstrates how data may be incorporated from a content storage unit.

To incorporate content, read a value type from the kODPropContents property of the content storage unit. Each storage unit referenced from the value stream should be cloned to the receiving part's draft. The part performing the clone must not internalize objects from cloned storage units until EndClone is called; if the clone has to be aborted for any reason, the storage units will be removed from the draft.

Each frame embedded during incorporation must have their containing frame, link status, and in-limbo fields set correctly. In addition, the previous in-limbo setting of each frame must be remembered for use during undo.

                    Each frame embedded must be inserted into the receiving part's frame hierarchy by calling the embedded frame's SetContainingFrame method. SetContainingFrame must be called after EndClone since it is illegal to internalize a frame until a cloning transaction successfully completes. SetContainingFrame must be called before the part displayed in the frame is internalized by calling AcquirePart. Internalizing the part causes DisplayFrameConnected to be called. The activation recipe (applied to the embedded part) specifies that the frame should become active if its a root frame, that is, its containing frame is null. The embedded frame will not have a containing frame until one is set by the part performing the incorporation.

If the embedded frame should appear in other display frames of the receiving part, additional embedded frames need to be created. The reader is referred to the Layout recipes for more details on embedding.

Each frame embedded must have its link status set by calling its ChangeLinkStatus method. All parts must set the link status of their embedded frames, even parts that do not otherwise support linking. Parts that support linking are referred to the Linking recipes for more information on setting link status. Parts that do not support linking should set all embedded frames to kODNotInLink.

This recipe incorporates the part's native content kind, kMyContentKind, from the content storage unit. Unless the user specifies a type to incorporate in the Paste As dialog, the part should incorporate the highest fidelity kind that it supports. This may not be the first kind in the kODPropContents property, nor may it be the part's native representation. The native content kind is incorporated here for simplicity.

The contentSU argument is the content storage unit of the data interchange object to incorporate from. The targetFrame argument identifies the frame being incorporated into and is passed to BeginClone. The cloneKind argument specifies the semantics of the operation and is passed to BeginClone. The embeddedFrameLinkStatus argument is used to set the link status of frames embedded from contentSU.

SOM_Scope void  SOMLINK MyPartMyReadFromContentSU(MyPart *somSelf,
                                                  Environment *ev,
  ODStorageUnit* contentSU,
  ODFrame* targetFrame,
  ODCloneKind cloneKind,
  ODLinkStatus embeddedFrameLinkStatus)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyReadFromContentSU");

  // Placeholders to be replaced by part-specific data
  ODByteArray data = CreateByteArrayStruct(kODNULL,0);
  ODStorageUnitRef aStrongRef;
  ODID strongClonedID = kODNULLID;
  ODID weakClonedID = kODNULLID;

  // Begin a clone transaction
  ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);
  ODDraftKey draftKey = 0;

  ODVolatile(myDraft);
  ODVolatile(draftKey);

  SOM_TRY

    draftKey = myDraft->BeginClone(ev,
                                   contentSU->GetDraft(ev),
                                   targetFrame,
                                   cloneKind);

    TRY

      // For simplicity, this example code incorporates kMyContentKind.
      // A real part should examine the content kinds present
      // in fidelity order and read the best kind it supports.
      contentSU->Focus(ev, kODPropContents, kODPosUndefined,
                       kMyContentKind, 0, kODPosUndefined);

      // For demonstration, read the data into an ODByteArray.
      contentSU->GetValue(ev, contentSU->GetSize(ev), &data);

      // Usually, the data will reference other storage units.
      // These objects need to be cloned into this part's draft.
      // You must wait until EndClone completes without
      // error to turn strongClonedID into a persistent object.
      // If strongClonedID is valid, you may create a storage unit
      // reference to it, but beware that the reference may become
      // invalid after EndClone or AbortClone is called.
      strongClonedID = somSelf->MyCloneStrongReference(ev,
                                                       contentSU,
                                                       aStrongRef,
                                                       draftKey);

      // Your part must distinguish between references that are
      // strong and references that are weak,
      // and clone them accordingly.
      weakClonedID = somSelf->MyCloneWeakReference(ev,
                                                   contentSU,
                                                   aStrongRef,
                                                   draftKey);
    CATCH_ALL
      myDraft->AbortClone(ev, draftKey);
      RERAISE;
    ENDTRY

    myDraft->EndClone(ev, draftKey);

    // Now incorporate the read data and cloned objects into
    // this part's content
    if (myDraft->IsValidID(ev, strongClonedID))
    {
        // Safe to internalize the cloned object.
        // As an example, the object's
        // storage unit is acquired and released here.
        ODStorageUnit* su = myDraft->AcquireStorageUnit(ev, strongClonedID);
        su->Release(ev);
    }
    else
    {
        // The object was not successfully cloned,
        // so exclude it from this part's content.
    }

    // If the incorporated content contains embedded frames,
    // you must ensure certain frame characteristics are set properly.
    // Be sure to call SetContainingFrame before the embedded
    // part is internalized.
    // Also, this part must remember the in-limbo status of each
    // embedded frame in its undo action data to implement
    // undo correctly.
    ODBoolean embeddedFrameWasInLimbo = embeddedFrame->IsInLimbo(ev);
    embeddedFrame->SetInLimbo(ev, kODFalse);
    embeddedFrame->SetContainingFrame(ev, targetFrame);
    embeddedFrame->ChangeLinkStatus(ev, embeddedFrameLinkStatus);

  SOM_CATCH_ALL
  SOM_ENDTRY
  DisposeByteArrayStruct(data);
}

Embedding a Content Storage Unit

     

This example demonstrates how a content storage unit can be embedded as a part. To embed the content storage unit, the part performing the transfer clones the content storage unit into the part's draft. If the content storage unit also contains a kODPropContentFrame property, the storage unit referenced by that property should be explicitly cloned into the part's draft. The frame storage unit must be explicitly cloned because it is only weakly referenced by the content storage unit, and would otherwise not be cloned.

If a frame was provided, the receiving part should call that frame's SetContainingFrame method, passing in the display frame of the active facet. Be sure to call SetContainingFrame before the embedded part is internalized by calling AcquirePart. If no frame was provided, the receiving part should create an embedded frame, using the frame shape specified by the kODPropSuggestedFrameShape property, if present. In either case, for each visible embedded frame, the receiving part should create facets by calling the CreateEmbeddedFacets method of the display frame of the active facet.

The contentSU argument is the content storage unit of the data interchange object to embed. The targetFrame argument identifies the frame being incorporated into and is passed to BeginClone. The cloneKind argument specifies the semantics of the operation and is passed to BeginClone. The embeddedFrameLinkStatus argument is used to set the link status of the embedded frame.

SOM_Scope void  SOMLINK MyPartMyEmbedContentSU(MyPart *somSelf,
                                                      Environment *ev,
  ODStorageUnit* contentSU,
  ODFrame* targetFrame,
  ODCloneKind cloneKind,
  ODLinkStatus embeddedFrameLinkStatus)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyEmbedContentSU");

  ODDraft* contentSUDraft = contentSU->GetDraft(ev);
  ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);

  ODDraftKey draftKey = 0;
  ODID embeddedFrameID = kODNULLID;
  ODID embeddedPartID = kODNULLID;

  ODPart* embeddedPart = kODNULL;
  ODFrame* embeddedFrame = kODNULL;

  // This part must remember the in-limbo status of the embedded frame in its
  //   undo action data to implement undo correctly.
  ODBoolean embeddedFrameWasInLimbo = kODTrue;

  // Placeholders to be replaced by part-specific data
  ODByteArray proxyData = CreateByteArrayStruct(kODNULL,0);

  ODVolatile(contentSUDraft);
  ODVolatile(draftKey);

  SOM_TRY

    // Begin a clone transaction into this draft.
    draftKey =
      contentSUDraft->BeginClone(ev, myDraft, targetFrame, cloneKind);

    TRY

      // Clone the content storage unit
      //   The scopeID kODNULLID indicates the clone is not restricted to a
      //   particular frame context.
      embeddedPartID = contentSUDraft->Clone(ev, draftKey,
                       contentSU->GetID(ev), kODNULLID, kODNULLID);

      // If a content frame is present, clone it, too
      if (ODSUExistsThenFocus(ev,
                              contentSU,
                              kODPropContentFrame,
                              kODWeakStorageUnitRef))
      {
        ODStorageUnitRef aSURef;
        StorageUnitGetValue(contentSU, ev, sizeof(ODStorageUnitRef),
                                                  &aSURef);
        embeddedFrameID = somSelf->CloneStrongReference(ev,
                                                        contentSU,
                                                        aSURef,
                                                        draftKey);
      }

      // Read proxy content if present.
      if (ODSUExistsThenFocus(ev,
                              contentSU,
                              kODPropProxyContents,
                              kMyContentKind))
      {
        // For demonstration, read the data into an ODByteArray.
        // Note that this may contain storage unit references,
        // such as to an ODLink or ODLinkSource storage unit,
        // so cloning may be necessary here.
        contentSU->GetValue(ev, contentSU->GetSize(ev), &proxyData);
      }

    CATCH_ALL
      contentSUDraft->AbortClone(ev, draftKey);
      RERAISE;
    ENDTRY

    contentSUDraft->EndClone(ev, draftKey);

    if (myDraft->IsValidID(ev, embeddedFrameID))
    {
      embeddedFrame = myDraft->AcquireFrame(ev, embeddedFrameID);
      embeddedFrameWasInLimbo = embeddedFrame->IsInLimbo(ev);
      embeddedFrame->SetInLimbo(ev, kODFalse);
      embeddedFrame->SetDragging(ev, kODFalse); // In case frame was dropped
      embeddedFrame->SetContainingFrame(ev, targetFrame);
    }
    else
    {
      // Create a new embedded frame for newPart,
      // using the suggested frame shape if present.
      TempODShape newShape = NULL;

      if (ODSUExistsThenFocus(ev,
                              contentSU,
                              kODPropSuggestedFrameShape,
                              kODNULL))
      {
        newShape = targetFrame->CreateShape(ev);
        newShape->ReadShape(ev, contentSU);
      }

      // Acquire the new embedded part
      TempODPart embeddedPart = myDraft->AcquirePart(ev, embeddedPartID);

      // Use my method to create an embedded frame of the desired shape
      embeddedFrame = somSelf->MyMakeEmbeddedFrame(ev,
                                                   targetFrame,
                                                   newShape,
                                                   embeddedPart);
    }

    // Set the link status of the embedded frame
    embeddedFrame->ChangeLinkStatus(ev, embeddedFrameLinkStatus);

    // Add the embedded frame and proxy content to this part's content
    //...

    // Now add facets to display the newFrame
    //...

  SOM_CATCH_ALL
  SOM_ENDTRY
}

Creating an Embedded Frame
   

This example demonstrates how a part could make a new frame for a part embedded from a content storage unit. This is mostly a wrapper around ODDraft::CreateFrame.

The containingFrame argument is the frame to contain the new embedded frame. The frameShape argument is the desired shape or null to use a default shape. The embeddedPart argument is the part to be displayed by the embedded frame.

SOM_Scope ODFrame* SOMLINK MyPartMyMakeEmbeddedFrame(MyPart *somSelf,
                                                     Environment *ev,
  ODFrame* containingFrame,
  ODShape* frameShape,
  ODPart* embeddedPart)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyMakeEmbeddedFrame");

  const ODBoolean notRoot = kODFalse;
  const ODBoolean notOverlaid = kODFalse;
  ODCanvas* nullCanvas = (ODCanvas*) kODNULL;

  ODFrame* embeddedFrame = kODNULL;
  TempODShape newShape = kODNULL;  // Automatically released

  SOM_TRY

    if (frameShape)
    {
      newShape = frameShape;
      newShape->Acquire(ev);
    }
    else
    {
      // No suggested frame shape, so use a default size
      ODRect rect = {0,0,80,80};
      newShape = containingFrame->CreateShape(ev);
      newShape->SetRectangle(ev, &rect);
    }

    embeddedFrame =
       somSelf->GetStorageUnit(ev)->GetDraft(ev)->CreateFrame(ev,
                   kODFrameObject,
                   containingFrame,
                   newShape,
                   nullCanvas,
                   embeddedPart,
                   _fSession->Tokenize(ev, kODViewAsFrame),
                   _fSession->Tokenize(ev, kODPresDefault),
                   notRoot,
                   notOverlaid);
  SOM_CATCH_ALL
  SOM_ENDTRY

  return embeddedFrame;
}

Paste As Dialog
   

Two data interchange objects, the clipboard and the drag and drop object, have a ShowPasteAsDialog method that present a dialog allowing the user to specify options on paste or drop. The ShowPasteAsDialog method is for the OS/2 and Windows platforms.

The ODPasteAsMergeSetting parameter to ShowPasteAsDialog specifies whether Merge with Contents or Embed as is initially chosen, and whether the other choice is available. This parameter allows the Paste As dialog to default to the same behavior your part uses on Paste. Specify this parameter using one of these ODPasteAsMergeSetting values:

kODPasteAsMerge

Merge with Contents is initially selected; Embed as allowed. Specify this constant if your part can incorporate one of the kinds in the content storage unit, and your part supports embedding.  

kODPasteAsMerge

Embed as is initially selected; Merge with Contents is allowed. Specify this constant if your part can incorporate one of the kinds in the content storage unit, but your part would psee embed it. This is the case if incorporation would result in a loss of fidelity, or if the content storage unit was created by copying a single embedded frame (that is, the content storage unit contains a kODPropContentFrame property).

kODPasteAsMergeOnly

Embed as is disabled. Specify this constant if your part does not support embedding, and your part can incorporate one of the kinds in the content storage unit, possibly translated.  

kODPasteAsEmbedOnly

Merge with Contents is disabled. Specify this constant if your part supports embedding, and no kind in the content storage unit can be incorporated.  

The ShowPasteAsDialog method (for the OS/2 and Windows platforms) returns its results in the ODPasteAsResult parameter. If ShowPasteAsDialog returns kODTrue, your part is responsible for disposing the selectedKind, translateKind, and editor fields of this structure. ShowPasteAsDialog will set any unspecified fields to nil.

Embedding a Part via the Paste As Dialog

When a part is embedded by the Paste As dialog, the user can specify a particular content kind. In addition, the user may specify the editor to bind to the embedded part. The caller of ShowPasteAsDialog must perform a few simple steps to ensure these choices are followed. This example uses the ShowPasteAsDialog method of the clipboard object.

ODPasteAsResult pasteAsResult;

// Parameters except pasteAsResult are omitted
if (clipboard->ShowPasteAsDialog(..., &pasteAsResult))
{
  if (pasteAsResult.mergeSetting == kODFalse)
  {
    // Clone the root storage unit from the clipboard as demonstrated
    //   in the general embedding recipe above
    key = clipDraft->BeginClone(...);
    newPartID = clipDraft->Clone(...)
    ...
    clipDraft->EndClone(...);

    // Acquire the newly embedded storage unit
    ODStorageUnit* newSU = myDraft->AcquireStorageUnit(ev, newPartID);

    // Set the preferred kind
    ODSetISOStrProp(ev, contentSU, kODPropPreferredKind,
                    kODISOStr, pasteAsResult.selectedKind);

    // Set the preferred editor
    if (pasteAsResult.editor != kODNoEditor)
    {
      ODSetISOStrProp(ev, contentSU, kODPropPreferredEditor,
                      kODEditor, pasteAsResult.editor);
    }

    // Release the storage unit
    newSU->Release(ev);
  }
}

// Complete embedding of the part
...

// When finished with pasteAsResult, be sure to dispose of the
//   selectedKind, translateKind, and editor fields.
if (pasteAsResult.selectedKind != kODNULL)
  DisposePtr(pasteAsResult.selectedKind)

if (pasteAsResult.translateKind != kODNULL)
  DisposePtr(pasteAsResult.translateKind)

if (pasteAsResult.editor != kODNULL)
  DisposePtr(pasteAsResult.editor)

Clipboard Recipes

 

This section describes the basic techniques for reading and writing the Clipboard. It assumes familiarity with "Data Interchange Basics" and references coding examples there. For further details on the Clipboard operations involving embedded frames, see "Imaging and Layout Recipes". For more information on Clipboard operations involving links, including the posting of link specifications and incorporation of linked content, see "Linking Recipes".

For a description of individual methods used in the recipes, see the OpenDoc Programming Reference for the appropriate class.

About Coding Examples

This section contains a number of coding examples in addition to descriptive text. The coding examples cover:
MyWriteToClipboard Writing intrinsic content to the clipboard.
MyCloneEmbeddedFrameToClipboard Writing a single embedded frame to the clipboard.
MyIncorporateFromClipboard Incorporating content from the clipboard.
MyEmbedFromClipboard Embedding content from the clipboard.

For simplicity, the coding examples use ODByteArrays to represent the content to be read or written. If your part does not maintain its content as an ODByteArray, you can either copy it into an ODByteArray, or, more likely, write data out or read data in chunks using the StorageUnitSetValue or StorageUnitGetValue utility functions.

The examples use exception handling in the form of TRY...CATCH_ALL...ENDTRY blocks to catch exceptions, and assumes that errors returned by SOM methods are automatically thrown upon return.

Note:

Code appearing in these recipes has not been tested, and may contain errors.

Header File

Parts that access the clipboard need to include the clipboard header file:

#ifndef SOM_ODClipboard_xh
#include <Clipbd.xh>
#endif

When Parts May Access the Clipboard

Parts should access the clipboard only when two conditions are true. First, the part must be running in the front-most process. The Clipboard is only defined for the front-most process; any attempt to access it in a background process is meaningless (the clipboard method Clear now returns error kODErrBackgroundClipboardClear if called when the process is in the background). Second, the part must own the clipboard focus.

If a part's draft is read only, it should disable Clipboard operations that would change the draft, such as Cut, Paste, and Paste As.

Except for the special case of AdjustMenus, described below, parts should complete their access to the clipboard before returning from the method that initiated the access. For example, parts should modify the clipboard within their HandleEvent method, and relinquish the clipboard focus before returning from HandleEvent. Parts should not spawn a thread that copies data to the clipboard, all the while holding the clipboard focus. Parts can minimize the amount of data transferred on a Cut or Copy by writing promises to the clipboard.

Determining that a Draft Can Be Modified

If a part calls an OpenDoc method that modifies a draft, and the draft's permissions do not allow modifications, the OpenDoc method will return an error. Parts can determine if a draft can be modified by testing the draft permissions:

ODDraftPermissions permissions =
  storageUnit->GetDraft(ev)->GetPermissions(ev);
if (permissions >= kODDPSharedWrite)
{
 ... the draft may be modified
}

Parts should check draft permissions before attempting an operation that modifies a draft. For example, a part should not enable the Cut, Paste, or Paste As menu items if the draft cannot be modified.

Clipboard Focus

To be thread safe, parts should acquire the clipboard focus prior to accessing the clipboard. Since parts usually need to inspect the clipboard to enable items on the Edit menu, they should call Arbitrator::RequestFocus from AdjustMenus to request the clipboard focus. If granted, a part can enable the Cut, Copy, Paste, and Paste As items as appropriate. A part can assume that a call to AdjustMenus will be followed by a call to HandleEvent; most parts should request the clipboard focus in AdjustMenus and relinquish the focus in HandleEvent.

Acquiring the Clipboard Focus


ODSession* session = somSelf->GetStorageUnit(ev)->GetSession(ev);
ODArbitrator* arbitrator = session ->GetArbitrator(ev);

ODTypeToken clipboardFocus = session->Tokenize(ev, kODClipboardFocus);
ODFrame* clipboardOwner = arbitrator->AcquireFocusOwner(ev, clipboardFocus);
if ((frame == clipboardOwner) ||
    arbitrator->RequestFocus(ev, clipboardFocus, frame))
{
  ODClipboard* clipboard = session->GetClipboard(ev);
  // Access the clipboard here.
  ...
}
ODReleaseObject(ev, clipboardOwner);

Parts may retain the clipboard focus while active, but must be prepared to relinquish the clipboard focus to other parts. Parts must relinquish the clipboard focus in their DeactivateFrame method if the deactivated frame owns the focus.    

Relinquishing the Clipboard Focus

 

// Insert in your part's DeactivateFrame method (only necessary if
// the part holds the Clipboard focus)

ODSession* session = somSelf->GetStorageUnit(ev)->GetSession(ev);
ODArbitrator* arbitrator = session ->GetArbitrator(ev);

arbitrator->RelinquishFocus(ev, clipboardFocus, frame);

// Insert into your part's RelinquishFocus method

ODSession* session = somSelf->GetStorageUnit(ev)->GetSession(ev);
ODArbitrator* arbitrator = session ->GetArbitrator(ev);

if (focus == clipboardFocus)
  arbitrator->RelinquishFocus(ev, clipboardFocus, frame);

Cloning to the Clipboard

   

Whenever a part puts data on the clipboard, even if object cloning is not performed, it should do so within a BeginClone-EndClone transaction (using the clone kind kODCloneCut or kODCloneCopy, as described in the Data Interchange Basics section). Using a clone transaction helps OpenDoc ensure that unfulfilled promises are resolved when a draft is closed.

The first paste following a cut is always a move in OpenDoc. There is no way for the part performing a paste to force pasting a copy of the data. This should not matter to your part; the paste recipes work whether the content is being moved or copied into your part. However, the behavior of links and embedded content will be different when moved versus copied.

Clipboard Update IDs

           

The update ID returned by the ActionDone and GetUpdateID methods of the clipboard object identifies a particular clipboard generation, not a particular change. When your part pastes data from the clipboard, your part must remember the clipboard update ID in case the operation is later undone, but your part should generate a new update ID (using the UniqueUpdateID method of the session object) to be associated with the pasted content. If the user pastes the contents of the clipboard twice, the clipboard's update ID will be the same both times. Each paste is a separate change, however, with which your part must associate different update IDs. See "Linking Recipes" for more information on update IDs.

Undoing Clipboard Operations

   

Parts that support Cut, Paste, Drag or Drop must support undoing and redoing those operations. If the user moves frames, links, or other objects from one part to another, via Cut and Paste or Drag and Drop, objects may be reused at the destination. If the clipboard or Drag and Drop operation is undone, the objects must be reinstated at the source. This can only happen if the part initiating the cut or drag and the part performing the paste or drop both support undo.

Your part is responsible for notifying the clipboard whenever a Cut, Copy, or Paste operation is done, undone, or redone. When your part performs a Cut, Copy, or Paste, call the ActionDone method of the clipboard. This method takes as its argument the cloneKind your part used to access the clipboard (either kODCloneCut, kODCloneCopy, or kODClonePaste). When your part undoes a Cut, Copy, or Paste, call the ActionUndone method of the clipboard object, specifying the update ID returned by the clipboard's ActionDone method at the time of the original action, and the clone kind used at the original access. When your part redoes a Cut, Copy, or Paste, call the ActionRedone method of the clipboard object instead.

When a cut or copy operation is undone, it is not necessary to restore the clipboard to its previous contents. Because of this, code implementing the redo of a paste operation cannot assume the clipboard content is the same as it was when the original paste was performed. Redo of a paste operation must be implemented by restoring content from a private cache.

When a part cuts an object to the clipboard, a reference to the object should be saved in an undo action. If the part's UndoAction method is called, it must (1) reinstate the object into its content model from its undo information, and (2) notify the clipboard that the cut was undone (see below). Similarly, if the part's RedoAction method is called, it should (1) remove the object from its content model, and (2) notify the clipboard that the cut was redone. When the part's DisposeActionState method is called, the object reference in the action data should be released. See the section below for special instructions on handling embedded frames that have been cut or pasted.

Undoing the Cut or Paste of Embedded Frames

                   

The part performing a cut or paste of one or more embedded frames is responsible for handling the in-limbo status of the frame correctly. If the user undoes the cut of an embedded frame, your part must set the in-limbo flag to kODFalse in its UndoAction method. If the user redoes the cut of an embedded frame, your part must set the in-limbo flag to kODTrue. When your DisposeActionState method is called to commit a cut, your part must examine the state of the in-limbo flag. If the IsInLimbo returns kODFalse, your part must release the frame reference it holds; if IsInLimbo returns kODTrue, your part must remove the frame by calling the Remove method of the frame.

Similarly, if the user undoes the paste of an embedded frame, your part's UndoAction method must restore the in-limbo flag to the value it had before the paste was performed. If the user redoes the paste of an embedded frame, your part must set the in-limbo flag to kODFalse. When your DisposeActionState method is called to commit a paste, your part must examine the state of the in-limbo flag. If the IsInLimbo returns kODFalse, your part must release the frame reference it holds. If IsInLimbo returns kODTrue, your part's action depends on the in-limbo status of the frame at the time the paste was performed. If the frame was in-limbo, your part must release the frame reference it holds (some other part will remove it.) If the frame was not in-limbo, your part must remove the frame by calling the Remove method of the frame.

Cleanup in ReleaseAll

   

When a part's ReleaseAll method is called, the part should check to see if it has a promise or a link specification on the clipboard. If so, it needs to fulfill the promise or remove the link specification.

Writing Intrinsic Content to the Clipboard

     

MyWriteToClipboard demonstrates how a part might implement a routine to copy intrinsic content to the clipboard. It uses the MyWriteToContentSU method, described in the Data Interchange Basics recipe, to write promises for the data. MyWriteToClipboard should only be called when this part holds the clipboard focus and is in the foreground, as described earlier.

The promiseData parameter is whatever information the part needs to identify the promised content when its FulfillPromise method is called. The contentShape parameter will be written as the suggested frame shape annotation. This property will be used as the frame shape should content be embedded from the clipboard.

The partName parameter will be written as the part name annotation. If your part associates a name with the content written, you may want the part created should the content be embedded at the destination to bear this name. If not, the partName may be null, and no part name annotation will be created.

The optional linkSpecData parameter will be written into a link specification if the operation is a copy. The cloneKind parameter is either kODCloneCut or kODCloneCopy.

This method returns the update ID of this clipboard operation. This ID can be saved and later compared to the current clipboard update ID to determine if the contents of the clipboard have been replaced.

ODUpdateID MyWriteToClipboard (Environment *ev,
  ODByteArray* promiseData,
  ODShape* contentShape,
  ODIText* partName,
  ODByteArray* linkSpecData,
  ODCloneKind cloneKind)
{
  ODClipboard* clipboard = fSOMSelf->
                           GetStorageUnit(ev)->
                           GetSession(ev)->
                           GetClipboard(ev);
  ODVolatile(clipboard);

  // Remove any existing data on the clipboard
  clipboard->Clear(ev);

  // Get the content storage unit for the clipboard
  ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);

  TRY

    self->MyWriteToContentSU(ev, clipContentSU, cloneKind, promiseData,
                             contentShape, partName);

    // If a copy operation is being performed,
    // write a link spec to the clipboard.
    if ((cloneKind == kODCloneCopy) && linkSpecData)
    {
      // See "Linking Recipes".
      ...
    }
    clipboard->ExportClipboard(ev);

  CATCH_ALL

    // If an exception was raised, clear the clipboard.
    //   The previous contents of the clipboard are lost.
    clipboard->Clear(ev);
    RERAISE;

  ENDTRY

  // Return the update ID associated with this clipboard operation.
  return clipboard->ActionDone(ev, cloneKind);
}

Copying Intrinsic Content to the Clipboard

   

Copying intrinsic content to the clipboard is as simple as calling MyWriteToClipboard with the suitable clone kind.

ODUpdateID clipboardUpdateID = self->MyWriteToClipboard(ev,
    promiseData,
    contentShape,
    partName,
    linkSpecData,
    kODCloneCopy);

Cutting Intrinsic Content to the Clipboard

       

If the operation is a cut, after putting content on the clipboard, the intrinsic content should be removed. When an embedded frame is cut, the part must delete any facets displaying that frame. Call the containing facet's RemoveFacet method to remove a facet, then delete the facet object. The cut frame's containing frame should be set to kODNULL to remove it from the layout hierarchy, but not removed from the part. A reference to the cut frame should be placed in undo action data. Other frames displaying the part are not affected.

When content is cut, any objects cloned to the clipboard may be reused if pasted back into the same draft. This includes embedded frames, link and link source objects that a part typically references directly. If the cut content references any other persistent objects, those references should retained in undo data so the operation can be undone. When the references are no longer needed to support undo, the objects should be released. See "Undoing Clipboard Operations" for more information.

Each embedded frame cut along with intrinsic content must have its in-limbo flag set to kODTrue.

cutEmbeddedFrame->SetInLimbo(ev, kODTrue);

Copying a Single Embedded Frame to the Clipboard

   

Your part should follow this recipe to put a single embedded frame on the clipboard. The promiseProxyData parameter. The similarity to MyWriteToClipboard; only the line calling MyCloneEmbeddedFrameToContentSU is different.

The embeddedFrame parameter is the frame being cut or copied. The optional promiseProxyData is proxy content that this part associates with the embedded frame and will be written as proxy content. The optional linkSpecData parameter will be written into a link specification if the operation is a copy. The cloneKind parameter is either kODCloneCut or kODCloneCopy.

This method returns the update ID of this clipboard operation.

ODUpdateID MyCloneEmbeddedFrameToClipboard (Environment *ev,
  ODFrame* embeddedFrame,
  ODByteArray* promiseProxyData,
  ODByteArray* linkSpecData,
  ODCloneKind cloneKind)
{
  ODClipboard* clipboard = somSelf->
                           GetStorageUnit(ev)->
                           GetSession(ev)->
                           GetClipboard(ev);
  ODVolatile(clipboard);

  // Remove any existing data on the clipboard
  clipboard->Clear(ev);

  // Get the content storage unit for the clipboard
  ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);

  TRY

    self->MyCloneEmbeddedFrameToContentSU(ev, clipContentSU, cloneKind,
                                          embeddedFrame, promiseProxyData);

    // If a copy operation is being performed, write a link spec to the
    //   clipboard if this part supports linking.
    if ((cloneKind == kODCloneCopy) && linkSpecData)
    {
      // See "Linking Recipes"; note conditions when
      // a link specification should not be written.
      ....
    }
    clipboard->ExportClipboard(ev);

  CATCH_ALL

    // If an exception was raised, clear the clipboard.
    // he previous contents of the clipboard are lost.
    clipboard->Clear(ev);
    RERAISE;

  ENDTRY

  // Return the update ID associated with this clipboard operation.
  return clipboard->ActionDone(ev, cloneKind);
}

Cutting a Single Embedded Frame to the Clipboard

   

Unlike Copy, Cut is an undoable operation. Your implementation of Cut can be based on Copy, with a little work before and after the copy.

// Save the current selection in an undo action.  MyMakeUndoAction is not
//  described here.
ODActionData actionState = somSelf->MyMakeUndoAction(ev);

ODUpdateID clipboardUpdateID =
    self->MyCloneEmbeddedFrameToClipboard(embeddedFrame, promiseData,
                                          kODCloneCut);

// Set the in-limbo flag of the cut frame to kODTrue.
embeddedFrame->SetInLimbo(ev, kODTrue);

// Add an undo action for this operation and delete the selection.
ODUndo* undo = somSelf->GetStorageUnit(ev)->GetSession(ev)->GetUndo(ev);
undo->AddActionToHistory(ev, somThis->fPartWrapper, actionState,
                         kODSingleAction, ...);

// MyDeleteSelection is a part-specific method not described here.
somSelf->MyDeleteSelection(ev);

// Associate an update ID with this cut, and notify containing parts
// of this change
ODUpdateID cutUpdateID = fSOMSelf->
                         GetStorageUnit(ev)->
                         GetSession(ev)->
                         UniqueUpdateID(ev);
myFrame->ContentUpdated(ev, cutUpdateID);

// Make sure that this change is saved when the document is closed.
somSelf->GetStorageUnit(ev)->GetDraft(ev)->SetChangedFromPrev(ev);

Incorporating Content from the Clipboard

     

Incorporating from the clipboard is demonstrated by this code that gets the clipboard content storage unit and calls the MyReadFromContentSU routine described in the Data Interchange Basics section. In this simple example, the incorporated content is inserted where there are no source links maintained by this part. Because Paste must be an undoable operation, action data is added to the undo history if incorporation is successful.

The targetFrame argument identifies the frame being incorporated into and is passed to MyReadFromContentSU.

This method returns the update ID of this clipboard operation. This is not the same as the update ID associated with the content change to this part.

ODUpdateID MyIncorporateFromClipboard (Environment *ev, ODFrame* targetFrame)
{
  ODClipboard* clipboard = fSOMSelf->
                           GetStorageUnit(ev)->
                           GetSession(ev)->
                           GetClipboard(ev);
  ODUpdateID clipboardUpdateID = kODUnknownUpdate;

  // Get the content storage unit of the clipboard
  ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);

  ODVolatile(clipboard);

  TRY
    fSOMSelf->MyReadFromContentSU(ev,
                                  clipContentSU,
                                  targetFrame,
                                  kODClonePaste,
                                  kODNotInLink);
    clipboardUpdateID = clipboard->ActionDone(ev, kODClonePaste);

    // If the incorporation was successful, add an undo action.
    // The undo data should include clipboardUpdateID and
    // the in-limbo status of any frames that were embedded.
    ODActionData actionState = ... // part specific
    ODUndo* undo = somSelf->GetStorageUnit(ev)->GetSession(ev)->GetUndo(ev);
    undo->AddActionToHistory(ev,
                             somThis->fPartWrapper,
                             actionState,
                             kODSingleAction,
                             ...);

    // Associate an update ID with this paste,
    // and notify containing parts of this change
    ODUpdateID pasteUpdateID =
          fSOMSelf->GetStorageUnit(ev)->GetSession(ev)->UniqueUpdateID(ev);
    targetFrame->ContentUpdated(ev, pasteUpdateID);

    // Make sure that this change is saved when the document is closed
    fSOMSelf->GetStorageUnit(ev)->GetDraft(ev)->SetChangedFromPrev(ev);

  CATCH_ALL

  ENDTRY

  // Return the update ID associated with this clipboard operation.
  return clipboardUpdateID;
}

Embedding a Part from the Clipboard

     

Embedding from the clipboard is demonstrated using the routine MyEmbedContentSU, which performs the actual work and is described in the Data Interchange Basics section.

The targetFrame argument identifies the frame being incorporated into and is passed to MyEmbedContentSU.

This method returns the update ID of this clipboard operation.

ODUpdateID MyEmbedFromClipboard (Environment *ev, ODFrame* targetFrame)
{
  ODClipboard* clipboard = fSOMSelf->
                           GetStorageUnit(ev)->
                           GetSession(ev)->
                           GetClipboard(ev);

  // Get the content storage unit for the clipboard
  ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);

  ODVolatile(clipboard);

  TRY

    fSOMSelf->MyEmbedContentSU(ev,
                               clipContentSU,
                               targetFrame,
                               kODClonePaste,
                               kODNotInLink);

    // Associate an update ID with this paste,
    // and notify containing parts of this change
    ODUpdateID pasteUpdateID = fSOMSelf->
                               GetStorageUnit(ev)->
                               GetSession(ev)->
                               UniqueUpdateID(ev);
    targetFrame->ContentUpdated(ev, pasteUpdateID);

    // Make sure that this change is saved when the document is closed
    fSOMSelf->GetStorageUnit(ev)->GetDraft(ev)->SetChangedFromPrev(ev);

  CATCH_ALL

  ENDTRY

  // Return the update ID associated with this clipboard operation.
  return clipboard->ActionDone(ev, kODClonePaste);
}

Container Application Requirements

       

Container applications need to call clipboard object methods to inform the clipboard of draft closings so the correct move or copy semantics can be enforced. Parts do not call these methods.

The DraftSaved method must be called to notify the clipboard object that a document draft has been saved. The DraftClosing method must be called to notify the clipboard object that a document draft is closing. The DraftClosing call must be made before the draft object is closed.

Drag and Drop

   

The Drag and Drop protocol provides a direct manipulation alternative to the clipboard for copying and moving data between parts in a document, between documents and between documents and the desktop.

This section contains a number of recipes for transferring data through the OpenDoc drag and drop mechanism. See the OpenDoc Programming Reference for a description of individual methods.

The examples use exception handling in the form of TRY...CATCH_ALL...ENDTRY blocks to catch exceptions, and assumes that errors returned by SOM methods are thrown upon return.

Note:

Code appearing in these recipes has not been tested, and may contain errors. They serve as examples of how to use the OpenDoc functions to accomplish specific objectives in particular circumstances. Actual part code usually needs to draw from several recipes to handle the variety of conditions that occur in a real application.

The drag and drop focus (or the lack of it) There is no drag and drop focus because a new drag and drop operation cannot occur until the previous operation completes.

Undo of a drag and drop A copy or move using drag and drop should be undoable. An undo transaction should be started by the part initiating the drag; the part receiving the drop may add an undo action to the transaction. The transaction should be ended after the drop completed by the part initiating the drag. See the "Undo" recipe for details.

Embedded frames and their in-limbo flag When an embedded frame is dragged or dropped, parts must set the in-limbo flag of the frame correctly. Parts are also responsible for setting the in-limbo flag when a drag and drop operation is undone, redone, or committed via their DisposeActionState method. The code examples here demonstrate the proper manipulation of this flag during the drag and drop, but do not cover how your part must support the in-limbo flag in your UndoAction, RedoAction, or DisposeActionState methods. The complete recipes for setting this flag, and for the actions to take in DisposeActionState, are discussed in the OpenDoc Programmer's Guide.

Preparing for a Drop

A part which can receive a drop should call SetDroppable on all its display frames which can accept a drop. This can be done during ODPart::DisplayFrameAdded or ODPart::DisplayFrameConnected.

SOM_Scope void  SOMLINK MyPartDisplayFrameAdded(AppleTest_Container *somSelf,
                                                Environment *ev,
                                                ODFrame* frame)
{
 ...
 frame->SetDroppable(ev, kODTrue);
 ...
}

If your part does not call SetDroppable on a display frame, the part will not receive any DragEnter, DragWithin, DragLeave and Drop from any facet on that frame.

If your part no longer wishes to receive drops from a particular frame, it can call ODFrame::SetDroppable with kODFalse.

Detecting a Drag (Handling Mouse-Down Events)

A drag can be initiated by any part even when the part does not have any display frame that is set to receive drops.

On the OS/2 platform, a drag is initiated when a part receives the PM WM_BEGINDRAG message. This message can be detected in the part's HandleEvent method upon receipt of kODEvtMouseDown or kODEvtMouseDownEmbedded by examining the event info original type.

Recipe: (for OS/2)

MyPartHandleEvent(...)
{
  case kODEvtMouseDown:
  case kODEvtMouseDownEmbedded:
   if (eventInfo->originalType == WM_BEGINDRAG)
       somSelf->InitiateDrag(...);
   ...
}

On platforms other than OS/2, parts should initiate a drag in response to kODEvtMouseDown or kODEvtMouseDownEmbedded, if the part-specific drag criteria has also been met. For example, the user moved the mouse over the part's selected content while the part's specified drag button was down.

When handling drag-related events, check to see if the coordinates of the mouse pointer in the facet are within an item that can be dragged, such as a content item, a text selection, or a selected or bundled embedded frame. For more information on handling events, see the "Basic Event Handling" recipe).

Initiating a Drag

Once a part knows that a drag can be initiated, the source part should call ODSession's GetDragAndDrop to get the ODDragAndDrop object. Then it should call ODDragAndDrop's Clear and ODDragAndDrop's GetContentStorageUnit to get the ODStorageUnit where data of the dragged object(s) is copied. Then it can initiate the drag by calling ODDragAndDrop's StartDrag. An image for dragging feedback must be provided by the source part.

Drag outline: The following provides platform-specific information for drag outline.

Dragging a frame: In general, it is a bad idea to drag a frame into itself. Therefore, if you are dragging a frame or a set of frames, you should call ODFrame::SetDragging using kODTrue for each frame being dragged. This will tell OpenDoc drag and drop that these frames are not supposed to accept this particular drop.

Mouse-down offset: The source part should write out the mouse-down offset from the bottom-left corner of the selection. This enables the destination part to place the selection at the correct offset from the mouse up position when it is dropped.

Clone kind: If the source part needs to clone its content (whether it is intrinsic or embedded parts), it should use kODCloneCut. kODCloneCopy is only used when the content cannot be moved. If kODCloneCopy is used, kODDropMove will never be returned as the return result of ODDragAndDrop::StartDrag.

Recipe:

// Get the ODDragAndDrop object from the session

ODDragAndDrop* dragAndDrop =
     somSelf->GetStorageUnit(ev)->GetSession(ev)->GetDragAndDrop(ev);

// Reinitialize the ODDragAndDrop object
dragAndDrop->Clear(ev);

// Get the Storage Unit where data for dragged objects are going to
// be written
ODStorageUnit* storageUnit = dragAndDrop->GetContentStorageUnit(ev);

// Write out the data.
// Refer to the following sections in Clipboard recipes:
//   Annotating the clipboard with a frame shape
//   Writing a frame shape to the clipboard
//   Always use BeginClone and EndClone
//   Putting intrinsic content on the clipboard
//   Copying content to the clipboard
//   Putting a single embedded frame on the clipboard.
 ...
// If you are dragging a frame or a set of frames,
// you should notify OpenDoc drag and drop
// that the frames are being dragged
embeddedFrame->SetDragging(ev, kODTrue);

// Anticipate a move by marking all dragged frames as in-limbo
embeddedFrame->SetInLimbo(ev, kODTrue);

// Write out the mouse down offset
storageUnit->AddProperty(ev, kODPropMouseDownOffset);
storageUnit->AddValue(ev, kODPoint);

// Calculate offset
 ...
// Write out the mouse down offset
storageUnit->SetValue(ev, &ba);

// create variable to inform ODDragAndDrop object of image type
ODType imageType;

#if defined(_PLATFORM_OS2_)
// *** example OS/2 image initialization: ***
imageType = kODOS2DragImage;
DRAGIMAGE dragRegion;

// Initialize the dragRegion/DRAGIMAGE structure.
 ...
#elif defined(_PLATFORM_WIN32_)
// *** example Windows image initialization: ***
imageType = kODWindowsRegionHandle;
ODRgnHandle dragRegion = kODNULL;

// Initialize the dragRegion/HRGN handle
 ...
#elif defined(_PLATFORM_UNIX_)
// *** example AIX image initialization: ***
imageType = kODAIXRegion;
ODRgnHandle dragRegion = XCreateRegion();

// Initialize the dragRegion/X Region handle
 ...
#endif// _PLATFORM_OS2_ else _PLATFORM_WIN32_ else _PLATFORM_UNIX_

// Create drag image byte array and
// refCon byte array ('refCon' is the event data)
ODByteArray dragImgBA = CreateByteArrayStruct(&dragRegion, sizeof(dragRegion));
ODByteArray eventBA = CreateByteArrayStruct(event, sizeof(ODEventData*));

// Initiate the drag
ODPart* destPart;

#if (defined(_PLATFORM_WIN32_) || defined(_PLATFORM_OS2_))
dropResult = dragAndDrop->StartDrag(ev, sourceFrame,
                                    imageType, &drgImagBA,
                                    &destPart, &eventBA);
#else
dropResult = dragAndDrop->StartDrag(ev, sourceFrame,
                                    imageType, &drgImagBA,
                                    &destPart, (ODByteArray*)event);
#endif // _PLATFORM_OS2_ or _PLATFORM_WIN32_ else _PLATFORM_UNIX_

// If the platform performs asynchronous drags, return immediately
// and move the remaining code to your part's DropCompleted method:
if (dropResult == kODDropUnfinished)
  return;

// If you have set any frames to not accept the drop, unset them.
embeddedFrame->SetDragging(ev, kODFalse);

// If the drag was not a move, dragged frames are no longer in limbo
if (dropResult != kODDropMove)
{
  embeddedFrame->SetInLimbo(ev, kODFalse);
}

if (dropResult == kODDropMove)
{
  // The selected data that was dragged should be removed.  See the
  // "Clipboard Recipes, Cutting Intrinsic Content to the Clipboard"
  // for an overview of the requirements.  Specifically note that
  // if a frame or set of frames was moved, old facets must be removed.
  // The source part cannot use the moved frame's facet iterator.
  // It must instead use the containing facet's iterator to identify
  // the facets to be deleted.
 ...
}

if (destPart != kODNULL)
  destPart->Release(ev);

Tracking a Drag

Entering a part's facet: ODPart's DragEnter is called when the mouse enters a facet. The part should examine the available data types of the dragged items using the ODDragItemIterator passed in. If the part can handle a drop of the dragged object, it should provide the appropriate feedback (e.g., adorning the droppable frame) If the destination part cannot handle the data types, nothing should be done.

If there is more than one drag item, the Part should make sure that it can accept all the drag items. If there is one or more drag items that the Part cannot accept, it should return kODFalse from DragEnter and do no visual feedback.

The following code fragment shows a simple example where a part which can only accept one kind (kMyKind) determines whether the current drag can be accepted.

ODBoolean CanAcceptThisDrag(Environment* ev, ODDragItemIterator* dragInfo)
{
 // assuming that we can accept all the drag items
 ODBoolean canAccept = kODTrue;
 for (ODStorageUnit* dragSU = dragInfo->First(ev);
   (dragInfo->IsNotComplete(ev) && (canAccept == kODTrue));
   dragSU = dragInfo->Next(ev))
 {
#ifdef _PLATFORM_OS2_
  if (dragSU->GetSession(ev)->GetDragAndDrop(ev)->CanIncorporate(ev, dragSU,
                                                   kMyKind))==kODFalse)
#else
  if (!dragSU->Exists(ev, kODPropContents, myType, 0))
#endif
   canAccept = kODFalse;
 }
 return canAccept;
}

However, checking the kinds is not enough to provide the full feedback according to the Drag Manager guidelines. The guidelines dictate that no highlighting should be shown on the frame from which the drag is initiated until the drag has left and returned to the frame.

In order to help part developers implement this, OpenDoc drag and drop provides two Drag Attributes:

kODDragIsInSourceFrame shows that the drag has not yet left the source frame. kODDragIsInSourcePart shows that the drag is currently in the source part.

ODDragResult MyPartDragEnter(MyPart* somSelf,
      Environment* ev,
      ODDragItemIterator* dragInfo,
      ODFacet* facet,
      ODPoint where)
{

 // Get the drag and drop object associated with this drag.
 // Note that the session from dropSU may not be the same as the session
 // of the target part.
 ODDragAndDrop* dad = dropSU->GetSession(ev)->GetDragAndDrop(ev);
 ODULong dragAttributes = dad->GetDragAttributes(ev);

 // Determine whether we can handle this drag.
 ODBoolean canAccept = CanAcceptThisDrag(ev, dragInfo);

 // If we can accept the data and the drag has left the source frame,
 // do some adornment.
 // Here we are going to display a drag hilite on the facet.
 // We are calling our part's InvertDragHilite method (shown below).

 if ((canAccept == kODTrue) && !(dragAttributes & kODDragIsInSourceFrame))
  somSelf->InvertDragHilite(ev, facet);

 // Return ODDragResult to show whether a Drop can happen in this facet.
 return canAccept;
}

 // *** Example OS/2 InvertDragHilite method ************
void MyPartInvertDragHilite(MyPart* somSelf, Environment * ev, ODFacet* facet)
{
   // this method toggles highlighting for the facet

   TRY

   HPS hps;

   CFocusWindow f(ev, facet, (ODShape*)kODNULL, &hps, (HWND*)kODNULL,
                  CFocusWindow::DragPS);

   ODTempPolygon poly;
   POLYGON polygon;
   ODContour *pContour;
   int i;
   TempODShape clipShape = facet->AcquireAggregateClipShape(ev, kODNULL);
   clipShape->CopyPolygon(ev,&poly);

   GpiSetMix(hps, FM_INVERT);
   GpiBeginPath(hps, 1);
   for (pContour = poly.FirstContour(), i = 0;
        i < poly.GetNContours();
        pContour = pContour->NextContour(), i++)
   {
      pContour->AsPOLYGON(polygon);
      GpiMove(hps, &polygon.aPointl[polygon.ulPoints-1]);
      GpiPolyLine(hps, polygon.ulPoints, polygon.aPointl);
      SOMFree(polygon.aPointl);
   }
   GpiEndPath(hps);
   GpiSetLineWidthGeom(hps, 8);
   GpiStrokePath(hps, 1, 0);

   CATCH_ALL
   ENDTRY
}
 // *** End of OS/2 example    *****************

Within a part's facet: ODPart's DragWithin is called continuously when the mouse is still in the facet. This allows the part to do any processing desired. One example is when the frame has several hot spots where objects can be dropped. If the mouse is not over these hot spots, the cursor may need to be changed to reflect that no dropping can be done there even though it is still in a droppable frame. Again, a ODDragItemIterator is passed in so that the part can examine the availabe data types of the dragged objects.

ODPart's DragWithin also provides a chance for the part to examine the state of the machine. For example, some part may want to find out whether the modifier keys are down or not.

Leaving a part's facet: ODPart's DragLeave is called when the mouse leaves a droppable frame. This allows the part to clean up after a drag within it (e.g., removing adornment on the frame).

Receiving a Drop

If the mouse is released within a facet, ODPart's Drop is called on the part which owns the facet. The part can then figure out whether it can receive the dropped data using the ODDragItemIterator passed in.

OpenDoc User Interface Guidelines defines guidelines for the destination part to decide whether a drop should be a move or a copy. OpenDoc drag and drop provides this information to the part through the OpenDoc Drag Attributes . The part can get the OpenDoc Drag Attributes through the ODDragAndDrop object. When ODDragAndDrop::GetDragAttributes is called, an ODULong is returned. The value of the OpenDoc Drag Attributes reflects important information about the Drop:

kODDropIsInSourceFrame

The drop happens in the frame where the drag is initiated.

kODDropIsInSourcePart

The drop happens in the part where the drag is initiated.

kODDropIsMove

The drop is a move.

kODDropIsCopy

The drop is a copy.

kODDropIsLink

The drop is a link

kODDropIsPasteAs

The destination part should put up the Paste As dialog box and enable the user to take further actions.

The destination part should look for kODPropMouseDownOffset to position the dropped data according to the original mouse-down offset.

ODDropResult MyPartDrop(MyPart* somSelf,
              Environment* ev,
              ODDragItemIterator *dropInfo,
              ODFacet* facet,
              ODPoint where)
{
 ...

 // Determine whether we can handle this drag.
 ODBoolean canAccept = CanAcceptThisDrag(ev, dragInfo);

 // Get the OpenDoc Drag Attributes for this drop
 ODDragAndDrop* dad = somSelf->
                      GetStorageUnit(ev)->
                      GetSession(ev)->
                      GetDragAndDrop(ev);
 ODULong dragAttributes = dad->GetDragAttributes(ev);

 ODDropResult dropResult = kODDropCopy;

 if(dragAttributes & kODDropIsMove)
         dropResult = kODDropMove;

 ODStorageUnit       *dropSU;

#ifdef _PLATFORM_OS2_
 ODStorageUnit       *OS2dropSU;
 for (OS2dropSU = dropInfo->First(ev);
               dropInfo->IsNotComplete(ev);
               dropSU = dropInfo->Next(ev))
 {
   // first thing is to check for dragitem value, can't do anything
   // more if it is not present...
   if (!dropSU->Exists(ev, kODPropContents, (ODValueType)kODDragitem, 0))
   {
      dropResult = kODDropFail;
      break;
   }

   OS2dropSU->Focus(ev, kODPropContents, kODPosUndefined,
                         (ODValueType)kODDragitem,   0, kODPosUndefined);

   // create a view of our focused value for the drag&drop object
   ODStorageUnitView *dropView = dropSU->CreateView(ev);

   // ask the drag&drop object to render the data
   if(!dropSU->GetSession(ev)->GetDragAndDrop(ev)->GetDataFromDragManager(ev,
                                        dropView,
                                        &dropSU))
#else
 for (dropSU = dropInfo->First(ev);
               dropInfo->IsNotComplete(ev);
               dropSU = dropInfo->Next(ev);

 {
if (!dropSU->Exists(ev, kODPropContents, myType, kODPosAll))
   {
      dropResult = kODDropFail;
      break;
   }
#endif
   if(dragAttributes & kODDropIsPasteAs) {
    // See the following clipboard recipe:
    // Embedding a part via the Paste As Dialog
    ...
   }
   else {
    // See the following clipboard recipes:
    // Incorporating content from the clipboard
    // Embedding a part from the clipboard
    ...
   }

   // For each frame embedded during the drop,
   // remember its current in-limbo status
   // before setting the status to false (not in-limbo)
   wasInLimbo = embeddedFrame->IsInLimbo(ev); // for use during undo
   embeddedFrame->SetInLimbo(ev, kODFalse);
 }
 return dropResult;
}

Move, Copy, Paste As: The destination part can also override a move and make the drop into a copy. However, changing a copy to a move is not allowed.

Returning the drop result: After the destination part has responded to the drop, it needs to notify the source part whether it has accepted the drop as a move or a copy. The following are the predefined values of ODDropResult:

kODDropFail

The drop has failed.

kODDropCopy

The drop is a copy.

kODDropMove

The drop is a move.

kODDropUnfinished

The drop is not finished. This value should never be returned by the destination part. It is only used by OpenDoc when DragAndDrop::StartDrag returns immediately in the case of an asynchronous drag.

This result is returned to the source part (by the system) to indicate whether the drop was accepted and what action the source part should take.

Incorporating Data from a Non-OpenDoc Document

When a Part's Drop method is called, it is given an ODDragItemIterator. ODDragItemIterator allows the user to access a collection of Drag Items. As described above, it is the receiver's (the destination part's) responsibility to iterate through all the Drag Items to find out whether it can receive the Drop.

If the Drop comes from an OpenDoc part, there is only one Drag Item in the collection. If the Drop comes from a non-OpenDoc application (for example, the desktop) there may be one or more Drag Items. If there is more than one Drag Item, the destination part can only accept the drop if it can accept all the drag items.

On the OS/2 platform, if the drag was initiated by the Workplace Shell and the dropped object is one or more files, the storage unit(s) rendered by GetDataFromDragManager will contain the following values in the kODPropContents property:

The selected kind

The part kind the selected RMF was mapped to; no value is set for this kind, the actual data has to be read from the dropped file.

kODFileType

The name of the dropped file.

kODFileTypeEA

Any extended attributes that are associated with the file.

On the Windows platform, if the drag was initiated by the Explorer Shell and the dropped object is one or more files, the storage unit(s) retrieved from the Drag Item Iterator will contain the following value in the kODPropContents property:

kODFileType

The name of the dropped file.

Embedding Data from a Non-OpenDoc Document

Even when the destination part cannot incorporate the data, there may be other parts on the system which can. Following the OpenDoc model, the destination part can then embed the non-OpenDoc file and let another Part Editor handle the data.

From the destination's point of view, the recipe for embedding data from a non-OpenDoc document is no different from embedding an OpenDoc part. It first clones the Content Storage Unit and then it calls AcquirePart using the cloned Storage Unit. The OpenDoc Binding mechanism will use the corresponding Part Editor to manipulate the content of the cloned Storage Unit.

Here's what the destination part should do in its Drop method:

...

ODDraft* dadDraft = dropSU->GetDraft(ev);
ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);
ODDraftKey key = 0;
ODID newPartID = kODNULLID;
ODPart* newPart = kODNULL;

ODVolatile(key);
ODVolatile(newPartID);

SOM_TRY

 // Begin a transfer from the drag and drop container into this draft.
 key = dadDraft->BeginClone(ev, myDraft, facet->GetFrame(ev), kODClonePaste);

 // Clone the Storage Unit (which contains information on the
 // non-OpenDoc document) from the drag and drop container.
 //   Since we are cloning only a Storage Unit, there is no scoping. 0
 //   is passed in to indicate that.
 newPartID = dadDraft->Clone(ev, key, dropSU->GetID(ev), 0, 0);

 dadDraft->EndClone(ev, key);

SOM_CATCH_ALL

 if (key != 0)
  dadDraft->AbortClone(ev, key);

 newPartID = kODNULLID;

SOM_ENDTRY

// Get the new embedded part
 if (newPartID == kODNULLID) {
  // cleanup and return error
 }
 else {
  newPart = myDraft->AcquirePart(ev, newPartID);
 }

...

Here's what the newly created part should do in its InitPartFromStorage method:

Note:

This recipe is an example for the OS/2 platform.

CHAR szFile[CCHMAXPATH];
// get the dropped file name
if (myStorageUnit->Exists(ev, kODPropContents, (ODValueType)kODFileType, 0))
{
   myStorageUnit->Focus(ev,
                        kODPropContents,
                        kODPosUndefined,
                        (ODValueType)kODFileType,
                        0,
                        kODPosUndefined);
   ULONG dataSize = myStorageUnit->GetSize(ev);
   StorageUnitGetValue(myStorageUnit, ev, dataSize, (ODValue) szFile);

   // check for the existance of a non-OPENDOC kind the part supports;
   // that should be the type of data in the file;
   // this has to be done for all such kinds, until a match is found
   if (myStorageUnit->Exists(ev, kODPropContents, myOS2Kind, 0)) {
     // open the file and read in the data
    ...
   }
   // another option would be to determine the file type by reading
   // the kODFileTypeEA value

   // remove any undesired values and add the primary value kind
   // to myStorageUnit
}

Promises

 

The basic mechanisms for transferring data between Parts are the Clipboard and drag and drop. If the data to be exported is very big, the part may choose to export the data as a promise. When the data is retrieved by the destination part, the source part will be requested to fulfill the promise it put out.

The format of a Promise is determined by the Part. The only restriction is that a Promise must be able to be written to a Storage Unit Value.

Putting Out a Promise

The following code fragment shows how a Part can put out a Promise in response to a mouse down event when a Drag is going to be initiated. (This recipe can also be used to put out a promise to the Clipboard).

ODBoolean MyPartHandleDragging(MyPart* somSelf,
                                Environment* ev,
                                ODFacet* facet,
                                ODPoint where,
                                ODEventData event)
{

 ...

 // Get the ODDragAndDrop object from the session.
 ODDragAndDrop* dragAndDrop =
     this->GetStorageUnit(ev)->GetSession(ev)->GetDragAndDrop(ev);

 // Reinitialize the ODDragAndDrop object.
 dragAndDrop->Clear(ev);

 // Get the Storage Unit for the dragged object data
 ODStorageUnit* storageUnit = dragAndDrop->GetContentStorageUnit(ev);

 // Focus the Storage Unit to the Property.
 storageUnit->Focus(ev,
                    kODPropContents,
                    kODPosUndefined,
                    kODNULL,
                    0,
                    kODPosUndefined);

 // Prepare the promise. This section is Part specific.
 MyPromise promise = ...

 // Put the promise data in an ODByteArray.
 ODByteArray ba;
 ...

 // Put out the promise.
 storageUnit->SetPromiseValue(ev,
       kODMyKind,           // type of promised data
       0,                   // offset
       &ba,                 // promise
       fPartWrapper);       // source part

 // Initiate the Dragging
 // Follow the drag and drop recipe.
 .........

}

Getting Data from a Value with a Promise

When the destination part retrieves the data (using ODStorageUnit::GetValue or ODStorageUnit::GetSize), the source part will be called to fulfill the promise. The destination part does not even know that the fulfillment of a promise is taking place.

The following is what the code for the destination part might look like. The same code is used whether the Value contains a promise or not.

ODDropResult YourPartDrop(YourPart* somSelf,
                          Environment* ev,
                          ODDragItemIterator* dropInfo,
                          ODFacet* facet,
                          ODPoint where)

{

 ODDropResult    dropResult = kODDropFail;
 ODStorageUnit  *dropSU;
 ODBoolean       notDone = kODTrue;

 // Iterate through the types and find a desired type
 for (dropSU = dropInfo->First(ev);
      dropInfo->IsNotComplete(ev) && notDone;
      dropSU = dropInfo->Next(ev))
 {
 ... // See recipe for "Receiving a Drop".
// if the desired type exists, import the data.
  if (dropSU->Exists(ev, kODPropContents, kODMyKind, 0) == kODTrue)

 // Focus to the property and value containing the desired data.
  dropSU->Focus(ev,
                   kODPropContents,
                   kODPosUndefined,
                   kODMyKind,
                   0,
                   kODPosUndefined);

 // Get the size of the data.
 // **** Note that the promise is being fulfilled here,
 // **** but the Part does not have to do anything special
 // **** to receive the promised data:

  ODULong size = dropSU->GetSize(ev);

  // Create an ODByteArray to hold the returned data.
  // The fields will be filled out by GetValue.
  ODByteArray ba;

  dropSU->GetValue(ev, size, &ba);

  // Put the data into the part's content.
  ...

  // Dispose the byte array buffer.
  SOMFree(ba._buffer);

  // Stop the iteration.
  notDone = kODFalse;

  // Set up drop result appropriately.
  dropResult = kODDropCopy;

  }
 }

 // Return the appropriate result code.
 return dropResult;

}

Fulfilling a Promise

The following code fragment is an example of what the source part might do in response to a request to fulfill a promise:

void MyPartFulfillPromise(MyPart* somSelf,
                          Environment* ev,
                          ODStorageUnitView* promiseSUView)

{
 // Do a check first to see whether it is the right size.
 ODULong size = promiseSUView->GetSize(ev);

 if (size == sizeof(MyPromise)) {

  // Get the Promise.
  // Either GetValue or GetPromiseValue can be used here.
  // OpenDoc ensures that if GetValue is used,
  // there will be no infinite recursion.
  // (i.e., GetValue -> FulfillPromise -> GetValue -> FulFillPromise).

  // Set up the byte array for promise.
  MyPromise  promise;
  ODByeArray promiseByteArray;
  promiseByteArray._length = promisedDataSize;
  promiseByteArray._maximum = promisedDataSize;
  promiseByteArray._buffer = &promise;

  promiseSUView->GetValue(ev, promiseDataSize, (ODValue) &promiseByteArray);

  // Get the Promised Data using the Promise.
  // This section is part-specific.

  ODPtr promisedData = ......(promise);

  // Set up the byte array for promisedData.
  ODByeArray promisedDataByteArray;
  promisedDataByteArray._length = promisedDataSize;
  promisedDataByteArray._maximum = promisedDataSize;
  promisedDataByteArray._buffer = promisedData;

  // Write the promised data back to the Value.
  // One can use use the promiseSUView to get the Storage Unit and write
  // out other Properties and Values other than the one refered to by
  // promiseSUView.
  promiseSUView->SetValue(ev, (ODValue) &promisedDataByteArray);

  // Cleanup - only need to dispose the promisedData.
  // The byte array is a stack variable.
  SOMFree(promisedData);
  }
 else {

  // Simply delete the promise.
  promiseSUView->DeleteValue(ev, size);

  // The destination will simply see a Value with no content.
  // It is the responsibility of the destination part to handle invalid values.
  // This is no different from the general case without promises.

 }
}

Forcing Promise Fulfillment

There are times when your part might need to force the fulfillment of a promise it has written, before another part actually tries to read the data. An example of this might be when your part takes some action that would prevent it from fulfilling the promise in the future. The simplest way for a part to force a promise to be fulfilled is to focus on the promised property/value and call the storage unit routine GetSize.

Drag and Drop - Promising Non-OpenDoc File

   

The Drag and Drop mechanism allows the source part to create a non-OpenDoc file when the destination is the Desktop or other applications which can accept a file.

Initiating a Drag

On the OS/2 platform, if the source part puts a promise value of "DRM_OS2FILE, DRF_UNKNOWN" type in the contents property of the content storage unit of the ODDragAndDrop object, the Drag and Drop mechanism would ask the source part to fulfill the promise when a drop happens in the Workplace Shell or other applications which can accept a file.

The following is the code fragment for creating such a promise. This piece of code is called right before ODDragAndDrop::StartDrag.

Note:

This code is an example for the OS/2 platform.

ODByteArray         thePromise;
dadsu->Focus(ev, kODPropContents, kODPosUndefined, kODNULL, 0,
                                              kODPosUndefined);
dadsu->SetPromiseValue(ev, "DRM_OS2FILE, DRF_UNKNOWN", 0,
                       &thePromise,_fPartWrapper);

Fulfilling the Promise/Creating the Non-OpenDoc File

In your part editor's FulfillPromise, you should handle the case when the promise value needs to be fulfilled. The drag and drop object will put the file name requested by the target in the kODPropContents property under the kODFileType value.

Note:

This recipe is an example for the OS/2 platform.

 ODValueType valueType = promiseSUView->GetType(ev);
 if (ODISOStrEqual(valueType, "DRM_OS2FILE, DRF_UNKNOWN))
 {
   CHAR szFile[CCHMAXPATH]
   ODStorageUnit* su = promiseSUView->GetStorageUnit(ev);
   su->Focus(ev,
             kODPropContents,
             kODPosUndefined,
             kODFileType,
             0,
             kODPosUndefined);
   long size = su->GetSize(ev);

   StorageUnitGetValue(su, ev, size, (ODValue)szFile);

   // Write the content to the file.
   ...

  SOM_ENDTRY
 }
 SOMFree(valueType);

Linking Recipes

     

This section contains a number of recipes for creating and maintaining links between content in OpenDoc parts. It assumes familiarity with "Data Interchange Basics", and references coding examples there. It also assumes you have read "Clipboard Recipes"; the basic clipboard recipes are augmented here for content kinds that support linking. See the OpenDoc Programming Reference for a description of individual methods.

Note:

Code has not been tested and may contain errors.

Header Files

   

Parts that support linking need to include three header files:

#ifndef SOM_ODLinkSource_xh
#include <LinkSrc.xh>
#endif

#ifndef SOM_ODLink_xh
#include <Link.xh>
#endif

#ifndef SOM_ODLinkSpec_xh
#include <LinkSpec.xh>
#endif

Utility Functions Used in Code Samples

To hide the details of passing ODByteArray parameters to ODStorageUnit methods, the code samples use the following utility functions:

StorageUnitSetValue(ODStorageUnit* su,
                    Environment* ev,
                    ODULong size,
                    ODPtr buffer);
StorageUnitGetValue(ODStorageUnit* su,
                    Environment* ev,
                    ODULong size,
                    ODPtr buffer);

These functions wrap their arguments into an ODByteArray and make the call to ODStorageUnit::SetValue or ODStorageUnit::GetValue.

Associating Links with Content

   

Parts are responsible for maintaining the association between link and link source objects and the linked content. The association is inherently part-specific, as it depends on the content model of a part.

Persistent Representation of Links

 

Because the information necessary to associate a link with a region of content is part specific, there is no universal standard for representing links in part content. However, all link representations need to include supplementary information in addition to a reference to the link or link source object.

For a source of a link, the update ID associated with the linked content should also be saved. This update ID may be different from the update ID of the link source object if the link is updated manually.

For a destination of a link, the supplementary information includes the fields from the ODLinkInfo structure: the creation and modification time, the update ID of the last content incorporated from the link, the autoUpdate setting, and the kind of content accessed from the link.

The format of a content kind defines the standard representation for links. This allows linked content to be transferred to another part that understands the same content kind, while maintaining the same characteristics as the original.

Implementing InitPartFromStorage

       

If a part supports linked content, its InitPartFromStorage method needs to verify persistent references to link source and link storage units, and ensure link source objects always identify the correct source part.

Even though parts strongly reference link and link source storage units, these references can become invalid during data interchange to maintain the link behavior users expect. InitPartFromStorage should use the IsValidStorageUnitRef method of the ODStorageUnit object to validate each link and link source reference before attempting to internalize the reference. If the reference is invalid, the part should eliminate the source or destination link from its content model, without alerting the user.

    When a part internalizes a valid link source reference, it should call ODLinkSource::SetSourcePart to ensure that the object identifies the part maintaining the source of the link. The part owning the source content of the link will change if that content is moved to another part.

Parts typically do not register for update notification in their InitPartFromStorage method, although they may. See "Registering for Update Notification" for information on when to register.

    AcquireLinkFromFocusedSU reads a link reference, validates the reference, and internalizes the link object. The storage unit parameter must be focused to a reference to a link storage unit. If this routine returns kODNULL, the caller should remove the reference from the part's content; this is part specific. If this routine returns a non-null result, the caller is responsible for releasing the object.

Note:

Because this routine internalizes a link object, it must not be called during a clone transaction.

SOM_Scope ODLink* SOMLINK MyPartAcquireLinkFromFocusedSU (MyPart
*somSelf, Environment *ev, ODStorageUnit* su) { MyPartData *somThis =
MyPartGetData(somSelf); MyPartMethodDebug("MyPart","
AcquireLinkFromFocusedSU");

  ODLink* link = kODNULL; ODStorageUnitRef aSURef;

  SOM_TRY

  StorageUnitGetValue(su, ev, sizeof(ODStorageUnitRef), &aSURef);

  if (su->IsValidStorageUnitRef(ev, aSURef))
  {
    ODID linkID = su->GetIDFromStorageUnitRef(ev, aSURef);
    link = su->GetDraft(ev)->AcquireLink(ev, linkID, kODNULL);
  }

  SOM_CATCH_ALL
  SOM_ENDTRY
}

AcquireLinkSourceFromFocusedSU is very similar to the preceding routine. In addition, it ensures that the link source object references the right part.

Note:

Because this routine internalizes a link object, it must not be called during a clone transaction.

SOM_Scope ODLinkSource* SOMLINK MyPartAcquireLinkSourceFromFocusedSU(
                                MyPart *somSelf,
                                Environment *ev,
                                ODStorageUnit* su)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart"," AcquireLinkSourceFromFocusedSU");

  ODLinkSource* linkSource = kODNULL;
  ODStorageUnitRef aSURef;

  SOM_TRY

  StorageUnitGetValue(su, ev, sizeof(ODStorageUnitRef), &aSURef);

  if (su->IsValidStorageUnitRef(ev, aSURef))
  {
    ODID linkSourceID = su->GetIDFromStorageUnitRef(ev, aSURef);
    linkSource = su->GetDraft(ev)->AcquireLinkSource(ev, linkSourceID);

    // Ensure the link source object references this part as the source
    //   of the link.
    linkSource->SetSourcePart(ev, somSelf->GetStorageUnit(ev));
  }

  SOM_CATCH_ALL
  SOM_ENDTRY
}

Implementing Externalize

   

In your part's Externalize method, ensure that the part's storage unit (or one it references) contains a persistent reference to the link and link source objects your part uses. Even though your part holds a reference to the internalized link or link source object, container suite objects are subject to garbage collection when a draft is saved. If there are no persistent references to an object when the draft is saved, the underlying container suite object can be garbage collected even though the object has been internalized and has a non-zero reference count. This should never be a problem for production parts, but as you are implementing linking be sure to write the externalization code early to avoid this potential problem.

About Cloning Linked Content

     

If your part supports linked content, it will need to clone link and link source objects. Example code in this section uses the MyCloneStrongReference routines described in the Data Interchange Basics document. These routines do not specify a particular destination storage unit to clone into. In fact, whenever your part clones a link or link source object, it must not specify a specific storage unit ID to clone into. Your part must clone link or link source objects by specifying kODNULLID as the destination storage unit.

Incorporating Linked Content from the Clipboard

       

Content on the clipboard may contain links. When that content is incorporated, any links should be incorporated, too. The representation of links is dependent on the content kind. However, all parts need to validate references to link and link source objects before using them, and must ensure that link source objects reference the part maintaining the source content.

This section shows two approaches for incorporating linked content. The first way is the easiest, so it is attractive when you are just getting linking working. The disadvantage of the first way is that it copies more data than necessary, potentially a lot more. Eventually, you will probably want to switch to the second recipe. It requires a little more work to implement, but can be significantly more efficient.

Incorporating Linked Content the Easy Way

The simplest way to incorporate linked content is to clone the content storage unit into your draft, extract the data format you want and incorporate it, and release the cloned storage unit. It leaves one or more abandoned storage units in your draft that will need to be garbage collected, but it is straightforward to implement.

The example described below is an elaboration on the MyReadFromContentSU method described in the Data Interchange Basics section.

SOM_Scope void  SOMLINK MyPartMyReadFromContentSU(
                        MyPart *somSelf,
                        Environment *ev,
                        ODStorageUnit* contentSU,
                        ODFrame* targetFrame,
                        ODCloneKind cloneKind,
                        ODLinkStatus embeddedFrameLinkStatus)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyReadFromContentSU");

  // Placeholders to be replaced by part-specific data
  ODByteArray data = CreateByteArrayStruct(kODNULL,0);
  ODStorageUnitRef aStrongRef;
  ODID strongClonedID = kODNULLID;
  ODID weakClonedID = kODNULLID;

  // Begin a clone transaction
  ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);
  ODDraftKey draftKey = 0;

  ODVolatile(myDraft);
  ODVolatile(draftKey);

  SOM_TRY
    draftKey = ContentSU->GetDraft->BeginClone(ev, myDraft),
                                    targetFrame,
                                    cloneKind);

    TRY
      contentSU->Focus(ev, kODPropContents, kODPosUndefined,
                       kMyContentKind, 0, kODPosUndefined);

      // Clone the content storage unit into this part's draft
      tempID = clipDraft->Clone(ev,
                                key,
                                contentSU->GetID(ev),
                                kODNULLID,
                                kODNULLID);

    CATCH_ALL
      myDraft->AbortClone(ev, draftKey);
      RERAISE;
    ENDTRY
    myDraft->EndClone(ev, draftKey);

    // Acquire a reference to the temporary storage unit.
    ODStorageUnit* tempSU = myDraft->AcquireStorageUnit(ev, tempID);

    // Focus to the desired content kind in the temporary storage unit.
    tempSU->Focus(ev,
                  kODPropContents,
                  kODPosUndefined,
                  kMyContentKind,
                  0,
                  kODPosUndefined);

    // Read content from the temporary storage unit,
    // and incorporate it into this part.
    // This is specific to the data format of kMyContentKind,
    // which is not described here.
    ...

    // If a persistent link reference is encountered, read the reference,
    //   check its validity, and internalize the link object.

    ODLink* link = somSelf->AcquireLinkFromFocusedSU(ev, tempSU);
    if (link != kODNULL)
    (
      // Add link to this object's content model
      ...
  )
    else
    {
      // The link was broken during the clipboard transfer to maintain
      // the link behavior users expect.  Do not incorporate this link
      // into this part.
    }

    // If a persistent link source reference is encountered, read the
    // reference, check its validity, and internalize the link source
    // object. Its also important to ensure that the link source
    // references its new source part.

    ODLinkSource* linkSource = somSelf->
                               AcquireLinkSourceFromFocusedSU(ev, tempSU);
    if (linkSource != kODNULL)
    (
      // Add linkSource to this object's content model
      ...
  )
    else
    {
      // The link was broken during the clipboard transfer to maintain
      // the link behavior users expect.  Do not incorporate this link
      // into this part.
    }

    // Release temporary storage unit.
    // As there are no persistent references to this storage unit,
    // it can be garbage collected when the draft is closed.
    tempSU->Release(ev);

    // If the incorporated content contains embedded frames,
    // you must ensure certain frame characteristics are set properly.
    // Also, this part must remember the in-limbo status of each
    // embedded frame in its undo action data to implement
    // undo correctly.
    ODBoolean embeddedFrameWasInLimbo = embeddedFrame->IsInLimbo(ev);
    embeddedFrame->SetInLimbo(ev, kODFalse);
    embeddedFrame->SetContainingFrame(ev, targetFrame);
    embeddedFrame->ChangeLinkStatus(ev, embeddedFrameLinkStatus);

  SOM_CATCH_ALL
  SOM_ENDTRY
  DisposeByteArrayStruct(data);
}

Incorporating Linked Content the Efficient Way

A more efficient method of incorporating linked content from the clipboard is to read only the content kind you are pasting into your part. If the part that placed the content on the clipboard wrote promises for all content kinds it can provide, this method will result in the transfer of only one representation between the two parts.

The difficulty with this recipe is that your part has to hold the object IDs of the link and link source objects it clones until cloning is completed by calling EndClone. (You can convert a valid object ID into a reference during cloning, but if the ID is invalid, there's no way to represent that as a reference.) Only then can you determine if the IDs are valid and turn them into storage unit references or internalized objects.

The method shown below uses the routine MyCloneStrongReference described in the Data Interchange Basics section.

SOM_Scope void  SOMLINK MyPartMyReadFromContentSU(
                        MyPart *somSelf,
                        Environment *ev,
                        ODStorageUnit* contentSU,
                        ODFrame* targetFrame,
                        ODCloneKind cloneKind,
                        ODLinkStatus embeddedFrameLinkStatus)
{
  MyPartData *somThis = MyPartGetData(somSelf);
  MyPartMethodDebug("MyPart","MyReadFromContentSU");

  // Placeholders to be replaced by part-specific data
  ODByteArray data = CreateByteArrayStruct(kODNULL,0);
  ODStorageUnitRef aLinkRef;
  ODStorageUnitRef aLinkSourceRef;
  ODID aLinkID = kODNULLID;
  ODID aLinkSourceID = kODNULLID;

  // Begin a clone transaction
  ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);
  ODDraftKey draftKey = 0;

  ODVolatile(myDraft);
  ODVolatile(draftKey);

  SOM_TRY

    draftKey = ContentSU->
               GetDraft->
               BeginClone(ev, myDraft), targetFrame, cloneKind);

    TRY

      contentSU->Focus(ev, kODPropContents, kODPosUndefined,
                       kMyContentKind, 0, kODPosUndefined);

      // For demonstration, read the data into an ODByteArray.
      contentSU->GetValue(ev, contentSU->GetSize(ev), &data);

      // Link objects referenced from the value stream must be cloned to
      // the document draft.
      // This part must hold the link object ID until
      // EndClone is called.
      // aLinkSourceRef is a value from the byte array read above.
      aLinkID = somSelf->
                MyCloneStrongReference(ev, contentSU, aLinkRef, draftKey);

      // Link source objects referenced from the value stream
      // must also be cloned to the document draft.
      // aLinkRef is a value from the byte array read above.
      aLinkSourceID = somSelf->
                      MyCloneStrongReference(ev,
                                             contentSU,
                                             aLinkSourceRef,
                                             draftKey);

    CATCH_ALL

      myDraft->AbortClone(ev, draftKey);
      RERAISE;

    ENDTRY

    myDraft->EndClone(ev, draftKey);

    // After EndClone has completed without error,
    // the IDs of cloned link and link source objects can be
    // converted to storage unit references or internalized.
    // The link or link source may have been broken during
    // the clipboard transfer to maintain the link behavior users
    // expect, so the cloned ID must be checked for validity.

    if (myDraft->IsValidID(ev, aLinkID))
    {
      ODLink* aLink = myDraft->AcquireLink(ev, aLinkID, kODNULL);
      // Add aLink to this object's content model
      ...
    }

    if (myDraft->IsValidID(ev, aLinkSourceID))
    {
      ODLinkSource* aLinkSource =
                    myDraft->AcquireLinkSource(ev, aLinkSourceID);
      // Add aLinkSource to this object's content model
      ...
    }

    // If the incorporated content contains embedded frames,
    // you must ensure certain frame characteristics are set properly.
    // Also, this part must remember the in-limbo status of each
    // embedded frame in its undo action data to implement
    // undo correctly.
    ODBoolean embeddedFrameWasInLimbo = embeddedFrame->IsInLimbo(ev);
    embeddedFrame->SetInLimbo(ev, kODFalse);
    embeddedFrame->SetContainingFrame(ev, targetFrame);
    embeddedFrame->ChangeLinkStatus(ev, embeddedFrameLinkStatus);

  SOM_CATCH_ALL
  SOM_ENDTRY

  DisposeByteArrayStruct(data);
}

Copying a Single Embedded Frame at the Source or Destination of a Link

Suppose your part maintains a link source that consists of a single embedded frame. Or, suppose your part maintains a link destination where the content from the link is embedded. In either case, your part maintains the source or destination of the link, but the content comes from, or is, embedded as a separate part.

If the user selects such an embedded frame and copies it to the clipboard or drag and drop object, your part should follow "Copying a Single Embedded Frame to the Clipboard". When your part adds the kODPropProxyContents property to the data transfer storage unit, the value (or values) you write should include a reference to a link or link source object you clone to the data transfer draft. In this way, if the embedded frame is copied or moved, the link source or destination will also be copied or moved if the part performing the paste or receiving the drop understands a value in the proxy content property.

When copying a single embedded frame at the source of a link, your part should also write a link spec, so additional destinations of the existing link source may be created. In this case, a part creating a new destination will not incorporate the proxy content on the clipboard or drag and drop object. When your CreateLink method is called to get the existing link, the proxy content available through the link will not contain the link source.

Posting a Link Specification to the Clipboard or Drag and Drop Object

 

When data is copied to the clipboard or drag and drop object, the part should usually write a link specification in addition to content. The link specification indicates to any part performing a paste or drop that a persistent link may be created to the original content, using any of the representations present in the kODPropContents property.

The data in a link spec is private to the part writing the spec. The data written to the spec will be returned to the part via its CreateLink method if a link is actually created. The part needs to be able to identify the selected content from the link spec data. Because the link spec is only valid during the lifetime of the part, the data can contain pointers to information maintained by the part.

This example shows adding a link specification to the clipboard. To add a link spec to a drag and drop object, just replace clipContentSU with the content storage unit of the drag and drop object.

ODClipboard* clipboard = mySession->GetClipboard(ev);
ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);

ODLinkSpec* linkSpec = kODNULL;
ODVolatile(linkSpec);

TRY

  clipContentSU->AddProperty(ev, kODPropLinkSpec);
  linkSpec = myDraft->CreateLinkSpec(ev,
                                     somThis->fPartWrapper,
                                     link spec byte array);
  linkSpec->WriteLinkSpec(ev, clipContentSU);

CATCH_ALL

  ODReleaseObject(linkSpec);
  RERAISE;

ENDTRY

ODReleaseObject(linkSpec);

When Not To Post a Link Specification

When copying data to a content storage unit of the clipboard or when using the drag and drop operation, do not write a link specification if the data being copied is from a region that cannot be edited. Parts should not write a link specification in any of the following cases:

Removing a Link Specification from the Clipboard

It is the responsibility of the part writing a link spec to remove it from the clipboard when:

In order to determine whether a link spec written by a part still resides on the clipboard, the part must remember the update ID of the clipboard when it writes the link specification. The following code fragment can be used to remove a link spec:

ODArbitrator* arbitrator = mySession->GetArbitrator(ev);
ODFrame* clipboardOwner = arbitrator->AcquireFocusOwner(ev, clipboardFocus);
if ((activeFrame == clipboardOwner) ||
 (arbitrator->RequestFocus(ev, clipboardFocus, activeFrame)))
{
  ODClipboard* clipboard = mySession->GetClipboard(ev);

  TRY
    if (myClipboardUpdateID == clipboard->GetUpdateID(ev))
    {
      ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);

      // Focus to the link spec property
      clipContentSU->Focus(ev,
          kODPropLinkSpec,
          kODPosUndefined,
          kODNULL,
          0,
          kODPosUndefined);

      // Remove the property
      clipContentSU->Remove(ev);
    }

  CATCH_ALL

    // Ignore errors.

  ENDTRY
}
ODReleaseObject(ev, clipboardOwner);

Specifying Kinds Supported via nmap Resources

The settings and choices in the Paste As dialog rely on accurate information about the kinds supported by a part. See the "Binding" recipe for details on how to specify the kinds supported by a part.

Implementing Paste with Link (Incorporating Content)

When the user chooses the Paste As menu item, the part should call the clipboard's ShowPasteAsDialog method to display the dialog (on the OS/2 and Windows platforms). If the ODPasteAsResult value set by that method specifies a link should be created to the source, this recipe should be followed.

Parts should enable the Paste As menu item only if the draft's permissions allow writing.

Basically, the receiving part should ignore the content on the clipboard, and read the link spec value there using ODLinkSpec::ReadLinkSpec. The link object is obtained via the receiving part's draft's AcquireLink method.

The routine MyPasteWithLink shown here takes three parameters. The first is the content storage unit of the clipboard or drag and drop object. The second is the structure returned by the ShowPasteAsDialog method of either the clipboard or drag and drop object. The third is a boolean indicating whether content is pasted from the clipboard or the drag and drop object; this makes a difference in how the routine adds undo actions.

MyPasteWithLink shows how the part performing the paste is expected to add undo history. Because link creation involves both the source and destination parts, an undo transaction is used to capture actions by both parts. If the link is being created from the clipboard, the destination must begin an undo transaction before calling AcquireLink. If the link is created during a drop, the part that initiated the drop is assumed to have already started an undo action, so this part simply adds its undo data as a single action.

MyPasteWithLink will register for update notification even if the user has requested manual updates. Because cross-document links may be created asynchronously, its not always possible to read data from a link immediately. Registering for notification gives the source part a chance to deliver the data. When this part's LinkUpdated method is called, the part should unregister if the destination updates manually.

void MyPasteWithLink(Environment *ev,
  ODStorageUnit* contentSU,
  ODPasteAsResult pasteAsResult,
  ODBoolean fromClipboard)
{
  ODDraft* myDraft = fSOMSelf->GetStorageUnit(ev)->GetDraft(ev);
  ODLinkSpec* linkSpec = kODNULL;

  ODIText* undoActionName = kODNULL;
  ODIText* redoActionName = kODNULL;
  ODActionData noActionData =
               (ODActionData) CreateByteArrayStruct(kODNULL,0);

  ODUndo* undo = fSOMSelf->GetStorageUnit(ev)->GetSession(ev)->GetUndo(ev);

  // For demonstration, locals are used; your part
  // must integrate these into its content
  ODLinkInfo linkInfo;
  ODLink* link = kODNULL;

  SOM_TRY

    // Initialize text for undo.
    // Not localizable!
    undoActionName = CreateIText(smRoman, langEnglish, "Undo Paste");
    redoActionName = CreateIText(smRoman, langEnglish, "Redo Paste");

    // Read the link spec from the content storage unit.
    // The link spec is created with null ODPart and ODByteArray
    // pointers since it is initialized by ReadLinkSpec.

    linkSpec = myDraft->CreateLinkSpec(ev, kODNULL, kODNULL);

    // Focus to the link spec property; ReadLinkSpec will focus
    // to the appropriate value.
    contentSU->Focus(ev,
         kODPropLinkSpec,
         kODPosUndefined,
         kODNULL,
         0,
         kODPosUndefined);

    linkSpec->ReadLinkSpec(ev, contentSU);

    // Establish the link
    TRY

      // If this is a paste from the clipboard, start an undo transaction
      if (fromClipboard)
      {
        undo->AddActionToHistory(ev,
                                 somThis->fPartWrapper,
                                 &noActionData,
                                 kODBeginAction,
                                 undoActionName,
                                 redoActionName);
      }

      link = myDraft->AcquireLink(ev, kODNULLID, linkSpec);

      linkInfo.change = kODUnknownUpdate; // not yet updated
      GetDateTime(&linkInfo.creationTime);
      linkInfo.kind = ODISOStrFromCStr(pasteAsResult.selectedKind);
      linkInfo.autoUpdate = pasteAsResult.autoUpdateSetting;

      // Add link and linkInfo to the content model of this part.
      //   This is part specific.
      ...

      // Add this action to the undo stack.
      // If this is a paste from the clipboard, end the undo
      // transaction started above.
      // Otherwise, this must be a drop, so add to the existing transaction.
      ODActionData* actionData = ...
      undo->AddActionToHistory(ev,
                               somThis->fPartWrapper,
                               actionData,
                               (fromClipboard ?
                               kODEndAction : kODSingleAction),
                               undoActionName,
                               redoActionName);

    CATCH_ALL

      // Abort the undo transaction.
      if (fromClipboard)
      {
        undo->AbortCurrentTransaction(ev);
      }
      RERAISE;

    ENDTRY

    // Always register for notification that the initial link content
    // is available.
    // If the user specified manual updating, this part should
    // unregister after LinkUpdated is called.
    if (!fSOMSelf->IsRegistered(ev, link))
    {
      // RegisterDependent will call LinkUpdated.
      link->RegisterDependent(ev, somThis->fPartWrapper, linkInfo.change);
    }
    else
    {
      // Get update now
      fSOMSelf->LinkUpdated(ev, link, link->GetUpdateID(ev));
    }

  SOM_CATCH_ALL

  SOM_ENDTRY

  DisposeIText(undoActionName);
  DisposeIText(redoActionName);
  delete linkSpec;
}

Implementing Paste with Link (Embedding Content)

Any part that supports embedding and linking should allow the user to embed content from the clipboard and create a link. When content from a link is embedded, the destination part (the part performing the paste) does not clone the content storage unit of the clipboard or drag and drop object. Instead, the part reads the link spec from the content storage unit, acquires the link from the link spec, and creates an embedded part by cloning the content storage unit of the link. When the link is updated, the destination part discards the embedded part and creates a new embedded part from the new link content. The embedded part is not involved in the maintenance of the link. The link border appears around the embedded part's frame and is drawn by the destination part.

Drop Result when Pasting a Link

When a link is created using drag and drop, be sure your part's Drop method returns the ODDropResult value kODDropCopy.

Registering for Update Notification

The "Implementing Paste with Link" recipes demonstrate how a destination part can register with the link to receive automatic notifications when the link is updated with new content. When a new link destination is created, parts should always register to receive notification that the initial content is available.

Parts register for updates by calling the RegisterDependent method of the ODlink object. The part supplies the update ID of the content it last read from the link. To get the initial content from the link, a part can use the update ID constant kODUnknownUpdate to ensure it receives notification. Parts receive notification of an update via their LinkUpdated method.

If a part's LinkUpdated method is called after registering to receive the initial content from a link, the part should unregister if the user specified that the destination should update manually.

Parts should register for notification when a part is internalized if the link updates automatically and the link is visible or may affect layout of the part. When your part calls RegisterDependent, its LinkUpdated method may be called before RegisterDependent returns, so be sure to call RegisterDependent only when your part is prepared to receive a notification. Parts may unregister and re-register for notification as they wish.

If a part is in a read-only draft, you may call RegisterDependent, but your part will not receive notification (your part's LinkUpdated method will not be called).

Undoable Actions and Linking

The following user actions involving links should be undoable (this list may not be exhaustive):

The updating of links, at the source or destination, is not an undoable user operation. A part updating the destination of a link does not have to create an undo action to restore its previous content. Similarly, a part updating a link from source content does not need to create an action state to restore the link to its previous contents.

When content containing a source link is removed by deleting or cutting, or when a source link is broken, your part should:

Your part should generally not update a removed or broken link source with empty content; just leave the link source in its existing state.

If the action is undone, the part should:

If a link or link source is broken, your part must change the link status of any affected frames; see the recipe in "Frame Link Status".

Similarly, links may be broken at their destination, or they may be deleted or cut along with the content at the destination of the link. When this happens, your part should:

Your part should hold a reference to each link or link source objects in undo action data until the part's DisposeActionState method is called.

Undoing the Paste or Drop of Linked Content

When designing and implementing undo for pasted or dropped content, you must take into account the behavior of destination links in the pasted or dropped content. Your part does not treat updating of a link destination as an undoable action. When a link updates, your part removes the existing content at the destination, and replaces it with new content from the link. Your part does not create an undo action, and does not save the content that is replaced. If the modification at the source of the link is undone, your part will receive another link update, putting the destination into an equivalent state as before but with different objects. Now suppose the user undoes the paste or drop. Your part must not assume the same objects that were created at the time of the paste are present. Your part must undo the paste with whatever objects are currently in place. In particular, the undo information your part associates with the paste or drop should not include specific objects cloned from links.

Accessing a Link

Because source and destination parts may attempt to access a link simultaneously, parts must acquire a lock in order to get the storage unit containing the content of the link. Many methods of classes ODLink and ODLinkSource require a valid link key that can only be obtained by successfully acquiring a lock first. Parts must not access the storage unit returned by GetContentStorageUnit after unlocking the link.

Basic Recipe for Writing to a Link

This basic recipe can be used to write the initial content into a link when created, and to update the link when the source content changes. This example routine illustrates the use of promises, which must be fulfilled by this part's FulfillPromise method. It uses the routine MyWriteToContentSU described in the Data Interchange Basics section.

To ensure that promises written into the link are fulfilled correctly, this routine adds a kODPropCloneKindUsed property before calling MyWriteToContentSU. In their FulfillPromise methods, all parts should check for this property to see if a clone kind other than kODCloneCopy should be used. When FulfillPromise is called to write to a link, BeginClone will fail with an inconsistent clone kind error if the clone kind is other than kODCloneToLink.

When the link is initially created or when its being updated, the justRewriting parameter should be kODFalse. If the part's CreateLink method is called to create another destination of an existing link, and the content at the source of the link has not changed since the link was last updated, the justRewriting parameter should be kODTrue. See "Implementing CreateLink" for more information on when to pass kODTrue to the justRewriting parameter.

The promiseData parameter is whatever information the part needs to identify the promised content when its FulfillPromise method is called.

The contentShape parameter will be written into the link in the suggested frame shape annotation. This property will be used as the frame shape should content be embedded at the destination of the link.

The partName parameter is passed thru to MyWriteToContentSU.

The Clear method removes the kODPropContents property from the link's content storage unit. After calling Clear, your part must call GetContentStorageUnit, add the contents property, write promise values (or actual values) into the link, and call ContentUpdated to notify destinations. After your part calls Unlock, OpenDoc will fulfill any promises for values that are used by destinations.

The Clear and ContentUpdated methods of ODLinkSource will return an exception if passed kODUnknownUpdate as the argument to the update parameter.

void MyWriteToLink (Environment *ev,
  ODLinkSource* linkSource,
  ODUpdateID updateID,
  ODBoolean justRewriting,
  ODByteArray* promiseData,
  ODShape* contentShape,
  ODIText* partName)
{
  ODVolatile(linkSource);

  ODLinkKey linkKey;
  ODVolatile(linkKey);

  if (linkSource->Lock(ev, 0, &linkKey))
  {
    // Remember the previous updateID in case updating fails
    ODUpdateID previousID = linkSource->GetUpdateID(ev);

    TRY
      // Call Clear to remove the contents property and allow writing
      // promises. If justRewriting is true, use the existing updateID
      // of the link
      linkSource->Clear(ev, (justRewriting ? previousID : updateID),
                                                                linkKey);

      ODStorageUnit* linkContentSU = linkSource->GetContentStorageUnit(ev,
                                                                linkKey);

      // Add the clone kind used property so promises are
      // fulfilled using kODCloneToLink
      ODSetULongProp(ev,
                     linkContentSU,
                     kODPropCloneKindUsed,
                     kODCloneKind,
                     kODCloneToLink);

      fSOMSelf->MyWriteToContentSU(ev,
                                   linkContentSU,
                                   kODCloneToLink,
                                   promiseData,
                                   contentShape,
                                   partName);

      // If the link is being updated, and not just rewritten, inform the
      // link source that it has changed, so destinations can be notified.
      if (!justRewriting)
        linkSource->ContentUpdated(ev, updateID, linkKey);

    CATCH_ALL

      // Clear a partially-updated link.  Destinations must deal
      //   gracefully with a link content storage unit that has no
      //   contents property.
      linkSource->Clear(ev, previousID, linkKey);
      RERAISE;

    ENDTRY

    linkSource->Unlock(ev, linkKey);
  }
}


Implementing CreateLink

The creation of a link is initiated when the part performing a paste or drop calls its draft's GetLink method, passing in a link spec read from the clipboard or drag and drop container. A recipe for pasting a link appears elsewhere in this document.

A link source is created by a part's CreateLink method. Any content kind written to the clipboard or drag and drop container should be available through the link. When providing the initial content for a link, a part may write either promises or actual data. By writing promises, a part can provide content for only the data kind(s) actually used by destinations of the link. When a destination reads data through the link, the source part will be called to fulfill its promise for a particular content kind. A separate document discusses promises in more detail.

This example implementation of CreateLink demonstrates how your part should add to the undo history when a new link source is created. The action will be added to an undo transaction (started by the part performing the paste, or this part if the operation is a drop). See "Better Undo Recipe for CreateLink" for the preferred way to handle undoing the link creation, although it is a little more work for your part.

CreateLink should only be called by drafts, not directly by parts. This example demonstrates how your part may implement its CreateLink method. It uses the routine MyWriteToLink to create the initial content of the link, using promises, and to ensure that all values are present each time an additional destination is created. If your part does not write promises to a link, the else-clause in this example can be eliminated.

SOM_Scope ODLinkSource* SOMLINK MyPartCreateLink(
                                MyPart *somSelf,
                                Environment *ev,
                                ODByteArray* data)
{
  MyPartData *somThis = MyPartGetData(somSelf);

  ODLinkSource* linkSource = kODNULL; ODVolatile(linkSource);

  // Placeholders to be replaced by part-specific data
  ODByteArray* promiseData;
  ODShape* contentShape;
  ODIText* partName;    // Optional
  ODUpdateID sourceUpdateID;

  SOM_TRY

    // Call a part-specific method to get the link source if it already
    // exists
    linkSource = somSelf->MyGetExistingLinkSource(ev, data);

    if (linkSource == kODNULL)
    {
      ODSession* session = somSelf->GetStorageUnit(ev)->GetSession(ev);

      // Call this part's draft to create the link source object
      ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);
      linkSource = myDraft->CreateLinkSource(ev, somThis->fPartWrapper);

      // Add the link to the content model of this part.
      //   This is part specific.
      ...

      // Associate a new update ID with the source content.
      sourceUpdateID = session->UniqueUpdateID(ev);

      TRY

        MyWriteToLink (ev,
            linkSource,
            sourceUpdateID,
            kODFalse,
            promiseData,
            contentShape,
            partName);

      CATCH_ALL

        // Remove the link from the content model of this part
        //...

        linkSource->Release(ev);
        linkSource = kODNULL;
        RERAISE;

      ENDTRY;

      // A new link source was successfully created,
      // so add an action to the undo stack.
      // The action names should never appear as menu items if
      // the parts implement undo properly.
      // The action data is part specific.
      ODIText* undoActionName = CreateIText(smRoman,
                                            langEnglish,
                                            "Undo Create Link");
      ODIText* redoActionName = CreateIText(smRoman,
                                            langEnglish,
                                            "Redo Create Link");
      ODActionData* actionData = ...
      session->GetUndo(ev)->AddActionToHistory(ev,
                                               somThis->fPartWrapper,
                                               actionData,
                                               kODSingleAction,
                                               undoActionName,
                                               redoActionName);
      DisposeIText(undoActionName);
      DisposeIText(redoActionName);
    }
    else
    {
      // If your part initialized the link with promises, add promises
      // for value kinds not already present in the link because
      // current destinations did not use them.

      // If the source content has not changed since the link was last
      // updated, rewrite the link, otherwise, update it now.
      // The variable sourceUpdateID contains the update ID currently
      // associated with the content at the source of the link.
      ODBoolean justRewriting =
        (sourceUpdateID == linkSource->GetUpdateID(ev));

      TRY

        MyWriteToLink (ev,
            linkSource,
            sourceUpdateID,
            justRewriting,
            promiseData,
            contentShape,
            partName);

      CATCH_ALL

        // If the link could not be updated, CreateLink should fail
        // rather than return a linkSource without a contents property.
        linkSource = kODNULL;
        RERAISE;

      ENDTRY;
    }

    // The result of this method must be released by the caller.
    linkSource->Acquire(ev);

  SOM_CATCH_ALL

  SOM_ENDTRY

  return linkSource;
}


Better Undo Recipe for CreateLink

If your part implements undo support as described in the recipe above, and the link is created via cross-document drag and drop, the undo action stack will contain two separate undo items; one for the drop, and another for the creation of the link source. There are two undo items in this case because the call to the source part's CreateLink method is postponed until after the drag completes and returns to the source part.

Your part can avoid introducing a second undo item in this case by doing the following. When you write a link spec to a drag and drop object's content storage unit, include in the part data information identifying the undo history transaction your part created for the drag and drop operation. Then, in your CreateLink method, check for this information, and instead of adding an undo action as described above, modify some private data your part associates with the drag and drop undo transaction so that the link creation is undone (or redone) as well.

When CreateLink Is Called To Get an Existing Link

CreateLink may be called multiple times with the same link spec to create multiple destinations of the same link. If your part writes actual content into the link, it can just return the link object. However, if your part writes promises, it must ensure all values are available in the link.

If CreateLink is called to get an existing link, its important that the link contain all value types available via the clipboard or drag and drop object. If a part writes promises into a link, the link will contain only actual content used by current destinations; if the draft is saved, unfulfilled promises will be removed. When CreateLink is called, the part must ensure that at least a promise is present for all value types. A part can handle this as shown in MyPartCreateLink above.

Parts can now use the Clear method to replace promises in a link without notifying existing destinations. Clear may be called with the existing update ID; by not calling ContentUpdated, destinations will not be notified (as shown in MyPartCreateLink). If ContentUpdated is called with the existing update ID, the circular link update alert will appear.

Updating a Link

Your part can use the MyWriteToLink method to update an existing link. The updateID argument should be the updateID responsible for changing the content at the source of the link. The justRewriting parameter should be kODFalse. This recipe can be used to update both an automatic and a manual link.

In general, it is not necessary, and sometimes undesirable, to update links immediately in response to a content change. When content is changing due to keyboard events, for example, it is reasonable to wait for a pause before updating any affected links.

Creating or Updating a Link Consisting of a Single Embedded Frame

If your part creates or updates a link source consisting of a single embedded frame, your part calls the embedded part's CloneInto method to supply the link content. To allow the embedded part to promise its content, the embedded part must know to use the ODCloneKind value kODCloneToLink (instead of kODCloneCopy) in its call to BeginClone in its FulfillPromise method. Before calling the embedded part's CloneInto method, your part should add a kODPropCloneKindUsed property to the link source object's content storage unit, add the value type kODCloneKind, and write the value kODCloneToLink as a 32-bit integer. For example:

ODSetULongProp(ev,
               contentSU,
               kODPropCloneKindUsed,
               kODCloneKind,
               kODCloneToLink);

This is demonstrated in the example routine MyWriteToLink in this section.

Notes on Implementing FulfillPromise

All parts should check for the presence of a kODPropCloneKindUsed property in their FulfillPromise method. The following code can be used to determine the correct clone kind argument to BeginClone:

 ODCloneKind cloneKind = kODCloneCopy;
 ODStorageUnit* promiseSU = promiseSUView->GetStorageUnit(ev);
 if (ODSUExistsThenFocus(ev,
                         promiseSU,
                         kODPropCloneKindUsed,
                         kODCloneKind))
    cloneKind = ODGetULongProp(ev,
                               promiseSU,
                               kODPropCloneKindUsed,
                               kODCloneKind);

The default clone kind is kODCloneCopy unless a specific clone kind is specified by a kODPropCloneKindUsed property.

Updating the Destination of a Link

A part can update the destination of a link in its LinkUpdated method, which is called automatically if the part registers as a dependent of a link, or in response to the user's request to update a manual link. The example method MyUpdateLinkDestination provides the skeleton structure; add code to incorporate or embed the data into your content. You should be able to use a common routine to integrate data whether it comes from a link, the clipboard, or a drop.

If the last update at the source of a link failed, leaving a link without content, destinations that try to read the link will get the error kODErrNoLinkContent back from GetContentStorageUnit. This ensures that a destination cannot attempt to embed a storage unit without content as a part. Your part should consider this a temporary problem and refrain from updating at this time. The MyUpdateLinkDestination routine shown here will raise this exception to the caller, which should just ignore the error.

If, however, your part gets back the error kODErrCannotEstablishLink from GetContentStorageUnit, this means that the link could not be established correctly. This error will only be returned after a link has been returned by your draft's AcquireLink method, but before any content is available through it. In response to this error, your part should remove the link from its content model, release its reference to the link, and alert the user.

Your part may encounter invalid persistent references to link or link source objects when updating from a link. The appropriate response is to ignore them and treat the content they were associated with as unlinked content.

Updating the destination of a link is not an undoable operation. You should not create an undo action for it.

For future compatibility, this recipe does not assume the link content storage unit is in the same draft as the destination part.

void MyUpdateLinkDestination(MyPart *somSelf,
                             Environment *ev,
                             ODLink* link,
                             ODLinkInfo* linkInfo)
{

  ODLinkKey linkKey;

  ODVolatile(link);
  ODVolatile(linkKey);

  ODFrame* myDisplayFrame;
  ODFrame* anEmbeddedFrame;

  if (link->Lock(ev, 0, &linkKey))
  {
    TRY

      ODStorageUnit* linkContentSU =
        link->GetContentStorageUnit(ev, linkKey);
      ODDraft* sourceDraft = linkContentSU->GetDraft(ev);

      // Remove the current content at the destination of the link.
      //   This is part specific.
      ...

      // Insert content from the link into the destination.
      somSelf->MyReadFromContentSU(ev,
          linkContentSU,
          kODNULL,
          kODCloneFromLink,
          kODInLinkDestination);

      // Update the link info for this destination
      linkInfo->change = link->GetUpdateID(ev);
      linkInfo->changeTime = link->GetChangeTime(ev);

    CATCH_ALL

      link->Unlock(ev, linkKey);
      RERAISE;

    ENDTRY

    link->Unlock(ev, linkKey);

    // Propagate changes to source links maintained by this part.
    //   This, too, is part specific.
    ...

    // Any time this part's content is changed, notify all containing parts.
    //   This method should be called for all frames displaying the changed
    //   content.
    myDisplayFrame->ContentUpdated(ev, linkInfo->change);

    // Make sure that this change is saved when the document is closed
    somSelf->GetStorageUnit(ev)->GetDraft(ev)->SetChangedFromPrev(ev);
  }
}

Link Source Moved Across Parts

OpenDoc allows content to be moved across parts; if the content contains links, the links are preserved if the destination uses a format supporting links. (If the destination uses a format that does not specify linking, links will be broken at the source.) If content is moved to a part that supports different content kinds, it may not be able to write all kinds into the link that the original source part could. Parts do not need to record content kinds they write to a link as part of the information they associate with a link source. When its time to update the link, they should just write the kinds irrespective of what might already be in the link. The Clear method of ODLinkSource will ensure that any obsolete value kinds are removed from the link.

At the destination of a link, a part must be prepared to deal with the content kind missing from the link. If possible, the part should attempt to use another format from the link. Otherwise, the destination should break the link and leave the existing content in place.

Note:

This function is not supported in the current release of OpenDoc.

Update IDs

All linked content, that is, every region of content at the source or destination of a link, has an update ID associated with it. The source part determines the update ID associated with the link. The destination part is responsible for remembering the update ID of the link at the time the destination last updated from the link.

Parts are responsible for correctly propagating update IDs to enable OpenDoc to detect circular links. If a circular link goes undetected, the parts involved can update each other indefinitely. When updating a link, the source part should reuse the update ID associated with the content causing the link to update. If the content change was due to an updated link destination, the update ID of the link destination should be adopted as the update ID of the affected link.

If a content change originates in a part, such as in response to keyboard events, all links directly affected should be associated with the same new update ID. A new update ID is created by calling the UniqueUpdateID method:

ODUpdateID updateID =
  somSelf->GetStorageUnit(ev)->GetSession(ev)->UniqueUpdateID(ev);

Your part must not create a distinct update ID for each affected link source; use the same ID for all of them.

Links Containing Embedded Frames

Links may be established to content containing embedded frames, just as content containing embedded frames may be copied and pasted without a persistent link. The destination of such a link contains a copy of the parts embedded at the source of the link. When the destination is updated, the previously embedded parts are discarded, and replaced by new ones cloned from the link. Display frames for the new parts are hooked into the frame hierarchy at the destination, and facets are created for the currently visible frames.

One special case is a link to one embedded frame. If the user copies a single embedded frame to the clipboard, the active part should clone the embedded part into the content storage unit of the clipboard, and then create a kODPropProxyContent property in the same content storage unit in which container-specific annotations about the embedded frame are written (see "Data Interchange Basics" for details). If a paste with link is performed at the destination, a link is created to the embedded frame, without involving other intrinsic content of the containing part. Should that embedded frame be again cut or copied to the clipboard, at either the source or destination of the link, the embedded part should be cloned into the content storage unit of the clipboard, as before. After the embedded part has cloned itself, the containing part should add a proxy content property to the same content storage unit, and write out a value that includes the link or link source (cloned to the clipboard and referenced by the proxy content). When the clipboard content is pasted into any part that understands the value type in the proxy content, the link can be preserved.

Frame Link Status

All frames have a link status that describes the frame's participation in links. The link status of a frame should be set to indicate whether it is embedded in the source of a link, in the destination of a link, or not involved in any link. Parts can use this information to determine when they should not allow creation of a new link, or not allow content to be cut.

All parts must set the link status of frames they embed, even parts that do not otherwise support linking. Whenever frames are embedded during data interchange, the active part must set the link status of each embedded frame using the ChangeLinkStatus method, even if a link is not created. Also, a part's LinkStatusChanged method is responsible for notifying its embedded frames, as described below under "Implementing LinkStatusChanged."

When a link is created, a part should call ODFrame::ChangeLinkStatus for all of its frames that display data at the source or destination of the link. ChangeLinkStatus will change the value of the link status (ODLinkStatus type) after examining the link status of its containing frame. ChangeLinkStatus will call ODPart::LinkStatusChanged for its displayed part in order to give the part the chance to call ChangeLinkStatus for any of its embedded frames. Frames and parts can examine the link status of a frame by calling ODFrame::GetLinkStatus.

If incorporation adds new embedded frames to the receiving part, the link status of those embedded frames must be set by calling their ChangeLinkStatus method. If an embedded frame lies within the destination of a link maintained by this part, its status should be changed to kODInLinkDestination (as would be the case when this is a paste with link operation). Otherwise, if the embedded frame lies within the source of a link, its status should be changed to kODInLinkSource. (When an embedded frame is contained in both a link source and a link destination, its status should be set to kODInLinkDestination.) If the embedded frame is not within any link maintained by this part, its status should be changed to kODNotInLink.

When a part breaks a link, the part needs to call the ChangeLinkStatus method of all affected embedded frames. The new status can be just the embedded frame's status with respect to the part, since ChangeLinkStatus takes the containing frame's status into account.

Parts are responsible for ensuring that the link status of an embedded frame is correct when it internalizes the frame. Parts that do not support linking can simply call the ChangeLinkStatus method of the embedded frame, specifying kODNotInLink. Parts that do support linking should set the link status according to the embedded frame's participation in links that the part maintains. The frame object will do nothing if the status is unchanged, and will take the containing frame's status into consideration to simplify the part's responsibilities.

Implementing LinkStatusChanged

The LinkStatusChanged method is called to notify a part that one of its display frames was included in the creation or deletion of a link. The part must pass this notification along to any embedded frames by calling ODFrame::ChangeLinkStatus. As an optimization, embedded frames which are already involved in links managed by this part do not need to be notified, since their status does not change (if the embedded frame is in a destination maintained by your part, its still in a destination; if its in a source, a destination could not be created around it).

Content Change Protocol

Because linked content may contain embedded frames, some protocol between parts is required to inform containing parts of changes to embedded content. Two methods are involved when content changes:

void ODFrame::ContentUpdated(Environment* ev,
                      ODUpdateID change);

void ODPart::EmbeddedFrameUpdated(Environment* ev,
                      ODFrame* frame,
                      ODUpdateID change);


If a content change in a part affects one of its display frames, the part should call ODFrame::ContentUpdated, passing it the unique update ID for the change. This unique ID may be obtained through a call to ODSession::UniqueUpdateID if the change originated in the part. ODFrame::ContentUpdated will call ODPart::EmbeddedFrameUpdated for all containing parts in the frame hierarchy. The containing parts then know that some of its embedded content has changed. If the embedded part is involved in a link source, the containing part maintaining the link can choose to update the link with the new data.

Implementing EmbeddedFrameUpdated

The EmbeddedFrameUpdated method is called to notify a part that some change to the content of an embedded frame has occurred. If the part maintains the source of a link that includes the embedded frame, the part should update the link. The part is passed a reference to the embedded frame, and the update ID associated with the modification.

Calling ODFrame::ContentUpdated

When content displayed by a frame is changed, call the affected frame's ContentUpdated method. The display frame will pass the notification on to all containing parts, unless the frame is the root frame of the document.

Displaying Link Info Dialogs

Recipes for displaying the link info dialogs are included in "Linking Recipes".

Displaying Link Borders

A border should be drawn around a link when the link is selected or when the "Show Links" setting is checked in the Document Properties notebook.

Whenever a part draws its content, it should call the ShouldShowLinks method of the facet's window. If ShouldShowLinks returns kODTrue, borders should be drawn.

if (myFacet->GetWindow(ev)->ShouldShowLinks(ev))
{
  // Draw borders around links in this facet.
}

Implementing RevealLink

Parts that create links need to implement the RevealLink method to enable users to navigate from the destination of a link to the source content. RevealLink may be called when the part's document is not the front-most process; bring the process to the front, if necessary. If the link cannot be made visible in an existing display frame, the part should open a new window.

This is the rough outline of a typical RevealLink implementation. It uses four methods not defined here: MySetFrontProcess, MyBestDisplayFrame, MyActivateFrame, and MyScrollToLink.

SOM_Scope ODLinkSource* SOMLINK MyPartRevealLink(MyPart *somSelf,
                                                 Environment *ev,
                                                 ODLinkSource* linkSource)
{
  MyPartData *somThis = MyPartGetData(somSelf);

  // Make sure this process is front-most
  somSelf->MySetFrontProcess(ev);

  // Choose a display frame for the source content.
  //   This is part specific.  If no suitable display frame exists,
  //   the part should open a new window

  ODFrame* frame = (ODFrame*) somSelf->MyBestDisplayFrame(ev, linkSource);

  // If the best frame has a containing frame, ensure the display
  //   frame is revealed.

  ODFrame* containingFrame = frame->AcquireContainingFrame(ev);
  if (containingFrame != kODNULL)
  {
    ODPart* containingPart = kODNULL;

    TRY
      containingPart = containingFrame->AcquirePart(ev);
      containingPart->RevealFrame(ev, frame, kODNULL);
    CATCH_ALL
    ENDTRY

    ODReleaseObject(ev, containingPart);
    ODReleaseObject(ev, containingFrame);
  }

  // Activate my frame by using my normal activation method.
  //   RevealLink may be called when the part's document is not the
  //   front-most process, so be sure to bring it to the front if
  //   necessary.

  somSelf->MyActivateFrame(ev, frame);

  // Scroll my frame as necessary to make the source content visible.

  somSelf->MyScrollToLink(ev, linkSource);
}

Editing in a Link Destination

If your display frame's link status is kODInLinkDestination (determined by calling ODFrame::GetLinkStatus), the frame is embedded in a link destination and editing is not usually allowed. If the user attempts to edit content in the frame, the part should call the EditInLink method of the display frame. In response, OpenDoc will call the EditInLinkAttempted method of the part maintaining the destination of the link. Parts that support linking and embedding need to override this method.

EditInLinkAttempted should check that it maintains a link destination including the argument embedded frame. If not, it should return kODFalse. If it does maintain a link destination, it should present an alert informing the user of the attempted edit to a link destination, and allow the user to find the source of the link or break the destination link In either case, the part should not activate one of its display frames. If the user chooses to break the link, the part should change the link status of all affected embedded frames. An alert for this purpose is included in the Example Part Resources document in the Sample Code folder.

ODFrame::EditInLink returns kODFalse if the part maintaining the link destination could not be found. In this unlikely event, the part should put up a simple alert informing the user that editing the destination of a link is not allowed. An alert for this purpose is included in the Example Part Resources document in the Sample Code folder.

Implementing Linking Dialogs

The Links selection in the OpenDoc Edit menu contains the the Link info item. The default setting for Link info is disabled. If your part supports linking it should enable Link info which will bring up the appropriate LinkInfo dialog box.

This recipe uses a private field fSelection which keeps track of the selection in a part specific manner. For simplicity's sake, we assume fSelection is an object which supports a few boolean methods regarding the nature of the selection.

If the part editor does not support linking, then it can ignore the Link Info cases. If the part editor does support linking, the LinkBroken method notifies the selection that the content is no longer linked, the UpdateSelectedLinks method causes source content to update a link and destination content to be replaced by the current link content, and the GetContentChangeID method returns the change id associated with the selected link content.

SOM_Scope void  SOMLINK XYZCompany_XYZPartAdjustMenus(
        XYZCompany_XYZPart *somSelf, Environment *ev,
        ODFrame* frame)
{
    _fMenuBar->EnableCommand(ev, kODCommandLinkInfo, kODTrue);
}

SOM_Scope void  SOMLINK XYZCompany_XYZPartDoLinkInfoCommand(
        XYZCompany_XYZPart *somSelf, Environment *ev)
{
     if (_fSelection->LinkSourceBorderSelected())
    {
        ODLinkInfoResult     infoResult;
        ODLinkSource* linkSource = _fSelection->GetSelectedLinkSource();
        if ( linkSource->ShowLinkSourceInfo(ev,
                                   _fSelection->GetFirstSelectedFacet(),
                                   _fSelection->GetContentChangeID(),
                                   fPermissions>=kODDPSharedWrite,
                                   &infoResult))
        {
            switch (infoResult.action)
            {

            case kODLinkInfoBreakLink:
                linkSource->SetSourcePart(ev, kODNULL);
                linkSource->Release(ev);
                _fSelection->LinkBroken();
                somSelf->GetStorageUnit(ev)->GetDraft(ev)
                    ->SetChangedFromPrev(ev);
                break;

            case kODLinkInfoUpdateNow:
                _fSelection->UpdateSelectedLink();
                break;

            case kODLinkInfoOk:
                if (infoResult.autoUpdate != linkSource->IsAutoUpdate(ev))
                {
                    linkSource->SetAutoUpdate(ev, infoResult.autoUpdate);
                    if (infoResult.autoUpdate &&
                          (_fSelection->GetContentChangeID() !=
                                linkSource->GetChangeID(ev))
                       )
                        _fSelection->UpdateSelectedLink();
                }
                break;
            default:
            }
        }
    }
    else if (_fSelection->LinkDestBorderSelected())
    {
        ODLinkInfoResult infoResult;
        ODLink* linkDest = _fSelection->GetSelectedLinkDest();
        ODLinkInfo* linkInfo = _fSelection->GetSelectedLinkInfo();

        if (linkDest->ShowLinkDestinationInfo(ev,
                                        _fSelection->GetFirstSelectedFacet(),
                                        linkInfo, fPermissions>=kODDPSharedWrite,
                                        &infoResult))

        {
            if (infoResult.action != kODLinkInfoBreakLink)
                if (infoResult.autoUpdate != linkInfo->autoUpdate)
                {
                    linkInfo->autoUpdate = infoResult.autoUpdate;
                    if (infoResult.autoUpdate)
                    {
                        linkDest->RegisterDependent(ev,
                                                        _fPartWrapper,
                                                        linkInfo->change);
                    }
                }

            switch (infoResult.action)
            {

            case kODLinkInfoFindSource:
                linkDest->ShowSourceContent(ev);
                break;

            case kODLinkInfoBreakLink:
                // Only unregister if there are no more destinations of
                //   this link in this part.
                linkDest->UnregisterDependent(ev, _fPartWrapper);
                linkDest->Release(ev);
                _fSelection->LinkBroken();
                somSelf->GetStorageUnit(ev)->GetDraft(ev)->
                    SetChangedFromPrev(ev);
                break;

            case kODLinkInfoUpdateNow:
                _fSelection->UpdateSelectedLink();
                break;

            default:
                break;
            }
        }
    }
}


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