10.5. Using Event Callback Nodes (Advanced)

If you require an event-handling behavior that is not provided by Inventor manipulators, you can create your own manipulator, or you can write your own event handler using an event callback node. Creating new manipulators is discussed in The Inventor Toolmaker, Chapter 8. Using event callback nodes is discussed in this section.

An event callback node contains a user-written function that is invoked whenever an event of a specified type occurs, when the specified path is picked, and when the handle event action traverses the event callback node. If no path is specified (that is, NULL), the event callback function is invoked automatically every time an event of the specified type occurs and the node is traversed by the handle event action. You can write multiple event callback functions and add them to the list of callback functions maintained by the SoEventCallback( C++ | Java | .NET ) node.

To specify which SoEvents the callback node is interested in and to specify the callback, use the addEventCallback() method:


C++
SoEventCallback *eventCB = new SoEventCallback;
eventCB->addEventCallback(SoKeyboardEvent::getClassTypeId(),
         myCallbackFunc, userData);
  

.NET
SoEventCallback eventCB = new SoEventCallback();
eventCB.AddEventCallback(typeof(SoKeyboardEvent),
        new SoEventCallback.EventCB(myCallbackFunc)); 
  

Java
SoEventCallback eventCB = new SoEventCallback();
eventCB.addEventCallback(SoKeyboardEvent.class, myEventCallbackCB, userData);
  

To specify the path to be monitored, use the setPath() method.

When the callback function is invoked, it is passed the user data and a pointer to the instance of SoEventCallback( C++ | Java | .NET ). To remove a callback function from the event callback list, use the removeEventCallback() method.

[Tip]

Tip: To have your callback invoked for every event type, pass SoEvent::getClassTypeId()SoEventSoEvent as the type.

The SoHandleEventAction( C++ | Java | .NET ), discussed earlier in this chapter, does its work behind the scenes when you use event callback functions. It performs a pick when necessary and caches the pick information. The event callback function itself is responsible for setting whether the event was handled (with the setHandled() method). If there are multiple event callback functions in an event callback node, all of them are invoked, regardless of whether one of them has handled the event.

The event callback function can use any of the following methods on SoEventCallback( C++ | Java | .NET ), which parallel those used in standard Inventor event handling:

getAction()

returns the handle event action applied.

getEvent()

returns the Inventor event to handle.

getPickedPoint()

returns the object hit. The pick is performed automatically by the SoHandleEventAction( C++ | Java | .NET ).

grabEvents()

tells the event callback node to grab events. However, the event callback functions are still invoked only for events of interest.

releaseEvents()

tells the event callback node to stop grabbing events.

setHandled()

tells the action that the event was handled.

isHandled()

returns whether the event has been handled.

Example 10.1, “ Using an Event Callback shows the use of event callback functions with the SoEventCallback( C++ | Java | .NET ) node. It creates an event callback node that is interested in key-press events. The callback function, myKeyPressCB, is then registered with the addEventCallback() method. The scene graph has four objects that can be selected by picking with the left mouse button. (Use the Shift key to extend the selection to more than one object.) When a key-press occurs, it checks to see if the up or down arrow is pressed and scales the picked object up or down accordingly.

Example 10.1.  Using an Event Callback


C++
// An event callback node so we can receive key press events
SoEventCallback *myEventCB = new SoEventCallback;
myEventCB->addEventCallback(
         SoKeyboardEvent::getClassTypeId(), 
         myKeyPressCB, selectionRoot);
selectionRoot->addChild(myEventCB);

...

// userData is the selectionRoot from main().
void
myKeyPressCB(void *userData, SoEventCallback *eventCB)
{
   SoSelection *selection = (SoSelection *) userData;
   const SoEvent *event = eventCB->getEvent();

   // Check for the Up and Down arrow keys being pressed.
   if (SO_KEY_PRESS_EVENT(event, UP_ARROW)) {
      myScaleSelection(selection, 1.1);
      eventCB->setHandled();
   } else if (SO_KEY_PRESS_EVENT(event, DOWN_ARROW)) {
      myScaleSelection(selection, 1.0/1.1);
      eventCB->setHandled();
   }
}
    

.NET
// An event callback node so we can receive key press events
SoEventCallback eventCB = new SoEventCallback();
selectionRoot.AddChild(eventCB);

eventCB.AddEventCallback(typeof(SoKeyboardEvent),
      new SoEventCallback.EventCB(keyCB));

private void keyCB(SoEventCallback sender)
{
    SoSelection selection = (SoSelection)data;
    SoKeyboardEvent evt = (SoKeyboardEvent)sender.GetEvent();
    if (!SoKeyboardEvent.IsKeyReleaseEvent(evt, evt.GetKey())) return;

    if (evt.GetKey() == SoKeyboardEvent.Keys.UP_ARROW)
    {
        myScaleSelection(selectionRoot, 1.1f);
        sender.SetHandled();
    }
    else if (evt.GetKey() == SoKeyboardEvent.Keys.DOWN_ARROW)
    {
        myScaleSelection(selectionRoot, 1.0f / 1.1f);
        sender.SetHandled();
    }
}
  

Java
// An event callback node so we can receive key press events
SoEventCallback eventCB = new SoEventCallback();
selectionRoot.addChild(eventCB);

eventCB.addEventCallback(SoKeyboardEvent.class,
                         new KeyPressCB(), selectionRoot);

class KeyPressCB extends SoEventCallbackCB
{
  public void invoke (SoEventCallback eventCB)
  {
    SoSelection selection = (SoSelection)userData;
    SoKeyboardEvent evt = (SoKeyboardEvent)eventCB.getEvent();
    if (!SoKeyboardEvent.isKeyReleaseEvent(evt, evt.getKey())) return;

    if (evt.getKey() == SoKeyboardEvent.Keys.UP_ARROW)
    {
        myScaleSelection(selection, 1.1f);
        eventCB.setHandled();
    }
    else if (evt.getKey() == SoKeyboardEvent.Keys.DOWN_ARROW)
    {
        myScaleSelection(selection, (float) (1.0 / 1.1));
        eventCB.setHandled();
    }
  }
}