text: /*
   *
   *  Copyright (C) 2000 Silicon Graphics, Inc.  All Rights Reserved. 
   *
   *  This library is free software; you can redistribute it and/or
   *  modify it under the terms of the GNU Lesser General Public
   *  License as published by the Free Software Foundation; either
   *  version 2.1 of the License, or (at your option) any later version.
   *
   *  This library is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   *  Lesser General Public License for more details.
   *
   *  Further, this software is distributed without any warranty that it is
   *  free of the rightful claim of any third person regarding infringement
   *  or the like.  Any license provided herein, whether implied or
   *  otherwise, applies only to this software file.  Patent licenses, if
   *  any, provided herein do not apply to combinations of this program with
   *  other software, or any other product whatsoever.
   * 
   *  You should have received a copy of the GNU Lesser General Public
   *  License along with this library; if not, write to the Free Software
   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   *
   *  Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
   *  Mountain View, CA  94043, or:
   * 
   *  http://www.sgi.com 
   * 
   *  For further information regarding this notice, see: 
   * 
   *  http://oss.sgi.com/projects/GenInfo/NoticeExplan/
   *
   */
  
  /*
   * Copyright (C) 1990-93   Silicon Graphics, Inc.
   *
   _______________________________________________________________________
   ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
   |
   |   $Revision: 1.2 $
   |
   |   Classes    : SoXtViewer
   |
   |   Author(s)	: Alain Dumesny
   |
   ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
   _______________________________________________________________________
   */
  
  #include <inttypes.h>
  #include <stdio.h>
  #include <math.h>
  #include <string.h>
  
  #include <X11/Intrinsic.h>
  #include <X11/Xlib.h>
  #include <X11/keysym.h>
  
  #ifdef __sgi
  #include <X11/extensions/SGIStereo.h>
  #endif
  
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <Inventor/SbBox.h>
  #include <Inventor/SbLinear.h>
  #include <Inventor/SbPList.h>
  #include <Inventor/SbViewportRegion.h>
  #include <Inventor/SoDB.h>
  #include <Inventor/SoPath.h>
  #include <Inventor/SoPath.h>
  #include <Inventor/SoPickedPoint.h>
  #include <Inventor/actions/SoGetBoundingBoxAction.h>
  #include <Inventor/actions/SoRayPickAction.h>
  #include <Inventor/actions/SoSearchAction.h>
  #include <Inventor/errors/SoDebugError.h>
  #include <Inventor/nodes/SoPackedColor.h>
  #include <Inventor/nodes/SoMaterialBinding.h>
  #include <Inventor/nodes/SoComplexity.h>
  #include <Inventor/nodes/SoDirectionalLight.h>
  #include <Inventor/nodes/SoDrawStyle.h>
  #include <Inventor/nodes/SoGroup.h>
  #include <Inventor/nodes/SoLightModel.h>
  #include <Inventor/nodes/SoOrthographicCamera.h>
  #include <Inventor/nodes/SoPerspectiveCamera.h>
  #include <Inventor/nodes/SoResetTransform.h>
  #include <Inventor/nodes/SoRotation.h>
  #include <Inventor/nodes/SoSeparator.h>
  #include <Inventor/nodes/SoSwitch.h>
  #include <Inventor/nodes/SoLocateHighlight.h>
  #include <Inventor/sensors/SoFieldSensor.h>
  #include <Inventor/fields/SoSFTime.h>
  #include <Inventor/Xt/devices/SoXtInputFocus.h>
  #include <Inventor/Xt/SoXtClipboard.h>
  #include <Inventor/Xt/viewers/SoXtViewer.h>
  
  // keep a pointer to global time, since we are going to access it a lot.
  SoSFTime *SoXtViewer::viewerRealTime = NULL;
  
  
  ////////////////////////////////////////////////////////////////////////
  //
  //  Constructor.
  //
  // Use: protected
  
  SoXtViewer::SoXtViewer(
      Widget parent,
      const char *name, 
      SbBool buildInsideParent,
      SoXtViewer::Type t, 
      SbBool buildNow) 
  	: SoXtRenderArea(
  	    parent,
  	    name, 
  	    buildInsideParent, 
  	    TRUE,   // getMouseInput
  	    TRUE,   // getKeyboardInput
  	    FALSE)  // buildNow
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // init local vars
      type = t;
      camera = NULL;
      cameraType = SoPerspectiveCamera::getClassTypeId();
      createdCamera = FALSE;
      viewingFlag = TRUE;
      altSwitchBack = FALSE;
      cursorEnabledFlag = TRUE;
      interactiveFlag = FALSE;
      startCBList = new SoCallbackList;
      finishCBList = new SoCallbackList;
      interactiveCount = 0;
      bufferType = isDoubleBuffer() ? BUFFER_DOUBLE : BUFFER_SINGLE;
      stereoOffset = 3.0;
  #ifdef __sgi
      useSGIStereoExt = FALSE;
  #endif
      sceneSize = 0.0;	// not computed yet.
      viewerSpeed = 1.0;	// default. SoXtFullViewer add UI to increase/decrease 
      
      // add Enter and Leave notify events for the viewers
      inputFocus = new SoXtInputFocus;
      registerDevice(inputFocus);
      
      if (! viewerRealTime)
  	viewerRealTime = (SoSFTime *) SoDB::getGlobalField("realTime");
      
      // init auto clipping stuff
      autoClipFlag = TRUE;
      minimumNearPlane = 0.001;
      autoClipBboxAction = new SoGetBoundingBoxAction(SbVec2s(1,1));  // ??? no valid size yet
      
      // copy/paste support
      clipboard = NULL;
      
      // init seek animation variables
      seekDistance = 50.0;
      seekDistAsPercentage = TRUE;
      seekModeFlag = FALSE;
      detailSeekFlag = TRUE;
      seekAnimTime = 2.0;
      seekAnimationSensor = new SoFieldSensor(SoXtViewer::seekAnimationSensorCB, this);
      
      //
      // build the small internal graph (nodes used for draw style stuff)
      //
      sceneRoot		= new SoSeparator(4);
      drawStyleSwitch	= new SoSwitch(6);
      drawStyleNode   	= new SoDrawStyle;
      lightModelNode  	= new SoLightModel;
      colorNode		= new SoPackedColor;
      matBindingNode	= new SoMaterialBinding;
      complexityNode	= new SoComplexity;
      sceneGraph 	    	= NULL;
      
      // note: we cannot setSceneGraph on the renderArea in the constructor
      // since it calls virtual functions, and all of our members aren't
      // initialized yet. We'll call it the first time our setSceneGraph
      // is called.
      sceneRoot->ref();
      sceneRoot->renderCaching.setValue(SoSeparator::OFF); // no caching there
      sceneRoot->renderCulling.setValue(SoSeparator::OFF); // no culling there
      sceneRoot->addChild(drawStyleSwitch);
      drawStyleSwitch->addChild(drawStyleNode);
      drawStyleSwitch->addChild(lightModelNode);
      drawStyleSwitch->addChild(colorNode);
      drawStyleSwitch->addChild(matBindingNode);
      drawStyleSwitch->addChild(complexityNode);
      
      // set the draw style vars and fields that don't change - once we
      // have a context, will will use glGetString() to pick a better default
      // draw style.
      stillDrawStyle = VIEW_AS_IS;
      interactiveDrawStyle = VIEW_SAME_AS_STILL;
      checkForDrawStyle = TRUE;
      drawStyleSwitch->whichChild = SO_SWITCH_NONE;
      
      drawStyleNode->setOverride(TRUE); // only use style field
      drawStyleNode->pointSize = 3.0;
      drawStyleNode->lineWidth.setIgnored(TRUE);
      drawStyleNode->linePattern.setIgnored(TRUE);
      
      lightModelNode->setOverride(TRUE);
      
      colorNode->setOverride(TRUE);
      
      matBindingNode->setOverride(TRUE);
      matBindingNode->value = SoMaterialBinding::OVERALL;
      
      complexityNode->setOverride(TRUE);
      complexityNode->textureQuality = 0; // always turn texture off under switch
      complexityNode->value = 0.15;
      
      addStartCallback(SoXtViewer::drawStyleStartCallback);
      addFinishCallback(SoXtViewer::drawStyleFinishCallback);
      
      //
      // headlightGroup - we have a rotation which keeps the headlight
      // moving whenever the camera moves,  and a reset xform so
      // that the rest of the scene is not affected by the first rot.
      // these leaves the direction field in the headlight open for the
      // user to edit, allowing for the direction to change w.r.t. the camera.
      //
      headlightGroup  = new SoGroup(3);
      headlightRot    = new SoRotation;
      headlightNode   = new SoDirectionalLight;
      headlightGroup->ref();
      headlightGroup->addChild(headlightRot);
      headlightGroup->addChild(headlightNode);
      headlightGroup->addChild(new SoResetTransform);
      headlightNode->direction.setValue(SbVec3f(.2, -.2, -.9797958971));
      headlightFlag = TRUE;
      
      // Build the widget tree, and let SoXtComponent know about our base widget.
      if (buildNow) {
  	Widget w = buildWidget(getParentWidget());
  	setBaseWidget(w);
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  //    Destructor.
  //
  // Use: protected
  
  SoXtViewer::~SoXtViewer()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // detach everything
      if ( sceneGraph != NULL )
  	setSceneGraph(NULL);
      sceneRoot->unref();
      
      // delete everything
      delete inputFocus;
      delete seekAnimationSensor;
      delete clipboard;
      delete autoClipBboxAction;
      delete startCBList;
      delete finishCBList;
      headlightGroup->unref();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //  	Set a new user supplied scene graph.
  //
  // use: virtual public
  //
  void
  SoXtViewer::setSceneGraph(SoNode *newScene)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // if we haven't already given the render area a scene graph sceneRoot,
      // give it the scene graph now. This is a one shot deal, which
      // cannot be done in the constructor.
      if (SoXtRenderArea::getSceneGraph() == NULL)
  	SoXtRenderArea::setSceneGraph(sceneRoot);
      
      // draw new scene graphs to the front buffer by default since
      // the scene will be different (we might has well see something
      // happening for the first redraw).
      if (isDrawToFrontBufferEnable())
  	drawToFrontBuffer = TRUE;
      
      //
      // detach everything that depends on the old sceneGraph
      //
      if ( sceneGraph != NULL ) {
  	setCamera(NULL);
  	sceneRoot->removeChild(sceneGraph);
      }
      
      sceneGraph = newScene;
      
      //
      // now assign the new sceneGraph, find or create the new camera 
      // and attach things back.
      //
      if ( sceneGraph != NULL ) {
  	sceneRoot->addChild(sceneGraph);
  	
  	// search for first camera in the scene
  	SoSearchAction sa;
  	sa.setType(SoCamera::getClassTypeId());
  	sa.setSearchingAll(FALSE); // don't look under off switches
  	sa.apply(sceneGraph);
  	
  	SoCamera *newCamera = NULL;
  	if (sa.getPath())
  	    newCamera = (SoCamera *)((SoFullPath *)sa.getPath())->getTail();
  	
  	// if no camera found create one of the right kind...
  	if ( newCamera == NULL ) {
  	    
  	    newCamera = (SoCamera*) cameraType.createInstance();
  	    if (newCamera == NULL) {
  #ifdef DEBUG
  		SoDebugError::post("SoXtViewer::setSceneGraph",
  		    "unknown camera type!");
  #endif
  		// ??? what should we do here ?
  		cameraType = SoPerspectiveCamera::getClassTypeId();
  		newCamera = new SoPerspectiveCamera;
  	    }
  	    createdCamera = TRUE;
  	    
  	    if (type == SoXtViewer::BROWSER)
  		// add camera after drawstyle stuff
  		sceneRoot->insertChild(newCamera, 1);
  	    else {
  		// check to make sure scene starts with at least a group node
  		if ( sceneGraph->isOfType(SoGroup::getClassTypeId()) )
  		    ((SoGroup *)sceneGraph)->insertChild(newCamera, 0);
  		else {
  		    // make scene start with a group node
  		    SoGroup *group = new SoGroup;
  		    group->addChild(newCamera);
  		    group->addChild(sceneGraph);
  		    sceneRoot->addChild(group);
  		    sceneRoot->removeChild(sceneGraph);
  		    sceneGraph = group;
  		}
  	    }
  	    
  	    newCamera->viewAll(sceneGraph, SbViewportRegion(getGlxSize()));
  	}
  	
  	setCamera(newCamera);
      }
      
      // recompute the scene size variables...
      recomputeSceneSize();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //  	Recomputes the scene sizes...
  //
  // use: virtual public
  
  void
  SoXtViewer::recomputeSceneSize()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (! sceneGraph || ! sceneRoot) {
  	sceneSize = 0.0;
  	return;
      }
      
      // Use assignment notation to disambiguate from expression (edison)
      SoGetBoundingBoxAction bboxAct = SoGetBoundingBoxAction(SbViewportRegion(getGlxSize()));
      bboxAct.apply(sceneRoot);
      SbBox3f bbox = bboxAct.getBoundingBox();
      
      if (bbox.isEmpty()) {
  	sceneSize = 0.0;
  	return;
      }
      
      float x, y, z;
      bbox.getSize(x, y, z);
      sceneSize = (x > z) ? x : z;
      if (y > sceneSize)
  	sceneSize = y;
      if (sceneSize <= 0.0)
  	sceneSize = 0.0;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //  	Return the user supplied scene graph.
  //
  // use: public, virtual
  
  SoNode *
  SoXtViewer::getSceneGraph()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      return sceneGraph;
  }
  
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Sets the camera to use.
  //
  // Use: virtual public
  void
  SoXtViewer::setCamera(SoCamera *newCamera)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // check for trivual return
      if (camera == newCamera)
  	return;
      
      //
      // detach everything that depended on the old camera
      //
      if ( camera != NULL ) {
  	
          if (headlightFlag) {
  	    setHeadlight(FALSE);
  	    headlightFlag = TRUE;  // can later be turned on
          }
      	
  	if (viewingFlag) {
  	    setViewing(FALSE);
  	    viewingFlag = TRUE;  // can later be turned on
  	}
  	
  	// remove the camera if we created one outside of the
  	// scene graph.
      	if (createdCamera && type == SoXtViewer::BROWSER) {
  	    if (sceneRoot->findChild(camera) >= 0)
      		sceneRoot->removeChild(camera);
  	    createdCamera = FALSE;
  	}
  	
      	camera->unref();
      }
      
      camera = newCamera;
      
      //
      // attach everything that depends on the new camera
      //
      if ( camera != NULL) {
  	camera->ref();
  	
  	if (headlightFlag) {
  	    headlightFlag = FALSE;  // enables the routine to be called
  	    setHeadlight(TRUE);
  	}
  	
  	if (viewingFlag) {
  	    viewingFlag = FALSE;  // enables the routine to be called
  	    setViewing(TRUE);
  	}
  	
  	saveHomePosition();
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Set the camera type to create.
  //
  // Use: virtual public
  void
  SoXtViewer::setCameraType(SoType type)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (type.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) ||
  	type.isDerivedFrom(SoOrthographicCamera::getClassTypeId()))
  	cameraType = type;
  #ifdef DEBUG
      else
  	SoDebugError::post("SoXtViewer::setCameraType",
  			"unknown camera type!");
  #endif
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    See the whole scene from the camera
  //
  // Use: public
  void
  SoXtViewer::viewAll()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if ( camera != NULL )
  	camera->viewAll(sceneGraph,SbViewportRegion(getGlxSize()));
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Sets the viewing mode.
  //
  // Use: virtual public
  void
  SoXtViewer::setViewing(SbBool flag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (flag == viewingFlag)
  	return;
      
      viewingFlag = flag;
      
      // if we are goind into viewing mode, then de-highlight any
      // currently highlighted nodes (since the object will never receive
      // any motion events).
      if (viewingFlag) {
  	SoGLRenderAction *glAct = getGLRenderAction();
  	if (glAct)
  	    SoLocateHighlight::turnOffCurrentHighlight(glAct);
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Enables/Disable the viewer cursor on the window.
  //
  // Use: virtual public
  void
  SoXtViewer::setCursorEnabled(SbBool flag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      cursorEnabledFlag = flag;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Sets the auto clipping mode
  //
  // Use: public
  void
  SoXtViewer::setAutoClipping(SbBool flag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (autoClipFlag == flag)
  	return;
      
      autoClipFlag = flag;
      
      // cause a redraw to correctly place the near and far plane now that
      // auto clipping is on.
      if (autoClipFlag)
  	scheduleRedraw();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	sets stereo mode
  //
  // Use: virtual public
  void
  SoXtViewer::setStereoViewing(SbBool flag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (flag == isStereoViewing())
  	return;
      
      // First, check to see if the OpenGL stereo visual can be created
      setStereoBuffer(flag);
      
  #ifdef __sgi
      // since OpenGL stereo failed, see if the SGI extension will work
      // by checking whether the X server supports it....
      int first_event, first_error;
      if (flag != isStereoViewing() && 
  	XSGIStereoQueryExtension(getDisplay(), &first_event, &first_error)) {
  	
  	if (flag) {
  	    // make sure the current window will support stereo
  	    // ??? if we havn't been managed yet, just assume this visual
  	    // ??? will support stereo viewing (see bug 
  	    if (! getNormalWindow())
  		useSGIStereoExt = TRUE;
  	    else if (XSGIQueryStereoMode(getDisplay(), getNormalWindow()) !=
  		X_STEREO_UNSUPPORTED)
  		// stereo will be turned on in the rendering....
  		useSGIStereoExt = TRUE;
  	    
  	    // save the camera original aspect ratio since it will be updated
  	    // during rendering to strech the objects. We will restore it
  	    // when stereo if OFF.
  	    camStereoOrigAspect = camera->aspectRatio.getValue();
  	    camStereoOrigVPMapping = camera->viewportMapping.getValue();
  	}
  	else {
  	    // turn stereo off on the window
  	    useSGIStereoExt = FALSE;
  	    
  	    // clear the left/right buffers to prevent gost images from
  	    // the other view...(until the user resets the monitor with setmon)
  	    if (isRGBMode()) {
  		SbColor color = getBackgroundColor();
  		glClearColor(color[0], color[1], color[2], 0);
  	    }
  	    else
  		glClearIndex(getBackgroundIndex());
  	    
  	    glDrawBuffer(GL_FRONT_AND_BACK);
  	    
  	    XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_LEFT);
  	    XSync(getDisplay(), False);
  	    glClear(GL_COLOR_BUFFER_BIT);
  	    
  	    XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_RIGHT);
  	    XSync(getDisplay(), False);
  	    glClear(GL_COLOR_BUFFER_BIT);
  	    
  	    glDrawBuffer( isDoubleBuffer() ? GL_BACK : GL_FRONT);
  	    
  	    // restore the camera original aspect ratio (saved above)
  	    camera->aspectRatio = camStereoOrigAspect;
  	    camera->viewportMapping = camStereoOrigVPMapping;
  	}
  	
  	// now cause a redraw to see the affect since we havn't changed
  	// the actual visual (unlike OpenGL)
  	if (flag == isStereoViewing())
  	    scheduleRedraw();
      }
  #endif
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	gets stereo mode
  //
  // Use: virtual public
  SbBool
  SoXtViewer::isStereoViewing()
  //
  ////////////////////////////////////////////////////////////////////////
  {
  #ifdef __sgi
      return (isStereoBuffer() || useSGIStereoExt);
  #else
      // done in SoXtGLWidget
      return isStereoBuffer();
  #endif
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    adds a directional light to the scene graph that is a headlight
  //    positioned over the left shoulder of the camera.  It has a sensor
  //    on the camera, so that it always is pointing in the same direction
  //    as the camera.  The sensor is not delayed, so that it is "always"
  //    accurate.
  //
  // Use: virtual public
  //
  void
  SoXtViewer::setHeadlight(SbBool insertFlag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // check for trivial return
      if (camera == NULL || headlightFlag == insertFlag) {
  	headlightFlag = insertFlag;
  	return;
      }
      
      //
      // find the camera parent to insert/remove the headlight
      //
      SoSearchAction sa;
      if (insertFlag)
  	sa.setNode(camera);
      else {
  	sa.setNode(headlightGroup);
  	sa.setSearchingAll(TRUE); // find under OFF switches for removal
      }
      sa.apply(sceneRoot);
      SoFullPath *fullPath = (SoFullPath *) sa.getPath();
      if (!fullPath) {
  #if DEBUG
  	SoDebugError::post("SoXtViewer::setHeadlight",
  			    insertFlag ? "ERROR: cannot find camera in graph" :
  			    "ERROR: cannot find headlight in graph");
  #endif
  	return;
      }
      SoGroup *parent = (SoGroup *) fullPath->getNodeFromTail(1);
      
      headlightFlag = insertFlag;
      
      //
      // inserts/remove the headlight group node
      //
      if (headlightFlag) {
  	int camIndex;
  	
  	// check to make sure that the camera parent is not a switch node
  	// (VRML camera viewpoints are kept under a switch node). Otherwise
  	// we will insert the headlight right before the switch node.
  	if (parent->isOfType(SoSwitch::getClassTypeId())) {
  	    SoNode *switchNode = parent;
  	    parent = (SoGroup *) fullPath->getNodeFromTail(2);
  	    camIndex = parent->findChild(switchNode);
  	}
  	else
  	    camIndex = parent->findChild(camera);
  	
  	// return if headlight is already there (this should be an error !)
  	if (parent->findChild(headlightGroup) >= 0)
  	    return;
  	
  	// insert the light group right after the camera
  	if (camIndex >= 0)
  	    parent->insertChild(headlightGroup, camIndex+1);
      }
      else {
  	if (parent->findChild(headlightGroup) >= 0)
  	    parent->removeChild(headlightGroup);
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //   	Sets the drawing style.
  //
  // Use: virtual public
  void
  SoXtViewer::setDrawStyle(
  	    SoXtViewer::DrawType type, SoXtViewer::DrawStyle style)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // prevent us from picking a default draw style if the user already
      // has done so...
      checkForDrawStyle = FALSE;
      
      if (type == STILL) {
  	if (stillDrawStyle == style)
  	    return;
  	if (style == VIEW_SAME_AS_STILL) {
  #ifdef DEBUG
  	    SoDebugError::post("SoXtViewer::setDrawStyle", 
  		"illegal VIEW_SAME_AS_STILL draw style passed for STILL !");
  #endif
  	    return; 
  	}
  	stillDrawStyle = style;
  	
  	if (! interactiveFlag || interactiveDrawStyle == VIEW_SAME_AS_STILL
  	    || (interactiveDrawStyle == VIEW_NO_TEXTURE && style != VIEW_AS_IS))
  		    setCurrentDrawStyle(style);
  	else if (interactiveFlag && interactiveDrawStyle == VIEW_NO_TEXTURE && 
  	    style == VIEW_AS_IS)
  		    setCurrentDrawStyle(interactiveDrawStyle);
      }
      else {
  	// else it type == INTERACTIVE
  	
  	if (interactiveDrawStyle == style)
  	    return;
  	interactiveDrawStyle = style;
  	
  	if (interactiveFlag) {
  	    if (style == VIEW_SAME_AS_STILL || 
  	       (style == VIEW_NO_TEXTURE && stillDrawStyle != VIEW_AS_IS))
  		setCurrentDrawStyle(stillDrawStyle);
  	    else
  		setCurrentDrawStyle(style);
  	}
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //   	Sets the current drawing style. This only changes the nodes to
  //  match what is passed (called from multiple places) and doesn't
  //  affect the current state.
  //
  // Use: private
  void
  SoXtViewer::setCurrentDrawStyle(SoXtViewer::DrawStyle style)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (style != VIEW_AS_IS)
  	drawStyleSwitch->whichChild = SO_SWITCH_ALL;
      
      switch(style) {
  	case VIEW_AS_IS:
  	    drawStyleSwitch->whichChild = SO_SWITCH_NONE;
  	    break;
  	    
  	case VIEW_HIDDEN_LINE:
  	    // texture is always off under the switch node.
  	    // List only stuff common to both rendering passes
  	    // (the rest is done when rendering)
  	    drawStyleNode->style.setIgnored(FALSE);
  	    drawStyleNode->pointSize.setIgnored(TRUE);
  	    lightModelNode->model = SoLightModel::BASE_COLOR;
  	    lightModelNode->model.setIgnored(FALSE);
  	    complexityNode->type.setIgnored(TRUE);
  	    complexityNode->value.setIgnored(TRUE);
  	    break;
  	    
  	case VIEW_NO_TEXTURE:
  	case VIEW_LOW_COMPLEXITY:
  	    // texture is always off under the switch node
  	    drawStyleNode->style.setIgnored(TRUE);
  	    drawStyleNode->pointSize.setIgnored(TRUE);
  	    lightModelNode->model.setIgnored(TRUE);
  	    colorNode->orderedRGBA.setIgnored(TRUE);
  	    matBindingNode->value.setIgnored(TRUE);
  	    complexityNode->type.setIgnored(TRUE);
  	    complexityNode->value.setIgnored(style != VIEW_LOW_COMPLEXITY);
  	    break;
  	    
  	case VIEW_LINE:
  	case VIEW_LOW_RES_LINE:
  	case VIEW_POINT:
  	case VIEW_LOW_RES_POINT:
  	    // texture is always off under the switch node
  	    drawStyleNode->style = (style == VIEW_LINE || style == VIEW_LOW_RES_LINE) ? 
  		SoDrawStyle::LINES : SoDrawStyle::POINTS;
  	    drawStyleNode->style.setIgnored(FALSE);
  	    drawStyleNode->pointSize.setIgnored(style != VIEW_POINT && style != VIEW_LOW_RES_POINT);
  	    lightModelNode->model = SoLightModel::BASE_COLOR;
  	    lightModelNode->model.setIgnored(FALSE);
  	    colorNode->orderedRGBA.setIgnored(TRUE);
  	    matBindingNode->value.setIgnored(TRUE);
  	    
  	    // Force a lower complexity for the low res draw styles
  	    // ??? this only works if the object didn't have
  	    // ??? something lower in the first place...
  	    if (style == VIEW_LOW_RES_LINE || style == VIEW_LOW_RES_POINT) {
  		complexityNode->type = SoComplexity::OBJECT_SPACE;
  		complexityNode->type.setIgnored(FALSE);
  		complexityNode->value.setIgnored(FALSE);
  	    }
  	    else {
  		complexityNode->type.setIgnored(TRUE);
  		complexityNode->value.setIgnored(TRUE);
  	    }
  	    break;
  	    
  	case VIEW_BBOX:
  	    // texture is always off under the switch node
  	    drawStyleNode->style = SoDrawStyle::LINES;
  	    drawStyleNode->style.setIgnored(FALSE);
  	    drawStyleNode->pointSize.setIgnored(TRUE);
  	    lightModelNode->model = SoLightModel::BASE_COLOR;
  	    lightModelNode->model.setIgnored(FALSE);
  	    colorNode->orderedRGBA.setIgnored(TRUE);
  	    matBindingNode->value.setIgnored(TRUE);
  	    complexityNode->type = SoComplexity::BOUNDING_BOX;
  	    complexityNode->type.setIgnored(FALSE);
  	    complexityNode->value.setIgnored(TRUE);
  	    break;
  	    
  	case VIEW_SAME_AS_STILL:
  #ifdef DEBUG
  	    SoDebugError::post("SoXtViewer::setCurrentDrawStyle", "VIEW_SAME_AS_STILL was passed !");
  #endif
  	    break;
      }
      
      setZbufferState();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //   	Gets the drawing style.
  //
  // Use: public
  SoXtViewer::DrawStyle
  SoXtViewer::getDrawStyle(SoXtViewer::DrawType type)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      return (type == STILL ? stillDrawStyle : interactiveDrawStyle);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //   	redefine this routine to also correctly set the buffering type
  //
  // Use: virtual public
  void
  SoXtViewer::setNormalVisual(XVisualInfo *vis)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // call parent class
      SoXtRenderArea::setNormalVisual(vis);
      
      // now update the buffering type
      if (isDoubleBuffer())
  	setBufferingType(BUFFER_DOUBLE);
      else
  	setBufferingType(BUFFER_SINGLE);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	redefine this routine from SoXtGLWidget to call the viewer
  //  SoXtViewer::setBufferingType() method which is a superset.
  //
  //  Use: virtual public
  //
  void
  SoXtViewer::setDoubleBuffer(SbBool flag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      setBufferingType(flag ? SoXtViewer::BUFFER_DOUBLE : SoXtViewer::BUFFER_SINGLE);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //   	Sets the buffering style.
  //
  // Use: virtual public
  void
  SoXtViewer::setBufferingType(SoXtViewer::BufferType type)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (bufferType == type)
      	return;
      
      // remove interactive callback
      if (bufferType == BUFFER_INTERACTIVE) {
  	removeStartCallback(SoXtViewer::bufferStartCallback);
  	removeFinishCallback(SoXtViewer::bufferFinishCallback);
      }
      
      bufferType = type;
      
      switch(bufferType) {
  	case BUFFER_SINGLE:
  	    SoXtRenderArea::setDoubleBuffer(FALSE);
  	    break;
  	case BUFFER_DOUBLE:
  	    SoXtRenderArea::setDoubleBuffer(TRUE);
  	    break;
  	case BUFFER_INTERACTIVE:
  	    SoXtRenderArea::setDoubleBuffer(FALSE);
  	    addStartCallback(SoXtViewer::bufferStartCallback);
  	    addFinishCallback(SoXtViewer::bufferFinishCallback);
  	    break;
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Externally set the viewer into/out off seek mode (default OFF). Actual
  //  seeking will not happen until the viewer decides to (ex: mouse click).
  //
  //  Note: setting the viewer out of seek mode while the camera is being
  //  animated will stop the animation to the current location.
  //
  // use: virtual protected
  
  void
  SoXtViewer::setSeekMode(SbBool flag)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (!isViewing())
  	return;
      
      // check if seek is being turned off while seek animation is happening
      if ( !flag && seekAnimationSensor->getAttachedField() ) {
  	seekAnimationSensor->detach();
  	seekAnimationSensor->unschedule();
  	interactiveCountDec();
      }
      
      seekModeFlag = flag;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Adjust the camera clipping planes before a redraw.
  //
  // use: virtual protected.
  
  void
  SoXtViewer::actualRedraw()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (isAutoClipping() && ! isStereoViewing())
  	adjustCameraClippingPlanes();
      
      // update the headlight if necessary
      if (headlightFlag && camera)
  	headlightRot->rotation.setValue(camera->orientation.getValue());
      
      // make sure that we have a valid sceneSize value - but don't compute
      // a new sceneSize value for every redraw since the walking speed should
      // really be constant.
      if (sceneSize == 0.0)
  	recomputeSceneSize();
      
      //
      // Check to see if we are in stereo mode, if so draw the scene
      // twice with the camera offseted between the two views, else
      // do a simple redraw.
      //
      
      if ( isStereoViewing() && camera != NULL) {
  	
  	// Check the camera type, since stereo is different:
  	//
  	// Ortho camera: setereo is accomplished by simply rorating
  	// the camera (around the point of interest) by 6 degree. 
  	//
  	// Perspective camera: we translate the camera and rotate
  	// them to look at the same point of interest (idealy we also would
  	// make sure the plane of convergence is exactly the same for
  	// both perspective views, unfortunatly we cannot do this with
  	// the current symetric view volumes).
  	//
  	
  	// save the camera original values to restore the camera after
  	// both views are rendered. This means we will use this in between
  	// left and right view for things like picking.
  	SbVec3f	    camOrigPos = camera->position.getValue();
  	SbRotation  camOrigRot = camera->orientation.getValue();
  	
  	// get the camera focal point
  	SbMatrix mx;
  	mx = camOrigRot;
  	SbVec3f forward( -mx[2][0], -mx[2][1], -mx[2][2]);
  	float radius = camera->focalDistance.getValue();
  	SbVec3f center = camOrigPos + radius * forward;
  	
  #ifdef __sgi
  	//
  	// if we are splitting the screen in half (loose vertical resolution)
  	// then change the aspect ratio to squish the objects to make them 
  	// look square again through the stereo glasses. This is done for
  	// every redraw since we need to manually update the aspect ourself.
  	//
  	if (useSGIStereoExt) {
  	    SbVec2s windowSize = getGlxSize();
  	    camera->aspectRatio = 0.5 * windowSize[0] / (float) windowSize[1];
  	    camera->viewportMapping = SoCamera::LEAVE_ALONE;
  	}
  #endif
  	
  	//
  	// change the camera for the LEFT eye view, and render
  	//
  #ifdef __sgi
  	if (useSGIStereoExt) {
  	    XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_LEFT);
  	    XSync(getDisplay(), False);
  	}
  	else
  #endif
  	    glDrawBuffer( (isDoubleBuffer() && !drawToFrontBuffer) ? 
  						    GL_BACK_LEFT : GL_FRONT_LEFT);
  	// rotate the camera by - stereoOffset/2 degrees
  	camera->orientation = 
  	    SbRotation(SbVec3f(0, 1, 0), - stereoOffset * M_PI / 360.0) * camOrigRot;
  	
  	// reposition camera to look at pt of interest
  	mx = camera->orientation.getValue();
  	forward.setValue( -mx[2][0], -mx[2][1], -mx[2][2]);
  	camera->position = center - radius * forward;
  	
  	if (isAutoClipping())
  	    adjustCameraClippingPlanes();
  	doRendering();
  	
  	//
  	// change the camera for the RIGHT eye view, and render
  	//
  #ifdef __sgi
  	if (useSGIStereoExt) {
  	    XSGISetStereoBuffer(getDisplay(), getNormalWindow(), STEREO_BUFFER_RIGHT);
  	    XSync(getDisplay(), False);
  	}
  	else
  #endif
  	    glDrawBuffer( (isDoubleBuffer() && !drawToFrontBuffer) ? 
  						    GL_BACK_RIGHT : GL_FRONT_RIGHT);
  	// rotate the camera by + stereoOffset/2 degrees
  	camera->orientation = 
  	    SbRotation(SbVec3f(0, 1, 0), stereoOffset * M_PI / 360.0) * camOrigRot;
  	
  	// reposition camera to look at pt of interest
  	mx = camera->orientation.getValue();
  	forward.setValue( -mx[2][0], -mx[2][1], -mx[2][2]);
  	camera->position = center - radius * forward;
  	
  	if (isAutoClipping())
  	    adjustCameraClippingPlanes();
  	doRendering();
  	
  	
  	//
  	// reset the camera original values now that we are done rendering
  	// the stereo views (leave aspect ratio to do correct picking).
  	camera->enableNotify(FALSE); // don't cause a redraw
  	camera->position = camOrigPos;
  	camera->orientation = camOrigRot;
  	camera->enableNotify(TRUE);
  	
  #ifdef __sgi
  	if (! useSGIStereoExt)
  #endif
  	    // restore to draw to both buffer (viewer feedback)
  	    glDrawBuffer( (isDoubleBuffer() && !drawToFrontBuffer) ? 
  						    GL_BACK : GL_FRONT);
      }
      //
      // else not stereo viewing, so do the regular rendering....
      //
      else
  	doRendering();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Do a multiple pass rendering if necessary, else simply call
  //  SoXtRenderAre::actualRedraw() method.
  //
  // use: private
  void
  SoXtViewer::doRendering()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      //
      // check if we need two pass rendering for hidden line rendering
      //
      
      SbBool drawHiddenLine = 
  	(stillDrawStyle == VIEW_HIDDEN_LINE && (! interactiveFlag ||
  				interactiveDrawStyle == VIEW_NO_TEXTURE ||
  				interactiveDrawStyle == VIEW_LOW_COMPLEXITY ||
  				interactiveDrawStyle == VIEW_SAME_AS_STILL)) 
  	|| (interactiveFlag && interactiveDrawStyle == VIEW_HIDDEN_LINE);
  	
      if (camera != NULL && drawHiddenLine) {
  	
  	// ??? what do we do about highlights ??
  	
  	// the smaller the near clipping plane is relative to the far
  	// plane, the smaller the zbuffer offset needs to be (because
  	// the granularity will be pretty big). The closer the clipping
  	// planes are relative to each other, the bigger the zbuffer offset
  	// needs to be (because the zbuffer granularity will be small).
  	// The scale factor was found empirically to work best with the
  	// current settings of near/far.
  	float zOffset = camera->nearDistance.getValue() / 
  	    (40 * camera->farDistance.getValue());
  	
  	//
  	// render the first pass as solid, using the background color
  	// for the object base color.
  	//
  	
  	drawStyleNode->style = SoDrawStyle::FILLED;
  	colorNode->orderedRGBA = getBackgroundColor().getPackedValue();
  	colorNode->orderedRGBA.setIgnored(FALSE);
  	matBindingNode->value.setIgnored(FALSE);
  	
  	// ??? this should match the SoXtRenderArea::actualRedraw()
  	// ??? method exactly (apart for not clearing the z-buffer)
  	glDepthRange(zOffset, 1); // enable wireframe to be draw on top
  	getSceneManager()->render(isClearBeforeRender(), TRUE);
  	
  	//
  	// render the second pass as wireframe
  	// (the first pass rendered the objects solid with base color
  	// set to the background color to set the zbuffer values)
  	//
  	
  	drawStyleNode->style = SoDrawStyle::LINES;
  	colorNode->orderedRGBA.setIgnored(TRUE);
  	matBindingNode->value.setIgnored(TRUE);
  	
  	// ??? this should match the SoXtRenderArea::actualRedraw()
  	// ??? method exactly (apart for not clearing the color and z-buffer)
  	glDepthRange(0,1-zOffset); // enable wireframe to be draw on top
  	getSceneManager()->render(FALSE, FALSE);
  	
  	glDepthRange(0, 1); // restore the range
      }
      else {
  	// ??? this should match the SoXtRenderArea::actualRedraw()
  	// ??? method exactly (apart for not clearing the z-buffer)
  	getSceneManager()->render(isClearBeforeRender(), ! isZbufferOff());
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	This is called when we are first mapped - this will check the
  //  configuration of the machine we are running on and decide on what
  //  draw style we should pick.
  //
  // use: virtual protected
  
  void
  SoXtViewer::afterRealizeHook()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // call the base class
      SoXtRenderArea::afterRealizeHook();
      
      // only do this once and only IF the user hasn't overwritten this
      if (! checkForDrawStyle)
  	return;
      checkForDrawStyle = FALSE;
      
      SbBool useTexture = TRUE;	// true by default (and for new machines)
      const char *renderer = (const char *) glGetString(GL_RENDERER);
      
      //
      // On the following SGI machines we don't want texture rendering 
      // to be turned on by default (for speed reason) - machines not listed
      // below are assumed to be fast enough to leave VIEW_AS_IS draw style.
      //
      
      if (strncmp((const char *)glGetString(GL_VENDOR), "SGI", 3) == 0) {
  	
  	// Indy and XL
  	if (useTexture && strncmp(renderer, "NEWPORT", 7) == 0)
  	    useTexture = FALSE;
  	// Personal Iris
  	if (useTexture && strncmp(renderer, "GR1", 3) == 0)
  	    useTexture = FALSE;
  	// VGX and VGXT
  	if (useTexture && strncmp(renderer, "VGX", 3) == 0)
  	    useTexture = FALSE;
  	// Indigo Entry
  	if (useTexture && 
  	    strncmp(renderer, "LG1", 3) == 0 ||
  	    strncmp(renderer, "LIGHT", 5) == 0)
  	    useTexture = FALSE;
  	// XS, XZ, Elan, and Extreme
  	if (useTexture && (
  	    strncmp(renderer, "GR2", 3) == 0 || 
  	    strncmp(renderer, "GR3", 3) == 0 ||
  	    strncmp(renderer, "GU1", 3) == 0))
  	    useTexture = FALSE;
      }
      
      if (! useTexture)
  	setDrawStyle(SoXtViewer::INTERACTIVE, SoXtViewer::VIEW_NO_TEXTURE);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Returns TRUE if the zbuffer should be off (based on the viewer
  //  draw styles).
  //
  // Use: private
  
  SbBool
  SoXtViewer::isZbufferOff()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      DrawStyle style = (interactiveFlag ? interactiveDrawStyle : stillDrawStyle);
      if (interactiveFlag && interactiveDrawStyle == VIEW_SAME_AS_STILL)
  	style = stillDrawStyle;
      
      // for these draw styles, turn the zbuffer off
      return (style == VIEW_LOW_RES_LINE || style == VIEW_LOW_RES_POINT 
  	|| style == VIEW_BBOX);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Sets the zbuffer state on the current window. This is called whenever
  //  the windows changes (called by SoXtGLWidget::widgetChanged()) or when
  //  the viewer draw style changes.
  //
  // Use: private
  
  void
  SoXtViewer::setZbufferState()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (getNormalWindow() == (Window) NULL)
  	return;
      
      glXMakeCurrent(getDisplay(), getNormalWindow(), getNormalContext());
      
      if (isZbufferOff())
  	glDisable(GL_DEPTH_TEST);
      else
  	glEnable(GL_DEPTH_TEST);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    saves the camera values for later restore.
  //
  // Use: virtual public
  
  void
  SoXtViewer::saveHomePosition()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      origPosition = camera->position.getValue();
      origOrientation = camera->orientation.getValue();
      origNearDistance = camera->nearDistance.getValue();
      origFarDistance = camera->farDistance.getValue();
      origFocalDistance = camera->focalDistance.getValue();
      
      // save camera height (changed by zooming)
      if (camera->isOfType(SoPerspectiveCamera::getClassTypeId()))
  	origHeight = ((SoPerspectiveCamera *)camera)->heightAngle.getValue();
      else if (camera->isOfType(SoOrthographicCamera::getClassTypeId()))
  	origHeight = ((SoOrthographicCamera *)camera)->height.getValue();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    reset the camera to it's saved values.
  //
  // Use: virtual public
  
  void
  SoXtViewer::resetToHomePosition()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      camera->position = origPosition;
      camera->orientation = origOrientation;
      camera->nearDistance = origNearDistance;
      camera->farDistance = origFarDistance;
      camera->focalDistance = origFocalDistance;
      
      // restore camera height (changed by zooming)
      if (camera->isOfType(SoPerspectiveCamera::getClassTypeId()))
  	((SoPerspectiveCamera *)camera)->heightAngle.setValue(origHeight);
      else if (camera->isOfType(SoOrthographicCamera::getClassTypeId()))
  	((SoOrthographicCamera *)camera)->height.setValue(origHeight);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description: _ HACK BUG WORKAOUND _
  //
  //  This updates the event own internal knowledge about which buttons and
  //  modifiers keys are down based of the event itself.
  //
  //  ??? for some reason Xt does NOT update the event button and modifier
  //  ??? state until AFTER the event has been sent to the widget. Knowing
  //  ??? which mouse buttons and which modifiers keys are down when receiving
  //  ??? ANY event (uncluding the event itself) is VERY useful and make
  //  ??? the viewer mode chaging code MUCH simpler.
  //
  // Use: private - BUG WORKAROUND 
  static void
  updateEventState(XAnyEvent *xe)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      XButtonEvent    *be;
      XKeyEvent	    *ke;
      KeySym	    keysym;
      
      switch(xe->type) {
  	case ButtonPress:
  	    be = (XButtonEvent *)xe;
  	    if (be->button == Button1)
  		be->state = (be->state | Button1Mask);
  	    else if (be->button == Button2)
  		be->state = (be->state | Button2Mask);
  	    break;
  	    
  	case ButtonRelease:
  	    be = (XButtonEvent *)xe;
  	    if (be->button == Button1)
  		be->state = (be->state & ~Button1Mask);
  	    else if (be->button == Button2)
  		be->state = (be->state & ~Button2Mask);
  	    break;
  	    
  	case KeyPress:
  	case KeyRelease:
  	    ke = (XKeyEvent *)xe;
  	    keysym = XLookupKeysym(ke, 0);
  	    
  	    if (keysym == XK_Control_L || keysym == XK_Control_R) {
  		if (xe->type == KeyPress)
  		    ke->state = (ke->state | ControlMask);
  		else
  		    ke->state = (ke->state & ~ControlMask);
  	    }
  	    break;
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Process a common set of events which are shared accross all 
  //  viewers. Returning TRUE if the event was processed.
  //
  // Use: protected
  SbBool
  SoXtViewer::processCommonEvents(XAnyEvent *xe)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      KeySym keysym;
      
      // check if the application wants to handle the event itself
      // instead of giving it to the viewer. This can be used to disable
      // some simple viewer functionality (like the Arrow keys or Esc key).
      // ??? this is a simple work around for bug #113991 - Xt translation
      // ??? tables would be better than dealing with events directly.
      if (SoXtRenderArea::invokeAppCB(xe))
  	    return TRUE;
      
      //
      // check for special key which turns viewing on/off
      //
      if (xe->type == KeyPress) {
  	XKeyEvent *ke = (XKeyEvent *)xe;
  	keysym = XLookupKeysym(ke, 0);
  	if (keysym == XK_Escape) {
  	    setViewing( !isViewing() );  // toggle the viewing mode...
  	    return TRUE;
  	}
  	else if (!isViewing() && (keysym == XK_Alt_L || keysym == XK_Alt_R)
  	    && !(ke->state & Button1Mask || ke->state & Button2Mask)) {
  	    // Alt-key goes from PICK to VIEW if
  	    // 1] we are not in VIEW mode already
  	    // 2] no mouse buttons are pressed
  	    //
  	    altSwitchBack = TRUE;   // later return back
  	    setViewing(TRUE);
  	    return TRUE;
  	}
      }
      else if (xe->type == KeyRelease) {
  	keysym = XLookupKeysym((XKeyEvent *)xe, 0);
  	if (altSwitchBack && (keysym == XK_Alt_L || keysym == XK_Alt_R)) {
  	    // if Alt-key, then return to PICK (if we had switched)
  	    setViewing(FALSE);
  	    altSwitchBack = FALSE;  // clear the flag
  	    return TRUE;
  	}
      }
      else if (xe->type == EnterNotify) {
  	XCrossingEvent *ce = (XCrossingEvent *)xe;
  	//
  	// because the application might use Alt-key for motif menu
  	// accelerators we might not receive a key-up event, so make sure
  	// to reset any Alt mode (temporary viewing) when the mouse re-enters
  	// the window.
  	//
  	if (! isViewing() && ce->state & Mod1Mask) {
  	    altSwitchBack = TRUE;   // later return back
  	    setViewing(TRUE);
  	}
  	else if (altSwitchBack && !(ce->state & Mod1Mask)) {
  	    setViewing(FALSE);
  	    altSwitchBack = FALSE;  // clear the flag
  	}
      }
      
      // send the event to the scene graph if viewing is off
      if ( !isViewing() ) {
  	// prevent renderArea from sending the event to the app twice 
  	// since it is done above...
  	SoXtRenderAreaEventCB *saveFunc = appEventHandler;
  	appEventHandler = NULL;
  	SoXtRenderArea::processEvent(xe);
  	appEventHandler = saveFunc;
  	
  	return TRUE;
      }
      
      // if no camera discard events
      if (camera == NULL)
  	return TRUE;
      
      SbBool handled = TRUE;
      
      // ??? workaround what seem to be an Xt bug...
      updateEventState(xe);
      
      switch(xe->type) {
  	case KeyPress:
  	    switch ( keysym ) {
  		case XK_Home:
  		    resetToHomePosition();
  		    break;
  		case XK_s:
  		    setSeekMode( !isSeekMode() );
  		    // ??? this is kind of a hack, but it is needed
  		    // ??? until a better solution is found
  		    if ( isSeekMode() && interactiveCount != 0 ) {
  			interactiveCount = 0;
  			finishCBList->invokeCallbacks(this);
  		    }
  		    break;
  		case XK_Left:
  		case XK_Up:
  		case XK_Right:
  		case XK_Down:
  		    arrowKeyPressed(keysym);
  		    break;
  		default:
  		    handled = FALSE;
  		    break;
  	    }
  	    break;
  	    
  	default:
  	    handled = FALSE;
  	    break;
      }
      
      return handled;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Increment the intercative viewing counter.
  //
  // Use: protected.
  
  void
  SoXtViewer::interactiveCountInc()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      interactiveCount++;
      
      if (interactiveCount == 1)
  	startCBList->invokeCallbacks(this);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Decrement the intercative viewing counter.
  //
  // Use: protected.
  
  void
  SoXtViewer::interactiveCountDec()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (interactiveCount > 0) {
  	interactiveCount--;
  	if (interactiveCount == 0)
  	    finishCBList->invokeCallbacks(this);
      }
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  // This routine is used by subclasses to initiate the seek animation. Given a
  // screen mouse location, this routine will return the picked point
  // and the normal at that point. It will also schedule the sensor to animate
  // if necessary. The routine retuns TRUE if something got picked...
  //
  // Note: if detailSeek is on, the point and normal correspond to the exact 
  //	 3D location under the cursor.
  //	 if detailSeek if off, the object bbox center and the camera 
  //	 orientation are instead returned.
  //
  // Use: protected.
  
  SbBool
  SoXtViewer::seekToPoint(const SbVec2s &mouseLocation)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL) {
  	setSeekMode(FALSE);
  	return FALSE;
      }
      
      // do the picking
      // Use assignment notation to disambiguate from expression (edison)
      SoRayPickAction pick = SoRayPickAction(SbViewportRegion(getGlxSize()));
      pick.setPoint(mouseLocation);
      pick.setRadius(1.0);
      pick.setPickAll(FALSE); // pick only the closest object
      pick.apply(sceneRoot);
      
      // makes sure something got picked
      SoPickedPoint *pp = pick.getPickedPoint();
      if ( pp == NULL ) {
  	setSeekMode(FALSE);
  	return FALSE;
      }
      
      //
      // Get picked point and normal if detailtSeek
      //
      if (detailSeekFlag) {
  	
  	seekPoint = pp->getPoint();
  	seekNormal = pp->getNormal();
  	
  	// check to make sure normal points torward the camera, else
  	// flip the normal around
  	if ( seekNormal.dot(camera->position.getValue() - seekPoint) < 0 )
  	    seekNormal.negate();
      }
      //
      // else get object bounding box as the seek point and the camera
      // orientation as the normal.
      //
      else {
  	// get center of object's bounding box
  	// Use assignment notation to disambiguate from expression (edison)
  	SoGetBoundingBoxAction bba = SoGetBoundingBoxAction(SbViewportRegion(getGlxSize()));
  	bba.apply(pp->getPath());
  	SbBox3f bbox = bba.getBoundingBox();
  	seekPoint = bbox.getCenter();
  	
  	// keep the camera oriented the same way
  	SbMatrix mx;
  	mx = camera->orientation.getValue();
  	seekNormal.setValue(mx[2][0], mx[2][1], mx[2][2]);
      }
      
      
      //
      // now check if animation sensor needs to be scheduled
      //
      
      computeSeekVariables = TRUE;
      if (seekAnimTime == 0) {
  	
  	// jump to new location, no animation needed
  	interpolateSeekAnimation(1.0);
      }
      else {
  	// schedule sensor and call viewer start callbacks
  	if ( ! seekAnimationSensor->getAttachedField() ) {
  	    seekAnimationSensor->attach(viewerRealTime);
  	    seekAnimationSensor->schedule();
  	    interactiveCountInc();
  	}
  	
  	seekStartTime = viewerRealTime->getValue();
      }
      
      return TRUE;    // successfull
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	computes what the final camera seek orientation should be.
  //  
  // Use: virtual protected
  
  void
  SoXtViewer::computeSeekFinalOrientation()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      SbMatrix mx;
      SbVec3f viewVector;
      
      // find the camera final orientation
      if ( isDetailSeek() ) {
  	
  	// get the camera new orientation
  	mx = camera->orientation.getValue();
  	viewVector.setValue(-mx[2][0], -mx[2][1], -mx[2][2]);
  	SbRotation changeOrient;
  	changeOrient.setValue(viewVector, seekPoint - camera->position.getValue());
  	newCamOrientation = camera->orientation.getValue() * changeOrient;
      }
      else
  	newCamOrientation = camera->orientation.getValue();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //  
  // Use: virtual protected
  
  void
  SoXtViewer::interpolateSeekAnimation(float t)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      //
      // check if camera new and old position/orientation have already
      // been computed.
      //
      if (computeSeekVariables) {
  	SbMatrix mx;
  	SbVec3f viewVector;
  	
  	// save camera starting point
  	oldCamPosition = camera->position.getValue();
  	oldCamOrientation = camera->orientation.getValue();
  	
  	// compute the distance the camera will be from the seek point
  	// and update the camera focalDistance.
  	float dist;
  	if ( seekDistAsPercentage ) {
  	    SbVec3f seekVec(seekPoint - camera->position.getValue());
  	    dist = seekVec.length() * (seekDistance / 100.0);
  	}
  	else
  	    dist = seekDistance;
  	camera->focalDistance = dist;
  	
  	// let subclasses have a chance to redefine what the
  	// camera final orientation should be.
  	computeSeekFinalOrientation();
  	
  	// find the camera final position based on orientation and distance
  	mx = newCamOrientation;
  	viewVector.setValue(-mx[2][0], -mx[2][1], -mx[2][2]);
  	newCamPosition = seekPoint - dist * viewVector;
  	
  	computeSeekVariables = FALSE;
      }
      
      
      //
      // Now position the camera according to the animation time
      //
      
      // use and ease-in ease-out approach
      float cos_t = 0.5 - 0.5 * cosf(t * M_PI);
      
      // get camera new rotation
      camera->orientation = SbRotation::slerp(oldCamOrientation, newCamOrientation, cos_t);
      
      // get camera new position
      camera->position = oldCamPosition + (newCamPosition - oldCamPosition) * cos_t;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //	Adjust the camera clipping planes based on the scene bounding 
  //  box. (called before every redraws)
  //
  // use: virtual protected
  
  void
  SoXtViewer::adjustCameraClippingPlanes()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      // get the scene bounding box
      autoClipBboxAction->setViewportRegion(SbViewportRegion(getGlxSize()));
      autoClipBboxAction->apply(sceneRoot);
  
      SbXfBox3f xfbbox = autoClipBboxAction->getXfBoundingBox();
      
      // get camera transformation and apply to xfbbox
      // to align the bounding box to camera space.
      // This will enable us to simply use the z values of the
      // transformed bbox for near and far plane values.
      SbMatrix mx;
      mx.setTranslate(- camera->position.getValue());
      xfbbox.transform(mx);
      mx = camera->orientation.getValue().inverse();
      xfbbox.transform(mx);
      
      // get screen align bbox and figure the near and far plane values
      SbBox3f bbox = xfbbox.project();
      // take negative value and opposite to what one might think 
      // because the camera points down the -Z axis
      float far = - bbox.getMin()[2];
      float near = - bbox.getMax()[2];
      
      // scene is behind the camera so don't change the planes
      if (far < 0)
  	return;
      
      // check for minimum near plane value (Value will be negative 
      // when the camera is inside the bounding box).
      // Note: there needs to be a minimum near value for perspective
      // camera because of zbuffer resolution problem (plus the values
      // has to be positive). There is no such restriction for
      // an Orthographic camera (you can see behind you).
      if (! camera->isOfType(SoOrthographicCamera::getClassTypeId())) {
  	if (near < (minimumNearPlane * far))
  	    near = minimumNearPlane * far;
      }
      
      // give the near and far distances a little bit of slack in case
      // the object lies against the bounding box, otherwise the object
      // will be poping in and out of view.
      // (example: a cube is the same as it's bbox)
      near *= 0.999;
      far *= 1.001;
      
      // finally assign camera plane values
      if (camera->nearDistance.getValue() != near)
  	camera->nearDistance = near;
      if (camera->farDistance.getValue() != far)
  	camera->farDistance = far;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  //  Copy the camera onto the clipboard.
  //
  //  Use: private
  //
  void
  SoXtViewer::copyView(Time eventTime)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      if (clipboard == NULL)
      	clipboard = new SoXtClipboard(getWidget());
      
      clipboard->copy(camera, eventTime);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  //  Retrieve the selection from the X server and paste it when it
  //  arrives (in our pasteDone callback).
  //
  //  Use: private
  //
  void
  SoXtViewer::pasteView(Time eventTime)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (clipboard == NULL)
      	clipboard = new SoXtClipboard(getWidget());
      
      clipboard->paste(eventTime, SoXtViewer::pasteDoneCB, this);
  }
  
  
  ////////////////////////////////////////////////////////////////////////
  //
  //  This is called by Xt when the data is ready to be pasted.
  //
  //  Use: static, private
  //
  void 
  SoXtViewer::pasteDoneCB(void *userData, SoPathList *pathList)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      SoCamera *newCamera = NULL;
      
      // search for a camera in the paste data
      for (int i = 0; i < pathList->getLength(); i++) {
  	SoFullPath *fullP = (SoFullPath *) (*pathList)[i];
  	if (fullP->getTail()->isOfType(SoCamera::getClassTypeId())) {
  	    newCamera = (SoCamera *) fullP->getTail();
  	    break;
  	}
      }
  
      if (newCamera != NULL)
  	((SoXtViewer *) userData)->changeCameraValues(newCamera);
      
      // We delete the callback data when done with it.
      delete pathList;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  //  Change the values of our camera to newCamera.
  //??? animate from old values to new?
  //
  //  Use: virtual, protected
  //
  void 
  SoXtViewer::changeCameraValues(SoCamera *newCamera)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      // only paste cameras of the same type
      if (camera->getTypeId() != newCamera->getTypeId())
  	return;
  
      // give our camera the values of the new camera
      camera->position	    = newCamera->position;
      camera->orientation	    = newCamera->orientation;
      camera->nearDistance    = newCamera->nearDistance;
      camera->farDistance	    = newCamera->farDistance;
      camera->focalDistance   = newCamera->focalDistance;
  
      // get the height or heightAngle
      if (camera->isOfType(SoPerspectiveCamera::getClassTypeId()))
  	((SoPerspectiveCamera *)camera)->heightAngle = 
  		((SoPerspectiveCamera *)newCamera)->heightAngle;
      else
  	((SoOrthographicCamera *)camera)->height = 
  		((SoOrthographicCamera *)newCamera)->height;
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Toggles the current camera type (perspective <--> orthographic)
  //
  //  Use: virtual protected
  //
  void
  SoXtViewer::toggleCameraType()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      // create the camera of the opposite kind and compute the wanted height
      // or heightAngle of the new camera.
      SoCamera *newCam;
      if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
  	float angle = ((SoPerspectiveCamera *)camera)->heightAngle.getValue();
  	float height = camera->focalDistance.getValue() * tanf(angle/2);
  	newCam = new SoOrthographicCamera;
  	((SoOrthographicCamera *)newCam)->height = 2 * height;
      }
      else if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) {
  	float height = ((SoOrthographicCamera *)camera)->height.getValue() / 2;
  	float angle = atanf(height / camera->focalDistance.getValue());
  	newCam = new SoPerspectiveCamera;
  	((SoPerspectiveCamera *)newCam)->heightAngle = 2 * angle;
      }
      else {
  #ifdef DEBUG
  	SoDebugError::post("SoXtViewer::toggleCameraType", "unknown camera type!");
  #endif
  	return;
      }
      
      newCam->ref();
      
      // copy common stuff from the old to the new camera
      newCam->viewportMapping = camera->viewportMapping.getValue();
      newCam->position = camera->position.getValue();
      newCam->orientation = camera->orientation.getValue();
      newCam->aspectRatio = camera->aspectRatio.getValue();
      newCam->focalDistance = camera->focalDistance.getValue();
      
      // search for the old camera and replace it by the new camera
      SoSearchAction sa;
      sa.setNode(camera);
      sa.apply(sceneRoot);
      SoFullPath *fullCamPath = (SoFullPath *) sa.getPath();
      if (fullCamPath) {
  	SoGroup *parent = (SoGroup *)fullCamPath->getNode(fullCamPath->getLength() - 2);
  	parent->insertChild(newCam, parent->findChild(camera));
  	SoCamera *oldCam = camera;
  	setCamera(newCam);
  	
  	// remove the old camera if it is still there (setCamera() might
  	// have removed it) and set the created flag to true (for next time)
  	if (parent->findChild(oldCam) >= 0)
  	    parent->removeChild(oldCam);
  	createdCamera = TRUE;
      }
  #ifdef DEBUG
      else
  	SoDebugError::post("SoXtViewer::toggleCameraType", "camera not found!");
  #endif
      
      newCam->unref();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Called by the processCommonEvent routine whenever the arrow keys
  //  are pressed. Translate the camera in the viewing plane in the arrow
  //  direction half a screen at a time.
  //
  //  Use: private
  //
  void
  SoXtViewer::arrowKeyPressed(KeySym key)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      if (camera == NULL)
  	return;
      
      // get the camera near plane height value
      float dist;
      if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
  	float angle = ((SoPerspectiveCamera *)camera)->heightAngle.getValue();
  	float length = camera->nearDistance.getValue();
  	dist = length * tanf(angle);
      }
      else if (camera->isOfType(SoOrthographicCamera::getClassTypeId()))
  	dist = ((SoOrthographicCamera *)camera)->height.getValue();
      dist /= 2.0;
      
      // get camera right/left/up/down direction
      SbMatrix mx;
      mx = camera->orientation.getValue();
      SbVec3f dir;
      switch(key) {
  	case XK_Up:
  	    dir.setValue(mx[1][0], mx[1][1], mx[1][2]);
  	    break;
  	case XK_Down:
  	    dir.setValue(-mx[1][0], -mx[1][1], -mx[1][2]); 
  	    break;
  	case XK_Right:
  	    dir.setValue(mx[0][0], mx[0][1], mx[0][2]);
  	    dist *= camera->aspectRatio.getValue();
  	    break;
  	case XK_Left:
  	    dir.setValue(-mx[0][0], -mx[0][1], -mx[0][2]);
  	    dist *= camera->aspectRatio.getValue();
  	    break;
      }
      
      // finally reposition the camera
      camera->position = camera->position.getValue() + dist * dir;
  }
  
  //
  ////////////////////////////////////////////////////////////////////////
  // static callbacks stubs
  ////////////////////////////////////////////////////////////////////////
  //
  
  
  
  void
  SoXtViewer::bufferStartCallback(void *, SoXtViewer *v)
  {
      v->SoXtRenderArea::setDoubleBuffer(TRUE);
  }
  
  void
  SoXtViewer::bufferFinishCallback(void *, SoXtViewer *v)
  {
      v->SoXtRenderArea::setDoubleBuffer(FALSE);
  }
  
  void
  SoXtViewer::drawStyleStartCallback(void *, SoXtViewer *v)
  {
      v->interactiveFlag = TRUE;  // must happen first
      
      // if still and move draw styles are the same, return...
      if (v->interactiveDrawStyle == VIEW_SAME_AS_STILL ||
  	v->interactiveDrawStyle == v->stillDrawStyle)
  	return;
      
      // if we have "MOVE NO TEXTURE", then we have nothing
      // to do if we have a current draw style (since they all have 
      // texturing turned off in the first place).
      if (v->interactiveDrawStyle == VIEW_NO_TEXTURE &&
  	v->stillDrawStyle != VIEW_AS_IS)
  	return;
      
      v->setCurrentDrawStyle(v->interactiveDrawStyle);
  }
  
  void
  SoXtViewer::drawStyleFinishCallback(void *, SoXtViewer *v)
  {
      v->interactiveFlag = FALSE;  // must happen first
      
      // if still and move draw styles are the same, return...
      if (v->interactiveDrawStyle == VIEW_SAME_AS_STILL ||
  	v->interactiveDrawStyle == v->stillDrawStyle)
  	return;
      
      // if we have "MOVE NO TEXTURE", then we have nothing
      // to do if we have a current draw style (since they all have 
      // texturing turned off in the first place).
      if (v->interactiveDrawStyle == VIEW_NO_TEXTURE &&
  	v->stillDrawStyle != VIEW_AS_IS)
  	return;
      
      v->setCurrentDrawStyle(v->stillDrawStyle);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  //	Called whenever the seek animation sensor fires. Finds the amount 
  //  of time since we started the seek and call the subclasses routine
  //  to do the correct interpolation.
  //
  //  Use: static private
  //
  void
  SoXtViewer::seekAnimationSensorCB(void *p, SoSensor *)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      SoXtViewer *v = (SoXtViewer *)p;
      
      // get the time difference
      SbTime time = viewerRealTime->getValue();
      float sec = float((time - v->seekStartTime).getValue());
      if (sec == 0.0)
  	sec = 1.0/72.0;	// at least one frame (needed for first call)
      float t = (sec / v->seekAnimTime);
      
      // check to make sure the values are correctly clipped
      if (t > 1.0)
      	t = 1.0;
      else if ((1.0 - t) < 0.0001)
  	t = 1.0; // this will be the last one...
      
      // call subclasses to interpolate the animation
      v->interpolateSeekAnimation(t);
      
      // stops seek if this was the last interval
      if (t == 1.0)
  	v->setSeekMode(FALSE);
  }
  
  
  
  //
  ////////////////////////////////////////////////////////////////////////
  // viewer feedback convenience routines
  ////////////////////////////////////////////////////////////////////////
  //
  
  
  
  /*
   * Defines
   */
  
  // color used in feedback
  #define DARK_COLOR	glColor3ub(90, 90, 90)
  #define LIGHT_COLOR	glColor3ub(230, 230, 230)
  
  #define LINE_THIN   3	// line thickness used in feedback
  #define	LINE_THICK  (LINE_THIN + 2)
  #define CROSS 	    8	// size of cross hair at screen center for Roll
  #define RADIUS	    15	// radius of center circle (in pix) for Roll
  #define ANGLE_LEN   14   // angular size in degrees of Roll anchor
  
  
  /*
   * Globals
   */
  
  #define ARROW_SIZE  6.0	// size in pix of arrow head
  
  // anchor arrow head description
  static float arrow_data[3][3] = {
      -ARROW_SIZE, 0, 0,
      0, 2*ARROW_SIZE, 0,
      ARROW_SIZE, 0, 0
  };
  
  
  /*
   * Macros
   */
  
  #define	DRAW_ARROW_MACRO    \
      DARK_COLOR;	\
      glBegin(GL_POLYGON);    \
      glVertex3fv(arrow_data[0]);	\
      glVertex3fv(arrow_data[1]);	\
      glVertex3fv(arrow_data[2]);	\
      glEnd();	\
      LIGHT_COLOR;	\
      glLineWidth(1); \
      glBegin(GL_LINE_LOOP);	\
      glVertex3fv(arrow_data[0]);	\
      glVertex3fv(arrow_data[1]);	\
      glVertex3fv(arrow_data[2]);	\
      glEnd();
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Sets the default ortho projection when doing viewer feedback. The 
  //  zbuffer/lighting is automatically turned off.
  //
  //  Use: static protected
  //
  void
  SoXtViewer::setFeedbackOrthoProjection(const SbVec2s &size)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // push the gl state to revert it back later....
      glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_LINE_BIT);
      
      // ??? should we worry about restoring this matrix later ?
      glViewport(0, 0, size[0], size[1]);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      glOrtho(0, size[0], 0, size[1], -1, 1);
      
      // disable zbuffer and lighting....
      glDisable(GL_DEPTH_TEST);
      glDisable(GL_LIGHTING);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    restores the state that was changed when setFeedbackOrthoProjection()
  //  is called.
  //
  //  Use: static protected
  //
  void
  SoXtViewer::restoreGLStateAfterFeedback()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // restore the gl state that were saved in setFeedbackOrthoProjection()
      glPopAttrib();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Draws a simple 2 colored cross at the given location.
  //
  //  Use: static protected
  //
  void
  SoXtViewer::drawViewerCrossFeedback(SbVec2s loc)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      LIGHT_COLOR;
      glLineWidth(4);
      glBegin(GL_LINES);
      glVertex2s(loc[0]-CROSS, loc[1]);
      glVertex2s(loc[0]+CROSS, loc[1]);
      glVertex2s(loc[0], loc[1]-CROSS);
      glVertex2s(loc[0], loc[1]+CROSS);
      glEnd();
      
      DARK_COLOR;
      glLineWidth(2);
      glBegin(GL_LINES);
      glVertex2s(loc[0]-CROSS+1, loc[1]);
      glVertex2s(loc[0]+CROSS-1, loc[1]);
      glVertex2s(loc[0], loc[1]-CROSS+1);
      glVertex2s(loc[0], loc[1]+CROSS-1);
      glEnd();
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    draws the anchor roll feedback given the point of rotation and the
  //  current mouse location.
  //
  //  Use: static protected
  //
  void
  SoXtViewer::drawViewerRollFeedback(SbVec2s center, SbVec2s loc)
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // get angle and distance of mouse from center of rotation
      float ang, dist;
      float vx = loc[0] - center[0];
      float vy = loc[1] - center[1];
      if (vx==0 && vy==0) {
  	ang = 0;
  	    dist = 3; // minimum size (given the circle thickness)
      }
      else {
  	ang = atan2(vy, vx) * 180.0 / M_PI;
  	dist = sqrtf(vx*vx + vy*vy);
  	if (dist < 3)
  	    dist = 3; // minimum size (given the circle thickness)
      }
      float cirAng = -ang + 90; // gluPartialDisk() angle is REALLY backward !!
      
      static GLUquadricObj *quad = NULL;
      if (! quad)	quad = gluNewQuadric();
      
      // draw all of the circles (first inner, then outer)
      glTranslatef(center[0], center[1], 0);
      LIGHT_COLOR;
      gluDisk(quad, RADIUS, RADIUS+LINE_THICK, 20, 2);
      gluPartialDisk(quad, dist-2, dist+LINE_THICK-2, 20, 2, cirAng - ANGLE_LEN, 2 * ANGLE_LEN);
      DARK_COLOR;
      gluDisk(quad, RADIUS+1, RADIUS+LINE_THICK-1, 20, 2);
      gluPartialDisk(quad, dist-1, dist+LINE_THICK-3, 20, 2, cirAng - ANGLE_LEN, 2 * ANGLE_LEN);
      glTranslatef(-center[0], -center[1], 0); // undo the translation
      
      // draw connecting line from center to outer circle
      glLineWidth(LINE_THICK);
      LIGHT_COLOR;
      glBegin(GL_LINES);
      glVertex2s(center[0], center[1]);
      glVertex2s(loc[0], loc[1]);
      glEnd();
      glLineWidth(LINE_THIN);
      DARK_COLOR;
      glBegin(GL_LINES);
      glVertex2s(center[0], center[1]);
      glVertex2s(loc[0], loc[1]);
      glEnd();
      
      // draw the CCW arrow
      glPushMatrix();
      glTranslatef(center[0], center[1], 0);
      glRotatef(ang+ANGLE_LEN, 0, 0, 1);
      glTranslatef(dist, 0, 0);
      DRAW_ARROW_MACRO
      glPopMatrix();
      
      // draw the CW arrow
      glPushMatrix();
      glTranslatef(center[0], center[1], 0);
      glRotatef(ang-ANGLE_LEN, 0, 0, 1);
      glTranslatef(dist, 0, 0);
      glScalef(1, -1, 1);
      DRAW_ARROW_MACRO
      glPopMatrix();
  }
  
  