Programming Guide: Working with Embedded Frames and Facets

# Programming Guide

## Working with Embedded Frames and Facets

This section contains information on parts that contain embedded frames. It discusses what information your part needs to maintain for embedded frames and how it creates those embedded frames and facets.

As a containing part, your part needs to maintain current information on the shapes and transforms for all its visible embedded frames and facets. If it makes changes to them, it should not only update its own information but in some cases also notify the embedded parts of the changes, so that they can update their own information. Your part must also support frame negotiation (see "Frame Negotiation"), to permit embedded parts to request additional frames or changes to the sizes of their existing frames.

If your part is a container part, the user can embed other parts into your part in several ways. Examples include pasting from the clipboard, using drag and drop, or even selecting a tool from a palette.

The overall process of embedding a part is summarized in the section "Adding an Embedded Part". The overall process of removing a part is summarized in "Removing an Embedded Part". Both processes make use of the specific tasks described in this section.

### Providing an Embedded-Frames Iterator

Your part must keep a list of all its embedded frames. OpenDoc does not specify the format of this list. However, your part must implement an iterator class (a subclass of ODEmbeddedFramesIterator) that gives a caller access to each of the frames in your list of embedded frames. The caller creates an iterator to access your embedded frames by calling your part's CreateEmbeddedFramesIterator method, which has this interface:

    ODEmbeddedFramesIterator CreateEmbeddedFramesIterator(in ODFrame frame);

Your implementation of CreateEmbeddedFramesIterator must provide First, Next, and IsNotComplete methods, as do other OpenDoc iterators. See "Accessing Objects through Iterators" for additional discussion.

### Creating a New Embedded Frame

If your part embeds a part that does not already have its own display frame, you need to create a new embedded frame that will be the embedded part's display frame. Also, if you create an additional view of an existing embedded part you need to create and embed a frame to hold the new view.

In these situations, your part (the containing part) initiates the embedding creation. Your method to create an embedded frame calls the CreateFrame method of the draft, which returns an initialized frame that has already been assigned to the embedded part (CreateFrame calls the DisplayFrameAdded method of the embedded part for this purpose). This call sequence ensures that the new frame is usable when the draft returns it.

When you call CreateFrame, you specify several features of the new frame, including the following:

- Containing frame: the frame (your display frame) that is to contain the embedded frame.

- Frame shape: the shape of the frame to be created. Your part can use a default shape or it can use information supplied with the data you are embedding; see "Frame Shape or Frame Annotation" for more information.

- Part: the part (your part) in which the frame is to be embedded.

- View type and presentation: the initial view type and presentation the part displayed in the embedded frame is to have.

- Subframe: whether or not the embedded frame is to be a subframe of its containing frame (your display frame). See "Using Multiple Facets" for examples.

- Overlay status: whether or not the frame should overlay, or float over, the containing frame.

Once the reference to the new frame is returned, take these steps:

1. Store it somewhere in your content and add it to your part's list of embedded frames. It's up to you to decide how your part internally stores its content, including its embedded frames.

2. Set the embedded frame's link status appropriately. See "Frame Link Status" for more information. If your part does not support linking, you still must set the new frame's link status (to kODNotInLink).

3. If the embedded frame is visible within the containing frame, you must create a facet for it. See "Adding a Facet".

Creating a frame should be an undoable action. See "Undo and Embedded Frames" for information on how to set a frame's in-limbo flag when you undo or redo its creation.

For more information on the DisplayFrameAdded method, see "Requesting an Additional Display Frame".

### Adding an Embedded Frame on Request

As a container part, your part may also need to support creation of an embedded frame when requested to do so by another part. As described in "Requesting an Additional Display Frame", an embedded part can call your part's RequestEmbeddedFrame method in order to get an additional frame for its content. The embedded part passes the necessary information about the requested frame, as shown in this interface:

    ODFrame RequestEmbeddedFrame(in ODFrame containingFrame,
                                 in ODFrame baseFrame,
                                 in ODShape frameShape,
                                 in ODPart embedPart,
                                 in ODTypeToken viewType,
                                 in ODTypeToken presentation,
                                 in ODBoolean isOverlaid);

Your RequestEmbeddedFrame method passes most of this information along to your draft's CreateFrame method. The base-frame parameter specifies which of the embedded part's existing display frames is to be the base frame for the new frame; the new frame will be a sibling of the base frame and will be in the same frame group (if any) as the base frame.

The frameShape parameter passed to this method expresses the requested frame shape in the coordinate system of the base frame. By this method, the embedded part can request, by specifying the origin of the frame shape, a relative location for the new frame compared to its base frame. Your RequestEmbeddedFrame method should take this information into account when granting the frame shape and assigning an external transform to its facet. You should incorporate the positioning information into the external transform, based on intrinsic content. Then the normalized frame shape is returned. A normalized frame shape is one in which the origin of the frame shape is at (0, 0).

Based on information in the existing frame, your RequestEmbeddedFrame method should also assign the new frame's group ID and sequence number, by calling its SetFrameGroup and ChangeSequenceNumber methods. Your part can assign the new frame any sequence number in the frame group, although by convention you should add the new frame to the end of the current sequence. Then the RequestEmbeddedFrame method should add the new frame to your part's list of embedded frames. The method should also create a facet for the new frame if it is visible.

Other tasks you perform are the same as when you initiate the creation of the frame (see "Creating a New Embedded Frame"). You might implement your RequestEmbeddedFrame method in such a way that it calls a private method to actually create the frame. You could then use that same private method in both situations.

### Resizing an Embedded Frame

To change a frame's size, the user typically selects the frame and manipulates the frame border's resize handles. The containing part is responsible for drawing the selected frame border, determining what resize handles are appropriate, and interpreting drag actions on them.

You need to notify an embedded part that its frame has changed in these situations:

- If your part is the containing part of a frame that is resized by the user

- If your part has other reasons to change the size of an embedded frame (for example, to enforce gridding or in response to editing of your own intrinsic content surrounding the frame)

- If the embedded part's display frame has called your RequestFrameShape method for frame negotiation

This is the interface to RequestFrameShape:

    ODShape RequestFrameShape(in ODFrame embeddedFrame,
                              in ODShape frameShape);

Return a copy of the changed shape (the same shape reference passed to you) in the method result. After you change the embedded frame's shape, call the frame's ChangeFrameShape method and pass it the new shape. (For efficiency, you can first acquire the embedded frame's existing frame shape, copy it, resize it, then call ChangeFrameShape, and finally release your reference to the prior existing frame shape). In response, the embedded part may request a different frame size, as discussed in "Resizing a Display Frame".

The containing part should also call the embedded facet's ChangeGeometry method specifying the new frame shape.

Resizing may result in your part having to adjust the layout of its own intrinsic content, such as wrapped text.

### Removing an Embedded Frame

You may need to remove an embedded frame from your part, either as a direct result of user editing (such as cutting or clearing a selection) or upon request of the part displayed in that frame.

An embedded part requests that you remove one of its display frames by calling your part's RemoveEmbeddedFrame method. This is its interface:

    void RemoveEmbeddedFrame(in ODFrame embeddedFrame);

Whether because of editing or by request to RemoveEmbeddedFrame, the basic procedure for removing an embedded frame is to delete all its facets, delete it from your private content structures, and call its Remove or Release method. OpenDoc then notifies the embedded part of the removal by calling its DisplayFrameRemoved method, as described in "Removing a Display Frame".

Removing an embedded frame should be an undoable action. Therefore, you should add a few steps to the procedure to retain enough information to reconstruct the frame (and all its embedded frames) if the user chooses to undo the deletion. You can follow steps such as these:

1. Remove all of the embedded facets from the frame; see "Removing a Facet".

2. Set the frame's containing frame to kODNULL, to indicate that the frame is no longer embedded in any part.

3. Place a reference to the frame in an undo action that you add to the undo history; see "Adding an Action to the Undo History".

4. Remove the frame from your embedded-frames list (with which you allow callers to iterate through your embedded frames). Set the removed frame's in-limbo flag to true, as shown in Table 5.

5. Remove the frame from your other private content structures (except for your undo structures).

If the user subsequently chooses to undo the action that led to the removal of the frame, you can then

1. Retrieve the reference to the frame from the undo action, and set its in-limbo flag to false, as shown in Table 5.

2. Re-establish your display frame as the frame's containing frame

3. Recreate any needed facets for the frame

If the undo action history is cleared, your part's DisposeActionState method is called and at that point you can remove the frame object referenced in your undo action. You call the frame's Remove method if its in-limbo flag is true; you call the frame's Release method if its in-limbo flag is false.

### Reconnecting and Releasing Embedded Frames

Your part connects and closes its embedded frames when the document containing your part opens and closes. On opening, when your part calls the draft's AcquireFrame method for each embedded frame that you want to display the frame in turn calls the DisplayFrameConnected method of its part. After your part and its embedded frames have been saved, and before the document closes, you call the Close method of each of your embedded frames; the frames in turn call the DisplayFrameClosed methods of their parts.

The DisplayFrameClosed and DisplayFrameConnected methods are described in "Responding to Reconnected and Closed Display Frames".

For efficient memory usage, your part can retrieve and connect only the embedded frames that are visible when its document opens, and it can also release and reconnect embedded frames during execution, as the frames become invisible or visible through scrolling or removal of obscuring content. This process is described in "Lazy Instantiation".

### Adding a Facet

OpenDoc needs to know what parts are visible in a window and where, so that it can dispatch events to them properly and make sure they display themselves. OpenDoc does not need to know the embedding structure of a document. OpenDoc is unconcerned with where embedded frames are located and their sizes or shapes. Because embedded frames are considered to be content elements of their containing part, each containing part maintains, in its own internal data structures, embedded-frame positions, sizes, and shapes. Therefore, containing parts need to give OpenDoc information about embedded frames only when they are visible. They do this is by creating a facet for each location where one of their embedded frames is visible.

An embedded frame in your part may become visible immediately after it is created, or when your part has scrolled or moved it into view, or when an obscuring piece of content has been removed. No matter how the frame becomes visible, your part must ensure that there is a facet to display the frame's contents. Follow these general steps:

1. If your own display frame has multiple facets, create an embedded facet for each of the facets in which the newly visible embedded frame appears. To do that, iterate through all the facets of your display frame, creating embedded facets where needed.

2. For each needed facet, if it does not already exist, make one by asking the containing facet (your display frame's facet) to create one. Call the containing facet's CreateEmbeddedFacet method.

   - In your call to CreateEmbeddedFacet, assign the embedded facet a clip shape equal to the embedded frame's frame shape, if the new facet is to be positioned in front of all sibling facets and other content in your part. Otherwise, adjust the clip shape as described in "Managing Facet Clip Shapes".

   - Assign the embedded facet an external transform, based on your part's internal data on the location of the embedded frame.

   - If you will draw offscreen through this facet, assign the facet a canvas. See "Canvases" for an explanation.

3. If you want to receive events sent to, but not handled by, the part displayed in this facet, set the event-propagating flag of the facet's frame. See "Propagating Events" for an explanation.

After you create the facet, OpenDoc calls the embedded part's FacetAdded method to notify it that it has a new facet.

### Removing a Facet

An embedded frame may become invisible when the containing part has deleted it, scrolled or moved it out of view, or placed an obscuring piece of content over it. In any of these instances, the containing part can then delete the facet, because it is no longer needed.

Your part deletes the facet of an embedded frame by calling the containing facet's RemoveFacet method. If the frame that is no longer visible has more than one facet, you need to iterate through all facets, removing each one that is not visible. OpenDoc in turn calls the embedded part's FacetRemoved method to notify it that one of its facets has been removed.

You do not have to actually delete the facet from memory the moment it is no longer needed; you can instead mark it privately as unused and wait for a call to your Purge method to actually remove it.

### Creating Frame Groups

A frame group is a set of display frames used in sequence. For example, a page-layout part uses a frame group to display text that flows from one frame to another. Each frame in the frame group has a sequence number; the sequence numbers establish the order of content flow from one frame into the next.

Sequence information is important for a frame group because the embedded part needs to know the order in which to fill the frames. Also your part (the containing part) needs to provide sequence information to the user, and it probably also needs to allow the user to set up or modify the sequence.

Your part creates and maintains the frame groups used by all its embedded parts. To create a frame group, you call the SetFrameGroup method of each frame that is to be in the group, passing it a group ID that is meaningful to you. You also assign each frame a unique sequence number within its group, by calling its SetSequenceNumber method. You should assign sequence numbers that increase uniformly from 1, so that the embedded part can recognize the position of each frame in the group. You can, of course, add and remove frames from the group and alter their sequence with additional calls to SetFrameGroup and SetSequenceNumber. The embedded part displayed in the frame group can find out the group ID or sequence number of any of its frames by calling the frame's GetFrameGroup and GetSequenceNumber methods.

### Synchronizing Embedded Frames

If your part wants to create multiple similar views of an embedded part, you must ask the embedded part to synchronize those views. Then, if the content of one of the frames is edited, the embedded part will know to invalidate and redraw the equivalent areas in the other frames.

Frame synchronization is necessary because each display frame of a containing part represents a separate display hierarchy. For invalidating and redrawing, OpenDoc itself maintains no direct connection between embedded frames in those separate hierarchies, even if they are exact duplicates that show the same embedded-part content. Figure 26 shows an example. Part A (in frame view type) is opened into a part window. Embedded frame B2, as displayed in the part window, is a duplicate of embedded frame B1 in the original frame. However, unless you synchronize the frames, Part B will not know to update the display of B1 if the user edits the content of B2.

Figure 26. Synchronizing Frames through AttachSourceFrame

View figure.

Your part (the containing part) makes the request to synchronize frames by calling the embedded part's AttachSourceFrame method. You should call AttachSourceFrame as soon as you create the frame that needs to be synchronized with the source frame (that is, before you add any facets to the frame). How an embedded part responds to AttachSourceFrame is described in "Grouping Display Frames". See "Synchronizing Display Frames" for a description of how an embedded part responds to AttachSourceFrame.

### Transmitting Your Container Properties to Embedded Parts

When one part is embedded in another part of the same or similar part category, the user may prefer that, by default, they share certain visual or behavioral characteristics. For example, if the user embeds a text part into another text part, it might be more convenient for the embedded part to adopt the text appearance (font, size, stylistic variation) of the containing part.

OpenDoc supports the communication necessary for this process by defining the concept of container properties. The containing part defines whatever characteristics it wishes to transmit to its embedded parts; embedded parts that understand those container properties can choose to adopt them. Container properties are passed from the containing part to its embedded part in a storage unit; the embedded part reads whatever container properties it understands from the storage unit, and adopts them for its own display if appropriate.

Note:

> Container properties are defined exclusively by individual parts and may apply to only a portion of a part's content. They are therefore generally unrelated to the properties of the part's storage unit, as described in "Storage-Unit Organization".

As the containing part, your part can define whatever container properties it wishes to provide for adoption by embedded parts. Only parts that understand the formats of your container properties, of course, can adopt them.

An embedded part learns what container properties it might adopt from your part by calling your part's AcquireContainingPartProperties method. This is its interface:

    ODStorageUnit AcquireContainingPartProperties(in ODFrame frame);

You should return your container properties to the caller in a storage unit.

Whenever your part changes any of the container properties that it expects embedded parts to adopt, it should notify each embedded part (other than bundled parts) of the change by calling the embedded part's ContainingPartPropertiesUpdated method.

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