This section introduces some of the storage-related concepts used in clipboard transfer, drag and drop, and linking, which are described in detail later in this chapter.
For the purposes of this section, the term data-transfer object means any of the following: the clipboard or drag and drop object (when reading or writing data), a link-source object (when writing linked data), or a link object (when reading linked data).
When a part places data on the clipboard or other data-transfer object, it writes a portion or all of its intrinsic content to the data-transfer object, and it also can cause the contents of one or more embedded parts to be written.
Whatever the nature of the data, once it is written to the data-transfer object it can be considered an independent part. It has its own intrinsic content and it may contain embedded parts. In writing or reading the data, your part is directly concerned only with the characteristics (such as part kind) of the intrinsic content of the transferred part. All embedded parts are transferred unchanged, as embedded parts, during the process.
The storage unit for the data-transfer object's intrinsic content is called its content storage unit. It is the main storage unit for the content of the data-transfer object, equivalent to a part's main storage unit (see "Main and Auxiliary Storage Units"). Any embedded parts in the transferred data, and any other OpenDoc objects, have their own storage units.
Note:
A link object or a link-source object is a persistent object, and as such has its own main storage unit. That storage unit is not the same as its content storage unit. Always access the content of a data-transfer object by calling its GetContentStorageUnit method.More specifically, the data-transfer object's intrinsic content is stored in the contents property (type kODPropContents) of the content storage unit. When writing to a data-transfer object, your part can store data in multiple formats in different values of the contents property. Just as with a part, each value in the contents property must be complete; it must not depend on other properties or other values of the property.
Your part accesses other properties besides the contents property in a data-transfer object. For example, when your part writes data to a data-transfer object, it may also write, into a separate property, a frame object or a frame shape for the destination part to use when constructing a display frame. See "Annotations" for more information.
Objects in memory take precedence over their stored versions in data transfer. If the user cuts or copies a part's frame to the clipboard or other data-transfer object, the moved data represents the current state of the part, including any edits that have not yet been saved to disk.
This section discusses the items, in addition to part content, that you can write to or read from the storage unit of a data-transfer object.
When copying content to the clipboard or drag and drop object, a part advertises the ability to create a link by writing a link specification in addition to content. When you copy data to the clipboard or drag and drop object, you should create a link specification, using your draft's CreateLinkSpec method, if the user chooses to link to the data when pasting or dropping it in the destination. "Writing Intrinsic Content" illustrates when to write a link specification.
You should write the link specification onto the content storage unit of the clipboard or drag and drop object as a property with the name kODPropLinkSpec. The data in a link specification is private to the part writing it. The data you place in your link specification is returned to your part if and when your part's CreateLink method is called to create the link. All that your part needs from the link specification data is sufficient information to identify the selected content.
Because the link specification is valid only for the duration of the current instantiation of your part, the link specification can contain pointers to information that you maintain.
Link specifications are not necessary in the following situations:
When you write a link specification to the clipboard, obtain and save the clipboard update ID (see "Clipboard Update ID"). You must remove a link specification from the clipboard if your source data changes so that the clipboard data no longer reflects it, and you need the update ID to test for that situation. See "Removing a Link Specification from the Clipboard".
You never need to remove a link specification from the drag and drop object, because it is valid only during the course of a single drag operation.
Note:
The kODPropLinkSpec property is not copied when the storage unit containing it is cloned.
When you place intrinsic content (with or without embedded frames) on the clipboard or other data-transfer object, there is no frame associated with that content. You should, nevertheless, write a frame shape to the data-transfer object to accompany the content; the shape is a suggestion to any part that reads the data and must embed it as a separate part. You write the frame shape into a property named kODPropSuggestedFrameShape in the data-transfer object's content storage unit.
Likewise, if your part receives a paste or drop from the data-transfer object and embeds the instrinsic data, your part should examine the kODPropSuggestedFrameShape property to get the source part's suggested frame shape for the data. If the kODPropFrameShape property does not exist, use your own part-specific default shape.
If you place one of your embedded parts (in the form of a single embedded frame) on the clipboard or other data-transfer object, you must place the frame object there also. You write the frame into a property with the name kODPropContentFrame in a storage unit of the data-transfer object's draft.
Likewise, if your part receives a paste or drop from the data-transfer object, you can note from the presence of the kODPropContentFrame property that the data represents a single frame that should be embedded, and you can retrieve the frame from the property.
When you transfer a single embedded frame, you can specify the frame location relative to your part's content (that is, the offset specified in the external transform of the frame's facet) by incorporating the offset into the frame shape that you write. Then, the receiving part of the paste or drop can, if appropriate to its content model, use that offset to construct an external transform.
Note:
Neither the kODPropSuggestedFrameShape nor kODPropContentFrame property is copied when the storage unit containing it is cloned.
When you write a single embedded frame to a data-transfer object, you can optionally write any intrinsic data that you want to associate with the frame. The intrinsic data might be a drop shadow or other visual adornment for the frame, or it might be information needed to reconstruct the frame as a link source or link destination.
This information is called proxy content; to write it, you add a the kODPropProxyContents property to the data-transfer object, and write your data into it as a value that your part recognizes. If the transferred part is subsequently embedded into a part that also recognizes that value and knows how to interpret it, the added frame characteristics can be duplicated.
If your part receives a paste or drop and embeds the single part, that proxy content for that frame exists. The proxy content exists due to the presence of the kODPropPrxoyContents property. If your part understands the format of the proxy content, you can read it and duplicate the frame characteristics. To determine if you frame understand the proxy content format, examine the value types in the property.
Note:
The kODPropProxyContents property is not copied when the storage unit containing it is cloned.
When a single embedded part is written to a data-transfer object, its containing part writes a property with the name kODPropCloneKindUsed to the data-transfer object. The property specifies the kind of cloning transaction used to clone the embedded part. If the embedded part writes a promise instead of actual data to the data-transfer object, it uses the information in the kODPropCloneKindUsed property when it later fulfills the promise.
For more information, see "Fulfilling a Promise".
If your part initiates a drag operation (see "Initiating a Drag"), you need to create a property named kODPropMouseDownOffset in the drag and drop object's storage unit. Write into that property a value that specifies the location of the mouse-down event that triggered the drag. The value should be of type ODPoint and should contain the offset from the origin of the content being dragged.
If your part receives a drop, it should likewise check for the presence of the kODPropMouseDownOffset property. If the property exists, and if taking it into account is consistent with your part's content model, use it to locate the dropped content in relation to the mouse-up event that marks the drop.
Note:
The kODPropMouseDownOffset property is not copied when the storage unit containing it is cloned.
Each content storage unit of the drag and drop object contains a PM DRAGITEM structure corresponding to a drag item in the current drag and drop operation. It is written by the drag and drop object under the kODDragitem value of the kODPropContents property. Usually parts don't have to interact with this value other than in their Drop method, when they have to create a storage unit view for it and pass it to the GetDataFromDragManager method of the drag and drop object to perform data rendering.
The drag and drop object supports data rendering for two mechanisms: DRM_SHAREDMEM and DRM_OS2FILE. If either one of these mechanisms has been selected (RMF selection is done by calling drag and drop CanEmbed or CanIncorporate methods from within the part's DragEnter method), the storage unit rendered by GetDataFromDragManager will contain the rendered data with no kODDragitem value. If the selected rendering mechanism is DRM_OS2FILE and this is not an OPENDOC initiated drag, the rendered storage unit will contain the following values for its kODPropContents property:
Selected kind | Part kind that 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 | Name of the dropped file; value type is ODISOStr. |
kODFileTypeEA | Any extended attributes that are associated with the file; value type is ODISOStr. |
If the selected rendering mechanism is not handled by the drag and drop object, the target part has to perform the data rendering. In this case, the kODPropContents in the storage unit rendered by GetDataFromDragManager will contain a set of values necessary for the part to carry out the rendering conversation:
kODDragitem | DRAGITEM structure; value type is DRAGITEM. |
kODSelectedRMF | Selected rendering mechanism and format pair; value type is ODISOStr. |
kODSelectedKind | Part kind that the selected RMF was mapped to; value type is ODISOStr. |
kODDragOperation | PM drag operation from the DRAGINFO structure associated with this drag; value type is ODUShort. |
In some situations an entity may need to store properties in the storage unit of a part or other object without the knowledge or cooperation of the part itself. For example, a service such as a spell checker might store a dictionary of exceptions as a property of a part's storage unit. The part is unaware of the existence of that property, but the spell checker would want the dictionary cloned whenever the part is cloned.
When a storage unit itself is cloned (see "Cloning"), all its properties are copied, no matter who wrote them into the storage unit. However, the in-memory version of an object is given preference over its storage unit during cloning, because recent, unsaved changes to the object should be included in the cloning operation. Unfortunately, when an in-memory object clones itself, any of its properties that the object itself is unaware of are not cloned, because it does not know to write them into the destination storage unit.
To support this capability, OpenDoc defines the property prefix constant kODPropPreAnnotation, whose value is OpenDoc:Annotation:. When that prefix is part of a property name (for example, OpenDoc:Annotation:Exceptions) in an object's storage unit, OpenDoc always copies that property when the object is cloned, even if the object being cloned is in memory and regardless of whether the object is aware of the existence of the property.
Therefore, if your part stores data in another object that the object itself does not use but that you want to be cloned along with the object, make sure you store it in a property whose name starts with the string defined by kODPropPreAnnotation.
You should always transfer data to and from the clipboard or other data-transfer object in the context of cloning. This section describes how cloning works, what the scope of a cloning operation is, and how to implement your part editor's CloneInto method.
Cloning an object is copying that objec, all objects it references, and all objects they in turn reference. Copies are made of all storage units or their equivalent proxies that are references with strong persistent references. Storage units referenced with weak persistent references are not copied. For more information on how strong and weak persistent references affect cloning, see "Persistent References" and Figure 60.
Actually, each object cloned during the operation decides, if it is in memory, which of its storage-unit's properties and which of its referenced objects should be included. If the object is not currently instantiated, all of its storage unit's properties and all of its strongly referenced objects are copied.
All persistent objects have a CloneInto method by which they clone themselves, but your part editor should not call the CloneInto method of any object directly. Instead, you clone an object by calling the Clone (or WeakClone) method of its draft object. The Clone method in turn calls the CloneInto method of the object involved. (Your parts, as persistent objects, must provide a CloneInto method. See "CloneInto Method of Your Part Editor" for more information.)
Cloning is a multistep transaction, designed so that it can be terminated cleanly if it fails at any point. You perform a clone by calling three methods, in this order:
First, call the BeginClone method of the draft object of the data to be cloned. If you are transferring data from your part, call your part's draft object; if you are transferring data from a data-transfer object, call that object's draft object. BeginClone sets up the cloning transaction.
(If you are cloning as a result of pasting or dropping data into your part, you must also specify the destination frame, the display frame of your part that is receiving the paste or drop.)
When you call BeginClone, you are returned a draft key, a number that
identifies that specific cloning transaction.
You pass that key to the other
cloning methods that you call during the transaction.
You also specify in the kind parameter to the BeginClone method the
kind of cloning operation that is to be performed,
so that OpenDoc can maintain the proper behavior for linked
data that is being transferred.
Table 9
lists the kinds of cloning transactions that OpenDoc recognizes.
Table 9. Kinds of Cloning Operations
kind parameter | Meaning |
---|---|
kODCloneCopy | Copy object from source to data-transfer object |
kODCloneCut | Cut object from source to data-transfer object |
kODClonePaste | Paste object from data-transfer object to destination |
kODCloneDropCopy | Copy object to the destination of a drop |
kODCloneDropMove | Move object to the destination of a drop |
kODCloneToLink | Copy object from source to update a link source |
kODCloneFromLink | Copy object from link to update a destination |
kODCloneAll | Clone all objects (your part should not use this) |
Even when transferring only intrinsic content (and not actually cloning any objects), you should still bracket your transfer with calls to BeginClone and EndClone. That way, you notify OpenDoc of the kind of operation (such as cut or copy) that is being performed and you ensure that the right actions occur at both the source and destination of the transfer.
For each object that you are cloning, call the draft's Clone method. Clone allows the draft object to specify and recursively locate all objects that are to be cloned. It calls the CloneInto method of the object to be copied, which results in calls to the CloneInto methods of all referenced objects, and so on. For example, when Clone calls the CloneInto method of a part, the part clones its embedded frames; the embedded frames, in turn, clone the parts they display, and so on.
(You sometimes call the draft's WeakClone method instead of Clone, especially when you are cloning within the context of your own CloneInto method. See "CloneInto Method of Your Part Editor" for more information.)
Take these steps when calling the Clone method:
Then, get the object's ID (See "Storage-Unit IDs") from the persistent reference by calling the GetIDFromStorageUnitRef method of the storage unit.
(If you are not actually cloning objects but simply reading or writing intrinsic data, this is the point at which to read or write, instead of calling Clone.)
In cloning, the in-memory version of an object takes precedence over its stored version. For this reason, an object does not need to be written to storage prior to being cloned. If the object is in memory, its CloneInto method is called to perform the clone; if the object is not in memory, its storage unit performs the clone operation.
This convention also means that, if an object is in memory, properties attached to its storage unit that the object itself does not know about might not be copied, unless they are specially named; see "Clonable Data Annotation Prefix" for an explanation.
Null IDs when cloning links
The Clone method returns a value of kODNullID if the desired object cannot be cloned. For example, Clone does not allow you to clone a link object or link-source object into a link, and it returns kODNullID if you attempt to do so. In this case, because you cannot clone the object, you should delete any data associated with it that you have written into the data-transfer object. However, in the case of a link or link source you should still write the content formerly associated with the object, but as unlinked content.
Annotation properties not cloned
When data is cloned from a data-transfer object, most of the annotation properties (such as kODPropLinkSpec) that the source part may have added to the content storage unit are not transferred, because they make no sense as properties outside of the data-transfer object. If you clone a storage unit from a data-transfer object and need these properties, you must read them from the data-transfer object's storage unit, not from the cloned storage unit.
Finally, call the draft's EndClone method. EndClone commits to and actually performs the cloning operation. After EndClone completes, you can then use or reconnect the cloned objects.
If, at any time after calling BeginClone, the operation cannot be completed, you can terminate the transaction by calling the AbortClone method instead of EndClone.
Note:
You cannot instantiate and use any cloned object until after the entire cloning operation is complete. If you are cloning several parts and link objects, for example, you cannot call AcquirePart or AcquireLink until after you have cloned all of the objects and EndClone has successfully returned.
For cloning, the scope defines the set of objects that are to be included in the cloning operation. Scope is expressed in terms of a frame object or its storage unit.
Because a part can have more than one display frame, and because each frame can include a separate set of embedded frames and parts, it is important to specify the frame whose enclosed objects are to be cloned. Otherwise, extra embedded parts or other objects not needed by the copy may be included unnecessarily. (You can specify null for the scope of a clone if you want all objects copied, regardless of what display frame they are associated with.)
In the example shown in the following figure, the user has selected some content in the root part that includes display frame 1 of embedded part A. The root part writes its intrinsic content and then clones part A, passing it a scope of frame A1. Any content that belongs only to frame A2 (such as part C) is not included in the clone.
Scope changes during the course of a clone. Continuing the example shown in the previous figure, the next figure shows how part A clones itself in the context of the scope (frame A1) specified by the root part. Part A writes the intrinsic content of its display frame A1 and then clones part B twice, first passing it a scope of frame B1 and then a scope of frame B2. Part B thus gets called to clone itself twice, with different scopes. Any content of B within frames B1 and B2 is included, but any content that belongs only to frame B3 is not.
Figure 62. Change in Scope as More Deeply Embedded Parts Are Cloned
If your part is an embedded part that is written to the clipboard (or other data-transfer object), your part's override of its inherited CloneInto method is called by your draft's Clone method. This is the interface to CloneInto:
void CloneInto(in ODDraftKey key, in ODStorageUnit toSU, in ODFrame scope); |
Your CloneInto method is passed a draft key for the clone operation and a frame that defines the scope of the cloning. The method should write your part's intrinsic content to the provided storage unit, and it should clone in turn any objects (such as embedded frames or auxiliary storage units) that it references. It needn't clone any objects or write any data that is outside of the scope.
Note:
Do not implement your CloneInto method by writing your part to storage and then cloning your storage unit. Doing so would levy performance penalties because of the extra time needed to store your data. Also, it could result in the copying of unneeded objects because the scope of the clone would be ignored.
To support efficient data transfer, your part should, if possible, write a promise (see next section) instead of writing its actual intrinsic data when CloneInto is called. It is possible to write a promise only when your part is placed into the data-transfer object as a single, standalone frame with no surrounding intrinsic content of its containing part. In any other situation, your CloneInto method might have been called to help fulfill a promise, in which case writing a promise would be inappropriate. You can determine whether you can write a promise by examining the provided storage unit. If it contains a property with the name kODPropContentFrame, your part is the sole content of the storage unit's contents property, and you can write a promise instead of actual data.
When you write a promise, be sure to identify (to yourself) the display frame or frames of your part that are within the scope of the clone operation, so that your FulfillPromise method can write the proper content when it is called.
Take these general steps in your CloneInto method:
An exception to this rule occurs when scope is significant. If your part is called to clone itself with different scopes during the same operation (see, for example, the previous figure), it may have to write additional data each time its CloneInto method is called.
(For data transfer, it is especially important to write a standard format in addition to your own native part kind, because the ultimate destination of the transferred data is unknown.)
(Calling WeakClone does not by itself cause an object to be copied; it only ensures that, if the object ends up being copied because of an existing strong persistent reference to it, your part's weak persistent reference will be maintained across the cloning operation.)
Whenever your part's display frame is cloned during data transfer, the frame calls your part's ClonePartInfo method:
void ClonePartInfo(in ODDraftKey key, in ODType partInfo, in ODStorageUnitView, in ODFrame scope); |
You should respond to this method call by writing your part info into the provided storage unit view (regardless of the state of your part-info dirty flag), and cloning any objects referenced in your part info data.
Clipboard transfer, drag and drop, and linking can make use of promises. A promise is a specification of data to be transferred at a future time. If a data transfer involves a very large amount of data, the source part can choose to write a promise instead of actually writing the data to a storage unit. When another part retrieves the data, the source part must then fulfill the promise. The destination part does not even know that a promise is being fulfilled; it simply accepts the data as usual.
The format of a promise is completely determined by the source part. The only restriction is that the promise must be able to be written to a storage-unit value.
You are encouraged to write promises in place of actual data in most cases; it minimizes memory requirements and increases performance.
Your part can follow these steps to write a promise when it is the source of a data transfer. The part is the data transfer source when it responds to a mouse-down event that initiates a drag, when it copies data to the clipboard, or when it updates a link source.
Each promise you write is for a single part kind. You can write several promises representing data of several kinds, so that the destination part has a better chance of being able to incorporate the data instead of embedding it. Because promises are private data, the actual content of all your promises can be the same, regardless of the part kind being promised. When you are called to fulfill the promise, you can inspect the provided storage-unit view object to find out which part kind is needed.
Because a promise is valid only for the duration of the current instantiation of your part, the promise can contain pointers to information that you maintain.
When the Drop method of a destination part retrieves the data from a drop, or when a destination part reads data from the clipboard (using the GetValue or GetSize methods of the dragged data's or clipboard's storage unit), the source part is called to fulfill the promise. The destination part does not even know that a promise is being fulfilled; it follows the procedures described in "Reading from a Data-Transfer Object" and uses the same code whether the value contains a promise or not.
OpenDoc calls your source part's FulfillPromise method when a promise must be fulfilled, passing it a storage-unit view object that contains the data of the promise. This is its interface:
void FulfillPromise(in ODStorageUnitView promiseSUView); |
In your implementation of the method, take steps similar to these:
If the property does not exist, use information that you saved when you wrote the promise to determine the kind of cloning transaction to use.
When your FulfillPromise method completes, the destination part receives the data.
When you fulfill a promise, you must be sure to supply the source content that was selected at the time the promise was written, even if that content no longer exists in your part.
If fulfilling your promise requires cloning, you must specify the scope and the appropriate kind of cloning transaction. If you have saved that information in the promise itself, extract it and pass it to your draft's BeginClone and Clone methods, respectively.
There are some times when your part may have to fulfill a promise on its own, without its FulfillPromise method having been called. For example, when your part closes, your part's ReleaseAll method (see "Closing Your Part") must fulfill all outstanding promises. To fulfill a promise in that manner, your part needs to have kept a record of the promises (and their update IDs) that it has written. Then, in ReleaseAll, it can:
You can also force fulfillment of one of your promises by focusing on the promised value and calling the GetSize method of its storage unit. That causes your FulfillPromise method to be called immediately.
The user can remove data from your part by selecting the Cut command or by using drag and drop to move (rather than copy) data from your part into another part. In this event, you need to take extra steps to account for the fact that the data is still valid but is no longer in your part, and also to allow the action to be undone. Keep these points in mind:
Once the objects are no longer needed for undo support, OpenDoc calls your DisposeActionState method. You should then either release them entirely or remove them from your draft, depending on whether other valid references to them remain. (Follow the guidelines listed in Table 5.) See also "Undo for Clipboard" and "Undo for Drag and Drop" for related information.
When data is pasted or dropped into a part, the part receiving the data can either embed the data as a separate part or incorporate the data as intrinsic content. The part may also, in some circumstances, translate the data, or it might even refuse to accept the pasted data.
This section discusses how OpenDoc and your part editor make these decisions, both with and without explicit user intervention. It also discusses when your part editor might explicitly translate data.
In the absence of other instructions, OpenDoc expects your part to follow these specific conventions when pasting data from the clipboard or when accepting dropped data during a drag and drop operation.
Note:
All parts may not behave in this way depending on how they were created.
The destination part should be written to place clipboard data at the insertion point; it should place dropped data at the point where the user releases the mouse button. Note also that a pasted or dropped part takes on the view type that its containing part prefers it to have.
The Paste As dialog box allows the user to override OpenDoc and specify whether transferred data is to be embedded, incorporated, or translated and then embedded or incorporated. It also allows the user to create a link to the source of the transferred data.
When the user chooses Paste as in the Edit menu (see "Edit Menu"), the active part should only call the ShowPasteAsDialog method on the OS/2 or Windows platforms to display the Paste As dialog box, shown in Figure 63. On the AIX platform, the part may treat a user's "Paste As" selection as a link automatically because there is no dialog to call. Also, when the user presses Alt in a drag and drop operation, the active part should call the ODDragAndDrop::ShowPasteAsDialog method (for the OS/2 and Windows platforms). See "Dropping" for more information.
Figure 63. Paste As Dialog Box
The user can select the following options from the dialog box:
When your part editor displays the dialog box, it can check the part kinds available in the data-transfer object and specify whether this option is enabled and whether it is checked by default. Disable this option if your part cannot incorporate the data, even after translation.
When your part editor displays the dialog box, it can check the part kinds available in the data-transfer object and specify whether this option is enabled and whether it is checked by default. Disable this option if your part does not support embedding.
If the user selects Embed As, the user can also make the following selections:
This option (choosing part kind) is not available if the transferred data consists of a single frame that is being moved (not copied) from its original source. The moved frame may not be the only frame displaying its part, and changing its kind would then affect the display in the other parts, which may not be what the user intended.
On the OS/2 platform and the Windows platform, the ShowPasteAsDialog method returns the results of the user's choices to your part in an ODPasteAsResult structure:
struct ODPasteAsResult { ODBoolean pasteLinkSetting; ODBoolean autoUpdateSetting; ODBoolean mergeSetting; ODTypeToken selectedView; ODType selectedKind; ODType translateKind; ODEditor editor; }; |
Depending on the contents of the returned structure, your part either embeds or incorporates the transferred data, accepts it, and either creates a link to its source or does not.