Programming Guide: User Interface

# Programming Guide

## User Interface

The user interface recipes are listed as follows:

- "Activation"
- "Basic Event Handling"
- "Dialogs"
- "Menus"
- "Pop-Up Menus"
- "Opening and Closing Windows"
- "Opening a Part into a Window"
- "Window Events"
- "Properties Notebook"
- "Stationery Support for the OS/2 Platform"
- "Help"
- "Undo"
- "Using Resources in OpenDoc"
- "Facet Windows"
- "Using Embedded PM Controls within a Facet (OS/2)"
- "Implementing a Dispatch Module"
- "Implementing a Focus Module"

Several of the code examples in these recipes are taken from the container part sample included in the Toolkit.

The following example is for the HandleEvent:

    SOM_Scope ODBoolean  SOMLINK ContainerPartHandleEvent(ContainerPart *somSelf,
                                                           Environment *ev,
                                                          ODEventData* event,
                                                          ODFrame* frame,
                                                          ODFacet* facet,
                                                          ODEventInfo* eventInfo)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartHandleEvent");

        ODBoolean handled = kODFalse;

        SOM_TRY
        switch (event->msg)
        {
           ...
           case WM_BUTTON1DOWN:
              {
              ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                               ODIntToFixed(SHORT2FROMMP(event->mp1)));

              handled = somSelf->HandleButton1Down(ev,
                                                   facet,
                                                   &windowPt,
                                                   event);
              break;
        }
           ...

           case WM_MOUSEMOVE:
              {
              ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                               ODIntToFixed(SHORT2FROMMP(event->mp1)));
              handled = somSelf->HandleMouseMove(ev,
                                                 facet,
                                                 frame,
                                                 &windowPt,
                                                 event);
              }
              break;
           ...

           case WM_CHAR:
              if (!((SHORT1FROMMP(event->mp1) & KC_KEYUP)))
              {
                handled = somSelf->HandleKeyDown(ev, frame, event);
              }
              break;

           case WM_ACTIVATE:
              handled = kODTrue; // actually ignored by dispatcher
              if (SHORT1FROMMP(event->mp1) != 0)
                 somSelf->ActivatingWindow(ev, frame);
              else
                 somSelf->DeActivatingWindow(ev, frame);
              break;
           ...

           case WM_COMMAND:
              if (SHORT1FROMMP(event->mp2) & CMDSRC_MENU ||
                  SHORT1FROMMP(event->mp2) & CMDSRC_ACCELERATOR)
              {
                handled = somSelf->HandleMenuEvent(ev, frame, event);
              }
              break;
           ...
           default:
              return kODFalse;
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
        return handled;
    }

### Activation

#### Focuses and the Arbitrator

OpenDoc has a flexible and platform-neutral model of part activation, in which part editors request ownership of named focuses from an arbitrator. The session has an arbitrator, which can be obtained by using ODSession::GetArbitrator(). The focuses are defined as ISO strings (in Foci.idl and its bindings Foci.xh and Foci.h), and the standard set includes:

    const ODFocusType kODSelectionFocus = "Selection";
    const ODFocusType kODMenuFocus = "Menu";
    const ODFocusType kODMenuFocus = "Key";
    const ODFocusType kODScrollingFocus = "Scrolling";
    const ODFocusType kODModalFocus = "Modal";
    const ODFocusType kODStatusLine = "StatusLine";

These strings can be tokenized using ODSession::Tokenize(). The methods of ODArbitrator and ODFocusSet use the tokenized form of the focus names.

Focuses are owned by frames. The active border is drawn around the frame with the selection focus, and Shift-clicks, Alt-clicks and Ctrl-clicks are sent to this frame. Menu and keystroke events are sent to the frames with the menu focus and keystroke focus, respectively. Page Up and Page Down keystrokes are sent to the frame with the scrolling focus. See the "Dialogs" recipe for information about the modal focus.

There is no assumption that the same frame owns the selection, key and menu focuses, even though this will often be the case. Furthermore, OpenDoc does not require that the selection focus be in an active window, even though this is usually what's desired, unless the active window is a modeless dialog. See "Requesting a Focus or Focus Set" and "Window Activation and Frame Activation".

#### Key Methods

From ODSession:

    ODArbitrator GetArbitrator();
    ODTypeToken Tokenize(in ODType type);

From ODArbitrator:

    ODBoolean RequestFocusSet(in ODFocusSet focusSet,
                              in ODFrame requestingFrame);
    ODBoolean RequestFocus(in ODTypeToken focus,
                              in ODFrame requestingFrame);
    void RelinquishFocusSet(in ODFocusSet focusSet,
                              in ODFrame relinquishingFrame);
    void RelinquishFocus(in ODTypeToken focus,
                              in ODFrame relinquishingFrame);
    void TransferFocus(in ODTypeToken focus,
                              in ODFrame transferringFrame,
                              in ODFrame newOwner);
    void TransferFocusSet(in ODFocusSet focusSet,
                              in ODFrame transferringFrame,
                              in ODFrame newOwner);
    ODFrame AcquireFocusOwner(in ODTypeToken focus);
    ODFocusSet CreateFocusSet();

From ODPart:

    ODBoolean BeginRelinquishFocus(in ODTypeToken focus,
                            in ODFrame ownerFrame,
                            in ODFrame proposedFrame);
    void CommitRelinquishFocus(in ODTypeToken focus,
                            in ODFrame ownerFrame,
                            in ODFrame proposedFrame);
    void AbortRelinquishFocus(in ODTypeToken focus,
                            in ODFrame ownerFrame,
                            in ODFrame proposedFrame);
    void FocusAcquired(in ODTypeToken focus,
                            in ODFrame ownerFrame);
    void FocusLost(in ODTypeToken focus,
                            in ODFrame ownerFrame);

#### Requesting a Focus or Focus Set

A part can request (for one of its frames) ownership of a single focus, or a set of focuses (an ODFocusSet) using the ODArbitrator methods RequestFocus and RequestFocusSet(). RequestFocusSet() performs a "two-phase commit". It first asks each current owner if it is willing to relinquish the focus (by calling Part::BeginRelinquishFocus()). If any focus owner is unwilling, the arbitrator aborts the request by calling each part's AbortRelinquishFocus() method, and RequestFocusSet() returns kODFalse. If all focus owners are cooperative, the arbitrator calls each one's CommitRelinquishFocus() method, and RequestFocusSet() returns kODTrue.

Note:

> OpenDoc does not activate parts. Parts activate themselves by requesting focuses for frames.

#### Transferring a Focus or Focus Set

A focus or set of focuses can also be forcibly transferred from one frame to another, without negotiation. ODArbitrator::TransferFocus() and TransferFocusSet() are used for this purpose. When a focus is transferred, the FocusAcquired() method of the new owner's part is called, and the FocusLost method of the old owner's part is called, unless it is the one doing the transferring (the transferring frame is passed to TransferFocus() and TransferFocusSet()).

Parts should generally request focuses rather than transfer them. However, the recipe for handling modal dialogs makes use of TransferFocus. Since modal dialogs might be nested, the recipe consists of saving the current owner of the modal focus, requesting the modal focus, and then transferring it back to the saved owner when the dialog is dismissed.

Note:

> Actually, if the nested modal dialogs do not themselves contain parts, the above is not strictly necessary, and the part displaying the modal dialog can simply relinquish the modal focus.

While parts generally do not transfer focuses, they should be prepared to have certain focuses forcibly removed. In this case the FocusLost() method will be called.

Note:

> FocusAcquired() and FocusLost() are not called during the request process. The requesting part knows it has acquired the focuses because RequestFocus or RequestFocusSet returns kODTrue. The requesting part may be tempted to call its own FocusAcquired() method when RequestFocus() or RequestFocusSet() returns kODTrue, but it is better to use separate private methods which can be called from both FocusAcquired() and when a request returns kODTrue. This way there is a clear separation between the external and internal calls.

Similarly, a part may be tempted to call its own FocusLost() method from its CommitRelinquishFocus() method, but it would be better to keep the external and internal interfaces separate.

##### Keyboard Navigation

A containing part (such as a forms package) could provide keyboard navigation of embedded parts by transferring the keyboard and/or selection focus from one embedded part to another. The containing part would also have to set the doesPropagateEvents flag of embedded frames, so that it could handle Tab or arrow keys not consumed by the embedded parts. This recipe needs further research, but the containing part should probably request the focus for its own frame (so that an active embedded part can refuse to relinquish it if appropriate), and then transfer the focus to the next embedded frame.

##### The Scrolling Focus

Page Down and Page Up keys are sent to the part with the scrolling focus. If an embedded part with no scroll bars is active, the user should still be able to page through the document. Typically, containers will have scroll bars, and embedded parts will not, although other configurations are possible. The following rules should be followed:

- An embedded part with the scrolling focus should always relinquish it when asked to.
- A root part in an active window should not relinquish the scrolling focus.

#### Basic Frame Activation

This section explains the following:

- Focuses and focus sets
- Requesting Focuses
- Relinquishing Focuses

##### Focuses and focus sets

Most frames will require a focus set containing the selection focus, keystroke focus and menu focus when the user clicks in the frame. But a frame with scroll bars might also need the scrolling focus. And a frame for a modeless dialog may require the menu focus, but not the selection focus, so that the active border remains around the frame which opened the dialog, even though it is in an inactive window.

It is best to preserve focus state on a per-frame basis, and this is one reason why those frame objects attached to the part info of ODFrame objects are so handy.

It is a good idea for each frame object to pre-allocate an ODFocusSet when it is initialized. The ODArbitrator method CreateFocusSet should be used to create the set.

    SOM_Scope void SOMLINK ContainerPartCommonInitContainerPart(
                           ContainerPart *somSelf, Environment *ev)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartCommonInitContainerPart");

        SOM_TRY
        ...
        _fSelectionFocus = _fSession->Tokenize(ev, kODSelectionFocus);
        _fMenuFocus = _fSession->Tokenize(ev, kODMenuFocus);
        _fKeyFocus = _fSession->Tokenize(ev, kODKeyFocus);
        _fMouseFocus = _fSession->Tokenize(ev, kODMouseFocus);

        _fClipboardFocus = _fSession->Tokenize(ev, kODClipboardFocus);
        if (_fSession->HasExtension(ev, kODExtStatusLine))
        {
          _fStatusLn =(ODStatusLineExtension*) _fSession->
                      AcquireExtension(ev,kODExtStatusLine);
          _fStatusFocus = _fSession->Tokenize(ev, kODStatusLineFocus);
        }
        _fFocusSet = _fSession->GetArbitrator(ev)->CreateFocusSet(ev);
        _fFocusSet->Add(ev, _fSelectionFocus);
        _fFocusSet->Add(ev, _fMenuFocus);
        _fFocusSet->Add(ev, _fKeyFocus);
        ...
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

##### Requesting Focuses

Frames are activated in a variety of circumstances - on clicks, when a window is first opened, when a window is activated.

Mouse Activation: When a frame is already selected, a mouse event in that frame will go to the containing frame so that it can allow the user to drag the selected frame. The embedded frame will receive a mouse event if the container decides a drag is not happening. Therefore frames should activate themselves if necessary on mouse events.

When the part editor receives an event, and decides it must activate the frame, it requests the set of focuses needed by the frame:

    SOM_Scope void  SOMLINK ContainerPartActivateFrame(
                            ContainerPart *somSelf,
                            Environment *ev,
                            ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartActivateFrame");
        SOM_TRY
        if (frame != kODNULL)
           {
           PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);
           if (!(pInfo->fIsActive))
           {
             ODBoolean succeeded = kODFalse;
             succeeded = _fSession->GetArbitrator(ev)->
                                  RequestFocusSet(ev, _fFocusSet,frame);
             if (succeeded)
             {
                somSelf->FocusAcquired(ev, _fSelectionFocus, frame);
                somSelf->FocusAcquired(ev, _fMenuFocus, frame);
                somSelf->FocusAcquired(ev, _fKeyFocus, frame);
             }
           }
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

##### Relinquishing Focuses

A part will usually relinquish a focus when another part asks for it via BeginRelinquishFocus(). In this case, the part simply returns kODTrue in BeginRelinquishFocus(), and removes menus, palettes, blinking cursors and so forth in CommitRelinquishFocus().

Most parts will willingly relinquish the common focuses when asked, with the exception of the modal focus. The code below is somewhat simplified. The part may in fact wish to relinquish the modal focus to another of its own frames in cases where a nested modal dialog is being displayed from within the frame that owns the modal focus.

If a part does anything more than return kODTrue or kODFalse in BeginRelinquishFocus(), it will have to undo the effects in AbortRelinquishFocus().

A part must also relinquish focuses when a frame is going away. In the methods delegated to by Part::DisplayFrameClosed() and Part::DisplayFrameRemoved(), or when the last facet is removed:

      fArbitrator->RelinquishFocusSet(ev, fFocusSet, frame);

A part may also wish to ensure that it keeps various focuses together, so it may wish to relinquish a set if it is notified via FocusLost() that it lost a particular focus.

#### Window Activation and Frame Activation

Not all platforms have a notion of active windows, but for those that do, following these recipes results in sensible behavior in several cases:

- A frame with the selection focus can record this fact when its window is deactivated, and preserve a background selection. When the window is reactivated (for example, by clicking on its title bar), the formerly active frame regains the selection focus. When the user clicks in the content area of an inactive window, the part which receives the event will activate the window, unless the click is in a background selection, in which case the part might allow the selection to be dragged without activating the window. In this case, the destination window will be activated when the drop is completed.

- When a new document is opened from a stationery document, the menus of the root part will appear. The same will be true when the user opens an existing document. It is not necessarily recommended, but an embedded part can choose to record its active state when saved and attempt to restore it when the document is opened. And the part at the root can choose to allow this, or always ensure that it is active itself.

The basic recipe is fairly simple. The frame handler object is attached to the part info in Part::DisplayFrameAdded() and and Part::DisplayFrameConnected(). The frame object attached to the part info makes use of two Boolean properties fNeedsFoci and fHasFoci. The fNeedsFoci flag is set to true if the frame is at the root. The flag is also set to true or false when a window is deactivated, depending on whether or not the frame is active. The fNeedsFoci flag is checked when the part receives a window activate event, and if it is true, the frame is activated.

##### Creating a New Window

The first time a window, and hence a root frame, is created, the part needs to ensure that the root frame is active when the window becomes active. The frame handler object will be created and attached in Part::DisplayFrameAdded():

    SOM_Scope void  SOMLINK ContainerPartDisplayFrameAdded(
                            ContainerPart *somSelf,
                            Environment *ev,
                            ODFrame* frame)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartDisplayFrameAdded");
        SOM_TRY
        TempODPart tempPart = frame->AcquirePart(ev);

        if (tempPart == _fPartWrapper)    // frame belongs to me
        {
          ODxOrderedCollectionIterator  displayFramesIter(_fDisplayFrames);
          ODFrame* displayFrame = (ODFrame*) displayFramesIter.First();

          if (displayFramesIter.IsNotComplete() == kODFalse)
            {
            ODxOrderedCollectionIterator  contentsIter(_fContents);
            Proxy*            proxy;
            for (proxy = (Proxy*) contentsIter.First();
                contentsIter.IsNotComplete();
                proxy = (Proxy*) contentsIter.Next())
            {
              proxy->frame->SetContainingFrame(ev, frame);
            }
          }

          PartInfoRec* pInfo = new PartInfoRec;
          pInfo->fGridOn = kODFalse;      //Default is grid off
          pInfo->partwindowID = (ODID)kODNULL;
          if (frame->IsRoot(ev))
            pInfo->fNeedsActivating = kODTrue;

          frame->SetPartInfo(ev, (ODInfoType) pInfo);
          _fDisplayFrames->AddLast(frame);
          frame->Acquire(ev);
          frame->SetDroppable(ev, kODTrue);

          _fNeedToExternalize = kODTrue;

          // if frame view is set do not change it.  If not, make it viewasframe
          if (frame->GetViewType(ev) == kODNullTypeToken)
            frame->SetViewType(ev, _fSession->Tokenize(ev, kODViewAsFrame));
          if (frame->GetPresentation(ev) == kODNullTypeToken)
            frame->SetPresentation(ev, _fSession->Tokenize(ev, kODPresDefault));
        }
        else
        {
           THROW(kODErrInvalidFrame);
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

##### Saving and Restoring Windows

When a window is saved in a document, and the document is reopened, the part's DisplayFrameConnected() method is called, and the frame handler object will be attached there.

##### When a window is activated or deactivated

When a window is first created or is brought to the front, each part will receive an activate event for each facet it has in that window. If the user clicks in the title bar of an inactive window, the window will be brought to the front and each part in the previously active window will get a deactivate event for each facet it has in that window. If a frame has focuses, it sets "fNeedsFoci" to true, so that the next time that window is activated, the part can reclaim the focuses.

##### When a different frame in the same window is activated

When the user clicks in a different frame in the same window, one part editor will request focuses and install menus and other interface elements. As described earlier, this kind of activation is done in several places (such as mouse events and window activate) , so it is a good idea to record that a frame has the selection focus, so that the method which activates a frame can exit quickly if the frame is already active. After RequestFocusSet() succeeds (such as when handling mouse down and mouse up), fNeedsFoci and fHasFoci are updated.

    SOM_Scope void  SOMLINK ContainerPartActivateFrame(ContainerPart *somSelf,
                                                        Environment *ev,
                                                       ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartActivateFrame");

        SOM_TRY
        if (frame != kODNULL)
           {
           PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);
           if (!(pInfo->fIsActive))
           {
             ODBoolean succeeded = kODFalse;
             succeeded = _fSession->GetArbitrator(ev)->
                         RequestFocusSet(ev, _fFocusSet,frame);

             if (succeeded)
             {
                somSelf->FocusAcquired(ev, _fSelectionFocus, frame);
                somSelf->FocusAcquired(ev, _fMenuFocus, frame);
                somSelf->FocusAcquired(ev, _fKeyFocus, frame);
             }
           }
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

    SOM_Scope void  SOMLINK ContainerPartDeActivateFrame(
                            ContainerPart *somSelf,
                            Environment *ev,
                            ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart","ContainerPartDeActivateFrame");
        SOM_TRY

        if (frame != kODNULL)
        {
           _fSession->GetArbitrator(ev)->RelinquishFocusSet(ev, _fFocusSet,
                                                            frame);
           somSelf->FocusLost(ev, _fSelectionFocus, frame);
           somSelf->FocusLost(ev, _fMenuFocus, frame);
           somSelf->FocusLost(ev, _fKeyFocus, frame);
           somSelf->FocusLost(ev, _fStatusFocus,frame);
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

    SOM_Scope void  SOMLINK ContainerPartActivatingWindow(
                            ContainerPart *somSelf,
                            Environment *ev,
                            ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartActivatingWindow");

        SOM_TRY
        PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);
        if (pInfo->fNeedsActivating)
        {
           somSelf->ActivateFrame(ev, frame);
           //somSelf->ShowPalette(ev);
           pInfo->fNeedsActivating = kODFalse;
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

    SOM_Scope void  SOMLINK ContainerPartDeActivatingWindow(
                            ContainerPart *somSelf,
                            Environment *ev,
                            ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartDeActivatingWindow");

        SOM_TRY
        PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);
             TempODFrame tempFrame = _fSession->GetArbitrator(ev)->
                                     AcquireFocusOwner(ev, _fSelectionFocus);

        if (frame == tempFrame)
        {
           pInfo->fNeedsActivating = kODTrue;
           if (_fMouseMode != kNormal)
             somSelf->ResetMouseMode(ev);
        }
        else
           pInfo->fNeedsActivating = kODFalse;
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

##### Saving An Embedded Selection

If a part does wish to save its selection persistently even when it is embedded, it can do the following.

Note:

> The root part must cooperate by not activating itself if a part already has the selection focus, as shown below.

    SOM_Scope void  SOMLINK ContainerPartWritePartInfo(
                            ContainerPart *somSelf,
                            Environment *ev,
                            ODInfoType partInfo,
                            ODStorageUnitView* storageUnitView)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartWritePartInfo");

        SOM_TRY
        if (partInfo)
        {
          ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
          ODPropertyName propName = storageUnitView->GetProperty(ev);
          ODSUForceFocus(ev, su, propName, kKindTestContainer);
          ODBoolean needsGrid = ((PartInfoRec*)partInfo)->fGridOn;
          StorageUnitSetValue(su, ev, sizeof(ODBoolean),
                        (ODValue)&needsGrid);
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
    }

    SOM_Scope ODInfoType SOMLINK ContainerPartReadPartInfo(
                                 ContainerPart *somSelf,
                                 Environment *ev,
                                 ODFrame* frame,
                                 ODStorageUnitView* storageUnitView)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartReadPartInfo");
        PartInfoRec* partInfo = kODNULL;
        SOM_TRY
        ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
        ODPropertyName propName = storageUnitView->GetProperty(ev);
        partInfo = new PartInfoRec;
        ODBoolean needsGrid = kODTrue;

        if (ODSUExistsThenFocus(ev, su, propName, kKindTestContainer))
        {
          StorageUnitGetValue(su, ev, sizeof(ODBoolean),
                        (ODValue)&(needsGrid));
        }
        else
        {
          WARN("Persistent partInfo is missing from this frame. Making one up.");
        }

        partInfo->fGridOn = needsGrid;
        SOM_CATCH_ALL
           ODDeleteObject(partInfo);
        SOM_ENDTRY
        return (ODInfoType) partInfo;
    }

When root part handles a window activate event:

      if (fNeedsFoci)
      {
        ODFrame* selectionFrame
          = fSession->GetArbitrator(ev)->AcquireFocusOwner(ev, fSelectionFocus);
        if (!selectionFrame)
        {
          // activate
        }
        ODReleaseObject(ev, selectionFrame);
    }

This works because activate events are sent bottom-up to the facets in the window. The part externalizing the flag must mark the draft as changed when it acquires the selection focus, so that Save is enabled.

### Basic Event Handling

This document describes the basics of event distribution to parts. It describes the events a part will need to handle, but does not describe in great detail what should be done with each event. This is heavily content-dependent, and will often be apparent to developers. This section also does not deal with semantic events.

See also:

- "Window Events"
- "Mouse Events"
- "Menus"
- "Opening and Closing Windows"
- "Dialogs"
- "Drag and Drop"

The OpenDoc shell and container applications contain an event loop, and call ODDispatcher::Dispatch() to deliver events to parts. This method takes an event record and returns a Boolean value. If the returned value is kODFalse, the shell may handle the event, otherwise a part, or the dispatcher itself has handled the event.

The dispatcher contains a table of dispatch modules. The dispatcher locates a dispatch module for a given event, and the dispatch module calls one of the following ODPart methods, shown here in IDL:

      void Draw(in ODFacet facet,
                in ODShape invalidShape);
      ODBoolean HandleEvent(inout ODEventData event,
                in ODFrame frame,
                in ODFacet facet,
                inout ODEventInfo eventInfo);

#### Event Types

Part editors should handle the following event types:

- kODEvtKeyDown
    Windows - WM_KEYDOWN message
    OS/2 - modified WM_CHAR message
    AIX - X11 KeyPress event

- kODEvtKeyUp
    Windows - WM_KEYUP message
    OS/2 - modified WM_CHAR message
    AIX - X11 KeyRelease event

- kODEvtActivate
    Windows - WM_ACTIVATE message
    OS/2 - WM_ACTIVATE message
    AIX - modified X11 FocusIn or FocusOut event

- kODEvtMenu
    Windows - WM_COMMAND message
    OS/2 - WM_COMMAND message
    AIX - synthetic event sent by menu widget callbacks

- kODEvtMouseDown
    Windows - WM_LBUTTONDOWN message
    OS/2 - WM_BUTTON1DOWN message
    AIX - X11 ButtonPress event

- kODEvtMouseUp
    Windows - WM_LBUTTONUP message
    OS/2 - WM_BUTTON1UP message
    AIX - X11 ButtonRelease event

Note:

> Part Editors will need to disable certain portions of their event handling when a draft is read-only. See the recipe "Part Init and Externalizing" for information on draft permissions.

#### Standard Events

This section describes the following events:

- Mouse events
- Keyboard events
- Update events
- Activate events
- Disk events
- Menu events

##### Mouse Events

In addition to the standard mouse events, part editors which support embedding must deal with mouse events in embedded frames which are selected, bundled or viewed as icons or thumbnails. Part editors must also handle special background mouse events to support Drag and Drop. See the "Mouse Events" and "Drag and Drop" recipes.

Mouse move events: Your part's HandleEvent method is passed a WM_MOUSEMOVE event on the OS/2 and Windows platforms (MotionNotify on AIX) along with a frame and a facet. he where value is included in the EventInfo structure nd contains a point in local (frame) coordinates.

Note:

> The following recipe is for OS/2.

    SOM_Scope ODBoolean  SOMLINK ContainerPartHandleEvent(
                                 ContainerPart *somSelf,
                                 Environment *ev,
                                 ODEventData* event,
                                 ODFrame* frame,
                                 ODFacet* facet,
                                 ODEventInfo* eventInfo)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartHandleEvent");
        ODBoolean handled = kODFalse;

        SOM_TRY
        switch (event->msg)
        {
          case WM_BUTTON1MOTIONSTART:
             {
                ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                                 ODIntToFixed(SHORT2FROMMP(event->mp1)));
                handled = somSelf->HandleButton1MotionStart(ev,
                                                            facet,
                                                            &windowPt,
                                                            event);
                break;
             }

           case WM_BUTTON1DOWN:
             {
               ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                                ODIntToFixed(SHORT2FROMMP(event->mp1)));
               handled = somSelf->HandleButton1Down(ev,
                                                    facet,
                                                    &windowPt,
                                                    event);
               break;
             }

           case kODEvtMouseDown:
             {
                ODPoint winPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                              ODIntToFixed(SHORT2FROMMP(event->mp1)));

                handled = somSelf->HandleButton1Click(ev, facet, &winPt, event);
                break;
             }

           case kODEvtMouseDownEmbedded:
             {
                ODPoint embPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                              ODIntToFixed(SHORT2FROMMP(event->mp1)));

                handled = somSelf->HandleButton1ClickInEmbeddedFrame(ev,
                                       facet, eventInfo->embeddedFacet,
                                       &embPt, event);
                break;
             }

           case kODEvtMouseDownBorder:
             {
               ODPoint brdPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                             ODIntToFixed(SHORT2FROMMP(event->mp1)));
               handled = somSelf->HandleButton1ClickInBorder(ev, facet,
                                     eventInfo->embeddedFacet,
                                     &brdPt, event);
               break;
            }

           case WM_BEGINDRAG:
             {
               ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                                ODIntToFixed(SHORT2FROMMP(event->mp1)));
               return somSelf->HandleMouseDrag(ev, facet,
                                          eventInfo->embeddedFacet,
                                          &windowPt, event);
              }

           case WM_CONTEXTMENU:
              handled = kODTrue;
              if (_fIgnoreContextMenu && !SHORT2FROMMP(event->mp2))
                 break;                // ignore if mouse event
              else
              {
                 PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);
                 if (!(pInfo->fIsActive))
                       somSelf->ActivateFrame(ev, frame);
                 _fPopup->Display(ev);
              }
              break;

           case WM_MOUSEMOVE:
              {
              ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                               ODIntToFixed(SHORT2FROMMP(event->mp1)));
              handled = somSelf->HandleMouseMove(ev,
                                                 facet,
                                                 frame,
                                                 &windowPt,
                                                 event);
              }
              break;
           ...
           default:
              return kODFalse;
        }

        SOM_CATCH_ALL
        SOM_ENDTRY
        return handled;
    }
    SOM_Scope ODBoolean  SOMLINK ContainerPartHandleMouseMove(
                                 ContainerPart *somSelf,
                                 Environment *ev,
                                 ODFacet* facet,
                                 ODFrame* frame,
                                 ODPoint* where,
                                 ODEventData* event)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartHandleMouseMove");
        ODBoolean handled = kODFalse;
        SOM_TRY
        RECTL rclBox;
        PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);

        switch (_fMouseMode)
        {
                                                 // 128682 - faa - starts
           case kNormal:
              if (_fPasteOn)
              {
                 if (somSelf->AllowPaste(ev, frame))
                 {
                    WinSetPointer(HWND_DESKTOP,
                                  WinQuerySysPointer(
                                  HWND_DESKTOP,
                                  SPTR_MOVE, FALSE));
                    handled = kODTrue;
                 }

                 else
                 {
                    // Been emptied in another docshell
                    handled   = kODFalse;
                 }
              }
              break;
              // 128682 - faa - ends

           case kRotateSelectCenter:
           case kScaleSelectRefPoint:
              WinSetPointer(HWND_DESKTOP,
                            WinQuerySysPointer(HWND_DESKTOP,
                            SPTR_MOVE,
                            FALSE));
              handled = kODTrue;
              break;

           case kTracking:
              if (_fTrackingFacet)
              {
                if (!SHORT1FROMMP(event->mp2))     // Mouse still captured?
                {
                  TempODTransform xform =
                   _fTrackingFacet->AcquireWindowFrameTransform(
                                    ev, _fTrackingFacet->GetCanvas(ev));
                  ODPoint localPoint = *where;   // [137664]
                  xform->InvertPoint(ev, &localPoint); // window to frame coords
                  somSelf->UpdateTrackRect(ev,
                                           &localPoint,
                                           _fTrackingFacet,
                                           kUpdateModeContinue);
                  _ptEnd = localPoint;
                }

                else                            // mouse is no longer captured
                {
                  somSelf->ResetMouseMode(ev);
                }
              }
              else if (facet->GetWindow(ev)->IsActive(ev))
              {
                 if (_fSelection->Count() >= 1)
                 {
                    ODPoint mouse = *where;
                    TempODTransform xform =
                            facet->AcquireWindowContentTransform(
                                   ev, facet->GetCanvas(ev));
                    ODRect bbox;
                    xform->InvertPoint(ev, &mouse);
                 }

              }
              break;
        }
        SOM_CATCH_ALL
        SOM_ENDTRY
        return handled;
    }

The following recipe is for Windows.

    SOM_Scope ODBoolean  SOMLINK ContainerPartHandleEvent(
                                 ContainerPart *somSelf,
                                 Environment *ev,
                                 ODEventData* event,
                                 ODFrame* frame,
                                 ODFacet* facet,
                                 ODEventInfo* eventInfo)

    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartHandleEvent");
        ODBoolean handled = kODFalse;

        SOM_TRY
        switch (event->message)
        {

           case WM_LBUTTONDOWN:
              {
              ODPoint windowPt(ODIntToFixed(SHORT1FROMMP(event->mp1)),
                               ODIntToFixed(SHORT2FROMMP(event->mp1)));
              handled = somSelf->HandleButton1Down(ev,
                                                   facet,
                                                   &windowPt,
                                                   event);
              break;
              }

           case kODEvtMouseDown:
            {
              ODPoint winPt(ODIntToFixed(LOWORD(event->lParam)),
                            ODIntToFixed(HIWORD(event->lParam)));
              handled = somSelf->HandleButton1Click(ev, facet, winPt, event);
              break;
            }

           case kODEvtMouseDownEmbedded:
            {
              ODPoint winPt(ODIntToFixed(LOWORD(event->lParam)),
                            ODIntToFixed(HIWORD(event->lParam)));
              handled = somSelf->HandleButton1ClickInEmbeddedFrame(ev, facet,
                                 eventInfo->embeddedFacet, &embPt, event);
              break;
            }

           case kODEvtMouseDownBorder:
            {
              ODPoint brdPt(ODIntToFixed(LOWORD(event->lParam)),
                            ODIntToFixed(HIWORD(event->lParam)));
              handled = somSelf->HandleButton1ClickInBorder(ev, facet,
                                 eventInfo->embeddedFacet, &brdPt, event);
              break;
            }

           case WM_MOUSEMOVE:
              {
              ODPoint windowPt(ODIntToFixed(LOWORD(event->lParam)),
                               ODIntToFixed(HIWORD(event->lParam)));
              handled = somSelf->HandleMouseMove(ev,
                                                 facet,
                                                 frame,
                                                 &windowPt,
                                                 event);
              }
              break;
           ...
           default:
              return kODFalse;
        }

        SOM_CATCH_ALL
        SOM_ENDTRY
        return handled;
    }

##### Keyboard Events

Keyboard events go to the frame with the keyboard focus, with the exception of the Page Up, Page Down, Home and End keys, which will go to the frame with the scrolling focus, if there is one.

##### Update Events

Update events are not passed to Part::HandleEvent(). Rather ODPart::Draw() will be called once for each facet in the window. See the "Imaging and Layout Recipes" recipes for examples of drawing.

##### Activate Events

Activate events are also delivered to each facet in a window, using Part::HandleEvent(). The facets are traversed bottom up (that is, the root facet comes last).

See "Window Activation and Frame Activation" for example code.

##### Disk Events

Currently, disk events are not distributed to parts.

##### Menu Events

On the OS/2 and Windows platforms, OpenDoc passes WM_COMMAND messages directly to the part's HandleEvent method. Parts should handle it as if they were within a PM or Windows application's message procedure. On the AIX platform, OpenDoc passes the part's HandleEvent method a synthetic event of type kODEvtMenu, in an OpenDoc MenuEventdata structure. This structure is defined in IDL file ODTypesM.idl. It has a /BEGIN-ITALICS/ menu /END-ITALICS/ field, which contains the ID of the selected menu; and a /BEGIN-ITALICS/ item /END-ITALICS/ field, which contains the ID of the selected menu item.

See the "Menus" recipe for more details.

#### Special Considerations

Special considerations need to be looked at for:

- Modal focus
- Mouse focus
- Propagating events

##### Modal Focus

Some events are constrained by the modal focus. A mouse click outside the frame with the modal focus will be sent to the modal focus frame, but a click in an embedded frame within the modal focus frame will still go to the embedded frame. When the user clicks outside the modal frame, a facet of kODNULL is passed to HandleEvent(). The part editor should check for this value, and beep or dismiss the dialog as desired.

See the "Dialogs" recipe for more details about the modal focus.

##### Mouse Focus

For modal situations, like a polygon drawing tool, the mouse focus captures mouse down, mouse up and mouse moved events. See the "Mouse Events" recipe.

##### Propagating Events

This feature of OpenDoc is not likely to be used by most parts. If a containing part sets the "DoesPropagateEvents" property of an embedded frame, the containing part will receive events not handled by the embedded frame, via the HandleEvent() method.

### Dialogs

#### Modal dialogs

In the case of modal dialogs, it is not necessary to create an ODWindow.

As a consequence of OpenDoc's implementation of floating windows, part editors must call the window state's DeactivateFrontWindows method before displaying a modal dialog, and the ActivateFrontWindows after dismissing it. These methods operate on all floating windows and the foremost non-floating window.

In this case, the part could relinquish the modal focus instead of transferring it back to its previous owner, but by saving and restoring the owner of the modal focus, this recipe should work for nested modal dialogs, if a dialog was itself built from parts.

A modal dialog box might be implemented by the following code segment.

Note:

> This recipe is for OS/2.

      ModalDialogBox(Event)
      {
        DosQueryModuleHandle(THISDLL, hmod);
        rc = WinDlgBox(HWND_DESKTOP,event->hwnd,DialogProc,hmod,ID_DIALOG);
      }

      DialogProc(hwnd,msg,mp1,mp2)
      {
        Switch
        {
          Case WM_COMMAND:
               WinDismissDlg(hwnd,TRUE);
        }
      }

#### Movable Modal Dialogs

A movable modal dialog allows the user to switch out to other processes. To implement such a dialog, an ODWindow must be created.

#### Modeless Dialogs

This section explains the following:

- How to show a dialog
- How to close a dialog
- When to activate and deactivate a frame

##### Showing the Dialog

Suppose the user chooses a menu item to show a modeless dialog. The part editor should do something like the following method of the Clock part, which shows the Alarm Settings dialog:

    void ClockGlobals::OpenAlarmSettingsDialog(
         Environment* ev, ODFrame* sourceFrame)
    {
     ODPart* sourcePart = sourceFrame->AcquirePart(ev);
     ODWindow* window = fSession->GetWindowState(ev)->AcquireWindow(
                                  ev, fAlarmSettingsWindow);
     if (window)
     {
      window->Show(ev);
      window->Select(ev);
      ODReleaseObject(ev, window);
     }

     else
     {
      this->CreateAlarmSettingsDialog(ev, sourceFrame);
      this->OpenAlarmSettingsDialog(ev, sourceFrame); // Ooh. Sneaky
     }
     ODReleaseObject(ev, sourcePart);
    }
    void ClockGlobals::CreateAlarmSettingsDialog(
    Environment* ev, ODFrame* sourceFrame)
    {
     ODSLong savedRefNum;
     ODWindow* settingsWindow = kODNULL;
     ODPart* sourcePart = sourceFrame->AcquirePart(ev);

     DosQueryModuleHandle(savedRefNum);
     fAlarmSettingsDialog = WinLoadDlg(kClock_AlarmSettingsDialogID,
                                       (Ptr)SOMMalloc(sizeof(DialogRecord)),
                                       (WindowPtr)-1L);
     EndUsingLibraryResources(savedRefNum);

     settingsWindow = fSession->GetWindowState(ev)->
      RegisterWindow(ev, fAlarmSettingsDialog,
      kODNonPersistentFrameObject,
       kODFalse,    // Keeps draft open
       kODFalse,    // Is resizable
       kODFalse,    // Is floating
       kODFalse,    // do not save
       kODFalse,    // should dispose
       sourcePart,
       fFrameView,  // View Type
       fAlarmSettingsPresentation,  // Presentation
       sourceFrame);

     if (fAlarmSettingsDialog && settingsWindow)
     {
      settingsWindow->Open(ev);
      fAlarmSettingsWindow = settingsWindow->GetID(ev);
      ODReleaseObject(ev, settingsWindow);
     }
     ODReleaseObject(ev, sourcePart);
    }

Dismissing the Dialog is done when your part receives the WM_COMMAND message during the Override of HandleEvent(). Your part should call WinDismissDlg() to remove the dialog from the screen. Your part should call WinDestroyWindow() when Dialog Window is no longer needed.

#### Closing a Dialog

When the user clicks in the close box of the modeless dialog, the part may wish to hide the window rather than close it, so that it is not destroyed and can be redisplayed rapidly.

In Part::HandleEvent:

     case kODEvtWindow:
     {
      switch (event->message)
      {
           case inGoAway:
       wasHandled = _fClockPart->CloseWindow(ev, frame);
       break;

#### When Activating or Deactivating a Frame

Parts should hide their palettes and dialogs when deactivated, and also when the process is deactivated . In the Test Clock example, the dialogs are shared between part instances.

When the selection focus is lost or gained, the global object managing the dialogs is notified:

    void ClockFrame::LostSelectionFocus(Environment* ev,
         ODFrame* proposedFrame)
    {
     ODBoolean samePart =
      (proposedFrame && (proposedFrame->
                                 GetPresentation(ev) ==
                                 fClockPart->fTimePresentation));

     if (!samePart)
      gClockGlobals->SuspendWindows(ev, kODFalse);
    }
    void ClockFrame::AcquiredSelectionFocus(Environment* ev)
    {
     gClockGlobals->AcquiringFocus(ev, fFrame);
     gClockGlobals->ResumeWindows(ev, kODFalse);
    }

The global object delegates to methods of the ClockFrame class stored in the part info:

    void ClockGlobals::SuspendWindows(Environment* ev,
         ODBoolean processChange)
    {
     OrderedCollectionIterator iter(fDialogFrames);

     for (ODFrame* aFrame = (ODFrame*)iter.First();
       iter.IsNotComplete();
       aFrame = (ODFrame*)iter.Next())
     {
      ClockFrame* clockFrame = (ClockFrame*) aFrame->GetPartInfo(ev);
      if (processChange)
       clockFrame->SuspendProcess(ev);
      else
       clockFrame->SuspendFocus(ev);
     }
    }
    void ClockGlobals::ResumeWindows(Environment* ev,
    ODBoolean processChange)
    {
     OrderedCollectionIterator iter(fDialogFrames);

     for (ODFrame* aFrame = (ODFrame*)iter.First();
        iter.IsNotComplete();
        aFrame = (ODFrame*)iter.Next())
     {
      ClockFrame* clockFrame = (ClockFrame*) aFrame->GetPartInfo(ev);
      if (processChange)
       clockFrame->ResumeProcess(ev);
      else
       clockFrame->ResumeFocus(ev);
     }
    }

When a suspend or resume event is received, the frame objects are notified directly:

     case kSuspendResumeMessage:
      {
       ClockFrame* clockFrame = (ClockFrame*) frame->GetPartInfo(ev);
       const short kResumeMask = 0x01; // High byte suspend/resume event
       ODBoolean goingToBackground = (event->message & kResumeMask) == 0;

       if (goingToBackground)
       {
        clockFrame->SuspendProcess(ev);
       }
       else
       {
        clockFrame->ResumeProcess(ev);
       }
      }

The frame objects hide and show their windows as needed:

    void ClockFrame::SuspendFocus(Environment* ev)
    {
     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
     {
      TempODWindow window = fFrame->AcquireWindow(ev);
      if (window->IsShown(ev))
      {
       window->Hide(ev);
       fShowWindowOnFocus = kODTrue;
      }
      else
      {
       fShowWindowOnFocus = kODFalse;
      }
     }
    }

    void ClockFrame::SuspendProcess(Environment* ev)
    {
     fInBackground = kODTrue;
     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
     {
      TempODWindow window = fFrame->AcquireWindow(ev);
      if (window->IsShown(ev))
      {
       fShowWindowOnResume = kODTrue;
       window->Hide(ev);
      }
      else
      {
       fShowWindowOnResume = kODFalse;
      }
     }
    }

    void ClockFrame::ResumeFocus(Environment* ev)
    {
     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
     {
      TempODWindow window = fFrame->AcquireWindow(ev);
      if (fShowWindowOnFocus && !fInBackground)
      {
       fShowWindowOnFocus = kODFalse;
       window->Show(ev);
      }
     }
    }

    void ClockFrame::ResumeProcess(Environment* ev)
    {
     fInBackground = kODFalse;
     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
     {
      TempODWindow window = fFrame->AcquireWindow(ev);
      if (fShowWindowOnResume)
      {
       // It may be hidden by user before next Suspend
       fShowWindowOnResume = kODFalse;
       fShowWindowOnFocus = kODFalse;
       window->Show(ev);
      }
     }
    }

### Menus

When a part is initialized (in ODPart::InitPart and ODPart::InitPartFromStorage is should call ODWindowState::CopyBaseMenuBar to obtain its own menu bar object. It can then add its own menus and/or menu items to the menu bar. In the below example ContainerPart::CommonInit is called from both InitPart and InitPartFromStorage.

#### Getting the Menu Bar

    SOM_Scope void SOMLINK
    ContainerPartCommonInitContainerPart(ContainerPart *somSelf,
                                         Environment *ev)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartCommonInitContainerPart");

        ......(other initialization code).......

        // Since the object is the part's copy,
        // we can add and subtract menus and
        // items without affecting other running parts.
         _fMenuBar = _fSession->GetWindowState(ev)->CopyBaseMenuBar(ev);
        if (_fMenuBar)
        {
           somSelf->InstallMenus(ev);
        }
    }

#### Adding Part Menus and Menu Items to the Base Menu Bar

##### OS/2 Example

    SOM_Scope void SOMLINK ContainerPartInstallMenus(ContainerPart *somSelf,
                                                       Environment *ev)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart",
                               "ContainerPartInstallMenus");

      HMODULE hmod = NULLHANDLE;
      HWND hwndMenu = NULLHANDLE;
      HWND hwndMoveSubmenu = NULLHANDLE;
      ODPlatformMenuItem mi;
      char string[256];

      //load up our background color menu
      hwndMenu = WinLoadMenu(HWND_OBJECT, _hmod, RESID_BACKGROUNDMENU);

      _fMenuBar->AddMenuLast(ev, RESID_BACKGROUNDMENU, hwndMenu, somSelf);
      _fMenuBar->SetMenuText(ev, RESID_BACKGROUNDMENU, "Background");

      //add a submenu above EDIT->MOVE
       hwndMoveSubmenu = WinLoadMenu(HWND_OBJECT,_hmod,RESID_ARRANGE);
       _fMenuBar->AddSubmenuBefore(ev, IDMS_EDIT, EDIT_ARRANGE,
                                       hwndMoveSubmenu, EDIT_MOVE);
       _fMenuBar->SetMenuText(ev, EDIT_ARRANGE, "Arrange");
        EDIT_MOVE, hwndMoveSubmenu);

      //add a menu item to the EDIT menu
        mi.id = IDMA_TOGGLE_GRID;
        mi.afStyle = MIS_TEXT;
        _fMenuBar->AddMenuItemLast(ev,IDMS_EDIT, mi.id, &mi);
        _fMenuBar->SetMenuItemText(ev,
                                   IDMS_EDIT,
                                   IDMA_TOGGLE_GRID,
                                   "Grid On");

    }

##### Windows Example

    SOM_Scope void SOMLINK ContainerPartInstallMenus(ContainerPart *somSelf,
                                                       Environment *ev)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart",
                               "ContainerPartInstallMenus");

      HMODULE hmod = NULLHANDLE;
      HWND hwndMenu = NULLHANDLE;
      HWND hwndMoveSubmenu = NULLHANDLE;
      ODPlatformMenuItem mi;
      char string[256];

      //load up our background color menu
      hwndMenu = LoadMenu(_hmod, MAKEINTRESOURCE(RESID_BACKGROUNDMENU));
      /* Note: This menu has three submenus. Pass the three IDs. */
      ODMenuIDInfo menuIDs[3];

      menuIDs[0].id    = IDMS_SELECTED;           /*1st submenu id*/
      menuIDs[0].hMenu = GetSubMenu(hwndMenu, 1); /*and pos in menu*/

      menuIDs[1].id    = SEL_OPENAS;              /*2nd submenu id*/
      menuIDs[1].hMenu = GetSubMenu(hwndMenu, 1); /*and pos in menu*/

      menuIDs[2].id    = SEL_SHOWAS;              /*3rd submenu id*/
      menuIDs[2].hMenu = GetSubMenu(hwndMenu, 1); /*and pos in menu*/

      _fMenuBar->AddMenuLastEx(ev, RESID_BACKGROUNDMENU, hwndMenu,
                               somSelf, 3, &menuIDs);
      _fMenuBar->SetMenuText(ev, RESID_BACKGROUNDMENU, "Background");

      //add a submenu above EDIT->MOVE
      hwndMoveSubmenu = LoadMenu(_hmod,RESID_ARRANGE);

      /* Note: the submenu has 1 submenu.  Pass the ID */
      ODMenuIDInfo submenuID[1];

      SubMenuID[0].id    = IDMA_ORDER;         /* 1st submenu id  */
      SubMenuID[0].hMenu = GetSubMenu(hwndMoveSubmenu, 2);
                                                         /* and pos in menu */

      _fMenuBar->AddSubMenuBeforeEx(ev, IDMS_EDIT, EDIT_ARRANGE,
                                    hwndMoveSubmenu, EDIT_MOVE,
                                    1, subMenuID);

      _fMenuBar->SetMenuText(ev, EDIT_ARRANGE, "Arrange");

      //add a menu item to the EDIT menu
        mi.id = IDMA_TOGGLE_GRID;
        _fMenuBar->AddMenuItemLast(ev,IDMS_EDIT,kODNULL, &mi);
        _fMenuBar->SetMenuItemText(ev,
                                   IDMS_EDIT,
                                   IDMA_TOGGLE_GRID,
                                   "Grid On");

    }

When a part activates itself, it should request the menu focus (along with other focuses). Once the part has the menu focus, it should call ODMenuBar::Display to make its menu the active menu.

#### Displaying the Menu Bar

    SOM_Scope void  SOMLINK
    ContainerPartFocusAcquired(ContainerPart *somSelf,
                               Environment *ev,
                               ODTypeToken focus,
                               ODFrame* ownerFrame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartFocusAcquired");

        ......(code for other focus types goes here)......

        if (focus == _fMenuFocus)
             _fMenuBar->Display(ev);
    }

When a user clicks anywhere within the menu bar, OpenDoc will determine which, part has the menu focus and calls Part::AdjustMenus. Here the part can enable, disable, check or uncheck menu items using the methods of ODMenuBar class.

#### Adjusting the Menu Bar

    SOM_Scope void  SOMLINK ContainerPartAdjustMenus (ContainerPart *somSelf,
                                                      Environment *ev,
                                                      ODFrame* frame)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart","ContainerPartAdjustMenus");

      PartInfoRec* pInfo = (PartInfoRec *) frame->GetPartInfo(ev);

      //enable the EDIT->Links
      _fMenuBar->EnableMenuItem(ev,IDMS_EDIT,EDIT_LINK_MENU,kODTrue);

      //put a check next to our default Edit link type
      _fMenuBar->CheckMenuItem(ev,EDIT_LINK_MENU,EDIT_PASTELINK,kODTrue);

      //enable the edit Redo menu item
      _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, EDIT_REDO, kODTrue);

     //enable menu items based on single/multiple/no selection
     switch (_fSelection->Count())
     {
        case 0:     // no selection
          _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, EDIT_SELECTALL, kODTrue);
        break;

        case 1:     // single selection
          _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, EDIT_DELETE, kODTrue);
          _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, EDIT_DESELECTALL, kODTrue);
          _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, IDMA_ROTATE, kODTrue);
        break;

        default: // multiple selection
          _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, EDIT_DELETE, kODTrue);
          _fMenuBar->EnableMenuItem(ev,IDMS_EDIT, EDIT_DESELECTALL, kODTrue);
      };
    }

Menu events are passed by OpenDoc to the Part::HandleEvent method. In this example, menu events are handled by a part-defined method called Part::HandleMenuEvent.

#### Handling Menu Events

Menu events are generated when the user either clicks on a menu item or uses an accelerator key. Your part's HandleEvent method will be called with a kODEvtMenu event. For the OS/2 and Windows platforms the eventData parameter will be a WM_COMMAND message. &foraix, it will be an OpenDoc ODMenuEventData structure.

    SOM_Scope ODBoolean  SOMLINK
    ContainerPartHandleEvent(ContainerPart *somSelf, Environment *ev,
                             ODEventData* event, ODFrame* frame,
                             ODFacet* facet, ODEventInfo* eventInfo)
    {
     ContainerPartData *somThis = ContainerPartGetData(somSelf);
     ContainerPartMethodDebug("ContainerPart","ContainerPartHandleEvent");

     ODBoolean handled = kODFalse;

     switch (event->msg)

     {
        ....(other messages go here).......

        case kODEvtMenu:
             handled = somSelf->HandleMenuEvent(ev, frame, event);
           break;
        default:
           return kODFalse;
     }
     return handled;
    }

    ContainerPartHandleMenuEvent(ContainerPart *somSelf, Environment *ev,
                                 ODFrame* focusFrame, ODEventData* event)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartHandleMenuEvent");
        PartInfoRec* pInfo = (PartInfoRec*) focusFrame->GetPartInfo(ev);

        ODBoolean handled = kODFalse;
        #if      (OS/2 platform)
            ODMenuItemID command = LOWORD(event->mp1);
        #else if (Windows platform)
            ODMenuItemID command = LOWORD(event->wParam);
        #else if (AIX platform)
            ODMenuEventData* menuEvent = (ODMenuEventData*) event;
            ODMenuItemID command = menuEvent->item;
        #endif

        switch (command)
        {
           case IDMA_COLOR_GRAY      :
           case IDMA_COLOR_RED       :
           case IDMA_COLOR_GREEN     :
           case IDMA_COLOR_YELLOW    :
           case IDMA_COLOR_BLUE      :
           case IDMA_COLOR_MAGENTA   :
           case IDMA_COLOR_CYAN      :
           case IDMA_COLOR_WHITE     :
                   somSelf->HandleColorMenu(ev, focusFrame, command);
                   handled = kODTrue;
                   break;
           case EDIT_DELETE     :
                   somSelf->DoClear(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case EDIT_CUT       :
                   somSelf->DoCut(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case EDIT_COPY      :
                   somSelf->DoCopy(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case EDIT_PASTE     :
                   somSelf->DoPaste(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case EDIT_SELECTALL      :
                   somSelf->DoSelectAll(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case EDIT_DESELECTALL      :
                   somSelf->DoDeSelectAll(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case IDMA_MOVETOFRONT :
                   somSelf->MoveToFront(ev, focusFrame);
                   handled = kODTrue;
                   break;
           case IDMA_MOVETOBACK  :
                   somSelf->MoveToBack(ev, focusFrame);
                   handled = kODTrue;
                   break;
           default:
              break;
        }
        return handled;
    }

OpenDoc provides a default accelerator table for the base menu bar items. Parts can add their part-defined accelerators to this table during menu bar construction in Part::InstallMenus.

#### Adding Accelerators

If your part has accelerators to add to the base accelerator table, it should add these accelerators during its menu construction.

##### OS/2 Example

    SOM_Scope void SOMLINK ContainerPartInstallMenus(ContainerPart *somSelf,
                                                       Environment *ev)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart","ContainerPartInstallMenus");

        //......other menu construction code goes here..............

        memset((PCH)&PartAccel, 0, sizeof(ODACCEL));
        PartAccel.aAccel[0].fs  = AF_SHIFT | AF_VIRTUALKEY;
        PartAccel.aAccel[0].key = VK_UP;
        PartAccel.aAccel[0].cmd = IDMA_MOVETOFRONT;
        PartAccel.aAccel[1].fs  = AF_SHIFT | AF_VIRTUALKEY;
        PartAccel.aAccel[1].key = VK_DOWN;
        PartAccel.aAccel[1].cmd = IDMA_MOVETOBACK;
        PartAccel.aAccel[2].fs  = AF_ALT | AF_VIRTUALKEY;
        PartAccel.aAccel[2].key = VK_UP;
        PartAccel.aAccel[2].cmd = IDMA_MOVEFORWARD;
        PartAccel.aAccel[3].fs  = AF_ALT | AF_VIRTUALKEY;
        PartAccel.aAccel[3].key = VK_DOWN;
        PartAccel.aAccel[3].cmd = IDMA_MOVEBACKWARD;

        _fMenuBar->AddToAccelTable(ev, 4L, &PartAccel);

    }

##### Windows Example

    SOM_Scope void SOMLINK ContainerPartInstallMenus(ContainerPart *somSelf,
                                                       Environment *ev)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart","ContainerPartInstallMenus");

        //......other menu construction code goes here..............

        memset((PCH)&PartAccel, 0, sizeof(ODACCEL));
        PartAccel.aAccel[0].fVirt = 0;
        PartAccel.aAccel[0].key = VK_UP;
        PartAccel.aAccel[0].cmd = IDMA_MOVETOFRONT;
        PartAccel.aAccel[1].fVirt  = 0;
        PartAccel.aAccel[1].key = VK_DOWN;
        PartAccel.aAccel[1].cmd = IDMA_MOVETOBACK;
        PartAccel.aAccel[2].fVirt  = 0;
        PartAccel.aAccel[2].key = VK_UP;
        PartAccel.aAccel[2].cmd = IDMA_MOVEFORWARD;
        PartAccel.aAccel[3].fVirt  = 0;
        PartAccel.aAccel[3].key = VK_DOWN;
        PartAccel.aAccel[3].cmd = IDMA_MOVEBACKWARD;

        _fMenuBar->AddToAccelTable(ev, 4L, &PartAccel);

    }

OpenDoc provides default status line text for the base menu bar items. Parts can add their part-defined status line strings during menu bar construction in Part::InstallMenus.

### Pop-Up Menus

When a part is initialized (in ODPart::InitPart and ODPart::InitPartFromStorage is should call ODWindowState::CopyBasePopup to obtain its own pop-up menu object. It can then add its own menus and/or menu items to the pop-up. In the below example ContainerPart::CommonInit is called from both InitPart and InitPartFromStorage.

#### Getting the Pop-Up

    SOM_Scope void SOMLINK
    ContainerPartCommonInitContainerPart(ContainerPart *somSelf,
                                         Environment *ev)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartCommonInitContainerPart");

        ......(other initialization code).......

        // Since the object is the part's copy, we can add and subtract menus
        // and items without affecting other running parts.

         _fMenuBar = _fSession->GetWindowState(ev)->CopyBaseMenuBar(ev);

        if (_fMenuBar) {
           _fPopup = _fSession->GetWindowState(ev)->CopyBasePopup(ev);
           somSelf->InstallMenus(ev);
        }
    }

#### Adding to the Pop-Up Menu

    SOM_Scope void  SOMLINK ContainerPartInstallMenus(ContainerPart *somSelf,
                                                       Environment *ev)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart","ContainerPartInstallMenus");

      ODPlatformMenuItem mi;

      ......(menu bar, accelerator, status line code go here).....

      //insert the print item on the pop-up menu
      //Items added to the pop-up menu in this method will always appear
      //on the pop-up

      memset((PCH)&mi, 0, sizeof(ODPlatformMenuItem));
      mi.id = IDMA_PRINT;
      _fPopup->AddMenuItemLast(ev, ID_BASEPOPUP, mi.id, &mi);
      _fPopup->SetMenuItemText(ev, ID_BASEPOPUP, IDMA_PRINT, "Print part");
    }

When a user presses mouse button 2 to bring up a pop-up menu, a message is passed to the part under the mouse if that part is not in an icon or thumbnail view, and is not bundled. When the part receives this message in its Part::HandleEvent method, it should activate itself if it is not active and display its pop-up menu.

#### Displaying the Pop-Up Menu

    SOM_Scope ODBoolean  SOMLINK ContainerPartHandleEvent(ContainerPart *somSelf,
                             Environment *ev,
                             ODEventData* event, ODFrame* frame,
                             ODFacet* facet, ODEventInfo* eventInfo)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart","ContainerPartHandleEvent");

      ODBoolean handled = kODFalse;

      switch (event->msg)
      {
       .....(other messages go here).......

         case WM_CONTEXTMENU:
            handled = kODTrue;
            {
             PartInfoRec* pInfo = (PartInfoRec*) frame->GetPartInfo(ev);
             if (!(pInfo->fIsActive)){
                   somSelf->ActivateFrame(ev, frame);
             somSelf->AdjustPopupMenu(ev, frame);
             _fPopup->Display(ev);
            }
          break;
      }
    }

It is up to the part to check and adjust its pop-up menu before displaying it. OpenDoc will not call the part's Part::Adjustmenus; that method is only valid for menu bar menus. There are a number of ways a part can adjust its pop-up menu before displaying it. Below are examples of how ContainerPart adjusts its pop-up menu using the methods ContainerPart::AdjustPopupMenu and ContainerPart::ClearPopupMenu.

#### Adjusting the Pop-Up Menu

    SOM_Scope void  SOMLINK ContainerPartAdjustPopupMenu(
                                                    ContainerPart *somSelf,
                                                    Environment *ev,
                                                    ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartAdjustPopupMenu");

        //this is a part-defined method that dynamically adds to the pop-up
        //menu with content-editing menu items based on selection

        //call this method to put the pop-up menu into a default state again
        somSelf->ClearPopupMenu(ev, frame);

        //add menu items based on single/multiple/no selection
        switch (_fSelection->Count())
        {
           case 0:     // no selection
            _fPopup->AddDefaultMenuItemBefore(ev, EDIT_SELECTALL, IDMS_HELP);
              break;

     case 1:     // single selection
         {
         _fPopup->AddDefaultMenuItemBefore(ev,EDIT_DELETE,IDMS_HELP);
         _fPopup->AddDefaultMenuItemBefore(ev,EDIT_DESELECTALL,EDIT_DELETE);
         memset((PCH)&mi, 0, sizeof(MENUITEM));
         mi.id = IDMA_SCALE;
         _fPopup->AddMenuItemLast(ev, ID_BASEPOPUP, mi.id, &mi);
         _fPopup->SetMenuItemText(ev, ID_BASEPOPUP, IDMA_SCALE, "Scale");
         };
      break;

      default: // multiple selection
        {
        _fPopup->AddDefaultMenuItemBefore(ev, EDIT_DELETE,IDMS_HELP);
        _fPopup->AddDefaultMenuItemBefore(ev, EDIT_DESELECTALL,EDIT_DELETE);
        };
     };
    }

    SOM_Scope void SOMLINK
    ContainerPartClearPopupMenu(ContainerPart *somSelf,Environment *ev,
                                ODFrame* frame)
    {
      ContainerPartData *somThis = ContainerPartGetData(somSelf);
      ContainerPartMethodDebug("ContainerPart",
                               "ContainerPartRestoreSelectedMenu");

    // check the pop-up for those items dependent on selection that this part
    // adds and remove them from the pop-up to return it to a default state

      if(_fPopup->ItemExists(ev, ID_BASEPOPUP, EDIT_DELETE))
        _fPopup->RemoveMenuItem(ev, ID_BASEPOPUP, EDIT_DELETE);

      if(_fPopup->Exists(ev, ID_BASEPOPUP, EDIT_SELECTALL);
        _fPopup->RemoveMenuItem(ev, ID_BASEPOPUP, EDIT_SELECTALL);

      if(_fPopup->Exists(ev, ID_BASEPOPUP, EDIT_DESELECTALL);
        _fPopup->RemoveMenuItem(ev, ID_BASEPOPUP, EDIT_DESELECTALL);

      if(_fPopup->Exists(ev, ID_BASEPOPUP, IDMA_SCALEL);
        _fPopup->RemoveMenuItem(ev, ID_BASEPOPUP, IDMA_SCALE);
    }

### Opening and Closing Windows

Part editors are required to create an ODWindow instance for each modeless platform window they create. An ODWindow contains a root frame, and thus provides the root of the layout hierarchy. This provides a means for OpenDoc to distribute events to parts, since the main event loop is within the OpenDoc shell.

ODWindow is a fairly thin cover for a platform window pointer. For some operations, the part editor will have to retrieve the platform window pointer from the ODWindow, and use platform facilities.

This document describes how to create, register, open and close windows. See also the "Opening a Part into a Window", "Window Events", "Dialogs", and "Activation" recipes.

#### Creating a Window

Part editors create windows using the CreatePlatformWindow method of the ODWindowState class.

#### Registering a Window

An ODWindow instance is created by registering a platform window with the ODWindowState object, which is obtained from the session like other session-wide services:

      fWindowState = fSession->GetWindowState(ev);

There are two similar methods for registering a window. RegisterWindow() creates and returns an ODWindow containing a newly-created root frame belonging to the supplied root part.

     ODWindow* window = fWindowState->RegisterWindow(ev,
             platformWindow, // The platform window pointer
             kODFrameObject, // frame type - kODFrameObject or
                             // kODNonPersistentFrameObject
             kODTrue,        // is root window - (keeps the document open)
             kODFalse,       // is resizable - OpenDoc will draw the resize icon
             kODFalse,       // is floating
             kODTrue,        // should save - saved as part of the document
             kODTrue,        // should dispose - OpenDoc will dispose platform
                             //   window
             fPartWrapper,   // root part
             fFrameView,     // Tokenized View Type - icon, thumbnail etc.
             fSettingsPresentation, // Tokenized Presentation
             kODNULL);       // Source frame

RegisterWindowForFrame() is similar, but takes an existing root frame as a parameter.

     window = windowState->RegisterWindowForFrame(ev,
                 platformWindow,
                 kODFrameObject,
                 isRootWindow,
                 isResizable,
                 isFloating,
                 shouldSave,
                 shouldDispose,
                 sourceFrame);

##### ODWindow Properties

Windows with the "is root" property set are windows that keep the document open. The shell closes the document when the last root window is closed. Most part editors will create a single root window in the Open method, but some may create multiple root windows with different views of the document.

The "should save" property determines whether the window state saves the window persistently. This should generally be set to kODTrue for root windows, and kODFalse for non-root windows. Some parts may wish windows created using the View in Window command or the Open Selection command to be persistent. If this is the case, then additional work on the part of the part editor is required, because it must persistently maintain the connection between the source frame and the window, so that the window can be closed when the source frame is deleted.

The "frame type", "view type" and "presentation" properties are passed on to the root frame which the window creates. The presentation property can be used by the part editor to distinguish frames and hence windows from one another during event handling.

The "source Frame" parameter is used when an embedded frame is opened into a window. In other circumstances it will be kODNULL.

##### Window IDs

Part editors should not hang onto ODWindow pointers, because the shell or window state may close a window and invalidate the reference. Instead, the window state assigns IDs (valid for a session), and parts can make use of the following two methods:

    ODID ODWindow::GetID();
    ODWindow ODWindowState::AcquireWindow(in ODID windowID);

    // Returns kODNULL if the window has been
    // removed from the window state, i.e Closed.

#### Opening a Window

After creating and registering a window, a part editor will usually do the following:

      window->Open(ev);
      window->Show(ev);
      window->Select(ev);

The Open() method builds the facet hierarchy, but does not make the window visible.

The Show() method makes the window visible, but does not change the window ordering.

The Select() method brings the window to the front.

#### Closing Windows

Under normal circumstances, the OpenDoc Shell handles the closing of a document. If the window in question is the last root window of a draft, the draft is closed, along with all its windows. When a part editor registers a window, it indicates whether or not it is a root window.

If a window being closed is not a root window, or is not the last root window, the shell will just close that window.

Sometimes a part editor will need to close a window programmatically. For example, a part editor may wish to intercept clicks in the close box, and the Close menu item, and merely hide the window so that it can be more quickly displayed later. This may be appropriate for dialog windows, for example. In this case, the part editor will have to retain the window ID of the window, and close it programmatically at a later time. To close a window programmatically, a part editor should call ODWindow::CloseAndRemove(), after which the window object is no longer usable.

    SOM_Scope ODID  SOMLINK ContainerPartOpen(ContainerPart *somSelf,
                                               Environment *ev, ODFrame* frame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart","ContainerPartOpen");

        ODID windowID = 0;

        SOM_TRY

        TempODWindow window = kODNULL;
        if (frame) // Doing a View As Window or Open Root
        {
          if (frame->IsRoot(ev))  // Create Window For Root Frame
          {
             WindowProperties props;
             BeginGetWindowProperties(ev, frame, &props);

             HWND hwndFrame = _fSession->
                              GetWindowState(ev)->
                              CreatePlatformWindow(ev, kODFalse);
             // position the window to shape it was closed in
             // open from file in some position/properties as was saved
             WinSetWindowPos(hwndFrame, HWND_TOP, props.boundsRect.xLeft,
                                               props.boundsRect.yBottom,
                                               props.boundsRect.xRight,
                                               props.boundsRect.yTop,
                                               (SWP_SIZE |
                                               SWP_MOVE|
                                               props.swpFlags));

             window =  _fSession->
                       GetWindowState(ev)->
                       RegisterWindowForFrame(ev,
                                     hwndFrame,
                                     frame,
                                     props.isRootWindow, // Keeps draft open
                                     kODTrue,   // Is resizable
                                     kODFalse,  // Is floating
                                     kODTrue, // should save
                                     kODTrue, // should dispose
                                     props.sourceFrame);

             EndGetWindowProperties(ev, &props); // Release source frame
             window->Open(ev);
             window->Show(ev);
          }

          else  // View In Window
          {
             window =
               _fSession->GetWindowState(ev)->AcquireWindow(ev, _fWindowID);
             if (window)
                window->Select(ev);

             else
             {
                window = somSelf->CreateWindow(ev, frame);
                _fWindowID = window->GetID(ev);
                window->Open(ev);
                window->Show(ev);
                window->Select(ev);
             }
          }
        }

        else
        {
          window = somSelf->CreateWindow(ev, frame);
          _fWindowID = window->GetID(ev);
          window->Open(ev);
          window->Show(ev);
          window->Select(ev);
        }
        windowID = window->GetID(ev);

        SOM_CATCH_ALL
        SOM_ENDTRY

        return windowID;
    }

    SOM_Scope ODWindow*  SOMLINK ContainerPartCreateWindow(ContainerPart
                                                           *somSelf,
                                                           Environment *ev,
                                                           ODFrame* sourceFrame)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart","ContainerPartCreateWindow");

        ODWindow* window = kODNULL;

        SOM_TRY

        Rect windRect;
        ODPlatformWindow platformWindow = kODNULL;

           platformWindow = _fSession->GetWindowState(ev)->
                            CreatePlatformWindow(ev,kODFalse);
        } /* endif */

        window =  _fSession->GetWindowState(ev)->
              RegisterWindow(ev, platformWindow,
                    kODFrameObject,
                    (sourceFrame==kODNULL),  // is root
                    kODTrue,      // Is resizable
                    kODFalse,      // Is floating
                    kODTrue,      // should save
                    kODTrue,      // should dispose
                    _fPartWrapper,
                     _fSession->Tokenize(ev, kODViewAsFrame),
                     _fSession->Tokenize(ev, kODPresDefault),
                    sourceFrame);

        SOM_CATCH_ALL
        SOM_ENDTRY

        return window;
    }

### Opening a Part into a Window

This document describes what you need to know to implement the Open() method of a part editor:

ODID Open(in ODFrame);

The Open() method is responsible for creating a window for the specified frame, and returning the ID of that window. If the window already exists, the method should simply activate it. The semantics are actually a little more complicated than this.

#### Open a Part From Nothing

When a new document is created from a stationery document, it contains no window state. In this case the Open() method of the root part is called by the OpenDoc shell, passing kODNULL as the frame.

#### Open an Embedded Frame

When the user selects a frame and chooses Open Selection from the Edit menu, the active part calls the selected part's Open() method, passing in the selected frame.

#### Open a Saved Document

When a document (actually a draft) is saved, the draft contains a list of root frames of persistently stored windows. OpenDoc annotates each root frame with a storage unit containing the window properties (bounding rectangle, etc.). When the document is opened, and the Window State internalized, the Window State calls the Open method of each frame's part, passing in the frame. The implementor of the Open() method can distinguish this case because the frame is a root frame, and contains the annotations. Utility functions are provided to access the window properties.

Note:

> This means that windows are ALWAYS created by the parts, even when opening a saved document. Thus parts can and should do whatever they need to to adjust the window size and position to a given monitor configuration.

#### Open As

When the user activates a frame and chooses the Open as window, the active part calls its Open() method, passing in its frame.

See "Closing Windows" for a sample implementation of the Open() method from the Container Part.

### Window Events

Events in the content region of a window are delivered to the appropriate part, which may be embedded several levels down. This document discusses events in the title bar and border of a window. See also the "Basic Event Handling" and "Mouse Events" recipes.

The OpenDoc Shell will handle events in the title bar and border of a window, including the system menu, minimize button and maximize button.

When the user double-clicks on the system menu of a window, the OpenDoc shell closes the window, after which it can not be reopened. For a dialog window or palette, the part editor may wish to simply hide the window, so that it does not have to be recreated if the user shows it again.

### Properties Notebook

OpenDoc provides a standard Info property notebook that is a subclass of ODObject. The ODInfo class provides for a notebook control with pages for displaying and modifying properties that are common across parts, such as part name, part kind, and so forth. The default notebook includes an implementation for the following pages:

- A Presentation page that allows users to select the view type for the frame, editing of the part's name, and check boxes to set selected property values.

- A Binding page that displays the category name and allows the user to select the part kind and the preferred part editor.

- A Document page that displays information about the part creation date/time as well as the size of the part, in bytes. The user can also view and edit part comments. If desired, a part can add pages to the notebook for part-specific properties.

#### Displaying the Properties Notebook

The properties notebook is displayed through the ODInfo object. The info object is instantiated when an OpenDoc session object is created and can be accessed by calling the session's GetInfo method. The ODInfo object's ShowPartFrameInfo method then needs to be called with a reference to the appropriate facet of the part.

    SOM_Scope ODBoolean  SOMLINK ContainerPartHandleMenuEvent(ContainerPart
                                                              *somSelf,
                                                              Environment *ev,
                                                              ODFrame*
                                                              focusFrame,
                                                              ODEventData* event)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart","ContainerPartHandleMenuEvent");
        switch (command)
        {
         ..........(other commands go here)..........
           case VIEW_PROPERTIES:
           {
           ODFrameFacetIterator* facets = focusFrame->CreateFacetIterator(ev);
           facets->InitFrameFacetIterator(ev,focusFrame);
           _fSession->GetInfo(ev)->ShowPartFrameInfo(ev,facets->First(ev),
                                                     kODTrue);
           delete facets;
           handled = kODTrue;
           }
           break;
        }
    }

#### ODSettingsExtension Programming

The ODInfo object will query the part for an ODSettingsExtension. If a part provides one, new pages will be added to the standard notebook for the settings. For a part to provide its own settings extension it needs to provide implementation for the ODPart HasExtension, AcquireExtension and ReleaseExtension methods. It also needs to create a new class that derives from the ODSettingsExtension class and overrides its ShowSettingsPages and InitSettingsExtension methods.

An example of the HasExtension, AcquireExtension and ReleaseExtension follows:

    SOM_Scope ODBoolean  SOMLINK ContainerPartHasExtension(ContainerPart
                                                           *somSelf,
                                                           Environment *ev,
                                                           ODType extensionName)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart","ContainerPartHasExtension");

        if(!strcmp(extensionName, kODSettingsExtension))
           return kODTrue;
        else
           return kODFalse;
    }

    SOM_Scope ODExtension* SOMLINK ContainerPartAcquireExtension(
                                   ContainerPart *somSelf,
                                   Environment *ev,
                                   ODType extensionName)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartAcquireExtension");

        if (!strcmp(extensionName, kODSettingsExtension))
        {
             if (!_fSettingsExt)
             {
                // Create an instance ODSettingsExtension subclass
                _fSettingsExt = new ContainerPartSettingsExtension;
                _fSettingsExt->InitSettingsExtension(ev, somSelf);
             }
             else _fSettingsExt->Acquire(ev);
             return _fSettingsExt;
        }
    }

    SOM_Scope void SOMLINK ContainerPartRelaseExtension(
                                   ContainerPart *somSelf,
                                   Environment *ev,
                                   ODExtension extension)
    {
        ContainerPartData *somThis = ContainerPartGetData(somSelf);
        ContainerPartMethodDebug("ContainerPart",
                                 "ContainerPartReleaseExtension");

        if (!extension) return;

        if (_fSettingsExt == extension) {
            delete _fSettingsExt;
            _fSettingsExt = 0;
        }
    }

#### Creating ODSettingsExtension

A part can provide for its own settings extension to be added to the properties notebook by creating a new class that derives from the ODSettingsExtension class and overrides the ShowSettingsPages and InitSettingsExtension methods.

The ShowSettingsPages method will provide the platform dependent code to create, initialize, and manage the part's settings page.

##### OS/2 Platform

The ShowSettingsPages method prototype is:

      void ShowSettingsPages(ODFacet *facet, HWND parent, ULONG notebookID);

The ShowSettingsPages method is called by the ODInfo object and provided with a handle to the property dialog owned by the ODInfo object, the id of its notebook child, and a pointer to the part's facet.

Because of the way that OS/2 implemented the Notebook control, it is necessary for any global messages, for example, when the OK, Cancel, and Help buttons are pressed, to be propagated directly to each page's dialog function. Therefore, a method has been implemented in the OS/2 ODSettingsExtension which allows the subclass to save the handle and ID of any pages that are created and added to the notebook. This will allow ODInfo to propagate these messages to the pages. The prototype for this method is:

      void AddNotebookSheet(HWND hwnd, ULONG pageId);

In the ShowSettingsPages method you need to do the following:

1. Have the Notebook add a new page by sending a BKM_INSERTPAGE as follows:

       ULONG pageID = (LONG) WinSendDlgItemMsg(parent, notebookID,
             BKM_INSERTPAGE, NULL, MPFROM2SHORT((BKA_STATUSEXTON |
             BKA_AUTOPAGESIZE | BKA_MAJOR), BKA_LAST));

2. Set the page's tab text by sending a BKM_SETTABTEXT message.

3. Load or create the desired dialog resource as follows:

       HWND pageHwnd = WinLoadDlg(parent, parent, (PFNWP) DialogProc,
                                 (HMODULE) hmod, ID_SETTINGSPAGEDLG, facet);

   Be sure to pass the facet in so it will be available in the dialog procedure.

4. Associate the handle of the new page to the notebook by sending a BKM_SETPAGEWINDOWHWND message.

5. Call the somSelf->AddNotebookSheet(HWND pageHwnd, ULONG pageId) method to store the page information.

##### Windows Platform

The ShowSettingsPages method protoype is:

      void ShowSettingsPages(ODFacet *facet, HWND hPropSheetDlg);

The ShowSettingsPages method is called by the ODInfo object and provided with a handle to the Property Sheet dialog owned by the ODInfo object and a pointer to the part's facet.

In the ShowSettingsPages method you need to do the following:

1. Create a PROPSHEETPAGE structure and fill in its fields. Be sure to include a pointer to the facet in the lParam field so it will be available to the dialog procedure.

2. Create a property sheet using the Win32 CreatePropertySheetPage(page) function.

3. Add the page to the Property sheet using the Win32 PropSheet_AddPage(hPropSheetDlg, hPage) macro or by sending a PSM_ADDPAGE message to hPropSheetDlg. Note that hPage is the handle returned from the previous call to CreatePropertySheetPage.

You also need to link with the comdlg32.lib and comctl32.lib libraries to include the platform Property Sheet control information.

##### AIX Platform

The ShowSettingsPages method protoype is:

      void ShowSettingsPages(ODFacet *facet, ODNotebook *notebook)

The ShowSettingsPages method is called by the ODInfo object and provided with a pointer to the ODNotebook object owned by the ODInfo object and a pointer to the part's facet.

To create a new page you need to do the following:

1. Derive a new class from the ODPropertyPage class and override the GetName, CreateWidget, Ok, Cancel, and Help methods.

   Note:

   > The ODPropertyPage has a public GetFacet method that provides access to the facet so your ODSettingsExtension subclass can get to the part's storage unit.

2. Pass an instance of the derived class to the notebook->AddPage method as follows:

       ODSettingsPage *page = new ODSettingsPage;
       // Do any specific initialization to the page here.
       notebook->AddPage(ev, page);

The Base Container sample part implements a settings extension and provides code examples. Please refer to the iodbasec.txt file located in the %ODSRC%\src\basecntr directory for more information.

#### Presentation Page

OpenDoc also provides an optional presentation page which allows a part to send a list of supported presentations to the notebook. A user can then choose between the options in a standard way. The procedure is to subclass the notebook as described above, and call the notebook's InsertPresentationPage method. A typelist with the presentation types supported is sent to this method as shown below, where a ClockPart allows a digital or analog presentation. The presentation page dialog procedure will call the part's PresentationChanged method with the user's choice.

    SOM_Scope void  SOMLINK ClockSetAddNotebookPages(ClockSet *somSelf,
                                                        Environment *ev,
                                                       HWND hwndNotebook)
    {

         ODPart * part = (ODPart *)somSelf->GetBase(ev);
         ODTypeList * pres = part->GetStorageUnit(ev)->GetSession(ev)->

    GetStorageSystem(ev)->CreateTypeList(ev,(ODTypeList*)kODNULL);
        pres->AddLast(ev, "Analog");
        pres->AddLast(ev, "Digital");

        somSelf->InsertPresentationPage(ev, hwndNotebook, prefs);
        ClockSet_parent_ODSettingsExtension_AddNotebookPages(somSelf, ev,
                                                      hwndNotebook);
        ODDeleteObject(pres);
    }

    SOM_Scope void  SOMLINK ClockPartPresentationChanged(ClockPart *somSelf,
                                                              Environment *ev,
                                                             ODFrame* frame)
    {

        ClockPartData *somThis = ClockPartGetData(somSelf);
        ClockPartMethodDebug("ClockPart","ClockPartPresentationChanged");

        ClockPartInfoRec* pInfo = (ClockPartInfoRec*)frame->GetPartInfo(ev);

        ODSession* session = somSelf->GetStorageUnit(ev)->GetSession(ev);
        ODFrameFacetIterator* facets = frame->CreateFacetIterator(ev);
        ODFacet* facet = facets->First(ev);
        ODShape* usedShape = frame->AcquireUsedShape(ev, kODNULL);

        if(frame->GetPresentation(ev) == session->Tokenize(ev, "Digital"))
          pInfo->clock-> SetMode(DM_TIME | DM_DIGITAL | DM_SECONDHAND);
        else
        {
          /* analog is the default choice here */
          pInfo->clock-> SetMode(DM_TIME | DM_ANALOG | DM_SECONDHAND);
          frame->SetPresentation(ev, session->Tokenize(ev, kODPresDefault));
        }

        somSelf->Draw(ev, facet, usedShape);
        ODReleaseObject(ev, usedShape);
        delete facets;

        return;
    }

### Stationery Support for the OS/2 Platform

Stationery support is also known as customized icon support. One of the important ideas behind OpenDoc is the concept of stationery. If you need to add spreadsheet information to your document, you "tear off" or drag a piece of spreadsheet stationery and drop it in. With this is mind, you can see that it is important to provide a meaningful icon for your OpenDoc Part.

#### Creating the Icon File

CI Lab's icon requirements are not very strict. They specify a 16 x 16 pixel icon and a 32 x 32 pixel icon for the part. For your icons on the OS/2 platform, you may want to add more sizes and types. To do this you can use iconedit.exe that comes with Visual Age C++ to create your icon or you can use your own preferred icon editor.

If you are not familiar with iconedit.exe, use the Device | List... menu options to add different icon sizes to your icon file such as 8514 Small Color Form, Black and White, etc.

You can make a copy of the bcpart.ico file delivered with OpenDoc if you like. It is a four color square set on top of a white piece of stationery. If you prefer, you can place your icon on top of a white piece of stationery. This provides a consistent look for users.

After you create your icon, compile it into your Part Editor dll. This is discussed in various Visual Age documents and demonstrated in some Visual Age samples for example, Carpp.cpp. Basically, you need to create a file with the extension .rc and compile it into your dll. You can also refer to the BaseContainer sample for help.

#### Updating Part Editor Source Code

Be sure to add code for icon support in your Part Editor. Query the icon information and write it to a storage unit. You could put this code in your InitPart method or you could put it in its own function that is called when other data is written to storage units. Following is an example.

    #ifdef _PLATFORM_OS2_
    // Required for customized icon support
            ULONG cbIconSize;
            PBYTE pIconData = NULL;
            HMODULE hmodPUTest;
            APIRET rc = 0;
            CHAR Error[256] = "";

       DosLoadModule(Error, sizeof(Error), "putest", &hmodPUTest);

       // note: ID_ICON should match the definition in your .rc file

       if ( !DosQueryResourceSize( hmodPUTest, RT_POINTER, ID_ICON, &cbIconSize)
            && !DosGetResource( hmodPUTest, RT_POINTER, ID_ICON, (PPVOID)&pIconData))
       {
          storageUnit->AddProperty(ev, kODPropIconFamily)->AddValue(ev, kODIconFamily);
          StorageUnitSetValue( storageUnit, ev, cbIconSize, (ODValue)pIconData );
          DosFreeResource(pIconData);
       }
       else
          PRINT("PUTest, DosGetResource failed![%d]\n",rc);

       DosFreeModule(hmodPUTest);
    #endif // _PLATFORM_OS2_

If you do not have a CloneInto method, you need to override the CloneInto method and be sure to clone the storage unit information. This will ensure that the icon information written to the storage unit during the InitPart will be properly cloned. Following is an example of code you can add to your CloneInto method.

    #ifdef _PLATFORM_OS2_
    // Required for customized icon support
        ODID scopeFrameID = 0;
        ODStorageUnit *theSU = somSelf->GetStorageUnit(ev);

        /* Clone the scopeFrame */
        if (scope != kODNULL)
          scopeFrameID = scope->GetStorageUnit(ev)->GetID(ev);
        theSU->CloneInto(ev, key, toSU, scopeFrameID);
    #endif // _PLATFORM_OS2_

Your clsGetWindowsIconFileName metaclass method should be correct. The WindowsIconFileName variable needs to be set with the name of your icon file. Following is an example.

    // Required for customized icon support
        string kWindowsIcon = "myPart.ico";
        string WindowsIconFileName = (string)SOMMalloc(strlen(kWindowsIcon)+1);
        strcpy(WindowsIconFileName,kWindowsIcon);

#### Creating A Part's Desktop Stationery

Now that you have your icon associated with your Part's dll you are ready to create the Stationery. This is done from the command line using odinst.exe:

      odinst yourPart.odz
      (For example: odinst iodbasec.odz)

For more information about package files see "Packaging Your OpenDoc Part Handler"

Note:

> Be sure that the SOMIR and LIBPATH variables point to your opendoc.ir and Part dll files.

When this is complete, the part is registered and a wps template is created in the "IBM OpenDoc | Stationery" folder. A yellow OS/2 Template icon is created with your Part's 16 x 16 pixel icon located in the center. Any stationery that is "torn off" or dragged uses the 32 x 32 pixel version.

If you do not see your icon but rather the default OpenDoc Stationery icon then you need to troubleshoot.

- Check the iconedit.exe and be sure that you created the proper sizes required for the icon. When OS/2 cannot find the 16 x 16 pixel icon it uses the default version.

- If your icon is created properly, check your CloneInto method. The odprtreg process makes a clone of your part and if you did not override CloneInto, the icon information that you stored in your InitPart is lost. In this case too, the default Stationery icon is displayed.

### Help

OpenDoc provides help panels for default base menu bar menu items. If a user presses F1 while keying through the menu items, an OD_HELP message is sent to the part. The OD_Help event is not supported on the AIX platform.

The first parameter of the ODEventData structure contains the type of window on which help was requested. This field will be one of the following:

- HLPM_ODMENU, the menu window.
- HLPM_ODWINDOW, the document shell window or selected part.

The second parameter of the ODEventData structure contains the menu ID if the type was HLPM_ODMENU.

Return a boolean value to indicated whether the OD_HELP event was handled or not. If it is not handled, then the document shell provides default handling.

#### Displaying Help for a Menu Item

This case statement is part of the HandleEvent method.

    case OD_HELP:
    {
      ODUShort usContext, usMenuID
      ODHelp * help = _fSession->GetHelp(ev);

      #ifdef _PLATFORM_WIN32_
      usContext = LOWORD(event->wParam);
      usMenuID  = event->lParam;
      #endif

      #ifdef _PLATFORM_OS2_
      usContext = SHORT1FROMMP(event->mp1);
      usMenuID  = SHORT1FROMMP(event->mp2);
      #endif

      if (usContext == HLPM_MENU)  // If F1 on Menu
      {
        switch (menuID)
        {
          case IDMA_COLOR_GRAY:
            help->DisplayHelp(ev,"cnt.hlp",IDMA_COLOR_GRAY_PANEL);
            eventHandled = kODTrue;
            break;
          case IDMA_COLOR_RED:
            help->DisplayHelp(ev,"cnt.hlp",IDMA_COLOR_RED_PANEL);
            eventHandled = kODTrue;
            break;
        }
      }

      else                         // if F1 on Part  (HLPM_ODWINDOW)
      {
        help->DisplayHelp(ev,"cnt.hlp",IDMA_GENERAL_PANEL);
        eventHandled = kODTrue;
        break;
      }
    } // end OD_HELP
    break;

#### Additional Notes

Parts should not create help subtables for their contextual help, but instead process the OD_HELP event. The HELP menu items are sent as WM_COMMANDS and should be handled as any other menu event. If help was requested on a control field within a part or on a menu that was not created by OpenDoc, a WM_HELP message is passed to the part and should be handled the same way as it is in Windows or PM programming.

### Undo

Undo under OpenDoc is multi-level.

Note:

> The ODPart function calls, ReadActionState and WriteActionState are not supported by Undo in the current release of OpenDoc.

Parts should add undoable actions to the undo history using AddActionToHistory. whichPart identifies your part object, actionData is a reference to your data (a magic cookie), actionType is described below. undoActionLabel and redoActionLabel are user-visible strings corresponding to the undo and redo menu items.

       void AddActionToHistory(in ODPart whichPart,
             in ODActionData actionData,
             in ODActionType actionType,
             in ODName undoActionLabel,
             in ODName redoActionLabel);

Here is some sample code:

     // CREATE UNDO MENU ITEM TEXT
     ODIText* undoActionName = CreateIText("Undo My Action");
     ODIText* redoActionName = CreateIText("Redo My Action");

     // CREATE A STRUCTURE TO HOLD DATA ABOUT ACTION
     MyUndoInfo* uInfo = (MyUndoInfo*)AllocMem(sizeof(MyUndoInfo));
     // ADD DATA TO STRUCTURE (TASK SPECIFIC)
     uInfo->someData = ...

     MyGetSession()->GetUndo()->AddActionToHistory(_fPartWrapper,
                                               (ODActionData)uInfo,
                                               kODSingleAction,
                                               undoMenuText,
                                               redoMenuText);

     DeleteIText(undoActionName);
     DeleteIText(redoActionName);

The above code illustrates adding a single action to the undo history. Undo makes a copy of the ODActionData and stores it away. To add a transaction to the undo history, such as the drag-moving of some data from one part to a another, parts must use the ODActionTypes, kODBeginAction and kODEndAction.

Note:

> Change in recipe for transactions:

> The part initiating an action that may span multiple parts should place an action on the stack using the kODBeginAction constant for the ODActionType parameter to AddActionToHistory. When the action is complete, that same part should place the "end" action on the stack using the kODEndAction constant. In between these times, other parts may add single actions to the action history. These actions become part of the entire transaction.

For example, in the case of drag-moving of some data from one part to a another, the source part should add a begin action to the action history, the receiving part should add item(s) to the undo stack about the data received using the kODSingleAction constant, and finally, the source part can close out the transaction by adding an end action to the history.

Another example: In the case of a paste with a link, the destination part should add the begin and end actions to the stack and the source part should add one or more single actions to the undo history.

If the part placing the begin and end actions on the stack notices an error at any point after placing the begin action onto the stack, it should roll back the transaction. Currently there is no clean way to do this other than to clear the entire undo history. We will mostly likely add a new functionality in the near future to allow for rolling back a transaction.

When an action is undone, ODPart::UndoAction is called for the part that furnished the action. Similarly ODPart::RedoAction is called when an action is redone. When a transaction is undone or redone, every action between the beginning and ending actions, as well as those actions themselves, will be undone or redone.

ODPart::DisposeActionState is called whenever an item on the undo or redo stack is permanently removed. You will be passed a copy of the ODActionData that you originally added with AddActionToHistory. You must take whatever action is relevant to your part. This may be the disposal of any auxiliary data associated with this undo action.

### Using Resources in OpenDoc

Resources(Dialogs/Menus/Bitmaps/Icons/Accelerators/Messages) are linked into your parts DLL by using IBM's CSet/2 Resource Compiler (RC).

Sample code (for the OS/2 platform) used to read a resource object from your resource data.

           DosQueryModuleHandle(THISDLL,hMod);              // Get DLL Handle
           DosGetResource(hmod,ulTypeID,ulNameID,ppObject)  // Get Resource
                                                            // Address
             ... Use of the resource Object

           DosFreeResource(ppObject);                       // Free Resource

Sample Code used to Load a Dialog from your resource data.

           DosQueryModuleHandle(THISDLL,hMod);              // Get DLL Handle
           hwndDlg = WinLoadDialog(HWND_DESKTOP,HwndOwner,dlgProc,
                                   hmod,DLG_ID,NULL);
           WinProcessDlg(hwndDlg);                          // Process Dialog

Sample Code used to Load a Menu from your resource data.

           DosQueryModuleHandle(THISDLL,hMod);              // Get DLL Handle
           Menu = WinLoadMenu(HWND_OBJECT,hmod,MenuID);

           fMenubar->AddMenuLast(ev,MenuID,Menu,myPart);

Sample Code used to Load Accelerators from your resource data.

           DosQueryModuleHandle(THISDLL,hMod);              // Get DLL Handle
           hAccel = WinLoadAccelTable(Hab,hmod,IDofAccelTable);
           WinCopyAccelTable(hAccel,&PartAccel,ulCopyMax);
           fMenuBar->AddToAccelTable(ev,num_accels,&PartAccel);

### Facet Windows

On the OS/2 platform, the ODOS2WindowCanvas object is created if the parent has an onscreen, dynamic canvas. On the Windows platform, the ODWin32WindowCanvas object is created. OpenDoc creates and initializes a Presentation Manager window for the root facet which is a child window of the Document Shell client window. The class of this window (in the PM sense) is registered by OpenDoc. The window class name for facet windows is OpenDoc:FacetWindow, and this class is registered as a private class on a per process basis. A separate window of this class is created for each embedded facet that is contained within the root facet. On the Windows platform, a child window is created for every facet. The child window has the same size as frame shape.

The distinction here between the concept of a window in the OpenDoc sense, and the use of Presentation Manager windows in OpenDoc. The OpenDoc class ODWindow and the type ODPlatformWindow refer to desktop level document windows. The ODPlatformWindow type on OS/2 is the window handle of the document frame window. The OS/2 implementation of OpenDoc uses Presentation Manager windows for facets that are displayed in an onscreen, dynamic canvas. These windows are specific to the OS/2 implementation of OpenDoc and have no relationship to the ODWindow class.

By default, when an embedded facet is created, the associated facet window is positioned on top of all other PM windows that are also children of the facet window for the containing facet. The size of the facet window is the same as the frame shape.

#### Querying the Facet Window Handle

All facets that are descended from a root facet with an onscreen, dynamic canvas have a unique facet window.

You can query the window handle (HWND) of the facet window for a given facet by calling ODFacet::GetFacetHWND. If the facet has a facet window, the HWND of the window will be returned, otherwise NULLHANDLE is returned. For the reasons discussed above, you should not use the HWND of a facet window for drawing your part's content. Instead, you should use the presentation space obtained from your facet's canvas. See the "Part Drawing" recipe for more information.

Given a facet window handle, you can obtain a pointer to the ODFacet object that is associated with the window by calling the class method M_ODFacet::clsGetFacetFromHWND. This call will succeed only if the facet object is in the same process.

#### Specifying the PS for a Facet Window

By default, calling ODPlatformCanvas::GetPS for a platform canvas object that has a window returns a cached presentation space for the specified facet's facet window. Because this is a cached PS, it must be released using ODPlatformCanvas::ReleasePS when you are done drawing. You should not keep a reference to this PS beyond the scope of the method where it was obtained. This means that you must set up any attributes, transforms and clipping that are required every time you want to draw you part's content. The Focuslib class which is provided in the sample utilities will help out with this, but you may want to set a micro or normal presentation space and set these values only when they change. You can do this by calling ODPlatformCanvas::SetPS in your part's FacetAdded method. You can then set the clip region and transform when you are notified that they have changed in your part's GeometryChanged method. Calling ODPlatformCanvas::ReleasePS for a non-cached PS has no effect.

On the OS/2 platform, the following example shows how you can set a presentation space for a facet window in your part's FacetAdded method.

    SOM_Scope void SOMLINK MyPartFacetAdded(MyPart* somself,
                                                        Environment* ev,
                                                        ODFacet *facet)
    {
      SOM_TRY

      HWND hwnd;
      HDC hdc;
      HPS hps;
      ODPlatformCanvas *platformCanvas;

      platformCanvas = facet->GetCanvas(ev)->GetPlatformCanvas(ev, kODPM);
      if (platformCanvas->HasWindow(ev))
      {
         // Get the window handle and DC
         hwnd = facet->GetFacetHWND(ev);
         hdc = WinOpenWindowDC(hwnd);

         // create micro PS and associate DC
         SIZEL sizl = {0, 0};
         hps= GpiCreatePS(WinQueryAnchorBlock(hwnd), hdc, &sizl,
                          PU_PELS | GPIT_MICRO | GPIA_ASSOC);

         // associate PS with the facet
         ((ODOS2WindowCanvas*)PlatformCanvas)->SetPS(ev,hps);
      }

      SOM_CATCH_ALL
      SOM_ENDTRY
    }

On the OS/2 platform, the following example shows how you can set the clip region and default viewing transform for a non-cached PS in your part's GeometryChanged method. You could also set default attributes based on the facet's highlight mode in your part's HighLightChanged method. Refer the file FOCUSLIB.CPP in the sample utilities to see what is done to prepare the presentation space for drawing when a CFocus object is instantiated.

    SOM_Scope void SOMLINK GeometryChanged(MyPart* somself,
                                                 Environment* ev,
                                                 ODFacet *facet,
                                       ODBoolean clipShapeChanged,
                                       ODBoolean externalTransformChanged)

    {
      SOM_TRY

      ODPlatformCanvas platformCanvas;
      platformCanvas = facet->GetCanvas(ev)->GetPlatformCanvas(ev, kODPM);

      if (platformCanvas->HasWindow(ev))
      {
         HPS hps = platformCanvas->GetPS(ev, facet);
         if (clipShapeChanged)
         {
            HRGN hrgnClip, hrgnOld;
            TempODShape tempShape =
               facet->AcquireAggregateClipShape(ev, kODNULL);
            TempODShape clipShape = tempShape->Copy(ev);
            TempODTransform xform =
               facet->AcquireContentTransform(ev, kODNULL);
            clipShape->Transform(ev, xform);
            hrgnClip = clipShape->CopyRegion(ev);
            GpiSetClipRegion(hps, hrgnClip, &hrgnOld);
            GpiDestroyRegion(hps, hrgnOld);
         }

         if (externalTransformChanged)
         {
            MATRIXLF mtx;
            TempODTransform xform =
               facet->AcquireContentTransform(ev, kODNULL);
            xform->GetMATRIXLF(ev, &mtx);
            GpiSetDefaultViewMatrix(ev, 9, &mtx, TRANSFORM_REPLACE);
         }
      }

      SOM_CATCH_ALL
      SOM_ENDTRY
    }

Now, when you're called in your Draw method, the presentation space for your facet window is already set up and it's not necessary to set the clip region, viewing transform and other attributes. You need to check the canvas type, though, to determine if it's a window canvas. In this example, we use the C language functions BeginFocus and EndFocus rather than the C++ class CFocus so that we can conditionally invoke EndFocus. Because EndFocus is not automatically invoked when it goes out of scope as in the CFocus destructor, we should make sure that it gets called if an exception is thrown. See the "Exception Handling" recipe for more information on handling exceptions in OpenDoc.

    SOM_Scope void  SOMLINK MyPartDraw(ContainerPart *somSelf,
                                 Environment *ev,
                                 ODFacet* facet,
                                 ODShape* invalidShape)

    {

       MyPartData *somThis = MyPartGetData(somSelf);
       MyPartMethodDebug("MyPart","MyPartDraw");
        CFocus* foc = 0;

       SOM_TRY
        HPS hps;
        ODPlatformCanvas* platCanv =
                 facet->GetCanvas(ev)->GetPlatformCanvas(ev, kODPM);

        if (platCanv->HasWindow(ev))
           hps = ((ODOS2WindowCanvas)PlatCanv)->GetPS(ev);
        else
           foc = new CFocus(ev, facet, invalidShape, &hps);

        // Draw your part's content here

        // Clean up
        if (foc)
           ODDeleteObject(foc);

       SOM_CATCH_ALL
        if (foc)
           ODDeleteObject(foc);
       SOM_ENDTRY
    }

### Using Embedded PM Controls within a Facet (OS/2)

You can embed PM controls within a facet that has a facet window. For the purpose of this discussion, the term "PM Control" is used to represent any Presentation Manager window that sends notification messages to its owner. You embed a PM control by creating the control using the WinCreateWindow function and specifying the facet window for your part's facet as the parent and owner of the control window. You should not specify the WS_CLIPSIBLINGS style flag for the window. The following sample code creates a button window.

    HWND hwndFacet, hwndButton;
    if (hwndFacet = facet->GetFacetHWND(ev))
    {
       hwndButton = WinCreateWindow(hwndFacet, WC_BUTTON, "Push",
                                     BS_PUSHBUTTON | WS_VISIBLE
                                     0, 0, 10, 30,
                                     hwndFacet, HWND_TOP, 0, 0, 0);

       facet->SetPartInfo(ev, (ODInfoType)hwndButton);  // Save handle
    }

Notification messages sent to the facet window will be forwarded to the facet's part in its HandleEvent method. You can identify these messages as coming from the facet window by the hwnd field in the event data structure. For normal events (those sent to your part by the OpenDoc dispatcher) the hwnd field will contain the handle for the document shell client window. For messages sent by a facet window, this field will contain the window handle of the facet window.

You position the control windows within your facet based on the facet's window frame transform, or the window content transform, depending upon whether you want the control anchored to a position in your part's content or on your display frame.

#### Managing Clipping

It is your part's responsibility to manage the clipping of embedded PM controls. You do this by calling the WinSetClipRegion function, specifying the window handle of the Facet Window and the region to be used for clipping, in the control's window coordinates. You calculate the clip region for the PM control based on your part's aggregate clip shape and, optionally, any embedded frames or content elements that obscure it.

On the OS/2 platform, the following sample code positions the button created above at the point (100,100) in content coordinates and sets the window clip region. The ideal place to call this code would be within your part's GeometryChanged method.

    HWND hwndButton = (HWND)facet->GetPartInfo(ev);
    if (hwndButton)
    {
       TempODTransform transform =
                         facet->AcquireWindowContentTransform(ev, kODNULL);

       // We want to display the button if our frame transform specifies
       // only translation and scale (no reflection, rotation or shear) because
       // these operations cannot be performed on PM windows.

       if (transform->GetType(ev) < kODLinearXform )
       {
          ODPlatformCanvas pcanv;

            // Set position of button window

          ODPoint origin(ODIntToFixed(100), ODIntToFixed(100));
          transform->TransformPoint(ev, &origin);
          POINTL ptl = origin.AsPOINTL();
          WinSetWindowPos(hwndButton, 0,
                          origin.x, origin.y, 0, 0,
                          SWP_MOVE);

            // Set clip shape of button window

          TempODShape clipShape = ODCopyAndRelease(ev,
                         facet->AcquireWindowAggregateClipShape(ev, kODNULL);
          clipShape->Transform(ev, transform);

          HRGN hrgnClip = clipShape->CopyRegion(ev);
          pcanv = facet->GetCanvas(ev)->GetPlatformCanvas(ev, kODGPI);
          HPS hps = pcanv->GetPS(ev);
          origin.x = -origin.x;

            // convert region to window coords of button window

          origin.y = -origin.y;
          GpiOffsetRegion(hps, hrgnClip, &origin);
          pcanv->ReleasePS(ev);
          WinSetClipRegion(hwndButton, hrgnClip);
          WinShowWindow(hwndButton, TRUE);
       }

       else
       {
          // We can't properly display the button with the given transform.
          // Hide the window and we'll draw the text into the frame in our Draw
          // method

          WinShowWindow(hwndButton, FALSE);
       }
    }

On the OS/2, Windows, and AIX platforms, the Z-order of facet windows is maintained by OpenDoc.

On the OS/2 platform, WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles are not used by OpenDoc. If you need to calculate the clip region OpenDoc provides the CopyAggregateClipRegion() facet method. The FocusLib utility takes advantage of this OS/2-specific facet method.

On the Windows platform, WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles are used by OpenDoc to clip the facet windows.

Using embedded PM controls can make your part more functional, however, you should always be prepared to render your part without the controls if they cannot be displayed properly. There are several situations in which you would not want to (or would not be able to) display embedded controls. One of these situations is if your part's facet has a frame transform that specified shear or rotation. Since there is no way to shear or rotate a PM window, you should display some alternate representation of your part's content that does not use the PM controls. Another situation where embedded PM controls cannot be displayed is if your part's facet is displayed in an off-screen canvas. In this case, your facet will still have a facet window and you can create the embedded PM controls but they will not be displayed. You should draw a representation of your part's content into the off-screen canvas that does not make use of the embedded controls.

#### Handling Mouse Events on OS/2

By default, mouse events that occur over embedded PM controls are handled by the controls. Your part can specify that it wants to handle mouse events that occur over embedded controls by calling the SetHandleMouseEvents method of the containing facet. You can specify that you want to handle mouse events (excluding drag/drop events), drag/drop events or both. The effect of specifying you want to handle these events in a facet is to make all the embedded controls within that facet transparent to these events.

### Implementing a Dispatch Module

The OpenDoc Dispatcher can be extended by developers to handle additional event types, perhaps associated with some exotic input device like a glove.

ODDispatcher maintains a dictionary of dispatch modules, with the event type as the key. When ODDispatcher::Dispatch() is called, the dispatcher looks up a dispatch module for the supplied event type, and calls its Dispatch() method.

To extend the dispatcher, a developer must implement a subclass of ODDispatchModule, and install it in the dispatcher.

#### Defining a Dispatch Module

ODDispatchModule is a System Object Model (SOM) class, just like ODPart, and must be subclassed (in IDL). The subclass should have an Init<ClassName> method, and should override the Dispatch() method:

    ODBoolean Dispatch(inout ODEventData, inout ODEventInfo)

The Init<ClassName> method should call InitDispatchModule, and the Dispatch() method will presumably locate a part to handle the event, and call its HandleEvent() method.

There is a method Redispatch() defined in ODDispatcher. When the standard OpenDoc dispatch module transforms an event (for example WM_CHAR into kODEvtKeyDown on OS/2, or WM_LBUTTONDOWN to kODEvtMouseDownBorder on Windows), it redispatches. This means that dispatch module patches can intercept the transformed event. The Dispatch() method of ODDispatchModule has an eventInfo parameter containing the additional information associated with some of the transformed events (such as the original event type).

A dispatch module should be installed by calling ODDispatcher::AddDispatchModule:

    void AddDispatchModule(in ODEventType eventType,
                           in ODDispatchModule dispatchModule);

For example:

      myDispatchModule = new MyDispatchModule();
      myDispatchModule->InitMyDispatchModule(ev, session);
      dispatcher->AddDispatchModule(ev, kFooBarEvent, myDispatchModule);

The installation could take place in a part's initialization method, or in a Shell Plug-In.

#### Monitors

A dispatch module can also be installed as a monitor, by calling ODDispatcher::AddMonitor() instead:

      void AddMonitor(in ODEventType eventType,
                      in ODDispatchModule dispatchModule);

Unlike regular dispatch modules, multiple monitors can be installed for a single event type. The dispatcher calls the Dispatch() method of all monitors for an event before calling the Dispatch() method of the single regular dispatch module for that event type. The Boolean result returned by the monitors' implementation of Dispatch() is ignored. In other words, a monitor cannot interfere with the regular dispatching of the event, unless it actually changes the event type on the fly [Shudder].

### Implementing a Focus Module

The OpenDoc Arbitrator can be extended by developers, to handle additional focus types, perhaps associated with some exotic peripheral device.

A focus is simply an ISO standard string. CI Labs will presumably set up a process for registering new focuses for public consumption. The OpenDoc functions use tokenized forms of these strings (using ODSession::Tokenize()).

ODArbitrator maintains a dictionary of focus modules, with the tokenized focus as the key. The methods of ODArbitrator (like RequestFocusSet()) look up a focus module for each focus, and calls appropriate methods of the focus module.

To extend the Arbitrator, a developer must implement a subclass of ODFocusModule, and install it in the arbitrator.

#### Defining a Focus Module

ODFocusModule is a System Object Model (SOM) class, just like ODPart, and must be subclassed (in IDL). The subclass should have an Init<ClassName> method, and should override the methods below. The Init<ClassName> method should call InitFocusModule.

      ODBoolean IsFocusExclusive(in ODTypeToken focus);
      void SetFocusOwnership(in ODTypeToken focus, in ODFrame frame);
      void UnsetFocusOwnership(in ODTypeToken focus, in ODFrame frame);
      void TransferFocusOwnership(in ODTypeToken focus,
              in ODFrame transferringFrame,
              in ODFrame newOwner);
      ODFrame AcquireFocusOwner(in ODTypeToken focus);
      ODFocusOwnerIterator CreateOwnerIterator(in ODTypeToken focus);
      ODBoolean BeginRelinquishFocus(in ODTypeToken focus,
                in ODFrame requestingFrame);
      void CommitRelinquishFocus(in ODTypeToken focus,
              in ODFrame requestingFrame);
      void AbortRelinquishFocus(in ODTypeToken focus,
              in ODFrame requestingFrame);

The programming reference documentation describes the semantics of each method in detail, and the source code for OpenDoc's standard exclusive focus module is included with these recipes. See ExFocus.idl and ExFocus.cpp.

#### Installing a Focus Module

A focus module should be installed by calling ODArbitrator::RegisterFocus:

       void RegisterFocus(in ODTypeToken focus,
                   in ODFocusModule focusModule);

For example:

     // this is assuming that somSelf is your ODPart object
     ODSession *Session = somSelf->GetStorageUnit(ev)->GetSession(ev);
     ODArbitrator *Arbitrator = Session->GetArbitrator(ev);

      const ODFocusType kFooFocus = "Foo";
      ODTypeToken FooFocus = Session->Tokenize(ev,kFooFocus);
      ODMyFocusModule *myFocusModule = new ODMyFocusModule;
      MyFocusModule->InitFocusModule(ev,Session);

      Arbitrator->RegisterFocus(ev,FooFocus,myFocusModule);

The installation could take place in a part's initialization method, or in a shell plug-in.

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