Chapter 11. Creating an Event and Device

This chapter describes how to create a new event and a new Xt device. The examples show how to subclass events and Xt devices and how to translate X events into Inventor events.

This chapter focuses on translating X events into Inventor events. This general process can be applied to the translation of any window-specific events into Inventor events, which are independent of the window system. Examples 11-1 and 11-2 show how to create the DialEvent, which is used by the dial and button device.

The details of setting up an SoXt device to work with a render area are also explained here. Note that new devices can be added only if you are using the Inventor Component Library. Examples 11-4 and 11-5 use the DialNButton device to show how to translate events and communicate with the render area.

Note that new events will not automatically be understood by existing manipulators. You can create new manipulators (or other objects that respond to events) to recognize the new events. Or you can register a new static method for an existing manipulator in the method list for the SoHandleEventAction. Then your new method for the manipulator can handle the new events appropriately (see Chapter 4).

Before reading this chapter, be sure to read Chapters 10 and 16 in The Inventor Mentor.

Creating an Event

This section describes creating a new event and offers background information on translating an event. For information on creating a new device, see “Creating a Device”. The device's main responsibility is translating events, which is described in more detail in “Dispatching Events”.

Overview

The file SoSubEvent.h contains the macros for defining new event classes. The SO_EVENT_HEADER() macro declares type identifier and naming variables and methods that all event classes must support. The SO_EVENT_SOURCE() macro defines the static variables and methods declared in the SO_EVENT_HEADER() macro.

Creating a new event requires these steps:

  1. Select a name for the new event class and determine what class it is derived from.

  2. Define an initClass() method to initialize the type information. Use the SO_EVENT_INIT_CLASS() macro. The application needs to call the event's initClass() method immediately after SoXt::init.

  3. Define a constructor.

  4. Define a destructor.

  5. Implement set() and get() methods for the additional information your event provides. For example, the dial event needs to include information on which dial was turned, and what its value is.

  6. Write convenience routines for the event to perform common queries and tasks (optional step). For the dial box, the convenience routine is isDialEvent().

  7. Write convenience macros for the event (optional step). These are static functions that are used in event callback functions. For the dial, the macro is DIAL_EVENT(). Note that it uses the convenience routines defined in step 6.

Translating Events

When an event is dispatched, the event translator creates an Inventor event from an X event and sets its values (see “Dispatching Events”). It provides all the information about the event, including the following:

  • Time the event occurred

  • Position of the locator when the event occurred

  • State of the modifier keys (Shift, Control, Alt) when the event occurred

  • Any additional information required by the event (for example, if a keyboard key is pressed, which key is it?)

Inventor includes three subclasses of SoEvent. SoButtonEvent includes additional information about the button state (is it up or down?). Subclasses of SoButtonEvent provide information about which button was pressed. SoMotion3Event includes information on translation and rotation values generated by an input device such as the spaceball. SoLocation2Event includes information on the absolute location of the cursor in window coordinates.

A value such as the event's time or position is read-only during event traversal because the event is passed as a const pointer. Only the creator of an event can set its values.

Creating a Dial Event

The dial and button input device generates two X events that need to be translated into Inventor events and handled by the database:

XDeviceMotionEvent 


provides value changes of the eight dials

XDeviceButtonEvent  


provides information about the state of the device's 32 buttons

The information provided by XDeviceMotionEvent is translated into a DialEvent. The XDeviceButtonEvent is translated into a ButtonBoxEvent, which is subclassed from SoButtonEvent and has button information specific to the button box.

This section discusses the code for the DialEvent, which describes the state of any of the eight dials. Note, however, that you could instead choose to create a more generic event that could be used for other devices in addition to the dial box. For example, you could create a ResetToHomePositionEvent that would be used when the user presses a button box button, clicks on a Home button on the screen, or performs some other designated action.


Tip: Be sure to call initClass() on the event after initializing Inventor.

Example 11-1 shows the code for the dial event include file.

Example 11-1. DialEvent.h


#include <Inventor/SbBasic.h>
#include <Inventor/events/SoEvent.h>
#include <Inventor/events/SoSubEvent.h>

// Convenience macro for determining if an event matches
#define DIAL_EVENT(EVENT, WHICH) \
   (DialEvent::isDialEvent(EVENT, WHICH))

class DialEvent : public SoEvent {

   SO_EVENT_HEADER();
   
  public:
   // Constructor
   DialEvent();
   
   // Which dial generated the event, 1-8
   void     setDial(int d)   { dial = d; }
   int      getDial() const  { return dial; }
   
   // Value of the dial turned
   void     setValue(int v)  { value = v; }
   int      getValue() const { return value; }
   
   // Convenience routines to see if an SoEvent is a turn of
   // the passed dial. Passing -1 matches any button.
   static SbBool        isDialEvent(const SoEvent *e, 
                                   int which = -1);
   
     static void          initClass();
   
  private:
   int                  dial;               // Which dial
   int                  value;              // Value of dial
};

Example 11-2 shows the complete source code for the dial event.

Example 11-2. DialEvent.c++


#include "DialEvent.h"

SO_EVENT_SOURCE(DialEvent);

// Class initialization
void
DialEvent::initClass()

{
   SO_EVENT_INIT_CLASS(DialEvent, SoEvent);
}

// Constructor
DialEvent::DialEvent()
{
   dial = 0;
   value = 0;
}

// Convenience routine - this returns TRUE if the event is a
// dial turn event matching the passed dial.
SbBool
DialEvent::isDialEvent(const SoEvent *e, int whichDial)
{
   SbBool isMatch = FALSE;
   
   // is it a dial event?
   if (e->isOfType(DialEvent::getClassTypeId())) {
     const DialEvent *de = (const DialEvent *) e;
   
     // did the caller want any dial turn? or do they match?
     if ((whichDial == -1) ||
        (de->getDial() == whichDial))
       isMatch = TRUE;
   }
   
   return isMatch;
}

Dispatching Events

Using the DialNButton device as an example, this section outlines in more detail how the event translator works with the X Server and the SoXt main loop to obtain X events and translate them into Inventor events.


Note: The way SoXt main loop works with the event translator is slightly complex, for the following reasons. The X Window System provides compile-time event types only for the mouse and keyboard devices. The X input extension provides events for all other devices. The complication arises because the Xt Library ignores the X input extension. For this reason, we must get the complete list of events at runtime. SoXt sets up its own event handler to dispatch the extension events, and Xt dispatches the standard events (see “SoXt Main Loop”). For more information, see the MIT X Consortium Standard, “X11 Input Extension Library Specification,” X Version 11, Release 5.


Translating an Event

Figure 11-1 shows the general sequence for translating an event. (Because the device's main responsibility is to translate events, it is referred to here simply as the translator.) Before events can be translated, certain information must be conveyed:

  1. The application registers its software devices with the render area. (See The Inventor Mentor, Chapter 16.) Mouse and keyboard devices are handled automatically. All additional devices must be explicitly registered using the SoXtRenderArea::registerDevice() method.

  2. The translator tells the X Server which event classes it is interested in.

  3. The translator tells the SoXt main loop which event types it is interested in.

    After these entities have been notified, the translator is ready to receive and translate events. The following steps describe this process. Remember that the dispatching of events is slightly complicated because X input extensions must be dealt with separately in the main loop routine:

    Figure 11-1. Sequence for Translating an Event


  4. The physical input device generates events.

  5. If these are X events, they are sent to the SoXt main loop for dispatching. (If the device doesn't generate X events, your event translator needs to do some extra work.)

  6. Within the SoXt main loop, Xt dispatches standard X events to the appropriate widget (the corresponding render area will then use the correct translator). SoXt dispatches the X input extension events to the render area, which in turn employs the translators to translate the event.

  7. The translator translates the event into an Inventor SoEvent subclass.

  8. The SoEvent is sent to the scene manager, which uses a handle event action to traverse the scene database until a node is found to handle the event (typically a manipulator or selection node).

SoXt Main Loop

The code for the SoXt main loop is shown in Example 11-3 so that you can see how the main loop dispatches both standard X events and X input extension events. You shouldn't have to modify this code.

Example 11-3. SoXtMainLoop


void
SoXt::mainLoop()
{
   XtAppContext context = getAppContext();
   XEvent event;
   
   for (;;) {
      SoXt::nextEvent(context, &event);
      SoXt::dispatchEvent(&event);
   }
}

void
SoXt::nextEvent(XtAppContext appContext, XEvent *event)
{
   XtAppNextEvent(appContext, event);
}

Boolean
SoXt::dispatchEvent(XEvent *event)
{
   Boolean success = True;
    
   if (event->type >= LASTEvent) {
      XtEventHandler proc;
      XtPointer clientData;
      Widget w;
      Boolean dummy;
      
      // Get the event handling function which was
      // registered with Inventor for handling this
      // event type in this widget
      SoXt::getExtensionEventHandler(event, w, proc, clientData);
      
      // Call the event handler!
      if (proc == NULL)
         success = False;
      else
         (*proc)(w, clientData, event, &dummy);
      
   }
   
   // else it is not an extension event - let Xt dispatch it
   else 
      success = XtDispatchEvent(event);
   
   return success;
}

Creating a Device

This section provides detailed information on creating a new device, using the dial and button box as a sample.

Overview

Creating a new SoXt device class requires these steps:

  1. Select a name for the new device class and determine what class it is derived from.

  2. Define a constructor (see “Constructor”).

  3. Define a destructor.

  4. Define an enable() method, which enables the device for a certain widget or render area (see “Enable Method”).

  5. Define a disable() method, which disables the device when the widget is destroyed.

  6. Define a translateEvent() method, which translates X events into Inventor events (see “Translate Event Method”).

Constructor

First, the constructor for the device obtains a list of the input devices currently attached to the display. In our example, it loops through the list and looks for a device named “dial+buttons.” If found, it opens that device (using XOpenDevice).

Next, the device queries the X Server for the event types it generates. (Recall that these const values are available only at runtime because they are part of the X input extension.) The DeviceMotionNotify() function returns the event class and event type for motion events. The DeviceButtonPress() and DeviceButtonRelease() functions return the event class and event type for the button-press and button-release events.

Enable Method

In its enable() method, the DialNButton device calls XSelectExtensionEvent() to register interest in its event classes with the X Server.

The device also must inform Inventor's main loop about the extension events it is prepared to translate. To do this, it calls addExtensionEventHandler() on SoXt main loop and passes in the event types (obtained earlier with DeviceMotionNotify(), DeviceButtonPress(), and DeviceButtonRelease()).

Translate Event Method

The DialNButton translateEvent() method contains two routines:

translateMotionEvent()  


translates an XDeviceMotionEvent into a DialEvent

translateButtonEvent()  


translates an XDeviceButtonEvent into a ButtonBoxEvent

First, the translateMotionEvent() sets the position, time, and the state of the Shift, Control, and Alt keys at the time of the event. Then, it sets the dial and value. The variables in the XDeviceMotionEvent are stored differently for different devices. Check the documentation for your device driver for information on how data is stored in this event. The XDeviceMotionEvent for the dial box stores which dial was turned in its first_axis field, and the value of that dial in its axis_data[0] variable.

As described in “Translating Events”, the translateEvent() method creates the SoEvent and then sets the time, state of the modifier keys, and so on.

Example 11-4 shows the code for DialNButton.h.

Example 11-4. DialNButton.h


#include <X11/X.h>
#include <X11/extensions/XInput.h>
#include <Inventor/Xt/devices/SoXtDevice.h>
#include <Inventor/events/SoButtonEvent.h>

class ButtonBoxEvent;
class DialEvent;

class DialNButton : public SoXtDevice {
  public:
   // The first constructor uses the display set when 
   // SoXt::init is called.
   DialNButton();
   DialNButton(Display *d);
   ~DialNButton();
   
   // These functions will enable/disable this device for the 
   // widget. The callback function f will be invoked when 
   // events occur in w. data is the clientData which will be
   // passed.
   virtual void        enable(Widget w, XtEventHandler f,
                          XtPointer data, Window win = NULL);
   virtual void        disable(Widget w, XtEventHandler f,
                          XtPointer data);
   
   // This converts an X event into an SoEvent,
   // returning NULL if the event is not from this device.
   //
      virtual const SoEvent * translateEvent(XAnyEvent *xevent);
   
   // Return whether or not the dial+button device exists for use.
   // The first uses the display set when SoXt::init is called.
   static SbBool        exists() 
                           { return exists(SoXt::getDisplay()); }
   static SbBool        exists(Display *d);
   
  protected:
   // Initialize the device.
   static void          init(Display *d);
   static SbBool        firstTime;
   
   // These event types are retrieved from the X server at run
   // time.
   static int           motionEventType;
   static int           buttonPressEventType;
   static int           buttonReleaseEventType;
   
   // Event classes passed to XSelectExtensionEvent.
   static XEventClass   eventClasses[3];//max of 3 event classes
   static int           eventTypes[3]; // max of 3 event types
   
   // Device id is set at runtime.
   static XDevice       *device;
   
   // Inventor events generated by this device.
   ButtonBoxEvent       *buttonEvent;
   DialEvent            *dialEvent;

   // Event translators!
   DialEvent       *translateMotionEvent(XDeviceMotionEvent *me);
   ButtonBoxEvent  *translateButtonEvent(
                       XDeviceButtonEvent *be,
                       SoButtonEvent::State whichState);
};

Example 11-5 shows the source code for DialNButton.c++.

Example 11-5. DialNButton.c++


#include <X11/Xlib.h>
#include <X11/extensions/XI.h>

#include <Inventor/SbTime.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/events/SoButtonEvent.h>

#include "ButtonBoxEvent.h"
#include "DialEvent.h"
#include "DialNButton.h"

extern "C" {
XDeviceInfo *XListInputDevices(Display *, int *);
XDevice     *XOpenDevice(Display *, XID);
int         XSelectExtensionEvent(Display *, Window, XEventClass *, int);
}

#define DEVICE_NAME "dial+buttons"

// There are 3 event classes for this device:
// motion, button down, button up.
static const int numEventClasses = 3;

// Static members
SbBool DialNButton::firstTime = TRUE;
int DialNButton::motionEventType;
int DialNButton::buttonPressEventType;
int DialNButton::buttonReleaseEventType;
XEventClass DialNButton::eventClasses[3];
int DialNButton::eventTypes[3];
XDevice *DialNButton::device;

// Description:
//  Initialize the dial+button device. 
//  We only need to do this once.

void
DialNButton::init(Display *display)
{    
   // If already initialized, return.
   if (! firstTime) 
      return;
   
   firstTime = FALSE;
   
   // Get the list of input devices that are attached to the
   // display now.
   XDeviceInfoPtr  list;
   int              numDevices;
   
   list = (XDeviceInfoPtr) XListInputDevices(display, &numDevices);
   
   // Now run through the list looking for the dial+button
   // device.
   device = NULL;
   for (int i = 0; (i < numDevices) && (device == NULL); i++) {
     // Open the device - the device id is set at runtime.
     if (strcmp(list[i].name, DEVICE_NAME) == 0) {
       device = XOpenDevice(display, list[i].id);
     }
   }
   
   // Make sure we found the device
   if (device == NULL) {
     fprintf(stderr, "DialNButton::init",
       "Sorry there is no dial and button attached to this display");
     return;
   }
   
   // Query the event types and classes
   unsigned long eventClass;
   
   DeviceMotionNotify(device, motionEventType, eventClass);
   eventClasses[0] = eventClass;
   eventTypes[0] = motionEventType;
   
   DeviceButtonPress(device, buttonPressEventType, eventClass);
   eventClasses[1] = eventClass;
   eventTypes[1] = buttonPressEventType;

   DeviceButtonRelease(device, buttonReleaseEventType, 
                       eventClass);
   eventClasses[2] = eventClass;
   eventTypes[2] = buttonReleaseEventType; 
   
   // Init all dial values to 0
   static int vals[8] = {0, 0, 0, 0, 0, 0, 0, 0};
   XSetDeviceValuators(display, device, vals, 0, 8);  
}

// Constructor using default display
DialNButton::DialNButton()
{    
   init(SoXt::getDisplay());

   buttonEvent = new ButtonBoxEvent;
   dialEvent = new DialEvent;
}
   
// Constructor
DialNButton::DialNButton(Display *d)
{    
   init(d);

   buttonEvent = new ButtonBoxEvent;
   dialEvent = new DialEvent;
}
   
// Destructor
DialNButton::~DialNButton()
{
   delete buttonEvent;
   delete dialEvent;
}

// Returns whether the dial+button device exists for use or
// not.
SbBool
DialNButton::exists(Display *display)
{
   // Get the list of input devices that are attached to the
   // display now.
   XDeviceInfoPtr  list;
   int              numDevices;
   
   list = (XDeviceInfoPtr) XListInputDevices(display, &numDevices);
   
   // Now run through the list looking for the dial + button
   // device.
   for (int i = 0; (i < numDevices) &&
               (strcmp(list[i].name, DEVICE_NAME) != 0); i++)
     ; // keep looping

   // If we broke out of the loop before i reached numDevices,
   // then the dial + button does in fact exist. 
   return (i < numDevices);
}

// This selects input for dial + button device events which
// occur in w.
// The callback routine is proc, and the callback data is
// clientData.
void
DialNButton::enable(
   Widget w,
   XtEventHandler proc, 
   XtPointer clientData,
   Window window)
{
   if (numEventClasses == 0) 
      return;
   
   Display *display = XtDisplay(w);
   if (display == NULL) {
     fprintf(stderr, "DialNButton::enable",
             "SoXt::init not properly called (Display is NULL).");
     return;
   }
   
   if (w == NULL) {
     fprintf(stderr, "DialNButton::enable",
             "widget is NULL.");
     return;
   }
   
   if (window == NULL) {
     fprintf(stderr, "DialNButton::enable",
             "widget must be realized (Window is NULL).");
     return;
   }
   
   // Select extension events for the dial + button which the
   // user wants.
   XSelectExtensionEvent(display, window,
                         eventClasses, numEventClasses);
   
   // Tell Inventor about these extension events!
   for (int i = 0; i < numEventClasses; i++)
     SoXt::addExtensionEventHandler(
       w, eventTypes[i], proc, clientData);
}

// This unselects input for dial + button device events which
// occur in w,
// i.e. dial + button events will no longer be recognized.
void
DialNButton::disable(
   Widget w,
   XtEventHandler proc, 
   XtPointer clientData)
{
   // Tell Inventor to forget about these classes.
   for (int i = 0; i < numEventClasses; i++)
     SoXt::removeExtensionEventHandler(
       w, eventTypes[i], proc, clientData);
}

// Translate X events into Inventor events.
const SoEvent *
DialNButton::translateEvent(XAnyEvent *xevent)
{
   SoEvent *event = NULL;
   
   // See if this is a dial + button event.
   if (xevent->type == motionEventType) {
     XDeviceMotionEvent *me = (XDeviceMotionEvent *) xevent;
     if (me->deviceid == device->device_id)
       event = translateMotionEvent(me);
   }
   else if (xevent->type == buttonPressEventType) {
     XDeviceButtonEvent *be = (XDeviceButtonEvent *) xevent;
     if (be->deviceid == device->device_id)
       event = translateButtonEvent(be, SoButtonEvent::DOWN);
   }
   else if (xevent->type == buttonReleaseEventType) {
     XDeviceButtonEvent *be = (XDeviceButtonEvent *) xevent;
     if (be->deviceid == device->device_id)
       event = translateButtonEvent(be, SoButtonEvent::UP);
   }
   
   return event;
}

// This returns a DialEvent for the passed X event.
DialEvent *
DialNButton::translateMotionEvent(XDeviceMotionEvent *me)
{
   setEventPosition(dialEvent, me->x, me->y);
   dialEvent->setTime(SbTime(0, 1000*me->time));
   dialEvent->setShiftDown(me->state & ShiftMask);
   dialEvent->setCtrlDown(me->state & ControlMask);
   dialEvent->setAltDown(me->state & Mod1Mask);
   
   // the dial that turned is stored as first_axis in the X event.
   // the value is always in axis_data[0].
   dialEvent->setDial(me->first_axis);
   dialEvent->setValue(me->axis_data[0]);
   
   return dialEvent;
}

// This returns a ButtonBoxEvent for the passed X event.
ButtonBoxEvent *
DialNButton::translateButtonEvent(
   XDeviceButtonEvent *be,
   SoButtonEvent::State whichState)
{
   setEventPosition(buttonEvent, be->x, be->y);
   buttonEvent->setTime(SbTime(0, 1000*be->time));
   buttonEvent->setShiftDown(be->state & ShiftMask);
   buttonEvent->setCtrlDown(be->state & ControlMask);
   buttonEvent->setAltDown(be->state & Mod1Mask);
   
   // Set which button along with its state.
   buttonEvent->setButton(be->button);
   buttonEvent->setState(whichState);
   
   return buttonEvent;
}