Programming Guide


Imaging and Layout Recipes

   

The imaging and layout recipes are listed as follows:

Adding a Display Frame

       

When a new frame is created, it automatically connects itself to its part as one of its display frames. This ensures that frames are always valid and usable. There is no extra work the object which creates the new frame need do besides create the frame itself.

On the other side of the process, the part being displayed must respond to the call to add the new display frame:

   void DisplayFrameAdded(in ODFrame* frame)

There are probably a number of things the part must do in response to gaining a new display frame. Most of these things will depend on the nature and implementation of the part itself; what these might be are left as an exercise for the reader. However, there are a few general actions a part should take:

Synchronizing with a Source Frame    

Parts may have multiple display frames. Sometimes, two or more views of a part must be synchronized. This is necessary to allow the part to update one frame when editing has taken place in another. In many cases, a part can determine these dependencies internally. But there are some cases when parts need to be synchronized externally.

A typical situation where frames should be synchronized occurs when the user opens a window view of an embedded frame. The embedded part's Open() method is called, wherein it will create the new window. The new window will have a root frame which will be added as a display frame of the part. Now, since the part is in control of this process, it already knows that it must keep these two views synchronized. However, if the part has any embedded parts, they won't know that the new display frames added to them should be synchronized with other frames. So the root part of the new window should use AttachSourceFrame() to do that.

The part being displayed should take whatever action necessary to synchronize the frames. As a minimum, if the two frames are the same kind of presentation, it should duplicate embedded frames in one frame into the other.

Adding and Removing Facets

This recipe contains instructions on how to add and remove facets.

Adding a Facet

       

Parts are notified when a facet is added to a display frame.

void FacetAdded(in ODFacet facet)

As when a display frame is added to a part, the part must take actions to handle the addition of a new facet to one of its display frames. Some of these actions are dependent on the nature of the part, but some are fairly standard. These are:


MyPartFacetAdded(Environment* ev, MyPart* somSelf, ODFacet* facet)
{

 ODFrame* frame = facet->AcquireFrame(ev);
 ODEmbeddedFramesIterator iter = somSelf->
                                 CreateEmbeddedFramesIterator(ev, frame);
 frame->Release(ev);

 for (ODFrame* embedded = iter->First(ev);
   iter->IsNotComplete(ev);
   embedded = iter->Next(ev))
 {
  if (/* Check to see if embedded frame is visible */)
  {

  // Set up new facet geometry...
  childFacet = facet->CreateEmbeddedFacet(ev, embedded, ...);
  // Set up part info...
  childFacet->SetPartInfo(ev, (ODInfoType)partInfo);
  }
 }
 delete iter;
}


Removing a Facet

       

Parts are also notified when a facet is removed from a display frame.

void FacetRemoved(in ODFacet facet)

The part must handle the removal of facets from its display frames. The actions the part needs to perform to do this will depend on what it did when the facet was added. Typically the part should:


MyPartFacetRemoved(Environment* ev, MyPart* somSelf, ODFacet* facet)
{
 ODFacetIterator iter = facet->CreateFacetIterator(ev,
            kODChildrenOnly, kODFrontToBack);
 for (ODFacet* childFacet = iter->First(ev);
   iter->IsNotComplete(ev);
   childFacet = iter->Next(ev))
 {
  // delete part info if it was allocated off the heap
  partInfo = childFacet->GetPartInfo(ev);
  delete partInfo;
  facet->RemoveFacet(ev, childFacet);
 }
 delete iter;
}


Adjusting the Active Border

   

The display of the active frame border is managed by OpenDoc. Neither the active part nor its containing part needs to display the active border; OpenDoc itself does that. All the active part needs to do to get the active border is to acquire the selection focus, and relinquishing the focus will remove the border.

Even though the containing part does not have to display the active border of an embedded frame, it does have to deal with the border to some extent. There are two things the containing part must do:

Adjusting the Border Shape

 

When an embedded frame acquires the selection focus, or a frame that already has the focus has its frame shape changed, OpenDoc will calculate a new active border shape, and then ask the frame's containing part to adjust it.     OpenDoc will call AdjustBorderShape on the containing part, passing the shape to be adjusted and the facet it is associated with.

Adjusting the active border shape is a straightforward process. Just clip out all the portions that are obscured by the containing part's intrinsic content or embedded frames.   Do not clip the shape to the containing part's clip shape; OpenDoc will do that. For the OS/2 and Windows paltforms, you do not need to clip the border shape.

About Reference Counts

 

The AdjustBorderShape method is considered a source for the returned shape. It may be creating a new shape that needs to be released later.     For that reason, if all your part does is return the shape parameter, it must make sure to inflate the reference count first by calling its Acquire method. The shape parameter may be released by the calling code after the method exits, so you cannot rely on that reference to keep the shape alive.

Clipping Obscured Content and Parts

         

After the containing part has adjusted the active border shape, it should store the adjusted shape in a variable or field. When the stored shape is non-null, the part should make sure that intrinsic content or embedded frames are clipped so they do not overwrite the active border.

When the active border is moved to a different frame, OpenDoc notifies the containing part by calling AdjustBorderShape with a value of kODNULL for the shape. The containing part can then un-clip obscured content and embedded frames. If the containing part receives several AdjustBorderShape calls in a row that all have non-null shapes, it should use the union of all those shapes for clipping its content and embedded frames. This is because there may be more than one facet on the active frame embedded in the same containing part.

The sample code below does not deal with intrinsic content, but only with embedded parts. See the "Clipping Embedded Facets" recipe for an example of how to generalize this code to deal with intrinsic content.

#include "Facet.xh"
#include "FacetItr.xh"
#include "Frame.xh"
#include "Shape.xh"
#include "Trnsform.xh"

  ...

ODShape* CMyPart::AdjustBorderShape(Environment *ev,
  ODFacet* embeddedFacet,
  ODShape* shape)
{
 ODFacet* dispFacet = embeddedFacet->GetContainingFacet(ev);
 ODCanvas* biasCanvas = dispFacet->GetCanvas(ev);

 if (shape == kODNULL)
 {
  fActiveBorderShape->Release(ev);
  fActiveBorderShape = kODNULL;
  this->ClipEmbeddedFacets(ev, dispFacet);
  return kODNULL;
 }

 ODTransform* xform = kODNULL;
 ODShape* tShape = kODNULL;
 ODShape* adjusted = shape->Copy(ev);

 xform = embeddedFacet->AcquireExternalTransform(ev, biasCanvas);
 adjusted->Transform(ev, xform);  // Now in cont frame coords (mine)
 xform->Release(ev)

 ODFrame* dispFrame = dispFacet->AcquireFrame(ev);
 ODShape* dispShape = dispFrame->AcquireUsedShape(ev, biasCanvas);
 adjusted->Intersect(ev, dispShape);
 dispShape->Release(ev);

 ODFacet* facet = kODNULL;
 ODBoolean above = kODFalse;
 ODFacetIterator* facets = dispFacet->CreateFacetIterator(ev,
                                      kODChildrenOnly,
                                      kODBackToFront);
                                      for (facet = facets->First(ev);
   facets->IsNotComplete(ev);
   facet = facets->Next(ev))
 {
  if (above)
  {
   ODFrame* frame = facet->AcquireFrame(ev);
   tShape = ODCopyAndRelease(ev,
            frame->AcquireUsedShape(ev, biasCanvas));
   xform = facet->AcquireExternalTransform(ev, biasCanvas);
   tShape->Transform(ev, xform);
   adjusted->Subtract(ev, tShape);
   tShape->Release(ev);
   xform->Release(ev);
   frame->Release(ev);
  }
  else
  {
   above = (facet == embeddedFacet);
  }
 }

 if (fActiveBorderShape == kODNULL)
  fActiveBorderShape = adjusted->Copy(ev)
 else
  fActiveBorderShape->Union(ev, adjusted);

 xform = embeddedFacet->AcquireExternalTransform(ev, biasCanvas);
 adjusted->InverseTransform(ev, xform);  // Now in embedded frame coords
 xform->Release(ev)

 this->ClipEmbeddedFacets(ev, dispFacet);
 return adjusted;
}


Clipping Embedded Facets

Containing parts manage the clipping of their embedded parts. Containing parts use the clip shape of embedded facets to indicate how embedded parts should clip their drawing operations. A facet's clip shape indicates which portion of the facet is visible within its containing facet. If the containing facet is itself obscured, that does not affect the embedded facet's clip shape.     To find out what portion of a facet is visible on its canvas, use the facet's AcquireAggregateClipShape.

        A containing part must update its embedded facets' clip shapes when the way they are clipped changes. There are a number of cases where this can happen when:

If the containing part does not allow embedded frames to be overlapped (like a text part), the number of situations requiring clipping is minimal. If a containing part allows complex, overlapping arrangements of embedded frames, like a drawing or presentation part, then there are many situations that require clipping.

Responsibilities

This section discusses the different responsibilities of the containing part and the embedded part.

  Containing Part

The responsibilities of the containing part are:

  Embedded Part

The embedded part is expected to:

Sample Code

   

The code below follows a very simple recipe for clipping embedded facets. More sophisticated techniques are possible and may be required for advanced imaging features like translucency or compositing.

The basic recipe for clipping an embedded facet is:

Some tips:

CMyPart::GetFacetForEmbeddedContentItem()

Given a content object in the containing part, this method returns the embedded facet that needs to be clipped. If the containing part only allows one facet per embedded frame in each of its display frames, it does not need to store a pointer to the facet in the content object. This method computes the intersection of the set of the immediate children of the containing part's display facet and the set of the facets of the embedded frame. In this case, the intersection should be a single frame.

If a containing part allows multiple facets on an embedded frame in its content, it will need to keep an explicit reference to a facet in the content object. In this case, this method can return that reference.

The following is the sample code.

#include "Facet.xh"
#include "FacetItr.xh"
#include "Frame.xh"
#include "Shape.xh"
#include "Trnsform.xh"
#include "FocusLib.h"
  ...
void CMyPart::ClipEmbeddedFacets(Environment* ev, ODFacet* facet)
{
 ODCanvas* biasCanvas = facet->GetCanvas(ev);
 ODGeometryMode geoMode = GetCanvasGeometryMode(ev,
                                                facet->GetCanvas(ev));
 ODShape* workingClip = ODCopyAndRelease(ev,
                                         facet->AcquireClipShape(ev,
                                         biasCanvas));
 workingClip->SetGeometryMode(ev,geoMode);

 // If an embedded frame is active, the active border should obscure
 // other embedded frames.
 // Border was recorded in ::AdjustActiveBorder()
 if (fActiveBorderShape != kODNULL)
  workingClip->Subtract(ev, fActiveBorderShape);

 ODFacet* embFacet = kODNULL;
 ODFrame* embFrame = kODNULL;
 ODShape* newClipShape = kODNULL;
 ODShape* newMaskShape = kODNULL;
 ODTransform* clipTrans = kODNULL;

 // Compute clipping by iterating all content, intrinsic and embedded
 ContentIterator* contents = this->CreateContentIterator(kODFrontToBack);
 for (item = contents->First();
      contents->IsNotComplete();
      item = contents->Next())
 {
  if (item->IsIntrinsic())
  {
   // Clip intrinsic content here if you need to
   // ...

   // Intrinsic content should obscure underlying frames
   // GetMaskShape() also includes selection handles if item is selected
   workingClip->Subtract(ev, item->GetMaskShape());
  }
  else
  {
   embFacet = this->GetFacetForEmbeddedContentItem(facet, item);
   embFrame = embFacet->AcquireFrame(ev);

   // Start with facet's frame shape
   newClipShape = ODCopyAndRelease(ev, embFrame->
                                   AcquireFrameShape(ev, biasCanvas));

   // Get UsedShape to obscure underlying facets
   newMaskShape = ODCopyAndRelease(ev, embFrame->
                                       AcquireUsedShape(ev, biasCanvas));

   clipTrans = embFacet->AcquireExternalTransform(ev, biasCanvas);

   // Now in containing frame coordinates
   newClipShape->Transform(ev, clipTrans);
   newClipShape->Intersect(ev, workingClip);

   // Now in embedded frame coordinates
   newClipShape->InverseTransform(ev, clipTrans);
   embFacet->ChangeGeometry(ev, newClipShape, kODNULL, biasCanvas);

   // Now in containing frame coordinates
   newMaskShape->Transform(ev, wclipTrans);
   workingClip->Subtract(ev, newMaskShape);

   embFrame->Release(ev);
   newClipShape->Release(ev);
   newMaskShape->Release(ev);
   clipTrans->Release(ev);
  }
 }
 delete contents;

 workingClip->Release(ev);
}

ODFacet* CMyPart::GetFacetForEmbeddedContentItem(ODFacet* facet,
                                     ContentItem* embeddedItem)
{
 // Find the facet that is facet of "embeddedFrame" and contained by "facet"
}

Content Update Notification

       

All OpenDoc parts are responsible for notifying their containing part when their content changes. This notification enables a containing part to update links that include embedded data. This following documentation summaries the responsibilites of a part that does not support linking. Developers of parts supporting linking are referred to the Linking recipes for more information.

Calling ODFrame::ContentUpdated

   

Parts call the ContentUpdated methods of their display frames to notify their containing parts of changes to their content. When a change originates in a part, for example, in response to keyboard input, the part should call the ContentUpdated method of each display frame affected by the change, passing it an update ID for the change. For changes originating within a part, this ID should be obtained by calling the UniqueUpdateID method of the session object. For example:

displayFrame->ContentUpdated(ev, mySession->UniqueUpdateID(ev));

It is not necessary to call ContentUpdated on each change, for example, on each keystroke. A part can batch individual changes into one call to ContentUpdated, by waiting for a reasonable pause in changes or until the part loses the selection focus. However, be sure to call UniqueUpdateID each time ContentUpdated is called.

Implementing EmbeddedFrameUpdated

   

A part that does not support linking does not need to take any action in its EmbeddedFrameUpdated method. There is no need to notify contianing parts.

Creating an Embedded Frames Iterator

         

Part editors must be able to create iterators for their embedded frames. When a part receives a CreateEmbeddedFramesIterator call, it should create the iterator and return it.

Creating the Iterator

The part editor DLL will include implementations for subclasses of ODPart and ODEmbeddedFramesIterator and perhaps other extensions. To create the embedded frames iterator the part allocates and initializes the instance.

    The CreateEmbeddedFramesIterator function is awkward to use due to a missing parameter in the initialization call. The iterator should only operate on the embedded frames of a particular display frame of the part. However, the InitEmbeddedFramesIterator call only takes an ODPart parameter and not an ODFrame parameter. Therefore, the private interface between the iterator and the part that allows the iterator access to the embedded frames must be able to handle the mapping to the particular frame.

For instance, after calling InitEmbeddedFramesIterator, the part could make another call to the iterator to let it know on which frame it is operating. The iterator passes the frame to the part to get the correct embedded frames to iterate. Alternately, the part could remember the mapping of the iterator to the frame, and the iterator could pass itself instead of the frame.

Caveats

The following caveats apply:

Sample Code


MyEmbeddedFramesIterator* CMyPart::CreateEmbeddedFramesIterator
                                   (Environment* ev, ODFrame* frame)
{
   MyEmbeddedFramesIterator* iter = new MyEmbeddedFramesIterator;
   this->MapFrameIterator(iter, frame);
   iter->InitEmbeddedFramesIterator(ev, somSelf);
   return iter;
}

OrderedCollection* CMyPart::GetEmbeddedFrames(Environment* ev,
                                              MyEmbeddedFramesIterator* iter)
{
   ODFrame* frame = this->GetFrameForIter(ev, iter);
   OrderedCollection* frameList =
                      this->GetEmbeddedListOfFrame(ev, frame);
   return frameList;
}

void MyEmbeddedFramesIterator::InitEmbeddedFramesIterator(
                               Environment* ev, ODPart* part)
{
  fPrivateIter = ((CMyPart*)part)->
                  GetEmbeddedFrames(ev, this)->
                  CreateIterator();
}


Determining Print Resolution

 

When a root part of a window receives a print command, it must set up a print job and cause all the pages to be imaged. A subtlety of creating the print job is determining the right resolution. Some graphics systems are resolution-dependent, and their associated print managers need advance knowledge of the resolution of the print job if it is not to be the default 72 dpi. If the root part or an embedded part needs extra resolution, for example if it is a 300 dpi graphic, the root part must create a print job that can handle that resolution. Because the root part cannot know or guess what resolution is right for all the embedded parts, it must ask them.

    The root part must iterate over each embedded frame that will be printed. It uses the part's GetPrintResolution method call to ask the part of each frame what print resolution it requires, and sets up the job with the maximum value of the set of answers. Of course, the root part must also consider the resolution required by its own frames.

Whether a print job needs to know its resolution and how to set its resolution are both graphics system dependent.

On the OS/2 and Windows platforms, the print resolution for a print job cannot be set in programs. However, a root part can try to query the resolution of available printers to find one that is satisfactory.

Embedding a Frame

Containing parts can embed other parts, or rather they may embed frames that display other parts. There are several situations that can cause a containing part to embed another part:

In the first three cases, the containing part itself initiates the actual embedding in response to some other action. In the last case, the embedded part initiates the embedding by calling containingPart->RequestEmbeddedFrame().

In any case, the containing part creates a frame in which to embed a part by asking the draft to create one:

newFrame = draft->CreateFrame(ev,
      kODFrameObject,
      containingFrame,
      newShape,
      biasCanvas,
      embeddedPart,
      viewType,
      presentation,
      kODFalse, // IsRoot must be false
      isOverlaid);

    The new frame returned from draft's CreateFrame method has already been initialized. The frame made itself a display frame of the embedded part by calling:

embeddedPart->DisplayFrameAdded(ev, somSelf);

This ensures that the new frame can be used as soon as it is returned by the draft. For more about what may have happened during DisplayFrameAdded(), see the recipe for "Adding a Display Frame".

Once the new frame is returned, the containing part must store it in its content. How the part stores its content internally is up to the developer. If the new frame is visible within the containing frame, the containing part must create a facet for it. See the recipe for "Making a Frame Visible".

            If the embedded part tries to negotiate a different frame shape during its DisplayFrameAdded call, the containing part will get the RequestFrameShape call before the CreateFrame call returns. For this reason, an embedded part should not try to negotiate for a new frame shape in its DisplayFrameAdded method. Instead, it should wait until a facet is added to the frame.

Frame Groups and Sequencing

                 

OpenDoc frames have two properties that can be used to indicate relationships among sibling frames within a containing part. The FrameGroup property indicates that two or more frames should be considered part of a group, and the sequenceNumber property describes the ordering within the group.

The ODFrame function includes the following calls for getting and setting the frame group and sequence number:

ODULong ODFrame::GetFrameGroup()
void    ODFrame::SetFrameGroup(in ODULong groupID)
ODULong ODFrame::GetSequenceNumber()
void    ODFrame::SetSequenceNumber(in ODULong sequenceNumber)

Containing Part Perspective

A frame's containing part has ownership of its FrameGroup and SequenceNumber; the containing part is the only object that may change those values. Only the containing part may call SetFrameGroup and SetSequenceNumber on an embedded frame.

If a frame is not grouped with any other frames, its FrameGroup and SequenceNumber properties should both be set to zero. This is the default state for a frame when it is created, so the containing part does not need to set it to be that way.

When a containing part responds to a RequestEmbeddedFrame call, it should place the new frame in the same group as the base frame, and should give the new frame a sequence number equal to the new size of the group. If the base frame was previously ungrouped (FrameGroup was zero), it should first be given a new group ID and a sequence number of one. Valid sequence numbers are contiguously allocated positive integers. If a frame is removed from the middle of a group, the frames above it should be renumbered so that their sequence numbers are contiguous.

A containing part may have a user interface that allows grouping and sequencing of embedded frames, but whether that is appropriate depends on the part's content model.

Embedded Part Perspective

   

When an embedded part requests an additional display frame from its containing part (via RequestEmbeddedFrame), the new frame will be in the same frame group as the base frame, and will have the highest sequence number in that group. Newly created frames have a frame group and sequence number of zero; the containing part sets those values shortly after creating the new frame. So the embedded part will receive a DisplayFrameAdded call with the new frame, and shortly after a SequenceChanged call notifying it of the new sequence number.

An embedded part has no control over the sequence ordering of its frames. However, it does have complete control over what content it displays within those frames. If the part needs to change how its content is displayed in a group of frames, it does not need to change their sequence numbers, just how it maps those numbers to content display. In most cases, the mapping should be straightforward: respect a user's choice of layout arrangement.

Frame Link Status

Frames have a link status that indicates the frame's participation in links maintained by containing parts. All OpenDoc parts that support embedding are responsible for ensuring that the link status of their embedded frames is set correctly. This document summarizes the part responsibilities for a part that does not support linking. Developers of parts supporting linking are referred to the "Linking Recipes" recipe for more information.

Implementing LinkStatusChanged

   

All parts that support embedding must implement the part's LinkStatusChanged method. All they must do is pass the notification along to all embedded frames by calling the frame's ChangeLinkStatus method. It is not necessary to internalize an embedded frame to make this call; it can be postponed until the embedded frame is internalized for some other reason.

Internalizing an Embedded Frame

Every part must ensure that the link status of their embedded frames is set correctly. The easiest way for a part editor that does not support linking to do this is to call the frame's ChangeLinkStatus method immediately after internalizing the embedded frame. The ODLinkStatus parameter should be the link status of the part's display frame containing the embedded frame. For example:

ODFrame* embeddedFrame = myDraft->AcquireFrame(ev, embeddedFrameID);
embeddedFrame->ChangeLinkStatus(ev, myDisplayFrame->GetLinkStatus(ev));

Embedding a Frame

   

Parts must set the link status of frames they embed, be it by a paste, drop, or some other means. If the part does not support linking, it should call the frame's ChangeLinkStatus method immediately after the frame is embedded and internalized.

Making a Frame Visible

OpenDoc maintains information about what parts are visible in a window, so that it may display them and dispatch events to them. OpenDoc knows little about the embedding structure of a document, as that information is stored by parts in encapsulated internal representations. Therefore, containing parts must tell OpenDoc about embedded frames that are visible. The way to do this is to make a facet for each place in which a frame is visible within a containing part.

An embedded frame may become visible when the containing part has scrolled or moved it into view, when it was just embedded, when an obscuring piece of content was removed, and so forth. When this happens, the containing part must ensure that there is a facet to display the frame. If there is no facet already, it can make one by asking a facet of its display frame to create it. The containing part must create a facet for each place the frame becomes visible. The part must create a facet for each facet of the containing part's display frames.

The following fragment makes a frame called "embeddedFrame" visible within a display frame called "frame" of a containing part. The clip shape of the new facet is set to be the same as the embeddedFrame's frame shape, but your part should alter that shape to reflect actual clipping. The external transform of the new facet is set from the internal content information stored within the containing part; the function ProxyForFrame() returns the internal object the part uses to hold information about the embedded frame within its content model. The new facet is created in front of all its sibling facets, if any.

ODShape* clip = kODNULL;
ODTransform* xform = kODNULL;
ODFrameFacetIterator* facets = frame->CreateFacetIterator(ev);
for (ODFacet* facet = facets->First(ev);
    facets->IsNotComplete(ev);
    facet = facets->Next(ev))
{
    clip = ODCopyAndRelease(ev, embeddedFrame->AcquireFrameShape(ev));
    xform = ODCopyAndRelease(ev,
                  ProxyForFrame(embeddedFrame)->transform);
    facet->CreateEmbeddedFacet(ev, embeddedFrame, clip, xform,
        kODNULL, kODNULL,    // No special canvas or bias
        kODNULL,             // Sibling facet
        kODFrameInFront);    // Frontmost
    clip->Release(ev);
    xform->Release(ev);
}

Creating and Using Offscreen Canvases

There are several situations where parts may want to create offscreen canvases. Two common cases are double-buffering and image manipulation. Embedded parts are likely to create offscreen canvases, so they may double-buffer output for greater display efficiency and quality. Containing parts can place embedded parts on offscreen canvases, so they can graphically manipulate the imaging output of the embedded part, and perhaps combine it with their own.

Creating an Offscreen Canvas

             

To create a canvas, a part must first create a platform-specific drawing context and then call ODFacet::CreatePlatformCanvas to create the platform canvas object. Then it can use the ODFacet::CreateCanvas() factory method to create an actual ODCanvas.

// As for your graphics system
newCanvas = facet->CreateCanvas(ev,
      kGraphicsSystem,                     // Enum code for your g.s.
      platformCanvas,
      kODTrue,                             // Using a dynamic canvas
      kODTrue);                            // This is OFFSCREEN
newCanvas->SetOwner(ev, fSelf);            // fSelf is my PartWrapper

Containing part    

For a containing part to place an embedded part on an offscreen canvas, it must attach the canvas to the embedded facet. This must be done at facet creation time, before the embedded part gets notified of the new facet via FacetAdded(), where it may attempt to attach its own offscreen canvas (see below).

embeddedFacet = facet->CreateEmbeddedFacet(ev,
       frame, clipShape, externalTransform,
       newCanvas,                // Canvas created above
       kODNULL,                  // No biasCanvas
       kODNULL, kODInFront);     // Frontmost facet

If an already-existing embedded facet has no canvas of its own, the containing part may add one. In that case, embedded parts will be notified automatically that their display canvas has changed.

facet->SetCanvas(ev, newCanvas);

Embedded part

An embedded part can attach a canvas to one of its facets: during FacetAdded() is a good time. The part can move the facet to an offscreen canvas later.

facet->SetCanvas(ev, newCanvas);

If the facet has a canvas assigned by the containing part, the embedded part may not attach a canvas to that facet. If it wants to image on its own canvas, it must create a subframe of the facet's frame, create a facet on that subframe, and attach the canvas to that facet.

Imaging on an Offscreen Canvas

If your part is written correctly, you should not have to do anything differently to image on an offscreen canvas. Some simple rules are:

Updating an Offscreen Canvas

The part which owns an offscreen canvas is responsible for transferring its contents to its parent canvas. This is because only the part which created the canvas can be assumed to know how to transfer its contents. The parent and child canvases may have different formats (for example, bitmap versus display list), or the owner may want to transform the canvas contents as it copies them (for example, rotate or tint).

When a containing part has placed an embedded facet on an offscreen canvas, it should force the embedded part to draw before the containing part itself draws any of its own contents. This ensures that the contents of the offscreen canvas are up to date, and can safely be combined with the containing part's contents. In the containing part's Draw method:

embeddedFacet->Draw(ev, invalidShape);

// In case it has embedded parts
embeddedFacet->DrawChildren(ev, invalidShape);

// Draw some content...
// ...
offscreen = MyExtractBitmap(embeddedFacet->GetCanvas(ev));
MyTransferBitmap(offscreen);

If embedded parts display asynchronously, the containing part which owns the canvas on which they image will be notified via the CanvasUpdated() call. The part may transfer the content from the offscreen canvas at this time, or it may choose to defer it until later, for reasons of efficiency or minimizing excessive redrawing.

Part Drawing

Part editors must be able to display their parts in response to the Draw() call.

void Draw(in ODFacet* facet, in ODShape* invalidShape)

Draw the part in the given facet. Only the portion in the invalid shape needs to be drawn.

There are several steps a part needs to take to perform the imaging.

  1. The part should look at the given facet and its frame. Both the frame and the facet may have some part info that the part has placed there, which the part can use to decide how it will display itself. The frame also has viewType and presentation fields that indicate what kind of view of the part should display.

  2. The part should examine its canvas to see how it should be imaged. The canvas can be obtained from the facet via ODFacet::GetCanvas(). If the canvas' isDynamic flag is kODTrue, the part is imaging onto a dynamic device like a CRT; otherwise, it is imaging to a static device like a printer. The part will probably display its content differently for static and dynamic views. For instance, it should not display scroll bars on a static canvas.

  3. The part must make sure the platform graphics system is prepared to draw into the correct context for the facet.

  4. Draw the part's contents.

  5. Restore the old graphics environment.

Part editors may sometimes need to display their parts asynchronously, that is, not in response to a Draw() call. This process is very similar to the basic drawing recipe, with minor modifications.

  1. Determine which of the part's frames should be drawn. A part may have multiple display frames, and more than one may need updating.

  2. For each frame being displayed, all visible facets must be drawn. ODFrame::CreateFrameFacetIterator() returns an iterator that will list all the facets of a frame. Draw the part's contents in each of these facets, using the recipe above.

  3. After drawing in a facet, call ODFacet::DrawnIn() on it to tell it you have drawn in it asynchronously. If the facet is on an offscreen canvas, this lets it get copied into the window.

Printing

   

When the Print command is selected from the Document menu, or when a document is dragged to a printer object on the Desktop, the document shell sends an OD_PRINT event to the HandleEvent method of the owning part of the active window's root frame. If the OD_PRINT event is not handled by the root part, the document shell creates a printing facet for the root frame and starts a print job.   The part handlers should support printing:

Of course, embedded parts will also be printed as the result of a Print command. But for an embedded part it's a lesser task; the part will be told to open a new frame or facet on a printing canvas, and will then be told to draw itself onto that canvas. If a part wants to display differently on a printout than onscreen, it can check whether the canvas is static. It can also retrieve the platform-dependent print job data from the canvas if it needs more information (for instance, to tell whether it is printing to a PostScript printer.)

Printing support, therefore, turns into two tasks:

We will cover these in separate sections

Managing the Print Job and Page Layout

   

As with imaging, OpenDoc provides no function for managing print jobs; you must use your operating system's native function to do this. However, an OpenDoc-based framework will probably provide support for printing; check your local listings. Once you have set up a print job, you stuff it into a new ODCanvas object and use that canvas as the basis for layout and imaging.

WYSIWYG dependencies in layout There are two kinds of page layout a part can implement; which one you choose depends on how WYSIWYG your part is. In the first and simpler method, the printed page has the same layout as your onscreen appearance. After you create a printing canvas, you create a new facet on the frame to be printed, attach that facet to the printing canvas, and image the facet (and any embedded facets).

The second method is more complex but allows the printed page to have a different layout than the onscreen representation. To allow a new layout, you must create a new frame for your part on the printing canvas, also adding frames for all your embedded parts. In this process, the embedded parts (if they in turn want to lay themselves out differently on the printout) can use frame negotiation to change their size or position.

The basic steps are:

  1. Retrieve any persistent print job settings.

  2. Display the printing dialog to the user.

  3. Tell the operating system to start the print job.

  4. Create a new static canvas, and stuff references to the print job and its drawing environment into the canvas.

  5. Create a new frame or facet (depending on your printing-layout model) for yourself on the printing canvas.

  6. Draw the root facet of the printing canvas and its embedded facets, if any.

  7. If your content does not fit on one page, scroll the root facet (by offsetting its external transform and clip shape) and draw the next page; repeat until finished.

  8. Close the print job.

Imaging Onto a Printing Canvas

   

Drawing to a printer is typically almost exactly like drawing to the screen. As usual, your part's Draw method will be called when it is time to draw a facet, and it should use the presentation space obtained from the facet-canvas' PlatformCanvas object to do imaging.

To determine whether the canvas is static (non-interactive), you can use the IsStatic method of the canvas. If the canvas is static, you may not want to draw purely interactive features such as scroll bars. Remember that, while a print job is always static, there can be other kinds of static canvases, such as onscreen page previews; a static canvas is therefore no guarantee that a print job is in progress.

References

The OpenDoc recipe "Determining Print Resolution".

Removing an Embedded Frame

In the simplest case, removing an embedded frame is straightforward. Just follow these steps:

However, this case does not allow the deletion to operate with Undo. Which brings us to undoable frame deletion.

If you follow the above recipe, you will destroy the embedding structure for all parts embedded within the frame being deleted. At the very least, this will be a lot of work to Undo; at the worst, you will not be able to reconstruct the hierarchy at all. So you should follow a different recipe if you want to Undo, which you almost certainly do.

If at some point the user chooses to undo the frame deletion, restoring it can be accomplished as follows:

When the part receives a DeleteActionData call, it should commit the action. If the action involves an embedded frame deletion, the deletion should be committed as follows:

RequestEmbeddedFrame

   

One of the methods which part editors that support embedding may need to override is RequestEmbeddedFrame.

As part of this recipe, the part editor can put the functionality of creating a frame for embedding into another method that can be called from RequestEmbeddedFrame. RequestEmbeddedFrame should only be called by an embedded part on its containing part to get a sibling frame. That is why this call takes a baseFrame parameter. The base frame must be embedded inside the part receiving the RequestEmbeddedFrame message.

The following caveats apply:

Sample code

ODFrame* MyPartRequestEmbeddedFrame(MyPart* somSelf, Environment* ev,
      ODFrame* containingFrame,
      ODFrame* baseFrame,
      ODShape* frameShape,
      ODPart* embedPart,
      ODTypeToken viewType,
      ODTypeToken presentation,
      ODBoolean isOverlaid)
{
 ODFrame* newFrame = somSelf->CreateEmbeddedFrame(ev,
       containingFrame,
       frameShape,
       externalTransform,
       embedPart,
       viewType,
       presentation,
       baseFrame->GetFrameGroup(),
       isOverlaid);
}

// Private implementation method:
ODFrame* MyPartCreateEmbeddedFrame(MyPart* somSelf, Environment* ev,
      ODFrame*           containingFrame,
      ODShape*           frameShape,
      ODTransform*       externalTransform,
      ODPart*            part,
      ODTypeToken        viewType,
      ODTypeToken        presentation,
      ODID               frameGroupID,
      ODBoolean          isOverlaid)
{
 ODFrame* newFrame
  = somSelf->GetStorageUnit(ev)->GetDraft(ev)->CreateFrame(ev,
       kODNULL,             // Default type is persistent
       containingFrame,
       frameShape,
       kODNULL,             // No bias canvas
       part,
       viewType,
       presentation,
       kODFalse,            // Not a subframe
       isOverlaid
 newFrame->SetFrameGroup(ev, frameGroupID);
 newFrame->SetSequenceNumber(ev, somSelf->NextInGroup(ev, frameGroupID);
 // Add frame to list of embedded frames, and keep track of
 // the external transform for that frame
 fMyEmbeddedFrameList->Add(newFrame,externalTransform);

 return newFrame;
}

Scrolling

This recipe discusses scrolling within OpenDoc.

Coordinate Systems

There are two coordinate systems a part can use within a display frame. The frame coordinate space describes where the frame itself is. The content coordinate space describes where the part's contents within that frame is.

By itself, a frame has no way to relate these coordinate spaces to that of any canvas. A facet on the frame can provide transform objects that map coordinates in either the frame or content spaces to the space of the facet's canvas (or window if needed).

The frame and content coordinate spaces are related by the frame's internal transform. This transform maps coordinates from the content space into the frame space. A part should change its display frame's internal transform to scroll its contents.

Simple Scrolling

 

Some parts display only content within their display frames. In this case, the entire area within a frame should scroll. Part handlers must have some way to scroll their content that requires no real estate within the frame: perhaps arrow keys or a hand cursor mode.

  1. Determine scroll offset.

  2. Alter the display frame's internal transform:

    xform = displayFrame->AcquireInternalTransform(ev);
    xform->MoveBy(ev, scrollOffset);
    displayFrame->ChangeInternalTransform(ev, xform);
    xform->Release(ev);
    

    In the above example, the part may alter the internal transform directly, without having to make a copy of it. This is because the part is the owner of the internal transform. However, the part must still use ChangeInternalTransform to let the display frame know that the transform has been changed.

Partial Scrolling

 

Some parts display adornments within their display frames that are not part of their scrollable content. The scroll bars should not move when the content in the frame is scrolled. Parts with such frames should keep a content shape which describes which portion of their frame is actually affected by the internal transform. There is no particular place this shape must be stored, as it is internal to the part's private implementation, but it might be handy to keep it in the partInfo of its frame. As with other shapes, this shape should be in the frame coordinate space.

Imaging

A part images in a particular facet of one of its display frames, as specified in Part::Draw(). In the simple case, a part will image everything in that frame using the content coordinate transformation. In the case of partial scrolling, the part should image the non-scrolled portion of its frame using the frame coordinate transform, and only the portion of the frame within the content shape should be imaged using the content transform.

User Interface Interaction

A part responding to mouse clicks, dragging, or other geometry-based interactions must use the internal transform of its frame to transform the relevant points into the content coordinate space. If a part scrolls everything in the frame, all mouse clicks, and so forth, may be transformed.     If the part only scrolls a portion of the frame, it must first hit-test the point with the content shape. Points which fall within the content shape should be transformed by the inverse of the internal transform to convert them into the content coordinate space.

Embedding Support

A part which scrolls only a portion of its frame needs to take extra care if it embeds other parts. The clip shapes of any embedded facets need to be intersected with the frame's content shape so that the embedded frames do not overwrite the non-scrolled portion of the containing frame.

The Other Way

The above method is recommended for most cases. There is a second way to implement scrolling that is more resource consumptive and has worse performance, but it may be useful in unusual circumstances.

The alternative is to implement the scrollable and non-scrollable portions of the frame as two separate frames. Each frame is a display frame of the same part, but the non-scrolling frame is the containing frame of the scrolling frame. The isSubframe flag of the scrolling frame must be kODTrue for this to work correctly. As usual, there must be a facet for each frame of the part. When the user mouses in the scroll bar in the parent frame, the part changes the internal transform of the child frame. One advantage of this technique is that no special care must be taken to manage the clip shape of any embedded frames.

View Types and Presentations

OpenDoc parts can display themselves in different ways in different frames, or in different ways in the same frame at different times. The part is in control of its display in its frames, but there is a protocol for other parts to suggest or request the part to display itself in a particular way.

There are two axes to how a part displays itself in a frame. One is the type of the view, represented by the viewType field in ODFrame objects. The viewType field indicates whether a frame shows the part as an icon, thumbnail, full content view, and so forth. The other is the kind of presentation represented by the presentation field in ODFrame objects. The presentation field describes which of the various ways a part can present itself is being used in that frame.

The view type and presentation are represented as tokenized ISO strings or type ODTypeToken. Parts are required to support all standard view types, but may define their own set of supported presentation kinds.

The objects which take part in this protocol are ODPart, ODFrame, ODDraft, and ODWindowState.

View Type


ODFrame: ODTypeToken GetViewType()

Returns the view type of the frame.

ODFrame: void SetViewType(in ODTypeToken viewType)

Sets the view type of the frame. This should only be called by the frame's part.

ODFrame: void ChangeViewType(in ODTypeToken viewType)

Changes the view type of the frame. This can be called by any part or other object. This sets the value of the frame's view type, then calls fPart->ViewTypeChanged(this) to notify its part of the change.

ODPart: void ViewTypeChanged(in ODFrame frame)

The part should change its display in the frame to be of the indicated type. Parts must support all standard view types.

Presentation


ODFrame: ODTypeToken GetPresentation()

Returns the presentation of the frame.

ODFrame: void SetPresentation(in ODTypeToken presentation)

Sets the presentation of the frame. This should only be called by the frame's part.

ODFrame: void ChangePresentation(in ODTypeToken presentation)

Changes the presentation of the frame. This can be called by any object. This sets the value of the frame's presentation, then calls fPart->PresentationChanged(this) to notify its part of the change.

ODPart: void PresentationChanged(in ODFrame frame)

If the part supports the new presentation kind, it should change its display in the frame to be of that kind. But if the part does not support the new presentation kind, it should instead pick a close match or a good default, and call frame->SetPresentation() to correct the value in the frame.

Frame Creation


ODDraft: ODFrame CreateFrame(...)

Pass the desired view type and presentation for the new frame. The frame will add itself to the part by calling fPart->AddDisplayFrame(this, sourceFrame). The part may correct the presentation if the specified kind is not supported.

ODPart: ODFrame RequestEmbeddedFrame(...)

Pass the desired view type and presentation for the new frame. Creates the embedded frame and initializes it as above.

ODWindowState: ODWindow CreateWindow(...)

Pass the desired view type and presentation for the window's root frame. Creates the window's root frame and initializes it as above.

Connecting Frames to Parts


ODPart: void DisplayFrameAdded(in ODFrame frame)

The part gets the view type and presentation kind for the new display from the fields in the frame. The part may correct the presentation if the specified kind is not supported.

ODPart: void InitPartFromStorage()

When the part internalizes its display frame(s), it gets the view type and presentation kind for each display from the fields in the frame. The part may correct the presentation if the specified kind is not supported. This may occur if the part was previously edited by a different editor which supported the same type of part.


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