Building a SOM OpenDoc Part

In a popular Microsoft Systems Journal article, "Building Component Software with Visual C++ and the OLE Custom Control Developer's Kit," Eric Lang described how to create an OLE Custom Control using Visual C++, MFC, and the CDK. Here, our authors do the same thing using OpenDoc for OS/2.


March 01, 1995
URL:http://www.drdobbs.com/cpp/building-a-som-opendoc-part/184409518

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 2


Copyright © 1995, Dr. Dobb's Journal

Figure 3


Copyright © 1995, Dr. Dobb's Journal

Figure 3


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 2


Copyright © 1995, Dr. Dobb's Journal

Figure 3


Copyright © 1995, Dr. Dobb's Journal

MAR95: Building a SOM OpenDoc Part

Building a SOM OpenDoc Part

No fancy tools or wizards, just the bare API

Robert Orfali and Dan Harkey

Bob and Dan are the authors of Client/Server Programming with OS/2 (VNR, 1993) and the Essential Client/Server Survival Guide (VNR, 1994), coauthored with Jeri Edwards. Bob and Dan have developed client/server systems for the last eight years and are affiliated with IBM. They can be reached at [email protected].


Component-software technology holds great promise. One instance of this technology is OpenDoc, which allows developers to build independently created "parts" that can collaborate on the desktop, across networks, and across operating-system platforms. At this writing, OpenDoc technology is in alpha, and will be released later this year for Windows, Macintosh, OS/2, and, eventually, UNIX. OpenDoc was originally designed at Apple, but is now being promulgated by Component Integration Laboratories (CI Labs), a consortium consisting principally of Apple, IBM, and WordPerfect. For a more complete background on OpenDoc technology, see the article, "OpenDoc," by Jeff Rush (Dr. Dobb's Special Report on Interoperable Objects, Winter 1994/95).

OpenDoc allows the user to open up "stationery" for a document container, populate it with parts from a parts bin, lay out the parts in some visually pleasing arrangement, create data links, and save the document. The document can serve as the integration point for data from local or remote sources. The parts can be linked to external data sources anywhere on the enterprise through CORBA-compliant Object Request Brokers (ORBs). A scripting facility allows parts to collaborate in customized arrangements.

How much effort does it take to create one of these components? It depends on which component technology you choose. For all practical purposes, the component technology choices today have narrowed to OLE versus OpenDoc. However, if OpenDoc lives up to its promise, you should be able to create an OpenDoc part that's also an OLE container/server. WordPerfect has demonstrated this technology on Windows since May 1994. In this article, we'll examine what it takes to create an OpenDoc part, particularly as compared to an OLE Custom Control (also known as OCX).

The Great Smiley Shoot-out

In his article, "Building Component Software with Visual C++ and the OLE Custom Control Developer's Kit" (Microsoft Systems Journal, September 1994), Eric Lang described how to create a Smiley-face OLE Custom Control using Microsoft Visual C++, Microsoft Foundation Classes (MFC), and a beta version of the Custom Control Developer's Kit (CDK). In this article, we'll implement the same Smiley part using an alpha version of OpenDoc for OS/2. Unfortunately, we can't use wizards and frameworks (these tools will come later). Consequently, our Smiley is built with raw OpenDoc, as compared to Eric's OCX, which was implemented with deluxe tools.

What does the Smiley part do? Figure 1(a) is the part running in a test container. Clicking on Smiley (a right-mouse click) turns it into the sad face in Figure 1(b). Click again and the smile returns. This is in-place editing; as you can see, OpenDoc lets you interact with any visible part directly. The mouse clicks demonstrate how OpenDoc parts handle events. Smiley is a persistent OpenDoc part: It knows how to save itself when you close the document. When you open the document again, everything looks the same (the smiling/sad state and document position are preserved). To sum up, Smiley is a simple OpenDoc part that knows how to visually embed itself in a container, draw itself, process events in place, and save and restore its contents from a Bento document file.

What SOM Brings to the Party

With its most recent release, the OpenDoc developer's kit supports IBM System Object Model (SOM), so we implemented Smiley as a SOM object. As a result, its methods can be invoked using any language that supports SOM bindings (in our case, IBM's C++). SOM allows objects written in different languages to communicate in the same address space, across address spaces, and across dissimilar operating systems over networks, through the services of a CORBA-compliant ORB. (For more on SOM, see "Interoperable Objects," by Mark Betz, DDJ, October 1994.)

SOM lets you package OpenDoc parts in binary class libraries and ship them as DLLs. In addition, SOM supports implementation inheritance, which means that you can subclass OpenDoc parts and either reuse or override their method implementations delivered in the DLL binaries. Also, by providing an external language for defining interfaces, the OpenDoc part handler binaries can be distributed independently of the client and, even more importantly, can be modified or replaced without having to recompile the client code that interacts with the part.

Using SOM, the typical OpenDoc part editor will inherit most of its behavior from the ODPart base class--OS/2's OpenDoc provides a class called SimplePart derived from ODPart to make it easier for you. In any case, you must override the methods that need to be customized to provide your part's behavior. At a minimum, your part must be able to allocate storage for its persistent data, initialize its data from a persistent store, draw its contents inside an area provided by its container, handle events, and externalize its data to the persistent store when the document is closed.

To IDL or Not to IDL

In CORBA, an Interface Definition Language (IDL) is the means by which objects tell their clients what interfaces are available and how to invoke them. Using IDL, you can define the types of objects, the methods they export, and their parameters. The IDL also lets you specify the parent classes. Like C++, CORBA supports multiple inheritance. Note that CORBA IDL only specifies a class's interface, not its implementation. You can think of IDL as a contract that binds the providers of a component service to their clients.

Many C++ programmers hate to deal with IDL because this introduces an extra step in class construction. On the other hand, programmers experienced in client/server RPCs know that IDL is the cleanest way to specify program services that live on different machines or in separate address spaces. Classes in C++ are single-address-space constructs, so a mechanism like IDL is needed to extend these across process boundaries. The good news for C++ programmers is that you don't have to deal with IDL if you don't want to. Simply write your classes in C++ and let a direct-to-SOM C++ compiler (like MetaWare's C++ or IBM's CSet++) generate the IDL for you by capturing the C++ class information from your headers. In our example, we'll use IDL explicitly, even though our part is written in C++, to give you a feel for programming with SOM and CORBA.

The steps in creating the Smiley part are:

  1. Define the interface for a SmileyPart class by creating the IDL source file smiley.idl (see Listing One).
  2. Run the SOM precompiler on the IDL file. It produces an implementation template--smiley.cpp--of the SmileyPart class (shaded lines in Listing Two).
  3. Add the body of the code (in C++) to the template of SmileyPart (non-shaded lines in Listing Two).
  4. Compile the class and create the part DLL--smiley.dll.

The Smiley IDL

In smiley.idl, we #include the file SimplPrt.idl, which is the interface definition for the parent class. Our Smiley part is derived from class SimplePart. Next, we define private data types for xSmiley by enclosing them with #ifdef __PRIVATE__ and #endif directives. The interface statement specifies the name of the part, its parent classes, and the name of the part's methods and their parameters. Smiley defines three new methods, all of which are private.

CORBA IDL is a purely declarative language; it is used solely to describe the interface to an object. SOM extends IDL with constructs that let you specify helpful implementation information. This information is bracketed within #ifdef __SOMIDL__ and #endif directives. The first line in the implementation section is the prefix that SOM will append to the method names (and other macros) it generates for that class. This is followed by the version number of the class. The releaseorder allows you to add new functions to a class without having to recompile the client programs that use it. It's a SOM feature that helps you maintain backward binary compatibility. All you need to do is list every method name introduced by the class, and not change the order. If you need to introduce new methods in the future, simply add them to the end of the list. However, if you decide to remove a method, you must still leave its name on the list.

Smiley's functionality results from overriding six methods of its parent class SimplePart, specified in the override section of the IDL. Notice that you don't need to specify the parameters for these methods. The SOM precompiler obtains the interface definitions for these methods from the parent's IDL. The last section in the IDL contains declarations for private instance data that is only of interest to the class members.

The SOM Precompiler's Output

The next step in creating the Smiley part is to run the IDL through the SOM precompiler, which reads the IDL, creating a .cpp skeleton implementation (see Listing Two). Notice that the precompiler introduces some include files and creates stubs for all the methods you declared in the IDL. It also generates stubs (with parameter declarations) of all the parent methods you are going to override. The precompiler automatically appends the prefix SmileyPart to all your method names--it's trying to be helpful in maintaining unique names within your class's implementation. Your clients don't have to know about the prefix; to the outside world, the method calls are polymorphic.

The terms SOM_Scope and SOMLINK appear in the prototype of all the stub methods. Ignore them. They're used by SOM to represent internal information. Notice that the first parameter in each method is always somSelf, which is a pointer to the target object--in this case, SmileyPart. Again, the precompiler is being helpful because CORBA requires that the first parameter in each method invocation be the target object. The precompiler also introduces a second strange parameter: ev. This is a pointer to the CORBA environment structure and is used to return error information.

The first statement in the method initializes a local variable, somThis, to point to a structure representing the instance variables introduced by Smiley. The second statement, SmileyPartMethodDebug, is a macro used for tracing. You can turn it off by setting the SOM_TraceLevel flag to a nonzero value. If you need to invoke another method that's introduced in your class, use the notation somSelf--><methodName>. To access an instance variable created by this object, use somThis--><variableName>, or simply precede the variable name with an underscore.

Programming OpenDoc Style

The OpenDoc run time is a collection of objects which belong to a set of about 50 classes. You interact with these objects by invoking methods on them whenever your part needs a service. Most of the time OpenDoc will call your part's class (or object) when it needs something from you. The trick is to figure out which of these methods will be called during the lifetime of your part, and then write code for them. You provide the part behavior by overriding the methods you're interested in.

Smiley will be called when it is first created or initialized in a container, when it needs to draw its contents, when it receives some event, and finally when the document is closed and Smiley is asked to save its contents in a Bento file. We can do all of this by overriding six methods from the SimplePart base class (see Listing Two). The complete listing is available electronically; see "Availability," page 3.

Initialize Smiley

OpenDoc asks Smiley to initialize itself by invoking either InitPart, if Smiley is being embedded for the first time in a container (for instance, a document), or InitPartFromStorage, if an existing document is opened with Smiley already embedded in it. Both methods invoke CommonInit--a private method of our class--to perform some common initialization functions.

CommonInit first records the StorageUnit object that was passed to it. A StorageUnit is the basic unit of persistent storage for a part. It contains a list of properties, identified by a unique name within the storage unit. Each property can have one or more values, which can be raw byte streams or multiple data types. The StorageUnit class is an abstraction on top of the Bento persistent-storage system (OpenDoc is layered over and does not expose the Bento APIs). Figure 2 shows the OpenDoc storage model. A Bento document can have multiple drafts or versions. Each draft contains multiple storage units, which in turn contain properties and their values. These entities are analogous to those in a conventional file system: A storage unit is like a directory, properties are like filenames, values like byte streams. Values can have pointers to other storage units. So, in essence, each OpenDoc part gets to manage its own file system within a file. Storage units can also reside in memory, on the clipboard, or as links--these are all just different instances of the same class.

In the Smiley code, the CommonInit method then records its "session" object, which is an instance of an OpenDoc class that encapsulates access to OpenDoc globals and maintains context for a root document. After that, it goes after a "focus set." A focus set is a mechanism that OpenDoc provides to allow parts to negotiate for resources atomically--you either get all the resources you're asking for or nothing. This all-or-nothing proposition helps avoid deadlocks and makes parts thread safe.

Smiley is interested in grabbing the focus for mouse selection, menu, and keyboard events. To accomplish this, CommonInit creates an instance of class ODFocusSet, an OpenDoc class that will store the desired foci. CommonInit then requests a session object to return some unique tokens for a particular OpenDoc type; this is done because the elements of the set are tokenized strings. Then, CommonInit adds the specified focus to the focus set via the add method.

After invoking CommonInit, the InitPart method creates a new property, KODPropSmile, in the StorageUnit received from OpenDoc. Notice the naming convention OpenDoc uses for properties (KODPropSmile is a string constant "SmileyPart:Property:Smile"). We assign to this property a value type of Boolean (KODBoolean). This value will be used to store the persistent state of our Smiley face.

The InitPartFromStorage method does not have to create a property because the property and its value are already there in the existing document. The method simply needs to find the Smile property and read its value. To do that, we must first invoke a Focus method on the StorageUnit object to get to the specified property and value, then invoke GetValue to read the contents of the value (that is, the stored Smiley state). At a later time, when the document contents need to be saved, the state of the Smiley face is written to persistent storage. This happens when OpenDoc invokes the Externalize method (see Listing Two), which calls Focus on the storageUnit object to target the KODPropSmile property, and then writes its value to storage using SetValue.

Drawing Smiley

When a part needs to render its contents, OpenDoc invokes the part's Draw method and passes it a "facet" and a "shape." Facets are OpenDoc objects that represent the visible area of a part's frame at run time. A visible part can have one or more facets. Facets are constructed on-the-fly for the visible frames when a document is opened. The draw method is called for each facet object in a part (the facet object is passed as an input parameter of the draw method). "Canvas" objects are platform-dependent presentation spaces or device contexts. The canvas is where the facets of a part render themselves. The shape is a description of space on a canvas. Shapes can be scaled, rotated, and transformed without having to know what's inside of them. A part gets a default amount of real estate when created but can later negotiate with its container for more space. The container always wins in space negotiations. OpenDoc provides the mechanisms for allowing parts to negotiate for space and to seamlessly coexist within a common visual container. The actual rendering is done by platform-specific API calls (in our case, OS/2 Presentation Manager calls).

The OpenDoc rendering model is shown in Figure 3. Every document must contain at least one part--the root part--which initially owns all the document's visual real estate in a single "frame." Frame objects are used for space-layout negotiations between containing parts and embedded parts. An OpenDoc part is not required to embed other parts, but most useful OpenDoc parts will (Smiley does not). In Figure 3, root part A has two embedded parts: B and C. Each part has its own frame. When a document is saved, the set of frames it contains are made persistent. These frames contain the geometry information that will be used to recreate the visual look of the document.

The Draw method implementation:

Handling Events

The OpenDoc run time includes an event dispatcher which routes user interface and semantic events to the correct part handler. The part handler helps out by negotiating for resources using the FocusSet.

The semantic service also provides powerful APIs for event dispatch resolution. The OpenDoc run time invokes a Handle-Event method on our part when it wants us to do something. As you can see from the code, we can treat these events like normal PM messages. The HandleEvent method processes two events: W_BUTTON1DOWN and WM_BUTTON2DOWN. The first of these activates the part. Here, we acquire the FocusSet resources that were specified when the part was first initialized. The second event is used to toggle the Smiley face, accomplished by toggling the _smile instance variable and issuing an invalidate method on the frame object.

Conclusion

Smiley is admittedly a minimalist OpenDoc part, but you can add more interesting behaviors, such as drag-and-drop, clipboard support, linking, embedding, irregular-shape support, multiple levels of undo/redo, reference counting, extensions, menus, property editing, and scripting.

Figure 1: (a) The Smiley OpenDoc part; (b) the Smiley OpenDoc part with a sad face.

Figure 2 OpenDoc document storage structure. Figure 3 The OpenDoc rendering model.

Listing One


/*-------- Smiley.IDL for Smiley OpenDoc part  (Excerpted)  -------------*/

#ifndef _SMILEYPRT_ 
#define _SMILEYPRT_ 
#ifndef _SIMPLPRT_ 
#include <SimplPrt.idl> 
#endif 
#ifdef __PRIVATE__                  // Implementation Types
   typedef long Rect;
#endif
interface SmileyPart : SimplePart
{
#ifdef __PRIVATE__
        void CommonInit(in ODStorageUnit storageUnit);
        void DrawSmileyFace(in HPS hpsDraw, in Rect frameRect);
        void SetOrigin(in ODFacet facet);
#endif
#ifdef __SOMIDL__
        implementation
        {
                functionprefix = SmileyPart;
                majorversion = 1;
                minorversion = 0;
#ifdef __PRIVATE__
        releaseorder:
                CommonInit,DrawSmileyFace,SetOrigin;
#endif
        override:
           InitPart,  InitPartFromStorage,  Draw,
           Externalize,  HandleEvent,  RemoveDisplayFrame;
#ifdef __PRIVATE__           //Instance Variables
             ODSession                  session;
             ODStorageUnit              storageUnit;
             ODFocusSet                 focusSet;
             ODTypeToken                selectionFocus;
             ODTypeToken                menuFocus;
             ODTypeToken                keyFocus;
             ODBoolean                  smile;
#endif
        };
#endif
};
#endif  // _SMILEYPRT_



Listing Two


/*---- Smiley example implemented in OpenDoc for OS/2. (Excerpted code.) --- */
#include "os2.h"
#include "smiley.xih"
const ODPropertyName kODPropSmile = "SmileyPart:Property:Smile";
//---- InitPart (OD Method): Called when part first created ----
SOM_Scope void SOMLINK SmileyPartInitPart(SmileyPart *somSelf,
                                Environment *ev,ODStorageUnit* storageUnit)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartInitPart");
    if (somSelf->IsInitialized(ev))
       return;
    SmileyPart_parent_SimplePart_InitPart(somSelf, ev, storageUnit);
    somSelf->CommonInit(ev, storageUnit);
    _smile = TRUE;
    storageUnit->AddProperty(ev, kODPropSmile)->AddValue(ev, kODBoolean);
}
//---- InitPartFromStorage (OD Method): Called during part internalization ----
SOM_Scope void SOMLINK SmileyPartInitPartFromStorage(SmileyPart *somSelf,
{                                Environment *ev,ODStorageUnit* storageUnit)
    SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartInitPartFromStorage");
    if (somSelf->IsInitialized(ev))
      return;
    somSelf->InitPersistentObjectFromStorage(ev, storageUnit);
    somSelf->CommonInit(ev, storageUnit);
    storageUnit->Focus(ev, kODPropSmile,kODPosUndefined,
                       kODBoolean,0,kODPosUndefined);
    storageUnit->GetValue(ev, sizeof(_smile), &_smile);
}
//-CommonInit (Private Method) Called by InitPart/InitPartFromStorage
SOM_Scope void SOMLINK SmileyPartCommonInit(SmileyPart *somSelf,
                                Environment *ev,ODStorageUnit* storageUnit)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartCommonInit");
    _session = storageUnit->GetSession(ev); // Record session and
    _storageUnit = storageUnit;            //  storage unit for later
    // create and initialize the focus set
    _focusSet = new ODFocusSet();
    _focusSet->InitFocusSet(ev);
    _selectionFocus = _session->Tokenize(ev, kODSelectionFocus);
    _menuFocus      = _session->Tokenize(ev, kODMenuFocus);
    _keyFocus       = _session->Tokenize(ev, kODKeyFocus);
    _focusSet->Add(ev, _selectionFocus);
    _focusSet->Add(ev, _menuFocus);
    _focusSet->Add(ev, _keyFocus);
}
//---- Draw (OD Method): Called to Render Part -----
SOM_Scope void SOMLINK SmileyPartDraw(SmileyPart *somSelf,
                    Environment *ev, ODFacet* facet, ODShape* invalidShape)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartDraw");
    // get presentation space
    HPS hpsDraw = facet->GetCanvas(ev)->GetPlatformCanvas(ev);
    GpiSavePS(hpsDraw);
    GpiResetPS(hpsDraw, GRES_ATTRS);
    // get frame and determine part rectangle
    ODFrame* displayFrame = facet->GetFrame(ev);
    HRGN frameRgn = displayFrame->GetFrameShape(ev)->GetRegion(ev);
    Rect frameRect;
    GpiQueryRegionBox(hpsDraw, frameRgn, &frameRect);
    // set up clipping
    HRGN saveClip;
    ODShape* clipShape = new ODShape;
    clipShape->CopyFrom(ev, facet->GetAggregateClipShape(ev));
    clipShape->Transform(ev, facet->GetContentTransform(ev));
    HRGN clip = clipShape->GetRegion(ev);
    GpiSetClipRegion(hpsDraw, clip, &saveClip);
    // set part origin
    somSelf->SetOrigin(ev, facet);
    // Draw the Smiley Face
    somSelf->DrawSmileyFace(ev, hpsDraw, frameRect);
    // Cleanup and return
    GpiRestorePS(hpsDraw, -1);
    GpiSetClipRegion(hpsDraw, 0, &saveClip);
    facet->GetCanvas(ev)->ReleasePlatformCanvas(ev);
    delete clipShape;
}
//--- DrawSmileyFace (Private Method): Called by Draw to render SmileyFace ---
SOM_Scope void SOMLINK SmileyPartDrawSmileyFace(SmileyPart *somSelf,
                                Environment *ev,HPS hpsDraw,Rect frameRect)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartDrawSmileyFace");
    // determine part center and smiley radius
    POINTL ptlCenter = {frameRect.xRight/2, frameRect.yTop/2};
    LONG   radius    = min(frameRect.xRight/2, frameRect.yTop/2)*.97;
    // paint white background for smiley face part
    GpiSetColor ( hpsDraw, CLR_WHITE );
    POINTL ptlBox = {frameRect.xRight, frameRect.yTop};
    GpiBox(hpsDraw, DRO_FILL, &ptlBox, 0, 0);
    // Draw Smiley Face Background
    GpiSetColor ( hpsDraw, CLR_YELLOW );
    GpiSetCurrentPosition(hpsDraw , &ptlCenter);
    GpiFullArc(hpsDraw , DRO_OUTLINEFILL , MAKEFIXED (radius , 0 ) ) ;
    // Initialize Line Characteristics
    GpiSetColor ( hpsDraw, CLR_BLACK);
    GpiSetPattern(hpsDraw, PATSYM_SOLID);
    GpiSetLineWidthGeom(hpsDraw, radius*.07);
    GpiSetLineEnd(hpsDraw, LINEEND_ROUND);
    // Draw Smiley Face Outline
    GpiBeginPath(hpsDraw, 1);
    GpiFullArc(hpsDraw , DRO_OUTLINE, MAKEFIXED (radius , 0 ) ) ;
    if (_smile)      // Draw a Smiling Mouth
    { POINTL ptlSmileY" = {ptlCenter.x-(radius*.6), ptlCenter.y-(radius*.3),
                           ptlCenter.x,             ptlCenter.y-(radius*.7),
                           ptlCenter.x+(radius*.6), ptlCenter.y-(radius*.3)};
      GpiSetCurrentPosition(hpsDraw , &ptlSmile[0]);
      GpiPointArc(hpsDraw, &ptlSmile[1]);
    }
    else   // Draw a Sad Mouth
    { POINTL ptlSad[]   = {ptlCenter.x-(radius*.6), ptlCenter.y-(radius*.6),
                           ptlCenter.x,             ptlCenter.y-(radius*.5),
                           ptlCenter.x+(radius*.6), ptlCenter.y-(radius*.6)};
      GpiSetCurrentPosition(hpsDraw , &ptlSad[0]);
      GpiPointArc(hpsDraw, &ptlSad[1]);
    }
    GpiEndPath(hpsDraw);
    GpiStrokePath(hpsDraw, 1, 0);
    // Draw Eyes/Nose
    POINTL ptlLEye = {ptlCenter.x-radius*.3, ptlCenter.y+radius*.3};
    GpiSetCurrentPosition(hpsDraw , &ptlLEye);
    GpiFullArc(hpsDraw , DRO_OUTLINEFILL , MAKEFIXED (radius*.07, 0 ) ) ;
    POINTL ptlREye = {ptlCenter.x+radius*.3, ptlCenter.y+radius*.3};
    GpiSetCurrentPosition(hpsDraw , &ptlREye);
    GpiFullArc(hpsDraw , DRO_OUTLINEFILL , MAKEFIXED (radius*.07, 0 ) ) ;
    POINTL ptlNose = {ptlCenter.x, ptlCenter.y-radius*.2};
    GpiSetCurrentPosition(hpsDraw , &ptlNose);
    GpiFullArc(hpsDraw , DRO_OUTLINEFILL , MAKEFIXED (radius*.07, 0 ) ) ;
}
//---- SetOrigin (Private Method): Called by Draw to position Smiley Face ----
SOM_Scope void SOMLINK SmileyPartSetOrigin(SmileyPart *somSelf,
                                         Environment *ev,ODFacet* facet)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartSetOrigin");
    ODTransform* localToGlobal = facet->GetContentTransform(ev);
    HPS hps = facet->GetCanvas(ev)->GetPlatformCanvas(ev);
    MATRIXLF mtx;
    facet->GetContentTransform(ev)->GetMATRIXLF(ev, &mtx);
    GpiSetModelTransformMatrix(hps, 9, &mtx, TRANSFORM_REPLACE);
    facet->GetCanvas(ev)->ReleasePlatformCanvas(ev);
}
//--- HandleEvent (OD Method): Called when the part receives a UI event ----
SOM_Scope ODBoolean  SOMLINK SmileyPartHandleEvent(SmileyPart *somSelf,
       Environment *ev,ODEventData* event,ODFrame* frame,ODFacet* facet)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartHandleEvent");
    ODDraft*draft;
    switch (event->msg)
    {  case WM_BUTTON1DOWN:      // Activate the Part
           if (_session->GetArbitrator(ev)->
                                 RequestFocusSet(ev, _focusSet,frame))
              somSelf->FocusAcquired(ev, _selectionFocus, frame);
           return kODTrue;
      case WM_BUTTON2DOWN:             // Toggle Smile/Sad Face
           _smile = !_smile;
           draft = _storageUnit->GetDraft(ev);
           draft->SetChangedFromPrev(ev);
           frame->Invalidate(ev, kODNULL);
           return kODTrue;
      default: return kODFalse;
    }
}
SOM_Scope void SOMLINK SmileyPartExternalize(SmileyPart *somSelf,
                                             Environment *ev)
{   SmileyPartData *somThis = SmileyPartGetData(somSelf);
    SmileyPartMethodDebug("SmileyPart","SmileyPartExternalize");
    _storageUnit->Focus(ev, kODPropSmile,kODPosUndefined,kODBoolean,0,
                            kODPosUndefined);
    _storageUnit->SetValue(ev, sizeof(_smile), &_smile);
}


Copyright © 1995, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.