10.2. Creating a New Viewer

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.

  1. Construct the viewer (required step for all new viewers). See the section called “Defining the Constructor”.

  2. 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”.

  3. Implement the seek function (optional step). See the section called “Implementing the Seek Function”.

  4. 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.

  5. Modify how the trim decoration is used (required step). See the section called “Using the SoXtFullViewer Trim Decoration”.

  6. Add push buttons (optional step). See the section called “Adding Push Buttons”.

  7. Change the preference sheet (optional step). See the section called “Changing the Preference Sheet”.

  8. Change the pop-up menu (optional step). See the section called “Changing the Pop-up Menu”.

  9. 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 .

  10. 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:

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:

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.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:

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.

[Important]

The viewer default push buttons all have a 24-by-24-pixel size, and the decoration trim is designed with that in mind. It is therefore recommended that you create pixmaps of this size for viewer or application push buttons.

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.


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.