Programming Guide: Standards

# Programming Guide

## Standards

The standards recipes are listed as follows:

- "Making Parent Method Calls"
- "ODByteArray"
- "Object Equality"
- "Parameters and Memory in OpenDoc"
- "Part Properties"

### Making Parent Method Calls

According to the SOMObject Developer Toolkit Users Guide.

  "The overriding method procedure can conveniently invoke the procedure that one or more of the parent classes uses to implement that method".
  "The SOM-generated implementation header file defines the following macros for making parent-method calls from within an overriding method: <className>_parent_<parentClassName>_<methodName> (for each parent class of the class overriding the method)".
  <className>_parents_<methodName>".

In OpenDoc, we recommend the use of the first form to make a parent-method call.

For example, if a part (called MyPart) needs to call its parent ODPart's InitPart method, it should use the following macro (defined in MyPart's implementation header file):

    MyPart_parent_ODPart_InitPart(somSelf,ev,storageUnit,partWrapper)

Note:

> There are other macros that are generated by SOM for making parent-method calls. However, those forms may be ambiguous if multiple inheritance is used. Some of these short forms exist only for backward compatibility and may not be supported in future versions of SOM.

### ODByteArray

#### What Is an ODByteArray?

An ODByteArray is defined as a sequence of octets:

    typedef struct
    {
      unsigned long _maximum;
      unsigned long _length;
      octet *_buffer;
    } _IDL_SEQUENCE_octet;
    typedef _IDL_SEQUENCE_octet ODByteArray;

where:

  _length is the number of bytes of relevant data.
  _maximum is the number of bytes in the buffer.
  _buffer is the pointer to the memory block containing the data.

Some rules about ODByteArray:

1. _length must be smaller or equal to _maximum.
2. kODNULL cannot be passed to any parameter which expects an ODByteArray pointer.
3. _buffer must be allocated by SOMMalloc if you are using a pointer to some type.

#### Why Use ODByteArray?

The original OpenDoc function accepted and returned buffer pointers and their corresponding sizes. However, the function has been updated to use ODByteArray. This renders the OpenDoc function CORBA compliant and ready for distribution.

#### Usage example

The following examples create ODByteArray on the stack. There is no restriction that ODByteArray is a stack variable. It can be allocated in the heap.

1. Setting a value on a storage unit with a pre-allocated buffer (ptr and size):

       // Allocate the byte array on the stack
       ODByteArray ba;

       // Set up the byte array
       ba._length = size;
       ba._maximum = size;
       ba._buffer = ptr;

       // Get the value
       su->SetValue(ev, &ba);

       // ba will be deallocated when the function exits.
       // ptr needs to be explicitly deallocated by client.

2. Getting a value from a storage unit:

       // Allocate the byte array on the stack. The fields are not pre-filled.
       ODByteArray ba;

       // Get the value
       ODULong length = su->GetValue(ev, desiredLength, &ba);

       // Use the buffer.
       SomeFunction(ba._buffer);

       // ba will be deallocated when the function exits.
       // ptr needs to be explicitly deallocated by client.
       SOMFree(ba._buffer);

3. Getting a return value which is an ODByteArray:

       // Byte array is allocated on the stack.
       // None of the fields in the ODByteArray needs to be set prior to use.
       ODByteArray ba = container->GetID(ev);

       // Use the buffer in the ODByteArray.
         .
         .
         .
       // Deallocate the buffer.
       // Even though the client of the method does not allocate
       // the memory, it is the client is responsibility to deallocate it.
       SOMFree(ba._buffer);

       // Byte Array is deallocated from the stack when the function exits.

### Object Equality

This section describes how object references should be compared in OpenDoc and the motivation behind it.

#### IsEqualTo

In many places in the part code, object equality is determined by comparing pointers. This will fail when OpenDoc is used in a distributed environment. That is why ODObject has the IsEqual method. The purpose of this method is to determine whether two object references belong to the same object. If so, kODTrue is returned; otherwise, kODFalse.

### Parameters and Memory in OpenDoc

This section describes the storage (or memory) responsibility of client arguments (or parameters).

### Part Properties

Part Properties are items that affect a part's behavior or presentation but are not strictly held in the part's content. Typical examples are background color and default font. These properties can be retained on a per-part or a per-frame basis.

Part frames in an OpenDoc document are maintained in a strict containment-based hierarchy, and a standard protocol is established for allowing properties to be maintained and inherited through this hierarchy. This mechanism allows properties to be changed within a section of a document without requiring specific changes to each sub-component within that section. For example, a top-level page layout component can change the page background color and have that color change propagate in to a contained text part which contains a drawing part which contains a clock such that the backgrounds all remain in sync.

Note:

> A given component can decouple from the standard property. For example, a component could decouple to maintain an independent background color. In this case, it does not respect changes to this property from its container and it explicitly prevents propagation of the property to its embeds to maintain a strict property hierarchy.

Two things are required for this mechanism to work properly:

1. A standard set of properties must be defined
2. Components must implement the standard recipes for containing and embedded parts.

#### Standard Properties

Properties within OpenDoc have two aspects: a property name and a value type. The property name is a unique ISOstring that denotes the desired intent of the property. The value type denotes the format the property is being held in. OpenDoc allows you to specify multiple value types so you could pass, for example, a background color property with RGB and HSB values and a part inspecting the property would be free to use whichever was appropriate for it.

An initial set of agreed standard properties for use within OpenDoc for OS/2 is given here. For each, the #define name is shown for both the property and primary value. The actual ISOstrings can be found in StdProps.idl in the OpenDoc toolkit:

Background color |
property | kODBackgroundColor
value | kODRGB2

Foreground color |
property | kODForegroundColor
value | kODRGB2

Default font |
property | kODFont
value&colon | kODFontNameSize (actually an ISOString with the same format as OS/2's Presparams)

Background transparency |
property | kODBackgroundTransparency
value | kODBoolean

While the first three of these properties can be thought of as properties of a container which embedded parts (frames) may or may not choose to respect, the background transparency should be thought of as a property of an embedded display frame which is controlled from the container side. Examples used in this recipe pertain directly to the first three.

#### Non-Container Part Protocol

Non-container parts should maintain only those standard properties which are relevant to their function; it makes no sense for a non-container image part to concern itself with the default text font (unless its editing capabilities include creation of text on an image). These parts should retain the current values persistently, and handle changes to the values either through direct user interaction, such as selection from a menu or drag and drop from a system color palette, or through notification of changes from its container through the part's ContainingPartPropertiesUpdated method. A sample implementation for this method for non-container parts is:

    SOM_SCOPE void SOMLINK MyPartContainingPartPropertiesUpdated
                                         (MyPart *somSelf,
                                          Environment *ev,
                                          ODFrame* frame,
                                          ODStorageUnit* propSU)
    {
      MyPartData *somThis = MyPartGetData(somSelf);
      MyPartMethodDebug("MyPart","ContainingPartPropertiesUpdated");

      RGBColor rgb;

      SOM_TRY

      // is the background color in there???
      if (ODSUExistsThenFocus( ev, propSU, kODBackgroundColor, kODRGB2) )

      {
        // read the value being passed in
        StorageUnitGetValue( propSU, ev, sizeof(rgb), &rgb);

        // it *should* be different, but check to be sure
        if (rgb != _fBGColor)

        {
          // Change our value.
          // This method should call SetChangedFromPrevious and
          // invalidate the frame
          somSelf->SetBGColor(ev, frame, rgb );
        }
      }

      SOM_CATCH_ALL
      SOM_RETURN
    }

In addition, when an embed is instantiated and its first display frame is added, it should ask its containing part for values for the standard properties using the AcquireContainingPartProperties method (of ODPart). It is conceivable that an embed not even retain the value and instead query the value whenever it was needed (such as when creating a thumbnail, or even when the Draw method was called), but calling up the hierarchy and building the set of standard properties each time should be expected to have relatively poor performance when compared to accessing instance data. An example of how to query your container's properties and extract the background color is:

    SOM_Scope void  SOMLINK MyPartDisplayFrameAdded( MyPart *somSelf,
                                         Environment *ev, ODFrame* myFrame)
    {
      MyPartData *somThis = MyPartGetData(somSelf);
      MyPartMethodDebug("MyPart","DisplayFrameAdded");

      SOM_TRY

      ... other code

      // now lets get our container's std properties
      TempODFrame containingFrame = myFrame->AcquireContainingFrame(ev);
      TempODPart  containingPart = containingFrame->AcquirePart(ev);
      TempODStorageUnit suProps = containingPart->
                                  AcquireContainingPartProperties(ev, myFrame);

      // Is the background color in there?
      if (ODSUExistsThenFocus( ev, propSU, kODBackgroundColor, kODRGB2) )

      {
        // read the value directly into our instance variable
        StorageUnitGetValue( propSU, ev, sizeof(_fBGColor), &_fBGColor);
      }

      SOM_CATCH_ALL
      SOM_RETURN
    }

An additional point concerns whether or not an embed should respect its container's standard properties. Ideally, a part will not only retain its current value for a standard property of interest, but also a flag specifying whether this value is coupled to that of its container. This allows an embed to change its background color and know not to follow in response to its container sending a background color change notification. Also ideally, a user interface would be provided whereby the user could control this coupling. For cases where no direct mechanism is to be provided, an alternative protocol is proposed: the part should start out initially coupled with its container. If its background color is changed directly to something other than that of its container, it becomes decoupled and stops responding to changes in this property from its container. Such a property can be re-coupled by changing the property in the embed to the same value that its container holds, as will be illustrated in the sample code for a container.

#### Container Protocol

Container parts have the greatest responsibility with regard to part properties. The are responsible for remembering and using the properties (as are embedded parts), propagating changes to embedded parts, and responding to queries from embedded parts. When a standard property is changed directly in a container, it is responsible for building a storage unit containing the modified property and passing the information to its embedded parts. This notification is performed by calling the part's ContainingPartPropertiesUpdated method on some or all of its embeds as appropriate. For a property like background color, the container should iterate over all of its embedded frames and make this call on the associated Part; for background transparency it is feasible to change the property for only those embeds that are selected.

When receiving a ContainingPartPropertiesUpdated notification from a container's container, the secondary container has several responsibilities:

- It should inspect the passed-in storage unit for any properties that it is interested in for its own presentation that it is actively inheriting and adopt any changed values.

- It should look for those properties that it is decoupled on and determine whether to re-couple; this will typically be a simple compare with its current value. If there are properties of interest that the sub-container wishes to remain decoupled from, it is responsible for taking them out of the storage unit using the Remove method.

- The sub-container should call ContainingPartPropertiesUpdated on its embeds to further pass along the notification.

Thus, a container using background color might have an implementation like:

    SOM_SCOPE void SOMLINK MyPartContainingPartPropertiesUpdated
                                          ( MyPart *somSelf,
                                          Environment *ev,
                                          ODFrame* frame,
                                          ODStorageUnit* propSU)

    {
      MyPartData *somThis = MyPartGetData(somSelf);
      MyPartMethodDebug("MyPart","ContainingPartPropertiesUpdated");

      SOM_TRY

      // is the background color in there???
      if (ODSUExistsThenFocus( ev, propSU, kODBackgroundColor, kODRGB2) )
      {
        RGBColor rgb;
        StorageUnitGetValue( propSU, ev, sizeof(RGBColor), &rgb);

        // are we doing background color matching?  (its the default)
        if (_fMatchBGColor)
        {
          // change our value (do not propagate here, done below)
          // I've put a parameter on this method to signify whether to propagate
          // the color change in the SetBGColor call...
          somSelf->SetBGColor(ev, frame, rgb, kODFalse);

        // we are not following this change so we will NOT propagate
        // this property
        }

        else
        {
          // If the new value matches our current value we re-couple.
          // The embeds already know our color
          // so we don't need to pass it along.
          if (rgb == _fBGColor)
          {
            _fMatchBGColor = kODTrue;
          }

          // If this is the only property in the su, abort here
          // so as to NOT propogate it
          if (propSU->CountProperties(ev) == 1)
          {
            return;
          }
         // Otherwise remove this property and leave the rest
          else
          {
            propSU->Focus(ev, kODBackgroundColor, 0, 0, 0, kODPosUndefined);
            propSU->Remove(ev);
          }
        }
      }

      // now pass any remaining notification along to embeds
      ODFrame *subframe = (ODFrame*)_fEmbeddedFrames->First();
      while (subframe)

      {
         TempODPart embeddedPart = subframe->AcquirePart(ev);
         if (embeddedPart)
         {
            embeddedPart->ContainingPartPropertiesUpdated(ev, subframe, propSU);
         }
         subframe = (ODFrame*)_fEmbeddedFrames->After(subframe);
      }

      SOM_CATCH_ALL
      SOM_RETURN
    }

A container should not call ContainingPartPropertiesUpdated on its embedded frames unless it is changing a properties value. It should not call this for a newly-created embed. It is the responsibility of the embed to query its container if it is interested in using its standard properties. To respond to such a query, the container must implement AcquireContainingPartProperties. Such a request must be passed up the hierarchy to the document root part and each frame in the hierarchy must insure that the appropriate inheritance is maintained. An example of this is shown here:

    SOM_Scope ODStorageUnit*  SOMLINK MyPartAcquireContainingPartProperties(
                                                    MyPart *somSelf,
                                                    Environment *ev,
                                                    ODFrame* embedFrame)
    {
      MyPartData *somThis = MyPartGetData(somSelf);
      MyPartMethodDebug("MyPart","AcquireContainingPartProperties");

      SOM_TRY

      ODStorageUnit* propSU = kODNULL;
      TempODFrame myFrame = embedFrame->AcquireContainingFrame(ev);

      // sanity check
      if (!_fDisplayFrames->Contains(myFrame))
      {
        return kODNULL;
      }

      // if we are not the root part, pass the request up the chain
      if (!myFrame->IsRoot(ev))
      {
        TempODFrame containingFrame = myFrame->AcquireContainingFrame(ev);
        TempODPart  containingPart = containingFrame->AcquirePart(ev);

        propSU = containingPart->AcquireContainingPartProperties(ev, myFrame);
      }

      // If we are the root part OR our container didn't give
      // us back an SU, create one
      if (!propSU)
      {
        propSU =
        somSelf->GetStorageUnit(ev)->GetDraft(ev)->CreateStorageUnit(ev);
      }

      // default to leave the existing property alone
      ODBoolean wemadeit = FALSE;

      // if the std prop isn't there, create it and we write it
      if (!ODSUExistsThenFocus( ev, propSU, kODBackgroundColor, kODRGB2) )
        ODSUForceFocus( ev, propSU, kODBackgroundColor, kODRGB2 );
        wemadeit = kODTrue;
      }

      // If we created the prop OR we are decoupled
      // from our container... write our color
      if (wemadeit || !_fMatchBGCo&lor ) {
        StorageUnitSetValue( propSU, ev, sizeof(RGBColor), &_fBGColor);
      }

      SOM_CATCH_ALL
      SOM_RETURN

      // return the filled storage unit
      return propSU;
    }

#### Additional Note for Root Containers

An additional note is that it can be beneficial for root containers to maintain values for the standard properties even if they do not use them directly. This would enable a user to make a change of any standard property at the root level and have it propagated throughout the document. For example, a PageLayout component that does not deal directly with text could still maintain the default font for drag and drop and even provide a menu item for changing the value, which could then be propagated to any embedded text-handling components. This would allow the user to change the default font for all text in a layout even though the root component does not handle text itself.

#### Notes on Background Transparency Handling

As mentioned above, background transparency should actually be thought of as a property of an embedded display frame that is controlled by its container. Because there is no AcquireEmbeddedPartCapabilities, it is not feasible for a 2-way dialog regarding transparency to occur so the following protocol is recommended.

By default containers and embeds should assume that the embed is opaque (that is, the value of kODBackgroundTransparency is kODFalse). As per the recipe for facet windows, to reduce flicker, the container can use the embed's UsedShape as a clip shape for rendering its own background; the embed has sole responsibility for rendering its area and should fill its background appropriately. If the user selects an embedded frame and changes its background rendering to transparent, the containing part should stop including the embed's UsedShape in its accumulated background clip shape. An example of processing for this is:

      // net embed shape for background clipping.  collect in WINDOW coords
      ODShape* contentClip = (ODShape*)facet->GetPartInfo(ev);
      // clear out any old clip shapes
      contentClip->Reset(ev);

      // utility shape
      TempODShape embedShape = facet->GetFrame(ev)->CreateShape(ev);

      // iterate over all embedded facets
      ODFacet* embFacet;
      ODFacetIterator* facets = facet->CreateFacetIterator(ev,
                                                           kODChildrenOnly,
                                                           kODFrontToBack);
      for (embFacet = facets->First(ev);
           facets->IsNotComplete(ev);
           embFacet = facets->Next(ev))

      {
        // get the embed's associated graphics object
        Proxy* p = somSelf->ProxyForFrame(ev, embFacet->GetFrame(ev));
        GtkEmbed* embedObj = (GtkEmbed*)_picture->Object(p->id);

        // if we got it and it is an embed that should be clipped off...
        if (embedObj && embedObj->IsEmbed() && !embedObj->IsTransparent()) {

          // get a copy of the embed's usedShape
          TempODShape tmpShape =
          embFacet->GetFrame(ev)->AcquireUsedShape(ev, kODNULL);
          embedShape->CopyFrom(ev, tmpShape);

          // convert to window coords
          xform = embFacet->AcquireWindowFrameTransform(ev, kODNULL);
          embedShape->Transform(ev, xform);
          ODReleaseObject(ev, xform);

          // add it to aggregate
          contentClip->Union(ev, embedShape);
        }
      }
      delete facets;

Whenever an area including a transparent embed is invalidated, the container will draw in the background first, before the embed is drawn. In addition to changing its clipping, the container should notify the embed that this property has changed. If the embed implements background transparency, it would then stop drawing its background in its Draw method, and only render its foreground (text or other content). This is implemented in the Draw method as follows:

    SOM_Scope void  SOMLINK MyPartDraw( MyPart *somSelf,  Environment *ev,
                                        ODFacet* facet, ODShape* invalidShape)
    {
        MyPartData *somThis = MyPartGetData(somSelf);
        MyPartMethodDebug("MyPart","Draw");

       SOM_TRY

        HPS hpsDraw;
        ODRect rect;
        TempODShape tempShape = facet->GetFrame(ev)->AcquireFrameShape(ev, kODNULL);
        tempShape->GetBoundingBox(ev, &rect);
        RECTL frameRect;
        rect.AsRECTL(frameRect);

        // Set up the drawing facet
        // Set up drawing environment
        CFocus f(ev, facet, invalidShape, &hpsDraw);

        // if we are not transparent, fill our background
        if (!_fTransparent)

        {
          GpiSetColor(hpsDraw, _fContentStruct.fColor);
          POINTL orig = {0, 0};
          GpiMove(hpsDraw, &orig);
          POINTL pt&l = {frameRect.xRight, frameRect.yTop};
          GpiBox(hpsDraw, DRO_FILL, &ptl, 0, 0);
        }

        // draw our foreground stuff
        GpiSetLineType(hpsDraw, LINETYPE_DASHDOUBLEDOT);
        GpiSetColor(hpsDraw, CLR_BLACK);

        for (int y = 0; y &< frameRect.yTop; y += YGRID) {
           ptl.y = y;
           ptl.x = 0;
           GpiMove(hpsDraw, &ptl);
           ptl.x = frameRect.xRight;
           ptl.y += frameRect.xRight;
           GpiLine(hpsDraw, &ptl);
        }

        for (int x = XGRID; x &< frameRect.xRight; x += XGRID) {
           ptl.x = x;
           ptl.y = 0;
           GpiMove(hpsDraw, &ptl);
           ptl.x += frameRect.yTop;
           ptl.y = frameRect.yTop;
           GpiLine(hpsDraw, &ptl);
        }

       SOM_CATCH_ALL
       SOM_ENDTRY
    }

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