Viewers are subclassed from SoXtRenderArea and can be thought of as smart rendering windows that respond to events and modify the camera. You can use one of the following classes as a base class for your own viewer:
SoXtViewer , the lowest base class for viewers, adds the notion of a camera to an SoXtRenderArea . The camera through which the scene is viewed is either found in the scene or created automatically by the viewer. SoXtFullViewer adds a decoration trim around the rendering area, which includes thumbwheels, a zoom slider, and push buttons. This class also creates a pop-up menu and a preference sheet with generic viewer functionality built into them. SoXtConstrainedViewer , the last base class, is used for viewers that have the notion of a world up-direction. These viewers constrain the camera orientation to prevent the user from being upside down relative to the given world up-direction (which defaults to +y).
The SoXtViewer class provides basic viewer functionality. This base class provides methods for changing the camera, including setCamera(), getCamera() , viewAll(), saveHomePosition(), and resetToHome- Position(). SoXtViewer also adds a headlight, as well as drawing styles, buffering types, and autoclipping. In addition, SoXtViewer adds support for seek, copy, and paste, enabling subclasses to redefine how a seek or a paste is performed.
If you create a viewer that is a subclass of SoXtViewer , you perform the following steps. Examples 10-3 and 10-4 illustrate each step.
Construct the viewer (required step for all new viewers). See the section called “Defining the Constructor”.
Implement the event-processing routines (required step for all new viewers). These routines include processEvent() and processCommonEvents(), which in turn calls translateCamera() and switchMode(). See the section called “Defining the Event-Processing Routines”.
Implement the seek function (optional step). See the section called “Implementing the Seek Function”.
Modify the cursor to be used for feedback (optional step). See the section called “Using the Cursor for Feedback”.
If you create a viewer that is subclassed from SoXtFullViewer , you can perform the following steps, in addition to steps 1 through 4. Only step 5 is required; the other steps are optional.
Modify how the trim decoration is used (required step). See the section called “Using the SoXtFullViewer Trim Decoration”.
Add push buttons (optional step). See the section called “Adding Push Buttons”.
Change the preference sheet (optional step). See the section called “Changing the Preference Sheet”.
Change the pop-up menu (optional step). See the section called “Changing the Pop-up Menu”.
Change the trim decoration (optional step). See the section called “Changing the Decoration Layout”.
If you create a viewer that is subclassed from SoXtConstrainedViewer , you can perform the following step, in addition to steps 1 through 9. See Section 10.3, “Creating a Constrained Viewer” for more information on creating a viewer subclassed from SoXtConstrainedViewer .
Define constraints for the viewer.
Examples 10-3 and 10-4 show how to create a simple viewer derived from SoXtFullViewer , similar to the SoXtPlaneViewer . The left mouse button is used to translate the camera in the viewer plane and to seek objects. This new viewer redefines the decoration thumbwheels to translate the camera. It also defines mouse cursors to reflect the viewer's current state (viewing, seeking, or picking).
The constructor for the viewer takes an
SoXtViewer::Type
parameter, which specifies whether the viewer is of type BROWSER (the default) or
EDITOR. This argument specifies the camera creation policy of the viewer. For more information on component constructors, see the section called “Defining the Constructor”.
Any new viewer must implement the processEvent() routine, defined in SoXtRenderArea , to send events directly to the scene graph. When viewing is turned on, the new viewer uses those events to manipulate the camera.
The base-class routine processCommonEvents(), defined in SoXtViewer , is first called when an event is received. This routine is used to handle a set of events that should be common across all viewers. These events are as follows:
The viewer calls the base class interactiveCountInc() and interactiveCountDec() methods when the left mouse button is pressed and released. These methods enable the viewer base class to call the user interactive start and finish callbacks and are also used to change the drawing styles and buffering types when interactive styles are chosen (for example, move wireframe).
The code to translate the camera using the new mouse position is called within the processEvent() routine (see the translateCamera() method in Example 10.4, “ SimpleViewer.c++ ”).
The switchMode() routine called within processEvent() switches between viewer modes. It also sets the correct cursor on the window (see the section called “Using the Cursor for Feedback”).
In addition to showing how the camera can be translated given the mouse events received, the processEvent() routine also shows how seek functionality is supported in the viewer. This topic is explained in detail in the following section.
The seek function moves the camera to the picked point (when detail seek is ON) or to the picked object (when detail seek is OFF). The seek functionality for viewers is provided by the base class SoXtViewer . The following public methods can be called to set seek parameters:
specifies whether to orient the camera toward the picked point (detail seek is ON), or toward the center of the object's bounding box (detail seek is OFF). The default is ON. | |
returns whether detail seek is ON. | |
sets the time for a seek to animate the new camera location. The default time is 2 seconds. | |
returns the seek time. |
Subclasses can then simply call seekToPoint(), passing the mouse location, and the base class SoXtViewer performs the seek animation. By default, detail seek is ON, and the base class changes the camera to be the focal distance away from the picked point and aligned to the point normal. When detail seek is OFF, the camera centers itself on the object's bounding box and keeps its orientation the same (the picked point has no real importance; only the picked object is used in this case).
Our simple viewer example uses the seek functionality defined in the base class. If a viewer needs to redefine how seek is performed ( SoXtFlyViewer and SoXtWalkViewer redefine it), the viewer can redefine the interpolateSeekAnimation() routine, which changes the camera.
The following protected variables are defined in SoXtViewer to help you redefine interpolateSeekAnimation() for a new viewer:
point and normal to seek | |
old and new camera orientation | |
seek distance (either a percentage or an absolute value) | |
whether the seek distance is a percentage or an absolute value | |
whether the final camera seek values have been computed. This flag is set to FALSE when interpolateSeekAnimation() is first called on a new seek animation. |
It is often desirable to have a viewer change the cursor to reflect the viewer's state. The file SoXtCursors.h defines a set of X bitmaps that can be used for defining cursors. Some of these bitmaps were created for specific viewers, whereas others are generic enough to be reused across viewers. Most viewers have a different cursor during viewing and nonviewing modes. A generic viewer cursor is supplied in the file SoXtCursors.h.
To have a different cursor when viewing is on, the viewer needs to redefine the setViewing() method to set the correct cursor on the window. Similarly, if the viewer supports the seek functionality, it also needs to redefine the setSeekMode() method to change the cursor. Example 10.4, “ SimpleViewer.c++ ” shows how to change the cursor.
The defineCursors() routine, also shown in Example 10.4, “ SimpleViewer.c++ ”, needs to be called only once to create the X cursor.
X cursors can be defined only when the widget is actually mapped onto the screen. It is thus a good idea to define the cursors whenever the first event is received in the processEvent() routine, since an event guarantees that the window is mapped onto the screen.
Cursors should be defined on the render-area window, not on the window found in the X event structure. This is because the actual window events are received from changes when the viewer switches between single- and double-buffering. The render-area window, however, stays constant, so the cursor is correctly specified.
The following sections deal with more advanced features of viewers, such as the trim decoration around the render area and the viewer pop-up menu.
Example 10.3, “ SimpleViewer.h ” shows the header file for the new simple viewer class.
Example 10.3. SimpleViewer.h
#include <Inventor/SbLinear.h> #include <Inventor/Xt/viewers/SoXtFullViewer.h> class simpleViewer : public SoXtFullViewer { public: // Constructor/destructor simpleViewer( Widget parent = NULL, const char *name = NULL, SbBool buildInsideParent = TRUE, SoXtFullViewer::BuildFlag flag = BUILD_ALL, SoXtViewer::Type type = BROWSER); ~simpleViewer(); // Redefine this to also change the cursor (viewerCursor) virtual void setViewing(SbBool onOrOff); protected: // Redefine this to process the events virtual void processEvent(XAnyEvent *anyevent); // Redefine this to also change the cursor (seekCursor) virtual void setSeekMode(SbBool onOrOff); // Define these thumbwheels to translate in the viewer plane virtual void bottomWheelMotion(float newVal); virtual void leftWheelMotion(float newVal); virtual void rightWheelMotion(float newVal); virtual void bottomWheelStart(); virtual void leftWheelStart(); // Redefine this to customize the preference sheet virtual void createPrefSheet(); // Define this to bring up the viewer help card virtual void openViewerHelpCard(); private: // Viewer state variables int mode; SbBool createdCursors; Cursor vwrCursor, seekCursor; SbVec2s locator; // mouse position // Camera translation variables SbVec3f locator3D; SbPlane focalplane; float transXspeed, transYspeed; void switchMode(int newMode); void defineCursors(); void translateCamera(); void computeTranslateValues(); };
Example 10.4. SimpleViewer.c++
#include <math.h> #include <X11/Intrinsic.h> #include <X11/Xlib.h> #include <X11/keysym.h> #include <Inventor/nodes/SoOrthographicCamera.h> #include <Inventor/nodes/SoPerspectiveCamera.h> #include <Inventor/Xt/SoXtCursors.h> #include "simpleViewer.h" enum ViewerModes { IDLE_MODE, TRANS_MODE, SEEK_MODE, }; // Constructor for the viewer simpleViewer::simpleViewer( Widget parent, const char *name, SbBool buildInsideParent, SoXtFullViewer::BuildFlag b, SoXtViewer::Type t) : SoXtFullViewer( parent, name, buildInsideParent, b, t, TRUE) // Tell base class to build (since we don't add // anything) { // Init local vars mode = IDLE_MODE; createdCursors = FALSE; setSize(SbVec2s(520, 360)); //def size // assign decoration titles setPopupMenuString("Simple Viewer"); setBottomWheelString("transX"); setLeftWheelString("transY"); setRightWheelString("Dolly"); setPrefSheetString("Simple Viewer Preference Sheet"); setTitle("Simple Viewer"); } simpleViewer::~simpleViewer() { } // Call the base class and set the correct cursor // on the window void simpleViewer::setViewing(SbBool flag) { if (flag == viewingFlag || camera == NULL) { viewingFlag = flag; return; } // Call the base class SoXtFullViewer::setViewing(flag); // Set the right cursor Widget w = getRenderAreaWidget(); if (w != NULL && XtWindow(w) != NULL) { if (isViewing()) { if (! createdCursors) defineCursors(); XDefineCursor(XtDisplay(w), XtWindow(w), vwrCursor); } else XUndefineCursor(XtDisplay(w), XtWindow(w)); } } // Process the given event to change the camera void simpleViewer::processEvent(XAnyEvent *xe) { // Let the base class handle the common set of events if (processCommonEvents(xe)) return; // Check if cursors need to be defined (they can only // be defined after the window has been mapped. // Receiving events guarantees that the window has // been mapped. if (! createdCursors) { defineCursors(); Widget w = getRenderAreaWidget(); XDefineCursor(XtDisplay(w), XtWindow(w), vwrCursor); } XButtonEvent *be; XMotionEvent *me; SbVec2s windowSize = getGlxSize(); switch (xe->type) { case ButtonPress: be = (XButtonEvent *) xe; locator[0] = be->x; locator[1] = windowSize[1] - be->y; if (be->button == Button1) { switch (mode) { case IDLE_MODE: interactiveCountInc(); switchMode(TRANS_MODE); break; case SEEK_MODE: seekToPoint(locator); break; } } break; case ButtonRelease: be = (XButtonEvent *) xe; if (be->button == Button1 && mode == TRANS_MODE) { switchMode(IDLE_MODE); interactiveCountDec(); } break; case MotionNotify: me = (XMotionEvent *) xe; locator[0] = me->x; locator[1] = windowSize[1] - me->y; if (mode == TRANS_MODE) translateCamera(); break; } } // Switches to the specified viewer mode. The correct // cursor is also set on the window. void simpleViewer::switchMode(int newMode) { // needed to define new cursors Widget w = getRenderAreaWidget(); Display *display = XtDisplay(w); Window window = XtWindow(w); if (! createdCursors) defineCursors(); // Switch to new viewer mode mode = newMode; switch (mode) { case IDLE_MODE: if (window != 0) XDefineCursor(display, window, vwrCursor); break; case TRANS_MODE: { // Figure out the focal plane SbMatrix mx; mx = camera->orientation.getValue(); SbVec3f forward(-mx[2][0], -mx[2][1], -mx[2][2]); SbVec3f fp = camera->position.getValue() + forward * camera->focalDistance.getValue(); focalplane = SbPlane(forward, fp); // Map mouse position onto the viewing plane SbVec2s windowSize = getGlxSize(); SbLine line; SbViewVolume cameraVolume = camera->getViewVolume(); cameraVolume.projectPointToLine( SbVec2f( locator[0] / float(windowSize[0]), locator[1] / float(windowSize[1])), line); focalplane.intersect(line, locator3D); } if (window != 0) XDefineCursor(display, window, vwrCursor); break; case SEEK_MODE: if (window != 0) XDefineCursor(display, window, seekCursor); break; } } // Call the base class and set the correct cursor // on the window. void simpleViewer::setSeekMode(SbBool flag) { if (! isViewing()) return; // Call the base class SoXtFullViewer::setSeekMode(flag); // Switch to the right mode switchMode(isSeekMode() ? SEEK_MODE : IDLE_MODE); } // Redefine this routine to customize the preference sheet void simpleViewer::createPrefSheet() { // Create the preference sheet shell and form widget Widget shell, form; createPrefSheetShellAndForm(shell, form); // Create most of the default parts Widget widgetList[10]; int num = 0; widgetList[num++] = createSeekPrefSheetGuts(form); widgetList[num++] = createZoomPrefSheetGuts(form); widgetList[num++] = createClippingPrefSheetGuts(form); layoutPartsAndMapPrefSheet(widgetList, num, form, shell); } // Bring up the viewer help card (called by "?" push button) void simpleViewer::openViewerHelpCard() { // Tell the component to open the file for us openHelpCard("simpleViewer.help"); } // Translate the camera right/left (called by thumbwheel). void simpleViewer::bottomWheelMotion(float newVal) { if (camera == NULL) return; // Get camera right vector and translate by wheel // delta rotation SbMatrix mx; mx = camera->orientation.getValue(); SbVec3f rightVector(mx[0][0], mx[0][1], mx[0][2]); float dist = transXspeed * (bottomWheelVal - newVal); camera->position = camera->position.getValue() + dist * rightVector; bottomWheelVal = newVal; } // Translate the camera up/down (called by thumbwheel). void simpleViewer::leftWheelMotion(float newVal) { if (camera == NULL) return; // Get camera up vector and translate by wheel // delta rotation SbMatrix mx; mx = camera->orientation.getValue(); SbVec3f upVector(mx[1][0], mx[1][1], mx[1][2]); float dist = transYspeed * (leftWheelVal - newVal); camera->position = camera->position.getValue() + dist * upVector; leftWheelVal = newVal; } // Moves the camera closer/further away from the plane // of interest, which is defined by the viewing normal // and the camera focalDistance field value. void simpleViewer::rightWheelMotion(float newVal) { if (camera == NULL) return; // Shorten/grow the focal distance given the wheel rotation float focalDistance = camera->focalDistance.getValue(); float newFocalDist = focalDistance / pow(2.0, newVal - rightWheelVal); // Finally, reposition the camera SbMatrix mx; mx = camera->orientation.getValue(); SbVec3f forward(-mx[2][0], -mx[2][1], -mx[2][2]); camera->position = camera->position.getValue() + (focalDistance - newFocalDist) * forward; camera->focalDistance = newFocalDist; rightWheelVal = newVal; } // This routine is used to define cursors, which can // only be called after the window has been realized. void simpleViewer::defineCursors() { XColor foreground; Pixmap source; Display *display = getDisplay(); Drawable d = DefaultRootWindow(display); // Set a red color foreground.red = 65535; foreground.green = foreground.blue = 0; // View plane translate cursor source = XCreateBitmapFromData(display, d, so_xt_flat_hand_bits, so_xt_flat_hand_width, so_xt_flat_hand_height); vwrCursor = XCreatePixmapCursor(display, source, source, &foreground, &foreground, so_xt_flat_hand_x_hot, so_xt_flat_hand_y_hot); XFreePixmap(display, source); // Seek cursor source = XCreateBitmapFromData(display, d, so_xt_target_bits, so_xt_target_width, so_xt_target_height); seekCursor = XCreatePixmapCursor(display, source, source, &foreground, &foreground, so_xt_target_x_hot, so_xt_target_y_hot); XFreePixmap(display, source); createdCursors = TRUE; } // Moves the camera into the plane defined by the camera // forward vector and the focal point (using the camera // focalDistance field) to follow the new mouse location. void simpleViewer::translateCamera() { if (camera == NULL) return; SbVec2s windowSize = getGlxSize(); SbVec2f newLocator(locator[0] / float(windowSize[0]), locator[1] / float(windowSize[1])); // Map new mouse location into the camera focal plane SbLine line; SbVec3f newLocator3D; SbViewVolume cameraVolume = camera->getViewVolume(); cameraVolume.projectPointToLine(newLocator, line); focalplane.intersect(line, newLocator3D); // Move the camera by the delta 3D position amount camera->position = camera->position.getValue() + (locator3D - newLocator3D); // You would think we would have to set locator3D to // newLocator3D here. But we don't, because moving // the camera essentially makes locator3D equal to // newLocator3D in the transformed space, and we will // project the next newLocator3D in this transformed space. } // Called by the bottom and left thumbwheels to compute // the translation factors (how fast should we translate // given a wheel rotation). void simpleViewer::computeTranslateValues() { if (camera == NULL) return; float height; if (camera->isOfType( SoPerspectiveCamera::getClassTypeId())) { float angle = ((SoPerspectiveCamera *) camera)->heightAngle.getValue() / 2; float dist = camera->focalDistance.getValue(); height = dist * ftan(angle); } else if (camera->isOfType( SoOrthographicCamera::getClassTypeId())) height = ((SoOrthographicCamera *) camera)->height.getValue() / 2; // Given the size of the viewing plane, figure out // the up/down and right/left speeds for the thumb wheels. transYspeed = height / 2; transXspeed = transYspeed * camera->aspectRatio.getValue(); } // Thumbwheels start callbacks void simpleViewer::bottomWheelStart() { computeTranslateValues(); // call parent class SoXtFullViewer::bottomWheelStart(); } void simpleViewer::leftWheelStart() { computeTranslateValues(); // call parent class SoXtFullViewer::leftWheelStart(); }
SoXtFullViewer is used as the base class for most viewers. This abstract class adds a decoration around the render area, a pop-up menu with viewer functions, and a preference sheet that can be used to customize a specific viewer. The decoration around the render area includes thumbwheels that duplicate direct viewing manipulation, a slider to change the camera zooming factor, and viewer/application push buttons. By default, the base class creates push buttons for viewing, home, set home, view all, and seek. Subclasses can easily add viewer-specific push buttons, as well as change the look of the decoration and the preference sheet. The creation of the decoration and preference sheet is accomplished by many small routines, so subclasses can redefine as much or as little as necessary.
SoXtFullViewer provides three thumbwheels around the render area. By default, these thumbwheels do nothing in the SoXtFullViewer base class and should therefore be implemented by each subclass. The subclass should implement functions so that the bottom and left thumbwheels duplicate the right-left and up-down functionality of the mouse during direct viewing. The right thumbwheel is used to dolly the camera (move forward and backward).
The simple viewer example defines the thumbwheel functionality by redefining methods from the base class as shown in Example 10.4, “ SimpleViewer.c++ ”.
For convenience, when you are defining thumbwheel functionality and redefining the decoration layout, the base class SoXtFullViewer provides the following thumbwheel variables. These variables include thumbwheel widgets, previous values (helpful for obtaining incremental rotation), and labels:
rightWheel, bottomWheel, leftWheel | thumbwheel widget variables |
rightWheelStr, bottomWheelStr, leftWheelStr | string label for each thumbwheel |
rightWheelVal, bottomWheelVal, leftWheelVal | previous value of each thumbwheel |
rightWheelLabel, bottomWheelLabel, leftWheelLabel | widget label for each thumbwheel |
When a viewer is derived from SoXtFullViewer , it should set the correct labels on the thumbwheels, the pop-up menu, the preference sheet, and the window title. This needs to be done only once and therefore should be done in the constructor. Example 10.4, “ SimpleViewer.c++ ” shows the code to fully support the SoXtFullViewer base class.
By default, the base class SoXtFullViewer creates a list of push buttons ( XmPushButton widgets with pixmaps). The method buildViewer- Buttons(), which subclasses do not need to redefine, uses a list of push buttons to construct all the buttons within a form widget. The button's form widget is then laid out within the right-trim form widget. Subclasses can easily add or remove any number of buttons from the existing list of buttons by redefining the createViewerButtons() method and appending, inserting, and removing from the SbPList of buttons.
Our simple viewer example does not add any new viewer buttons, but here is some sample code that adds a push button to the existing list.
void SoXtExaminerViewer::createViewerButtons(Widget parent) { // Get the default buttons SoXtFullViewer::createViewerButtons(parent); // Allocate our buttons - this simple case doesn't // set the XmNlabelType to be a pixmap, just a simple letter. Arg args[2]; int n = 0; XtSetArg(args[n], XmNshadowThickness, 2); n++; XtSetArg(args[n], XmNhighlightThickness, 0); n++; Widget button = XmCreatePushButtonGadget(parent, "P", args, n); XtAddCallback(button, XmNactivateCallback, (XtCallbackProc) SoXtExaminerViewer::pushButtonCB, (XtPointer) this); // Add this button to the list to have it laid out by the // parent class (removing a widget from the list will // prevent the corresponding push button from being laid // out and managed; therefore it will not show up in the // decoration). viewerButtonWidgets->append(button); }
Look at the file SbPList.h for a description of methods available on the SoXtFullViewer::viewerButtonWidgets protected variable.
Preference sheets allow the user to customize the behavior of a viewer. A default preference sheet is created by the SoXtFullViewer class. Subclasses typically make changes to the default preference sheet to give the user control over viewer-specific parameters. Like the decoration in SoXtFullViewer , the preference sheet is made up of many small building blocks to make it easier for subclasses to redefine it. The following protected methods are used to build the different parts of the preference sheet:
void setPrefSheetString(const char *name);
virtual void createPrefSheet();
void createPrefSheetShellAndForm(Widget &shell, Widget &form);
void createDefaultPrefSheetParts(Widget widgetList[ ], int &num, Widget form);
void layoutPartsAndMapPrefSheet(Widget widgetList[ ], int num, Widget form, Widget shell);
Widget createSeekPrefSheetGuts(Widget parent);
Widget createSeekDistPrefSheetGuts(Widget parent);
Widget createZoomPrefSheetGuts(Widget parent);
Widget createClippingPrefSheetGuts(Widget parent);
Widget createStereoPrefSheetGuts(Widget parent);
To change only the preference-sheet title, use the setPrefSheetString() method. Use the createPrefSheet() method to redefine the preference sheet for a subclass, as follows:
// This creates the preference sheet in a separate window. It // calls other routines to create the actual content of the // sheet. void SoXtFullViewer::createPrefSheet() { // Create the preference sheet shell and form widget Widget shell, form; createPrefSheetShellAndForm(shell, form); // Create all of the default parts Widget widgetList[10]; int num = 0; createDefaultPrefSheetParts(widgetList, num, form); layoutPartsAndMapPrefSheet(widgetList, num, form, shell); } // This simply creates the default parts of the pref sheet. void SoXtFullViewer::createDefaultPrefSheetParts( Widget widgetList[], int &num, Widget form) { widgetList[num++] = createSeekPrefSheetGuts(form); widgetList[num++] = createSeekDistPrefSheetGuts(form); widgetList[num++] = createZoomPrefSheetGuts(form); widgetList[num++] = createClippingPrefSheetGuts(form); widgetList[num++] = createStereoPrefSheetGuts(form); }
When a subclass creates its own preference sheet, it only needs to redefine the createPrefSheet() routine and write it like the base class routine. The simple viewer example redefines the preference sheet to omit some of the default parts. Example 10.4, “ SimpleViewer.c++ ” shows the createPrefSheet() method for simpleViewer.
Subclasses can easily add new items to the preference sheet by adding them to the widget list that is passed to the layoutPartsAndMapPrefSheet() method, just like the default parts. The custom items should all be built within a form widget that is automatically laid out and managed within the layoutPartsAndMapPrefSheet() method. The layout is from top to bottom in the shell widget.
The preference-sheet widget and all of its child widgets are destroyed when the preference-sheet window is closed by the user. This behavior is intended, since the preference sheet is only a temporary window, and we don't want to carry the unwanted widget around when it is no longer needed.
The SoXtFullViewer pop-up menu, which includes a rich set of viewer functions, can be changed in subclasses by redefining any of the following pop-up menu build routines:
void setPopupMenuString(const char *name);
virtual void buildPopupMenu();
Widget buildFunctionsSubmenu(Widget popup);
Widget buildDrawStyleSubmenu(Widget popup);
To change the pop-up menu title, use the setPopupMenuString() method. To change the pop-up menu, subclasses can redefine the buildPopupMenu() method. Subclasses can also append new entries to the pop-up menu by directly adding a Motif-compliant xmToggleButton or xmPushButton to the pop-up menu widget.
On rare occasions, you may want to change the decoration surrounding the rendering area. The SoXtWalkViewer class, for example, adds an extra thumbwheel and label in the left-hand trim. To simplify the redefining of the decoration, the base class SoXtFullViewer constructs the decoration in many small and manageable steps. The following functions are used to create the decoration and can be redefined by subclasses at any level:
Widget buildWidget(Widget parent);
virtual void buildDecoration(Widget parent);
virtual Widget buildLeftTrim(Widget parent);
virtual Widget buildBottomTrim(Widget parent);
virtual Widget buildRightTrim(Widget parent);
virtual Widget buildZoomSlider(Widget parent);
virtual Widget buildViewerButtons(Widget parent);
virtual Widget buildAppButtons(Widget parent);
void setBottomWheelString(const char *name);
void setLeftWheelString(const char *name);
void setRightWheelString(const char *name);
Example 10.5, “ Building the Viewer Decoration in SoXtFullViewer ” contains pseudocode that shows how the decoration is built in the base class SoXtFullViewer . Only important parts of the code are given to illustrate how the decoration is built.
Example 10.5. Building the Viewer Decoration in SoXtFullViewer
// Build the render area and the decoration trim within the
// given parent widget.
Widget
SoXtFullViewer::buildWidget(Widget parent)
{
// Create a form to hold everything together
mgrWidget = XtCreateWidget(getWidgetName(),
xmFormWidgetClass, parent,
args, n);
// Build the render area and the decoration
raWidget = SoXtRenderArea::buildWidget(mgrWidget);
if (decorationFlag)
buildDecoration(mgrWidget);
// Lay out and manage the render area and decoration
...
return mgrWidget;
}
// Build the viewer decoration (left, right and bottom trim)
void
SoXtFullViewer::buildDecoration(Widget parent)
{
// Build the trim sides
leftTrimForm = buildLeftTrim(parent);
bottomTrimForm = buildBottomTrim(parent);
rightTrimForm = buildRightTrim(parent);
// Lay out the trims but let the buildWidget() manage them
...
}
// Build the left trim decoration
Widget
SoXtFullViewer::buildLeftTrim(Widget parent)
{
// Create a form to hold all the parts
Widget form = XtCreateWidget("LeftTrimForm",
xmFormWidgetClass, parent,
NULL, 0);
// Create all the parts
buildLeftWheel(form);
Widget butForm = buildAppButtons(form);
// Lay out and manage the parts
...
return form;
}
SoXtWalkViewer redefines only the buildLeftTrim() routine in order to build the default parts as well as the extra thumbwheel and label. The viewer then simply returns a form containing its new left trim to the buildDecoration() routine, and everything works as before. Only the new trim has to be modified.