This section provides detailed information on creating a new device, using the dial and button box as a sample.
Creating a new SoXt( C++ ) device class requires these steps:
Select a name for the new device class and determine what class it is derived from.
Define a constructor (see the section called “Constructor”).
Define a destructor.
Define an enable() method, which enables the device for a certain widget or render area (see the section called “Enable Method”).
Define a disable() method, which disables the device when the widget is destroyed.
Define a translateEvent() method, which translates X events into Inventor events (see the section called “Translate Event Method”).
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.
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( C++ ) main loop and passes in the event types (obtained earlier with DeviceMotionNotify(), DeviceButtonPress(), and DeviceButtonRelease()).
The DialNButton translateEvent() method contains two routines:
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 the section called “Translating Events”, the translateEvent() method creates the SoEvent( C++ | Java | .NET ) and then sets the time, state of the modifier keys, and so on.
Example 11.4, “ DialNButton.h ” 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, “ DialNButton.c++ ” 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; }