This section describes the general process of creating a new component. The sample class shown here is derived from SoXtRenderArea . The second half of this chapter, beginning with Section 10.2, “Creating a New Viewer”, describes creating a more specialized component, a viewer.
There are no special macros for creating new component classes. Creating a new component requires these steps:
Select a name for the new component class and determine what class it is derived from (see the section called “Overview” for a discussion of deriving new viewers).
Define a constructor for the new class. If you want other programmers to be able to derive classes from your new class, you need to define two constructors for the class, a public constructor and a protected one (see the section called “Defining the Constructor”). If no one will be deriving classes from your new class, you can simply define a public constructor.
Implement show() and hide() methods for your component (optional step). The base class, SoXtComponent , takes care of showing and hiding your new component. But if your component needs to show other components when it is shown, or hide other components when it is hidden, you need to implement these two methods for your new class (see the section called “The show() and hide() Methods”).
Implement the visibility-changed callback function (optional step). This function is called when the component changes state between visible and invisible (see the section called “Visibility-Changed Callback Function”).
See the SoXtComponent.h file for additional methods you may choose to implement. Possibilities include the following:
If you are sure that no one will need to derive classes from your new component, you can simply implement one public constructor. This constructor needs to do the following:
Build the widget (or the widget tree if your component is made up of more than one widget)
Call setBaseWidget() so that the SoXtComponent base class methods—such as show() and hide()—work properly
If you want to be sure that programmers can derive new classes from your class, you need to provide a protected constructor in addition to the public constructor. Here's why. The widget tree is built when the component is constructed. If you derive a component subclass, the parent class constructor is called before the constructor of the subclass. This means that the parent class widget is built before the subclass widget. The problem arises if you want the component subclass to provide a container widget for the parent class. The Xt Library requires that a parent widget be supplied when a child widget is created and provides no way to reparent a widget. A little fancy footwork is required for the subclass to provide the parent widget, and that maneuver is provided by the protected constructor.
In Inventor, every SoXtComponent class has two constructors: a public constructor and a protected constructor. The protected constructor has one additional parameter, buildNow, which is a Boolean value that specifies whether to build the widget tree now or later:
protected: SoXtComponent( Widget parent, const char *name, SbBool buildInsideParent, SbBool buildNow);
If you use the protected constructor and specify FALSE for buildNow, you can have explicit control over which widgets are built and in what order. For example, your new class may want to build a container widget such as a Motif-style form or bulletin board before it lets the parent class build its widget. In this case, your new class can call its buildWidget() method first and then later it can call the buildWidget() method of its parent class. In Inventor, the SoXtFullViewer class uses this technique. It builds a form widget with user interface trim and then has the parent class, SoXtRenderArea , later build its widget inside this form.
In Inventor, and in Examples 10-1 and 10-2, the basic constructor tasks are put into the constructorCommon() method, which is called by both constructors. Although this is a useful technique, it is not required. The constructorCommon() method is where the actual building of this widget occurs. This method checks the buildNow flag and builds the widget.
Let's analyze the constructorCommon() code in Example 10.2, “ SceneTumble.c++ ” in a bit more detail. After setting up sensors, a camera, and a light for the component, the following calls are made:
addVisibilityChangeCallback(visibilityChangeCB, this); setClassName("SceneTumble"); setTitle("Tumble"); // If we do not build now, the subclass will build when ready if (buildNow) { Widget w = buildWidget(getParentWidget()); setBaseWidget(w); }
The visibility-changed callback is described in the section called “Visibility-Changed Callback Function”. The setClassName() method sets the name of the class for X resource lookup, which occurs while the widget is being built. The setTitle() method sets the title used in the shell window if this is a top-level component. Although not shown here, you can also call setIconTitle() to set the title used when the component is iconified.
The constructor then checks the buildNow flag. If this flag is TRUE, it builds the widget tree. Also, note that the buildWidget() method uses getParentWidget() to obtain the parent widget, which is not necessarily the parent widget passed in to the constructor. (The parent passed in to the constructor could be NULL, or the buildInsideParent parameter could be FALSE.)
Next, the constructor calls the setBaseWidget() method, which letsSoXtComponent know what the root of the widget tree is. This widget is used for layout, and by the show() and hide() methods.
In Inventor, because we want to allow subclasses to have explicit control over building the widget tree, we implement a separate buildWidget() method. If you are providing only the public constructor, you can simply build the widget in the constructor and do not need to create a separate buildWidget() method. This method, called by the constructor of your new class (or by subclasses of your new class), builds the widget hierarchy and returns its topmost widget.
If your widget supports X resources, be sure to call registerWidget() immediately after you create the topmost container widget and before you build the rest of the widget hierarchy. This method associates the Motif-style widget with the Inventor component to which it belongs. When you create other widgets in the hierarchy, Inventor uses the class name of the component instead of the widget name during resource lookup. For example, the base widget of a render area is a Motif-style bulletin board. Once you have called registerWidget(), you can set the background color resource directly on the render area without affecting other bulletin board widgets in your hierarchy.
To define and retrieve your own resources, see the Open Inventor C++ Reference Manual on SoXtResource . For more information on X resources, see the Xlib Programming Manual by Adrian Nye (O'Reilly & Associates, 1990).
The base class SoXtComponent will show and hide your new component automatically. However, if your component needs to show or hide other components, you must implement your own show() and hide() methods. In Inventor, if the material editor and color editor are on the screen and the program tells the material editor to hide itself, the material editor needs to tell the color editor to hide itself as well. Similarly, when a viewer hides itself, it also hides the preference sheet if it is visible.
Using addVisibilityChangeCallback(), your new class can register a callback function with SoXtComponent that is called when its visibility changes. A component can be shown, hidden, or iconified; whenever it changes state between visible and invisible (hidden or iconified), the visibility-changed callback function is invoked. This callback is useful, for example, if your component contains a scene with animation. When your component is hidden or iconified, it can stop the animation. Another example of using this callback is the render area, which detaches its redraw sensor when it is hidden or iconified.
See Example 10.2, “ SceneTumble.c++ ” for an illustration of using visibilityChangeCB().
The following examples show the header file and source code for a simple component derived from SoXtRenderArea . This component animates a camera to rotate the scene. It uses a visibility-changed callback function to stop the tumbling when the component is not visible. A slider at the bottom of the window controls the speed of tumbling.
Example 10.1, “ SceneTumble.h ” shows the include file for the SceneTumble class. Example 10.2, “ SceneTumble.c++ ” shows the source code for this class.
Example 10.1. SceneTumble.h
#include <Inventor/Xt/SoXtRenderArea.h>
class SoPerspectiveCamera;
class SoRotation;
class SoSeparator;
class SoTimerSensor;
class SceneTumble : public SoXtRenderArea {
public:
// Constructor for public consumption
SceneTumble(
Widget parent = NULL,
const char *name = NULL,
SbBool buildInsideParent = TRUE);
~SceneTumble();
virtual void setSceneGraph(SoNode *newScene);
virtual SoNode *getSceneGraph();
void setTumbling(SbBool onOff);
SbBool isTumbling() const;
protected:
// Constructor subclasses can call if they don't want the
// widget built right away (i.e. the subclass wants to create
// a container widget first.)
SceneTumble(
Widget parent,
const char *name,
SbBool buildInsideParent,
SbBool buildNow);
Widget buildWidget(Widget parent);
void doTumbleAnimation();
void setSpeed(int s) { speed = s; }
int getSpeed() const { return speed; }
Widget speedSlider;
private:
SoNode * userScene;
SoPerspectiveCamera *camera;
SoRotation *rotx;
SoRotation *roty;
SoRotation *rotz;
SoSeparator *root;
int speed;
SoTimerSensor *animationSensor;
void constructorCommon(SbBool buildNow);
static void visibilityChangeCB(void *userData,
SbBool visible);
static void animationSensorCB(void *userData, SoSensor *);
static void speedCB(Widget, XtPointer, XtPointer);
};
Example 10.2. SceneTumble.c++
#include <Xm/Form.h> #include <Xm/Scale.h> #include <Inventor/Xt/SoXtResource.h> #include <Inventor/nodes/SoDirectionalLight.h> #include <Inventor/nodes/SoPerspectiveCamera.h> #include <Inventor/nodes/SoRotation.h> #include <Inventor/nodes/SoSeparator.h> #include <Inventor/sensors/SoTimerSensor.h> #include "SceneTumble.h" #define MIN_SPEED 0 #define MAX_SPEED 100 // Speed factor is a small angle #define SPEED_FACTOR (M_PI/3600.0) // Public constructor SceneTumble::SceneTumble( Widget parent, const char *name, SbBool buildInsideParent) : SoXtRenderArea(parent, name, buildInsideParent, FALSE, FALSE) { // Passing TRUE means build the component right now constructorCommon(TRUE); } // Protected constructor for subclasses to call SceneTumble::SceneTumble( Widget parent, const char *name, SbBool buildInsideParent, SbBool buildNow) : SoXtRenderArea(parent, name, buildInsideParent, FALSE, FALSE) { // Subclass tells us whether to build now constructorCommon(buildNow); } // Actual work done at construction time void SceneTumble::constructorCommon(SbBool buildNow) { speed = MAX_SPEED/2; animationSensor = new SoTimerSensor(SceneTumble::animationSensorCB, this); animationSensor->setInterval(1/60.0); // 60 frames per second userScene = NULL; root = new SoSeparator; camera = new SoPerspectiveCamera; rotx = new SoRotation; roty = new SoRotation; rotz = new SoRotation; root->addChild(camera); root->addChild(new SoDirectionalLight); root->addChild(rotx); root->addChild(roty); root->addChild(rotz); root->ref(); addVisibilityChangeCallback(visibilityChangeCB, this); setClassName("SceneTumble"); setTitle("Tumble"); // If we do not build now, the subclass will build when ready if (buildNow) { Widget w = buildWidget(getParentWidget()); setBaseWidget(w); } } // Destructor SceneTumble::~SceneTumble() { root->unref(); delete animationSensor; } // Set the scene graph to tumble. We add this scene graph // to our local graph so that we can rotate our own camera // to create the tumbling effect. Our local scene graph // root is passed to the render area for rendering. void SceneTumble::setSceneGraph(SoNode *newScene) { // Replace the existing scene with this one if (userScene != NULL) root->replaceChild(userScene, newScene); else root->addChild(newScene); userScene = newScene; // Make certain the scene is in view camera->viewAll(root, getViewportRegion(), 2.0); // Render area will handle redraws for us SoXtRenderArea::setSceneGraph(root); } // Return the user's scene graph, not our local graph SoNode * SceneTumble::getSceneGraph() { return userScene; } // Build the widget - create a form widget, and place // in it a render area and a scale slider to control // the speed. Widget SceneTumble::buildWidget(Widget parent) { Arg args[8]; int n; // Create a form widget as the container. Widget form = XtCreateWidget(getWidgetName(), xmFormWidgetClass, parent, NULL, 0); // Register the widget, so we can get resources registerWidget(form); // Get our starting speed from the resource. // Resource file should say: // *SceneTumble*speed: <int between 0 and 100> short s; SoXtResource xr(form); if (xr.getResource("speed", "Speed", s)) { if (s > MAX_SPEED) speed = MAX_SPEED; else if (s < MIN_SPEED) speed = MIN_SPEED; else speed = s; } // Create render area Widget raWidget = SoXtRenderArea::buildWidget(form); // Create slider to control speed n = 0; XtSetArg(args[n], XmNminimum, MIN_SPEED); n++; XtSetArg(args[n], XmNmaximum, MAX_SPEED); n++; XtSetArg(args[n], XmNvalue, speed); n++; XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++; speedSlider = XtCreateWidget("Speed", xmScaleWidgetClass, form, args, n); // Callbacks on the slider XtAddCallback(speedSlider, XmNdragCallback, SceneTumble::speedCB, this); XtAddCallback(speedSlider, XmNvalueChangedCallback, SceneTumble::speedCB, this); // Layout n = 0; XtSetArg(args[n], XmNtopAttachment, XmNONE); n++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; XtSetValues(speedSlider, args, n); n = 0; XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNbottomAttachment,XmATTACH_WIDGET); n++; XtSetArg(args[n], XmNbottomWidget, speedSlider); n++; XtSetValues(raWidget, args, n); // Make the widgets visible XtManageChild(speedSlider); XtManageChild(raWidget); return form; } // Do the tumble animation. This entails updating our three // rotation nodes, one each for the x,y,and z axes. void SceneTumble::doTumbleAnimation() { SbRotation r; float angle; // Rotate about three axes in three speeds angle = speed * SPEED_FACTOR; r = rotx->rotation.getValue() * SbRotation(SbVec3f(1, 0, 0), angle); rotx->rotation.setValue(r); angle = speed * SPEED_FACTOR * 1.5; r = roty->rotation.getValue() * SbRotation(SbVec3f(0, 1, 0), angle); roty->rotation.setValue(r); angle = speed * SPEED_FACTOR * 2.0; r = rotz->rotation.getValue() * SbRotation(SbVec3f(0, 0, 1), angle); rotz->rotation.setValue(r); } // Turn tumbling on and off. We simply schedule or unschedule // the animation sensor. void SceneTumble::setTumbling(SbBool onOff) { if (onOff) animationSensor->schedule(); else animationSensor->unschedule(); } // Return whether we are tumbling. SbBool SceneTumble::isTumbling() const { return animationSensor->isScheduled(); } // This is called when the render area visibility changes // because it is shown, hidden, or iconified. If the // component is not visible, we turn off the tumble animation. void SceneTumble::visibilityChangeCB(void *userData, SbBool visible) { // Set tumbling on when the component is visible, // and set it off when the component is not visible. SceneTumble *tumbler = (SceneTumble *) userData; tumbler->setTumbling(visible); } // Animation sensor callback keeps the tumbling going. void SceneTumble::animationSensorCB(void *userData, SoSensor *) { ((SceneTumble *) userData)->doTumbleAnimation(); } // This is invoked when the speed slider changes value. // We use the value of the slider to change the tumble speed. void SceneTumble::speedCB(Widget, XtPointer userData, XtPointer clientData) { SceneTumble *tumbler = (SceneTumble *) userData; XmScaleCallbackStruct *data = (XmScaleCallbackStruct *) clientData; tumbler->setSpeed(data->value); }