Title Banner

Previous Book Contents Book Index Next

Inside Macintosh: OpenDoc Cookbook /
Chapter 2 - SamplePart Tutorial


Handling Events

OpenDoc calls a part's HandleEvent method when a user event occurs within the purview of a focus currently owned by the part. For example, keystroke events are dispatched to the part that owns the keystroke focus. Geometry-based events, such as mouse clicks, are generally dispatched to the part within whose frames they occur, regardless of which part is currently active.

If the part editor handles the event, it should return a value of kODTrue. It can return a value of kODFalse if it does not handle the event. If the frame's DoesPropagateEvents method returns kODTrue, then the event is sent to the containing frame. If all containing frames fail to handle the event, and they propagate it, the OpenDoc shell attempts to handle the event itself.

For a given event, the dispatcher locates a dispatch module, and the dispatch module calls the part's HandleEvent method. The facet parameter of the HandleEvent method may be null (kODNULL), depending on the kind of event. The frame parameter is always valid, except in the case of some null (idle) events.

Event Constants

OpenDoc expects parts to handle the user events that are standard on the Mac OS, as represented by the following list of constants:

kODEvtNull
kODEvtMouseDown
kODEvtMouseUp
kODEvtKeyDown
kODEvtKeyUp
kODEvtAutoKey
kODEvtUpdate
kODEvtActivate
kODEvtOS
In addition to the standard Mac OS user events, parts should expect to receive OpenDoc-defined events. All parts may receive the events represented by the following constants:

kODEvtMenu
kODEvtWindow
kODEvtMouseEnter
kODEvtMouseWithin
kODEvtMouseLeave
kODEvtBGMouseDown
Container parts (those that can embed other parts) may also receive the events represented by the following constants:

kODEvtMouseDownEmbedded
kODEvtMouseUpEmbedded
kODEvtMouseDownBorder
kODEvtMouseUpBorder
kODEvtBGMouseDownEmbedded
The constant names representing the events differ slightly from the standard Mac OS event names for cross-platform compatibility. Part editors handle these events differently according to their own requirements. Refer to the OpenDoc Programmer's Guide for the Mac OS for detailed information about handling these types of events.

The HandleEvent Method

Generally, the implementation of a part editor's HandleEvent method works in much the same way as event-handling code in a standard Mac OS application. That is, the implementation acquires the event record, then branches to the appropriate event-handling routine based on the type of event. Unlike the standard Mac OS application, you don't need to poll for events by calling WaitNextEvent; in the case of standard events, the event record is passed as a parameter to the HandleEvent method.

The SamplePart object's implementation of the HandleEvent method performs the following actions:

  1. Performs a case switch on expected events.
    An event is represented by an OpenDoc constant compared to the what field of an ODEventData structure, a pointer to which is passed in the event parameter of the HandleEvent call.

  2. Branches to the appropriate subroutine method.
    The HandleEvent method handles simple events without branching.

  3. Returns a Boolean value indicating whether or not the event was handled.
Listing 2-27 shows the implementation of the HandleEvent method.

Listing 2-27 HandleEvent method

ODBoolean SamplePart::HandleEvent( Environment*   ev,
                                   ODEventData*   event,
                                   ODFrame*       frame,
                                   ODFacet*       facet,
                                   ODEventInfo*   eventInfo )
{
   SOM_Trace("SamplePart","HandleEvent");

   ODBooleaneventHandled = kODFalse;

   switch ( event->what )
   {
      case kODEvtMouseDown:
      case kODEvtMouseUp:
         eventHandled = this->HandleMouseEvent(ev, event, facet, eventInfo);
         break;
      case kODEvtMenu:
         eventHandled = this->HandleMenuEvent(ev, event, frame);
         break;
      case kODEvtActivate:
         this->WindowActivating(ev, frame, (event->modifiers & activeFlag));
         eventHandled = kODTrue;
         break;
      case kODEvtMouseEnter:
      case kODEvtMouseLeave:
         SetCursor(&ODQDGlobals.arrow);
         eventHandled = kODTrue;
         break;
      case kODEvtMouseWithin:
         eventHandled = kODTrue;
         break;
      case kODEvtNull:
      case kODEvtMouseDownEmbedded:
      case kODEvtMouseUpEmbedded:
      case kODEvtMouseDownBorder:
      case kODEvtMouseUpBorder:
      case kODEvtWindow:
      case kODEvtKeyDown:
      case kODEvtKeyUp:
      case kODEvtAutoKey:
      case kODEvtOS:
      case kODEvtDisk:
      default:
         break;
   }
   return eventHandled;
}
The SamplePart object's HandleEvent method illustrates a minimal set of event handlers that every part editor should implement. Naturally, you must also prepare to handle other events to which your part must respond to behave correctly.

The HandleMouseEvent Method

SamplePart calls its own internal HandleMouseEvent method from its HandleEvent method when it receives a mouse event of type kODEvtMouseUp or kODEvtMouseDown. OpenDoc passes the mouse event as a parameter to the part's HandleEvent method when the user clicks the mouse button within the bounds of one of the part's facets.

When a frame is inactive, the first mouse-up event (kODEvtMouseUp) it receives should activate it. Inactive frames do not receive mouse-down events (kODEvtMouseDown).

The HandleMouseEvent method performs the following actions:

  1. Ensures that the facet in which the mouse event occurred is valid.
    If the facet parameter is null, the mouse event occurred outside the bounds of a modal window, in which case the implementation causes the Mac OS to sound a single system beep.

  2. Handles a mouse-up event.
    After determining that the event occurred inside a valid facet, the method tests the event type against the kODEvtMouseUp constant.

  3. Handles the window's activation state.
    If the event is a mouse-up event, HandleMouseEvent checks the facet's window. If the window is not active, the method selects it and returns a value of kODTrue, which indicates that the method handled the mouse-up event. If the facet's window is already active, the method continues.

  4. Handles the frame's activation state.
    HandleMouseEvent retrieves the facet's frame and the frame's CFrameInfo part info object. Using this information, the method determines if this is the active frame; if not, it calls its ActivateFrame method, which activates the frame by requesting the selection and menu foci.

    The method stores the active facet in its frame's CFrameInfo object, so the part editor will be able to position a part window properly if the user later chooses the View as Window command. If the ActivateFrame method call returned successfully, HandleMouseEvent returns kODTrue; otherwise it returns kODFalse.

  5. Handles a mouse-down event.
    If the event was not a mouse-up event, HandleMouseEvent tests if it was of type kODEvtMouseDown. If so, the method localizes the coordinates of the mouse-down event to the facet's coordinates and calls the SamplePart object's internal DoMouseEvent method.

    The SamplePart object's DoMouseEvent method is empty. A part editor with real work to do in response to a mouse-down event would do it at this point. For example, if your part supports selection of its content by dragging the mouse, as with a marquee or lasso tool, you would handle those events at this point. Similarly, you would handle buttons or other controls here if they were managed directly by your part.

Listing 2-28 shows the HandleMouseEvent method. The ActivateFrame method is included in the "Activation" section as Listing 2-38.

Listing 2-28 HandleMouseEvent method

ODBoolean SamplePart::HandleMouseEvent( Environment*   ev,
                                        ODEventData*   event,
                                        ODFacet*       facet,
                                        ODEventInfo*   eventInfo )
{
   SOM_Trace("SamplePart","HandleMouseEvent");

   if ( facet != kODNULL )
   {
      if ( event->what == kODEvtMouseUp )
      {
         ODWindow* window = facet->GetWindow(ev);
         TRY
            if ( !window->IsActive(ev) )
               window->Select(ev);
            else
            {
               ODFrame* frame = facet->GetFrame(ev);
               
               CFrameInfo* frameInfo = (CFrameInfo*) frame->GetPartInfo(ev);
               if ( !frameInfo->IsFrameActive() )
               {
                  if ( this->ActivateFrame(ev, frame) )
                     frameInfo->SetActiveFacet(facet);
                  else
                     return kODFalse;
               }
            }
         CATCH_ALL
         ENDTRY
      }
      else if ( event->what == kODEvtMouseDown )
      {
         Point where;
         where.h = FixedToInt(eventInfo->where.x);
         where.v = FixedToInt(eventInfo->where.y);
         this->DoMouseEvent(ev, facet, &where);
      }
   }
   else
   {
      SysBeep(1);
   }
   return kODTrue;
}

The HandleMenuEvent Method

SamplePart calls its own internal HandleMenuEvent method when it receives a menu event (type kODEvtMenu). OpenDoc converts a mouse-down event that occurs in the menu bar, or its keyboard equivalent, into a menu event. On receiving an event of this type, the SamplePart object's HandleEvent method calls HandleMenuEvent, passing the event record and a pointer to the active frame.

The HandleMenuEvent method performs the following actions:

  1. Retrieves the message field of the event record.
    The method uses the message field to determine the number of the menu (contained in the high-order word) and the number of the menu item (contained in the low-order word), for the menu selection made by the user.

  2. Retrieves the position-independent number of the command.
    With the menu and item numbers, the method calls the menu bar object's GetCommand method, which returns the command number of the user's menu selection.

  3. Branches to the appropriate command handler method.
    Comparing the command number to constants representing the commands SamplePart can handle, the HandleMenuEvent method proceeds into a switch statement. SamplePart implements only two commands: About and View As Window. These cases call their appropriate subroutine method and return kODTrue. The remaining unimplemented command numbers return kODFalse by way of the default clause.

Listing 2-29 shows the implementation of the HandleMenuEvent method.

Listing 2-29 HandleMenuEvent method

ODBoolean SamplePart::HandleMenuEvent( Environment*   ev,
                                       ODEventData*   event,
                                       ODFrame*       frame )
{
    SOM_Trace("SamplePart","HandleMenuEvent");

   ODULong  menuResult= event->message;
   ODUShort menu     = HiWord(menuResult);
   ODUShort item     = LoWord(menuResult);

   switch ( gGlobals->fMenuBar->GetCommand(ev, menu, item) )
   {
      case kODCommandAbout:
         this->DoDialogBox(ev, frame, kAboutBoxID);
         break;

      case kODCommandViewAsWin:
         this->Open(ev, frame);
         break;

      case kODCommandOpen:
      case kODCommandInsert:
      case kODCommandPageSetup:
      case kODCommandPrint:
      case kODCommandUndo:
      case kODCommandRedo:
      case kODCommandCut:
      case kODCommandCopy:
      case kODCommandPaste:
      case kODCommandPasteAs:
      case kODCommandClear:
      case kODCommandSelectAll:
      case kODCommandGetPartInfo:
      case kODCommandPreferences:
      default:
         return kODFalse;
   }
   return kODTrue;
}

The AdjustMenus Method

OpenDoc calls a part's AdjustMenus method when a user event of type kODEvtMouseDown occurs in the menu bar and the same part owns the menu focus. AdjustMenus is a general-purpose menu-handling method. Its purpose is to ensure that the visible state of the part's menus accurately reflect the state of the part. Accordingly, the AdjustMenus method enables and disables menu items, depending on whether or not their commands are available, and it changes the menu item text as necessary to describe accurately the actions ensuing from choosing those items.

The SamplePart object's implementation of the AdjustMenus method performs the following actions:

  1. Validates the menu bar if this part is the root part.
    The menu bar object always calls the root part's AdjustMenus method before calling the menu focus owner's AdjustMenus method. Any other part can swap out the base menu bar at any time. Therefore, if the menu bar object has changed since it was previously copied, the method recopies the base menu bar from the window-state object. After copying the menu bar, you must also reinstall your part's menus.

  2. Enables or disables the menu commands, depending on conditions.
    The method enables the View As Window command, but only if the frame that owns the menu focus (a pointer to which is passed into the method as it is called) is not the root frame of the window. (The frame that owns the menu focus is usually the active frame.)

  3. Sets the text of the About menu item correctly.
    The method puts a reference to the focus owner's frame into a temporary frame object and tests it against the frame reference passed into this method call. If this frame owns the menu focus, the method gets the About menu item text from the SamplePart menu string resource, creates a temporary international text structure for the text, and sets the menu item. The temporary object automatically disposes of the memory allocated for the international text.

Listing 2-30 shows the implementation of the AdjustMenus method.

Listing 2-30 AdjustMenus method

void SamplePart::AdjustMenus( Environment*   ev,
                              ODFrame*       frame )
{
   SOM_Trace("SamplePart","AdjustMenus");

   if ( frame->IsRoot(ev) )
   {
      if ( gGlobals->fMenuBar->IsValid(ev) == kODFalse )
      {
         ODReleaseObject(ev, gGlobals->fMenuBar);
         gGlobals->fMenuBar = 
               ODGetSession(ev,fSelf)->GetWindowState(ev)->CopyBaseMenuBar(ev);
      }
   }     

   gGlobals->fMenuBar->EnableCommand(ev, kODCommandViewAsWin, !frame->IsRoot(ev));
   
   TRY
      ODArbitrator* arbitrator = ODGetSession(ev,fSelf)->GetArbitrator(ev);
      TempODFrame menuOwner = 
                     arbitrator->AcquireFocusOwner(ev, gGlobals->fMenuFocus);
   
      if ( ODObjectsAreEqual(ev, frame, menuOwner) )
      {
         Str63 text;
         ODGetIndString(text, kMenuStringResID, kAboutTextID);
         TempODIText menuItem(CreateIText(gGlobals->fEditorsScript, 
                        gGlobals->fEditorsLanguage, (StringPtr)&text));
         gGlobals->fMenuBar->SetItemString(ev, kODCommandAbout, menuItem);
      }
   CATCH_ALL
      // Consume exception
   ENDTRY
}

The DoDialogBox Method

SamplePart calls its own internal DoDialogBox method from its HandleMenuEvent method when the user chooses the About command. SamplePart also calls DoDialogBox from other methods to display error messages to the user. The method illustrates how parts can display a modal dialog box properly.

The DoDialogBox method performs the following actions:

  1. Gets access to the session object.
    Access to the session object is provided by the ODGetSession utility function. The session object, in turn, provides needed access to the arbitrator and window-state objects.

  2. Gets a valid frame.
    Only frames own foci. If the calling method does not pass in a valid frame reference, the DoDialogBox method gets one from SamplePart's internal list of display frames. This frame requests the modal focus needed to keep other parts from displaying a modal dialog box simultaneously.

  3. Requests the modal focus from the arbitrator.
    If its focus request is not satisfied, the method causes the Mac OS to sound its system beep. Being unable to acquire the modal focus indicates that another modal dialog box is already being displayed.

  4. Deactivates the frontmost document window.
    If its focus request is satisfied, the method calls the window-state object's DeactivateFrontWindows method.

  5. Displays the About box.
    The method uses the OpenDoc utility routine BeginUsingLibraryResources to make the resources in its shared library available and uses the Mac OS Toolbox routine GetNewDialog to retrieve the dialog resource.

    If an error number greater than 0 was passed into this method, it sets up an error dialog box to display.

    If the dialog box resource has loaded properly, the DoDialogBox method ensures that the cursor is an arrow, shows the dialog box window, and calls the Mac OS Toolbox routine ModalDialog to display and handle the user's interaction with the dialog box.

  6. Cleans up after itself.
    The method disposes of the dialog resource returned from the previous GetNewDialog routine. Finally, it restores the resource chain by calling EndUsingLibraryResources, relinquishes the modal focus to the arbitrator, and reactivates the frontmost document window.

Listing 2-31 shows the implementation of the DoDialogBox method.

Listing 2-31 DoDialogBox method

void SamplePart::DoDialogBox( Environment*    ev,
                              ODFrame*        frame,
                              ODSShort        dialogID,
                              ODUShort        errorNumber )
{
   SOM_Trace("SamplePart","DoDialogBox");

   ODFrame* focusFrame = frame;
   ODSession*session = ODGetSession(ev,fSelf);
   
   if ( focusFrame == kODNULL )
   {
      CListIterator fiter(fDisplayFrames);
      for ( CFrameProxy* proxy = (CFrameProxy*) fiter.First();
            fiter.IsNotComplete(); proxy = (CFrameProxy*) fiter.Next() )
      {
         if ( proxy->FrameIsLoaded() )
            focusFrame = proxy->GetFrame(ev);
         if ( focusFrame ) break;
      }
   }
   if ( session->GetArbitrator(ev)->RequestFocus(ev, gGlobals->fModalFocus,
                                                       focusFrame) )
   {
      DialogPtrdialog;
      ODSShort itemHit;
      session->GetWindowState(ev)->DeactivateFrontWindows(ev);
            
      ODSLong rfRef;
      rfRef = BeginUsingLibraryResources();
      {        
         dialog = GetNewDialog(dialogID, kODNULL, (WindowPtr) -1L);
         if ( dialog )
         {
            if ( errorNumber > 0 )
            {
               HandleitemHandle;
               Rect  itemRect;
               short itemType;
               Str255errStr;
               
               GetIndString(errStr, kErrorStringResID, errorNumber);
               GetDialogItem(dialog, kErrStrFieldID, &itemType, 
                                          &itemHandle, &itemRect);
               SetDialogItemText(itemHandle, errStr);
               HideDialogItem(dialog, cancel);
               SetDialogDefaultItem(dialog, ok);
            }
            SetCursor(&ODQDGlobals.arrow);
            ShowWindow(dialog);
            ModalDialog(kODNULL, &itemHit);
            DisposeDialog(dialog);
         }
         else
         {
            SysBeep(2);
         }
      }
      EndUsingLibraryResources(rfRef);
      session->GetArbitrator(ev)->RelinquishFocus(ev, gGlobals->fModalFocus,
                                                        focusFrame);
      session->GetWindowState(ev)->ActivateFrontWindows(ev);
   }
   else
      SysBeep(2);
}

The View As Window Command

If the user chooses the View As Window command, the HandleMenuEvent method calls the SamplePart object's Open method, which is described in "Opening the Part Into a Window".


Previous Book Contents Book Index Next

© Apple Computer, Inc.
16 JUL 1996




Navigation graphic, see text links

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help