// @configure_input@

/**************************************************************************\
 * Copyright (c) Kongsberg Oil & Gas Technologies AS
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\**************************************************************************/

// NOTE: The So@Gui@Viewer.cpp sourcecode file is completely
// autogenerated from "templatized" source code.

// *************************************************************************

/*!
  \class So@Gui@Viewer Inventor/@Gui@/viewers/So@Gui@Viewer.h
  \brief The So@Gui@Viewer class is the top level base viewer class.

  \ingroup components viewers

  This is an abstract class, which adds the following features to its
  So@Gui@RenderArea superclass: convenient methods for camera
  handling, an automatic headlight configuration.

  As for the camera handling: when setting a new scene graph for the
  viewer, the scene graph will automatically be scanned for a node
  derived from SoCamera. If not found, the viewer will itself set up a
  camera for the scene. The camera can then be conveniently controlled
  by the application programmers in many aspects:

  <ul>

  <li>camera type: toggle between using an orthographic camera and a
  perspective camera with So@Gui@Viewer::toggleCameraType()</li>

  <li>zoom out to exactly encompass all scene geometry within the view
  by using So@Gui@Viewer::viewAll()</li>

  <li>tag a specific position and orientation for the camera as the
  "home" position with So@Gui@Viewer::saveHomePosition(), which one
  can then return to by using
  So@Gui@Viewer::resetToHomePosition()</li>

  <li>automatically fit the near and far clipping planes of the camera
  around the scene's geometry by using
  So@Gui@Viewer::setAutoClipping()</li>

  <li>control stereo viewing parameters</li>

  </ul>

  Note that there is no dragger or manipulator attached to the scene
  camera.  The camera transform manipulation is calculated in a more
  direct manner in the non-abstract viewer classes inheriting
  So@Gui@Viewer by reading mouse and keyboard events and interpreting
  how these should influence the camera. The calculations results in
  new values for SoCamera::position, SoCamera::orientation, and the
  other SoCamera field values for the camera designated to be the
  viewer viewpoint camera. These values are then inserted directly
  into the viewer's SoCamera node.

  See e.g. the source code for So@Gui@ExaminerViewer::processSoEvent()
  for the details.



  The So@Gui@Viewer class automatically adds a headlight to the scene,
  which will always point in the approximate same direction as the
  current viewer camera, thereby securing that the scene geometry is
  always lighted and visible. (If you don't want the constant
  headlight, but rather want to light the scene on your own, this
  behavior can be turned off with So@Gui@Viewer::setHeadlight()).


  So@Gui@Viewer-derived viewers all inherit the following keyboard
  controls from this class (but only when the viewer is in "examine
  mode", i.e. So@Gui@Viewer::isViewing() returns \c TRUE):

  <ul>

  <li>"s": put the viewer in "seek mode", where the end user may click
  anywhere on scene geometry to trigger an animation which moves the
  camera towards the point clicked</li>

  <li>"Home": hit this key to move camera back to last saved "home
  position"</li>

  <li>arrow keys: moves camera slightly left, right, up or down</li>

  <li>"q": exit application</li>

  </ul>
*/

// *************************************************************************

/*!
  \enum So@Gui@Viewer::AutoClippingStrategy

  Enum for auto clipping strategy.
  
  \sa setAutoClippingStrategy()
*/

/*!
  \var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::CONSTANT_NEAR_PLANE

  Constant near plane auto clipping strategy. Explained in detail in
  the documentation for the So@Gui@Viewer::setAutoClippingStrategy()
  method.
*/

/*!
  \var So@Gui@Viewer::AutoClippingStrategy So@Gui@Viewer::VARIABLE_NEAR_PLANE

  Variable near plane auto clipping strategy. Explained in detail in
  the documentation for the So@Gui@Viewer::setAutoClippingStrategy()
  method.
*/

// *************************************************************************

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cfloat> // FLT_MAX

#include <Inventor/SbLinear.h>
#include <Inventor/SoDB.h>
#include <Inventor/SoLists.h>
#include <Inventor/SoPickedPoint.h>
#include <Inventor/SoSceneManager.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/actions/SoGetMatrixAction.h>
#include <Inventor/actions/SoRayPickAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/misc/SoCallbackList.h>
#include <Inventor/nodekits/SoBaseKit.h>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoComplexity.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoLocateHighlight.h>
#include <Inventor/nodes/SoMaterialBinding.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSwitch.h>
#include <Inventor/sensors/SoTimerSensor.h>
#if HAVE_SOSCENEMANAGER_GETBACKGROUNDCOLORRGBA
#include <Inventor/SbColorRGBA.h>
#endif

#ifdef HAVE_SOPOLYGONOFFSET
#include <Inventor/nodes/SoPolygonOffset.h>
#endif // HAVE_SOPOLYGONOFFSET

#include <Inventor/@Gui@/So@Gui@.h>
#include <Inventor/@Gui@/SoAny.h>
#include <Inventor/@Gui@/common/SbGuiList.h>
#include <Inventor/@Gui@/common/gl.h>
#include <Inventor/@Gui@/nodes/SoGuiViewpointWrapper.h>
#include <Inventor/@Gui@/viewers/So@Gui@Viewer.h>
#include <Inventor/@Gui@/viewers/SoGuiViewerP.h>
#include <so@gui@defs.h>

// *************************************************************************

// (note: this *must* be a #define, not a static variable -- to avoid
// initialization race conditions with the static variables being set
// to the value of this)
#define UNINITIALIZED_ENVVAR -1 // value of envvars before tested

// Environment variable for debugging purpose: display a running
// frames-per-second counter. See code comments above
// So@Gui@ViewerP::recordFPS() function below for more information.
static int COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR;

// *************************************************************************

#define PRIVATE(ptr) (ptr->pimpl)
#define PUBLIC(ptr) (ptr->pub)

// *************************************************************************

So@Gui@ViewerP::So@Gui@ViewerP(So@Gui@Viewer * publ)
{
  PUBLIC(this) = publ;
  this->searchaction = new SoSearchAction;
  this->matrixaction = new SoGetMatrixAction(SbViewportRegion(100,100));
  this->superimpositions = NULL;

  this->storedcamera = NULL;

  // initialize auto clipping parameters
  this->autoclipstrategy = So@Gui@Viewer::VARIABLE_NEAR_PLANE;
  this->autoclipvalue = 0.6f;
  this->autoclipcb = NULL;

  this->stereotype = So@Gui@Viewer::STEREO_NONE;
  this->stereotypesetexplicit = FALSE;
  this->stereostencilmaskvp = SbViewportRegion(0, 0);
  this->stereostencilmask = NULL;
  this->stereostenciltype = So@Gui@Viewer::STEREO_NONE;
  this->stereoanaglyphmask[0][0] = TRUE;
  this->stereoanaglyphmask[0][1] = this->stereoanaglyphmask[0][2] = FALSE;
  this->stereoanaglyphmask[1][0] = FALSE;
  this->stereoanaglyphmask[1][1] = this->stereoanaglyphmask[1][2] = TRUE;
}

So@Gui@ViewerP::~So@Gui@ViewerP()
{
  // This impossible to miss reminder was inserted so we don't
  // accidentally let an So* v2 slip out the door without fixing this
  // API design flaw. 20030625 mortene.
#if (SO@GUI@_MAJOR_VERSION == 2)
#error This is a reminder: when jumping to version 2 of an So* toolkit, the viewer destructors (at least, possibly also further up in the inheritance hierarchy) should be made virtual.
#endif // version = 2

  delete[] this->stereostencilmask;

  delete this->superimpositions;
  delete this->searchaction;
  delete this->matrixaction;

  if (this->storedcamera) { this->storedcamera->unref(); }
}

SoSeparator *
So@Gui@ViewerP::createSuperScene(void)
{
  static const char * superSceneGraph[] =
  {
    "#Inventor V2.1 ascii",
    "",
    "Separator {",
    "  renderCaching OFF",
    "  renderCulling OFF",
    "  pickCulling OFF",
    "  boundingBoxCaching OFF",
    
    // Headlight. By inserting this before any scene graph camera, the
    // light will always be pointing in the correct direction.
    "  DEF so@gui@->headlight DirectionalLight {",
    "    direction 1 -1 -10",
    "  }",

    "  DEF so@gui@->drawstyleroot Switch {",
    "    whichChild -1",
    "    DEF so@gui@->lightmodel LightModel {",
    "      model BASE_COLOR",
    "    }",
    "    DEF so@gui@->drawstyle DrawStyle {",
    "      pointSize ~",
    "      lineWidth ~",
    "      linePattern ~",
    "    }",
    "    DEF so@gui@->complexity Complexity {",
    "      textureQuality 0.0",
    "      value 0.1",
    "    }",
    "  }",
    "  DEF so@gui@->hiddenlineroot Switch {",
    "    whichChild -1",
    "    DEF so@gui@->basecolor BaseColor { }",
    "    DEF so@gui@->materialbinding MaterialBinding {",
    "      value OVERALL",
    "    }",
    "    DEF so@gui@->polygonoffsetparent Switch {",
    "      whichChild -1",
#ifdef HAVE_SOPOLYGONOFFSET
    "      DEF so@gui@->polygonoffset PolygonOffset { }",
#endif // HAVE_SOPOLYGONOFFSET
    "    }",
    "  }",
    "  DEF so@gui@->userscenegraphroot Separator {",
    // turn off caching to make it possible for users to disable
    // caching in their scene graphs.
    "    renderCaching OFF\n", 
    "  }",
    "}",
    NULL
  };

  int i;
  size_t bufsize;
  for (i = 0, bufsize = 0; superSceneGraph[i]; i++)
    bufsize += strlen(superSceneGraph[i]) + 1;
  char * buf = new char [bufsize + 1];
  for (i = 0, bufsize = 0; superSceneGraph[i]; i++) {
    strcpy(buf + bufsize, superSceneGraph[i]);
    bufsize += strlen(superSceneGraph[i]);
    buf[bufsize] = '\n';
    bufsize++;
  }
  SoInput * input = new SoInput;
  input->setBuffer(buf, bufsize);
  SoNode * root = NULL;
  SbBool ok = SoDB::read(input, root);
  delete input;
  delete [] buf;
  if (!ok) {
    // FIXME: this looks unnecessary robust, and I believe it should
    // be replaced by an assert()..? 20030430 mortene.
    SoDebugError::post("So@Gui@ViewerP::createSuperScene",
		       "couldn't create viewer superscene");
    return NULL;
  }
  assert(root->isOfType(SoSeparator::getClassTypeId()));
  root->ref();

  this->searchaction->reset();
  this->searchaction->setSearchingAll(TRUE);
  this->searchaction->setInterest(SoSearchAction::FIRST);

#define LOCATE_NODE(member, type, name) \
  do { \
    member = NULL; \
    this->searchaction->setName(SbName(name)); \
    this->searchaction->apply(root); \
    if (this->searchaction->getPath() != NULL) { \
      SoNode * node = this->searchaction->getPath()->getTail(); \
      assert(node != NULL); \
      if (node->isOfType(type::getClassTypeId())) \
        member = (type *) node; \
    } else { \
      SoDebugError::post("So@Gui@ViewerP::createSuperScene", \
                         "didn't locate node \"%s\"", name); \
    } \
  } while (FALSE)

  LOCATE_NODE(this->headlight, SoDirectionalLight, "so@gui@->headlight");
  LOCATE_NODE(this->drawstyleroot, SoSwitch, "so@gui@->drawstyleroot");
  LOCATE_NODE(this->hiddenlineroot, SoSwitch, "so@gui@->hiddenlineroot");
  LOCATE_NODE(this->polygonoffsetparent, SoSwitch,
	      "so@gui@->polygonoffsetparent");
  LOCATE_NODE(this->usersceneroot, SoSeparator, "so@gui@->userscenegraphroot");

  LOCATE_NODE(this->sobasecolor, SoBaseColor, "so@gui@->basecolor");
  LOCATE_NODE(this->socomplexity, SoComplexity, "so@gui@->complexity");
  LOCATE_NODE(this->sodrawstyle, SoDrawStyle, "so@gui@->drawstyle");
  LOCATE_NODE(this->solightmodel, SoLightModel, "so@gui@->lightmodel");
  LOCATE_NODE(this->somaterialbinding, SoMaterialBinding, "so@gui@->materialbinding");
  if (this->sobasecolor) this->sobasecolor->setOverride(TRUE);
  if (this->socomplexity) this->socomplexity->setOverride(TRUE);
  if (this->sodrawstyle) this->sodrawstyle->setOverride(TRUE);
  if (this->solightmodel) this->solightmodel->setOverride(TRUE);
  if (this->somaterialbinding) this->somaterialbinding->setOverride(TRUE);
#ifdef HAVE_SOPOLYGONOFFSET
  LOCATE_NODE(this->sopolygonoffset, SoPolygonOffset, "so@gui@->polygonoffset");
  if (this->sopolygonoffset) this->sopolygonoffset->setOverride(TRUE);
#endif // HAVE_SOPOLYGONOFFSET

#undef LOCATE_NODE
  this->searchaction->reset();

  root->unrefNoDelete();
  return (SoSeparator *) root;
}

// Returns the coordinate system the current camera is located in. If
// there are transformations before the camera in the scene graph,
// this must be considered before doing certain operations. \a matrix
// and \a inverse will not contain the transformations caused by the
// camera fields, only the transformations traversed before the camera
// in the scene graph.
void
So@Gui@ViewerP::getCameraCoordinateSystem(SoCamera * cameraarg,
                                          SoNode * root,
                                          SbMatrix & matrix,
                                          SbMatrix & inverse)
{
  this->searchaction->reset();
  this->searchaction->setSearchingAll(TRUE);
  this->searchaction->setInterest(SoSearchAction::FIRST);
  this->searchaction->setNode(cameraarg);
  this->searchaction->apply(root);

  matrix = inverse = SbMatrix::identity();
  if (this->searchaction->getPath()) {
    this->matrixaction->apply(this->searchaction->getPath());
    matrix = this->matrixaction->getMatrix();
    inverse = this->matrixaction->getInverse();
  }
  this->searchaction->reset();
}


// These functions do this:
//
//       * when going from orthocam -> perspectivecam: set the
//       heightAngle field to its default value (45 degrees), and move
//       camera to a position where the scene/model would fill about
//       the same screenspace as it did in the orthocam
//
//       * when going from perspectivecam -> orthocam: keep the
//       current position, but tune the view-volume height so the
//       scene/model takes up about the same screenspace
//
// 20020522 mortene.

void
So@Gui@ViewerP::convertOrtho2Perspective(const SoOrthographicCamera * in,
                                         SoPerspectiveCamera * out)
{
  out->aspectRatio.setValue(in->aspectRatio.getValue());
  out->focalDistance.setValue(in->focalDistance.getValue());
  out->orientation.setValue(in->orientation.getValue());
  out->position.setValue(in->position.getValue());
  out->viewportMapping.setValue(in->viewportMapping.getValue());
  
  SbRotation camrot = in->orientation.getValue();

  float focaldist = in->height.getValue() / float(2.0*tan(M_PI / 8.0));
  
  SbVec3f offset(0,0,focaldist-in->focalDistance.getValue());

  camrot.multVec(offset,offset);
  out->position.setValue(offset+in->position.getValue());

  out->focalDistance.setValue(focaldist);
  
  // 45 degrees is the default value of this field in SoPerspectiveCamera.
  out->heightAngle = (float)(M_PI / 4.0);

#if SO@GUI@_DEBUG && 0 // debug
  SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective",
                         "perspective heightAngle==%f",
                         180.0f * out->heightAngle.getValue() / M_PI);
#endif // debug
}

void
So@Gui@ViewerP::convertPerspective2Ortho(const SoPerspectiveCamera * in,
                                         SoOrthographicCamera * out)
{
  out->aspectRatio.setValue(in->aspectRatio.getValue());
  out->focalDistance.setValue(in->focalDistance.getValue());
  out->orientation.setValue(in->orientation.getValue());
  out->position.setValue(in->position.getValue());
  out->viewportMapping.setValue(in->viewportMapping.getValue());

  float focaldist = in->focalDistance.getValue();

  out->height = 2.0f * focaldist * (float)tan(in->heightAngle.getValue() / 2.0);

#if SO@GUI@_DEBUG && 0 // debug
  SoDebugError::postInfo("So@Gui@ViewerP::convertOrtho2Perspective",
                         "ortho height==%f",
                         out->height.getValue());
#endif // debug
}

void
So@Gui@ViewerP::reallyRedraw(const SbBool clearcol, const SbBool clearz)
{
  // Recalculate near/far planes. Must be done in reallyRedraw() --
  // not actualRedraw() -- so the clipping planes are correct even
  // when rendering multiple times with different camera settings.
  if (this->camera && PUBLIC(this)->isAutoClipping()) {
    // Temporarily turn off notification when changing near and far
    // clipping planes, to avoid latency.
    const SbBool notif = this->camera->isNotifyEnabled();
    this->camera->enableNotify(FALSE);
    this->setClippingPlanes();
    this->camera->enableNotify(notif);
  }

  if (this->drawAsHiddenLine()) {

    // First pass: render as filled, but with the background color.

    this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE
    this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points
    this->socomplexity->type.setIgnored(TRUE); // as-is rendering space
    this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes
    // textureQuality field of socomplexity node is always 0.0

    this->sobasecolor->rgb.setValue(PUBLIC(this)->getBackgroundColor());
    this->sobasecolor->rgb.setIgnored(FALSE);
    this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL
    this->polygonoffsetparent->whichChild = SO_SWITCH_ALL;
    
    PUBLIC(this)->getSceneManager()->render(clearcol, clearz);
    
    // Second pass, render wireframe on top.

    this->sodrawstyle->style = SoDrawStyle::LINES;
    this->sodrawstyle->style.setIgnored(FALSE); // force lines
    this->sobasecolor->rgb.setIgnored(TRUE); // use as-is line colors
    this->somaterialbinding->value.setIgnored(TRUE); // as-is
    this->polygonoffsetparent->whichChild = SO_SWITCH_NONE;

    PUBLIC(this)->getSceneManager()->render(FALSE, FALSE);

    return;
  }
  if (this->drawAsWireframeOverlay()) {
    // First pass: render as-is, with polygon offset

    this->solightmodel->model.setIgnored(TRUE);
    this->somaterialbinding->value.setIgnored(TRUE);
    this->sobasecolor->rgb.setIgnored(TRUE);
    this->sodrawstyle->style.setIgnored(TRUE); // draw as-is filled/lines/points
    this->socomplexity->type.setIgnored(TRUE); // as-is rendering space
    this->socomplexity->value.setIgnored(TRUE); // as-is complexity on non-simple shapes
    this->socomplexity->textureQuality.setIgnored(TRUE);

    this->somaterialbinding->value.setIgnored(TRUE); // override with OVERALL
    this->polygonoffsetparent->whichChild = SO_SWITCH_ALL;

    PUBLIC(this)->getSceneManager()->render(clearcol, clearz);

    // Second pass, render wireframe on top.
    this->sobasecolor->rgb.setValue(this->wireframeoverlaycolor);
    this->sobasecolor->rgb.setIgnored(FALSE);
    this->somaterialbinding->value.setIgnored(FALSE); // override with OVERALL

    this->solightmodel->model.setIgnored(FALSE); // override as SoLightModel::BASE
    this->sodrawstyle->style = SoDrawStyle::LINES;
    this->sodrawstyle->style.setIgnored(FALSE); // force lines
    this->polygonoffsetparent->whichChild = SO_SWITCH_NONE;
    this->socomplexity->textureQuality.setIgnored(FALSE);

    PUBLIC(this)->getSceneManager()->render(FALSE, FALSE);

    // disable override nodes
    (void) this->sobasecolor->rgb.enableNotify(FALSE);
    this->sobasecolor->rgb.setIgnored(TRUE);
    (void) this->sobasecolor->rgb.enableNotify(TRUE);

    (void) this->somaterialbinding->value.enableNotify(FALSE);
    this->somaterialbinding->value.setIgnored(TRUE);
    (void) this->somaterialbinding->value.enableNotify(TRUE);

    (void) this->solightmodel->model.enableNotify(FALSE);
    this->solightmodel->model.setIgnored(TRUE);
    (void) this->solightmodel->model.enableNotify(TRUE);

    (void) this->socomplexity->textureQuality.enableNotify(FALSE);
    this->socomplexity->textureQuality.setIgnored(TRUE);
    (void) this->socomplexity->textureQuality.enableNotify(TRUE);

    (void) this->sodrawstyle->style.enableNotify(FALSE);
    this->sodrawstyle->style.setIgnored(TRUE);
    (void) this->sodrawstyle->style.enableNotify(TRUE);
    return;
  }

  SbBool clearzbuffer = TRUE;
  So@Gui@Viewer::DrawStyle style = this->currentDrawStyle();
  switch (style) {
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
  case So@Gui@Viewer::VIEW_BBOX:
    clearzbuffer = FALSE;
  default:
    break; // Include "default:" case to avoid compiler warning.
  }

  PUBLIC(this)->getSceneManager()->render(clearcol, clearzbuffer && clearz);
}


// *************************************************************************

// Returns a boolean to indicate if the dynamic drawstyle equals
// the static drawstyle.

SbBool
So@Gui@ViewerP::drawInteractiveAsStill(void) const
{
  SbBool moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_SAME_AS_STILL;
  if (! moveasstill)
    moveasstill = this->drawstyles[So@Gui@Viewer::INTERACTIVE] == this->drawstyles[So@Gui@Viewer::STILL];
  if (! moveasstill)
    moveasstill =
      this->drawstyles[So@Gui@Viewer::INTERACTIVE] == So@Gui@Viewer::VIEW_NO_TEXTURE &&
      this->drawstyles[So@Gui@Viewer::STILL] != So@Gui@Viewer::VIEW_AS_IS;
  return moveasstill;
}

// Returns the current drawing style.
So@Gui@Viewer::DrawStyle
So@Gui@ViewerP::currentDrawStyle(void) const
{
  SbBool interactivemode = PUBLIC(this)->getInteractiveCount() > 0 ? TRUE : FALSE;

  if (!interactivemode || this->drawInteractiveAsStill())
    return this->drawstyles[So@Gui@Viewer::STILL];
  else
    return this->drawstyles[So@Gui@Viewer::INTERACTIVE];
}

// Returns a boolean to indicate if the current drawstyle settings implies
// hidden line rendering.
SbBool
So@Gui@ViewerP::drawAsHiddenLine(void) const
{
  return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_HIDDEN_LINE) ? TRUE : FALSE);
}

// Returns a boolean to indicate if the current drawstyle settings
// implies wirefram overlay rendering.
SbBool
So@Gui@ViewerP::drawAsWireframeOverlay(void) const
{
  return ((this->currentDrawStyle() == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY) ? TRUE : FALSE);
}

// Use the given style setting to set the correct states in the
// rendering control nodes. This will affect the way the scene is
// currently rendered.
void
So@Gui@ViewerP::changeDrawStyle(So@Gui@Viewer::DrawStyle style)
{
  // Turn on/off Z-buffering based on the style setting.
  switch (style) {
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
  case So@Gui@Viewer::VIEW_BBOX:
    PUBLIC(this)->glLockNormal();
    // FIXME: shouldn't this be done "lazy", i.e. before we do any
    // actual rendering? 20001126 mortene.
    glDisable(GL_DEPTH_TEST);
    PUBLIC(this)->glUnlockNormal();
    break;

  default:
    PUBLIC(this)->glLockNormal();
    // FIXME: shouldn't this be done "lazy", i.e. before we do any
    // actual rendering? 20001126 mortene.
    glEnable(GL_DEPTH_TEST);
    PUBLIC(this)->glUnlockNormal();
    break;
  }

  // Render everything as its supposed to be done, don't override
  // any of the settings in the ``real'' graph.
  if (style == So@Gui@Viewer::VIEW_AS_IS) {
    this->drawstyleroot->whichChild = SO_SWITCH_NONE;
    return;
  }

  this->drawstyleroot->whichChild = SO_SWITCH_ALL;
  if ((style == So@Gui@Viewer::VIEW_HIDDEN_LINE) ||
      (style == So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY)) {
    this->hiddenlineroot->whichChild = SO_SWITCH_ALL;
    return;
  } else {
    this->hiddenlineroot->whichChild = SO_SWITCH_NONE;
  }

  // Set or unset lightmodel override.
  switch (style) {
  case So@Gui@Viewer::VIEW_NO_TEXTURE:
  case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
    this->solightmodel->model.setIgnored(TRUE); // as-is BASE or PHONG
    break;

  case So@Gui@Viewer::VIEW_LINE:
  case So@Gui@Viewer::VIEW_POINT:
  case So@Gui@Viewer::VIEW_BBOX:
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
    this->solightmodel->model.setIgnored(FALSE); // force BASE lighting
    break;

  default:
    assert(FALSE); break;
  }


  // Set or unset drawstyle override.
  switch (style) {
  case So@Gui@Viewer::VIEW_NO_TEXTURE:
  case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
    this->sodrawstyle->style.setIgnored(TRUE); // as-is drawing style filled/lines/points
    break;

  case So@Gui@Viewer::VIEW_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_BBOX:
    this->sodrawstyle->style = SoDrawStyle::LINES;
    this->sodrawstyle->style.setIgnored(FALSE); // force line rendering
    break;

  case So@Gui@Viewer::VIEW_POINT:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
    this->sodrawstyle->style = SoDrawStyle::POINTS;
    this->sodrawstyle->style.setIgnored(FALSE); // force point rendering
    break;

  default:
    assert(FALSE); break;
  }

  // Set or unset complexity value override.
  switch (style) {
  case So@Gui@Viewer::VIEW_NO_TEXTURE:
  case So@Gui@Viewer::VIEW_LINE:
  case So@Gui@Viewer::VIEW_POINT:
  case So@Gui@Viewer::VIEW_BBOX:
    this->socomplexity->value.setIgnored(TRUE); // as-is complexity
    break;

  case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
    this->socomplexity->value.setIgnored(FALSE); // force complexity setting of 0.1
    break;

  default:
    assert(FALSE); break;
  }

  // Set or unset complexity textureQuality override (the value of the
  // override-field is always 0.0, i.e. signalling "textures off").
  switch (style) {
  case So@Gui@Viewer::VIEW_HIDDEN_LINE:
  case So@Gui@Viewer::VIEW_NO_TEXTURE:
  case So@Gui@Viewer::VIEW_LINE:
  case So@Gui@Viewer::VIEW_POINT:
  case So@Gui@Viewer::VIEW_BBOX:
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
    this->socomplexity->textureQuality.setIgnored(FALSE); // textures off
    break;

  default:
    this->socomplexity->textureQuality.setIgnored(TRUE); // don't override
    break;
  }

  // Set or unset complexity type override.
  switch (style) {
  case So@Gui@Viewer::VIEW_NO_TEXTURE:
  case So@Gui@Viewer::VIEW_LOW_COMPLEXITY:
  case So@Gui@Viewer::VIEW_LINE:
  case So@Gui@Viewer::VIEW_POINT:
  case So@Gui@Viewer::VIEW_LOW_RES_LINE:
  case So@Gui@Viewer::VIEW_LOW_RES_POINT:
    this->socomplexity->type.setIgnored(TRUE); // as-is
    break;

  case So@Gui@Viewer::VIEW_BBOX:
    this->socomplexity->type = SoComplexity::BOUNDING_BOX;
    this->socomplexity->type.setIgnored(FALSE); // force bounding box rendering
    break;

  default:
    assert(FALSE); break;
  }

#if 0 // debug
  SoDebugError::postInfo("So@Gui@Viewer::changeDrawStyle",
                         "\n"
                         "\tdrawstyle style: 0x%02x (isIgnored() == %s)\n"
                         "\tlightmodel model: 0x%02x, (isIgnored() == %s)\n"
                         "\tcomplexity type: 0x%02x, (isIgnored() == %s)\n"
                         "\tcomplexity value: %f, (isIgnored() == %s)\n"
                         "",
                         this->sodrawstyle->style.getValue(),
                         this->sodrawstyle->style.isIgnored() ? "T" : "F",
                         this->solightmodel->model.getValue(),
                         this->solightmodel->model.isIgnored() ? "T" : "F",
                         this->socomplexity->type.getValue(),
                         this->socomplexity->type.isIgnored() ? "T" : "F",
                         this->socomplexity->value.getValue(),
                         this->socomplexity->value.isIgnored() ? "T" : "F");
#endif // debug
}

// Position the near and far clipping planes just in front of and
// behind the scene's bounding box. This will give us the optimal
// utilization of the z buffer resolution by shrinking it to its
// minimum depth.
//
// Near and far clipping planes are specified in the camera fields
// nearDistance and farDistance.
void
So@Gui@ViewerP::setClippingPlanes(void)
{
  // This is necessary to avoid a crash in case there is no scene
  // graph specified by the user.
  if (this->camera == NULL) return;

  if (this->autoclipbboxaction == NULL)
    this->autoclipbboxaction =
      new SoGetBoundingBoxAction(PUBLIC(this)->getViewportRegion());
  else
    this->autoclipbboxaction->setViewportRegion(PUBLIC(this)->getViewportRegion());

  this->autoclipbboxaction->apply(this->sceneroot);

  SbXfBox3f xbox = this->autoclipbboxaction->getXfBoundingBox();

  // Bounding box was calculated in camera space, so we need to "flip"
  // the box (because camera is pointing in the (0,0,-1) direction
  // from origo.
  float nearval = this->camera->nearDistance.getValue();
  float farval = this->camera->farDistance.getValue();
  if ( !xbox.isEmpty() ) {
    SbMatrix cammat;
    SbMatrix inverse;
    this->getCameraCoordinateSystem(this->camera, this->sceneroot, cammat, inverse);
    xbox.transform(inverse);
  
    SbMatrix mat;
    mat.setTranslate(- this->camera->position.getValue());
    xbox.transform(mat);
    mat = this->camera->orientation.getValue().inverse();
    xbox.transform(mat);
    SbBox3f box = xbox.project();

    nearval = -box.getMax()[2];
    farval = -box.getMin()[2];
  }

  // FIXME: what if we have a weird scale transform in the scene graph?
  // Could we end up with nearval > farval then? Investigate, then
  // either use an assert() (if it can't happen) or an So@Gui@Swap()
  // (to handle it). 20020116 mortene.

  // Check if scene is completely behind us.
  // Do NOT check for orthographic cameras.
  if (farval <= 0.0f && !this->camera->isOfType(SoOrthographicCamera::getClassTypeId())) { return; }

  if (this->camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
    // Disallow negative and small near clipping plane distance.
    
    float nearlimit; // the smallest value allowed for nearval
    if (this->autoclipstrategy == So@Gui@Viewer::CONSTANT_NEAR_PLANE) {
      nearlimit = this->autoclipvalue;
    }
    else {
      assert(this->autoclipstrategy == So@Gui@Viewer::VARIABLE_NEAR_PLANE);
      // From glFrustum() documentation: Depth-buffer precision is
      // affected by the values specified for znear and zfar. The
      // greater the ratio of zfar to znear is, the less effective the
      // depth buffer will be at distinguishing between surfaces that
      // are near each other. If r = far/near, roughly log (2) r bits
      // of depth buffer precision are lost. Because r approaches
      // infinity as znear approaches zero, you should never set znear
      // to zero.

      GLint depthbits[1];
      glGetIntegerv(GL_DEPTH_BITS, depthbits);
      
      int use_bits = (int) (float(depthbits[0]) * (1.0f-this->autoclipvalue)); 
      float r = (float) pow(2.0, (double) use_bits);
      nearlimit = farval / r;
    }
    
    if (nearlimit >= farval) {
      // (The "5000" magic constant was found by fiddling around a bit
      // on an OpenGL implementation with a 16-bit depth-buffer
      // resolution, adjusting to find something that would work well
      // with both a very "stretched" / deep scene and a more compact
      // single-model one.)
      nearlimit = farval / 5000.0f;
    }
    
    // adjust the near plane if the the value is too small.
    if (nearval < nearlimit) { nearval = nearlimit; }
  }

  // Some slack around the bounding box, in case the scene fits
  // exactly inside it. This is done to minimize the chance of
  // artifacts caused by the limitation of the z-buffer
  // resolution. One common artifact if this is not done is that the
  // near clipping plane cuts into the corners of the model as it's
  // rotated.
  const float SLACK = 0.001f;

  // FrustumCamera can be found in the SmallChange CVS module. We
  // should not change the nearDistance for this camera, as this will
  // modify the frustum.
  //
  // FIXME: quite the hack that So@Gui@ needs to know about the
  // FrustumCamera class. Wouldn't it be better if FrustumCamera
  // instead registered a callback with setAutoClippingStrategy() and
  // handled this itself?  20040908 mortene.
  if (this->camera->getTypeId().getName() == "FrustumCamera") {
    nearval = this->camera->nearDistance.getValue();
    farval *= (1.0f + SLACK); 
    if (farval <= nearval) {
      // nothing is visible, so just set farval to some value > nearval.
      farval = nearval + 10.0f;
    }
  }
  else {
    // For orthographic cameras also negative values nearval/farval
    // are reasonable.
    nearval *= (1.0f - (nearval>0?1:-1)*SLACK);
    farval *= (1.0f + (farval>0?1:-1)*SLACK);
  }

  if (this->autoclipcb) {
    SbVec2f nearfar(nearval, farval);
    nearfar = this->autoclipcb(this->autoclipuserdata, nearfar);

    nearval = nearfar[0];
    farval = nearfar[1]; 
  }

  if (nearval != this->camera->nearDistance.getValue()) {
    this->camera->nearDistance = nearval;
  }
  if (farval != this->camera->farDistance.getValue()) {
    this->camera->farDistance = farval;
  }

  // FIXME: there's a possible optimization to take advantage of here,
  // since we are able to sometimes know for sure that all geometry is
  // completely inside the view volume. I quote from the "OpenGL FAQ
  // and Troubleshooting Guide":
  //
  //  "10.050 I know my geometry is inside the view volume. How can I
  //  turn off OpenGL's view-volume clipping to maximize performance?
  //
  //   Standard OpenGL doesn't provide a mechanism to disable the
  //   view-volume clipping test; thus, it will occur for every
  //   primitive you send.
  //
  //   Some implementations of OpenGL support the
  //   GL_EXT_clip_volume_hint extension. If the extension is
  //   available, a call to
  //   glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT,GL_FASTEST) will inform
  //   OpenGL that the geometry is entirely within the view volume and
  //   that view-volume clipping is unnecessary. Normal clipping can
  //   be resumed by setting this hint to GL_DONT_CARE. When clipping
  //   is disabled with this hint, results are undefined if geometry
  //   actually falls outside the view volume."
  //
  // 20020117 mortene.


  // Debug assistance, can be turned on without recompilation (just
  // set the environment variable SO@GUI@_DEBUG_CLIPPLANES):

#if SO@GUI@_DEBUG
  static int debugoutputnearfar = -1;
  if (debugoutputnearfar == -1) {
    const char * env = SoAny::si()->getenv("SO@GUI@_DEBUG_CLIPPLANES");
    debugoutputnearfar = (env && atoi(env) > 0) ? 1 : 0;
  }

  if (debugoutputnearfar == 1) { // debug
    SoDebugError::postInfo("So@Gui@Viewer::setClippingPlanes",
                           "near, far: %f (%f), %f (%f)",
                           nearval, this->camera->nearDistance.getValue(),
                           farval, this->camera->farDistance.getValue());
  }
#endif // debug
}

// Translate camera a distance equal to the difference in projected,
// normalized screen coordinates given by the argument.
void
So@Gui@ViewerP::moveCameraScreen(const SbVec2f & screenpos)
{
  SoCamera * cam = PUBLIC(this)->getCamera();
  assert(cam);

  if (SO@GUI@_DEBUG && 0) { // debug
    SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen",
                           "screenpos: <%f, %f>, campos: <%f, %f, %f>",
                           screenpos[0], screenpos[1],
                           cam->position.getValue()[0],
                           cam->position.getValue()[1],
                           cam->position.getValue()[2]);
  }

  SbViewVolume vv = cam->getViewVolume(PUBLIC(this)->getGLAspectRatio());
  SbPlane panplane = vv.getPlane(cam->focalDistance.getValue());

  SbLine line;
  vv.projectPointToLine(screenpos + SbVec2f(0.5, 0.5f), line);
  SbVec3f current_planept;
  panplane.intersect(line, current_planept);
  vv.projectPointToLine(SbVec2f(0.5f, 0.5f), line);
  SbVec3f old_planept;
  panplane.intersect(line, old_planept);

  // Reposition camera according to the vector difference between the
  // projected points.
  cam->position = cam->position.getValue() - (current_planept - old_planept);

  if (SO@GUI@_DEBUG && 0) { // debug
    SoDebugError::postInfo("So@Gui@Viewer::moveCameraScreen",
                           "newcampos: <%f, %f, %f>",
                           cam->position.getValue()[0],
                           cam->position.getValue()[1],
                           cam->position.getValue()[2]);
  }
}

// Called when viewer enters interactive mode (animation, drag, ...).
void
So@Gui@ViewerP::interactivestartCB(void *, So@Gui@Viewer * thisp)
{
  // In interactive buffer mode, doublebuffering is used during interaction.
  if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) {
    PRIVATE(thisp)->localsetbuffertype = TRUE;
    thisp->So@Gui@RenderArea::setDoubleBuffer(TRUE);
    PRIVATE(thisp)->localsetbuffertype = FALSE;
  }

  // Use the dynamic drawstyle.
  if (!PRIVATE(thisp)->drawInteractiveAsStill())
    PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::INTERACTIVE]);
}

// Called when viewer goes out of interactive mode and into "frozen"
// mode.
void
So@Gui@ViewerP::interactiveendCB(void *, So@Gui@Viewer * thisp)
{
  // In interactive buffer mode, doublebuffering is used during
  // interaction, singelbuffering while the camera is static.
  if (PRIVATE(thisp)->buffertype == So@Gui@Viewer::BUFFER_INTERACTIVE) {
    PRIVATE(thisp)->localsetbuffertype = TRUE;
    thisp->So@Gui@RenderArea::setDoubleBuffer(FALSE);
    PRIVATE(thisp)->localsetbuffertype = FALSE;
  }

  // Back to static drawstyle.
  if (!PRIVATE(thisp)->drawInteractiveAsStill())
    PRIVATE(thisp)->changeDrawStyle(PRIVATE(thisp)->drawstyles[So@Gui@Viewer::STILL]);
}

// Called repeatedly during the seek animation.
void
So@Gui@ViewerP::seeksensorCB(void * data, SoSensor * s)
{
  SbTime currenttime = SbTime::getTimeOfDay();

  So@Gui@Viewer * thisp = (So@Gui@Viewer *)data;
  SoTimerSensor * sensor = (SoTimerSensor *)s;

  float t =
    float((currenttime - sensor->getBaseTime()).getValue()) / PRIVATE(thisp)->seekperiod;
  if ((t > 1.0f) || (t + sensor->getInterval().getValue() > 1.0f)) t = 1.0f;
  SbBool end = (t == 1.0f);
  
  t = (float) ((1.0 - cos(M_PI*t)) * 0.5);
  
  PRIVATE(thisp)->camera->position = PRIVATE(thisp)->camerastartposition +
    (PRIVATE(thisp)->cameraendposition - PRIVATE(thisp)->camerastartposition) * t;
  PRIVATE(thisp)->camera->orientation = 
    SbRotation::slerp(PRIVATE(thisp)->camerastartorient,
                      PRIVATE(thisp)->cameraendorient, 
                      t);

  if (end) thisp->setSeekMode(FALSE);
}

// Reset the frames-per-second counter upon window resize events,
// abnormal delays, etc.
//
// The methods for recording FPS values are Coin extensions, not
// available in the original Open Inventor API.
//
// \sa addFrametime(), recordFPS()
void
So@Gui@ViewerP::resetFrameCounter(void)
{
  this->framecount = 0;
  for (int i = 0; i < So@Gui@ViewerP::FRAMESARRAY_SIZE; i++)
    this->frames[i] = SbVec2f(0.0f, 0.0f);
  this->totalcoin = 0.0f;
  this->totaldraw = 0.0f;
  this->lastgettimeofday = SbTime::getTimeOfDay().getValue();
}

// Adds the time spent drawing the last frame to the array of past
// frame times. Returns the current averaged fps-value.
//
// The methods for recording FPS values are Coin extensions, not
// available in the original Open Inventor API.
//
// \sa resetFrameCounter(), recordFPS()
SbVec2f
So@Gui@ViewerP::addFrametime(const double ft)
{
  this->framecount++;

  int arrayptr = (this->framecount - 1) % FRAMESARRAY_SIZE;

  this->totalcoin += (float(ft) - this->frames[arrayptr][0]);
  float coinfps =
    this->totalcoin / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE);

  double timeofday = SbTime::getTimeOfDay().getValue();
  double ct = timeofday - this->lastgettimeofday;
  this->totaldraw += (float(ct) - this->frames[arrayptr][1]);
  float drawfps =
    this->totaldraw / So@Gui@Min(this->framecount, (int) FRAMESARRAY_SIZE);

  this->frames[arrayptr] = SbVec2f((float)ft, (float)ct);
  this->lastgettimeofday = timeofday;

  return SbVec2f(1.0f / coinfps, 1.0f / drawfps);
}

static unsigned char fps2dfont[][12] = {
  {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, //
  {  0,  0, 12, 12,  0,  8, 12, 12, 12, 12, 12,  0 }, // !
  {  0,  0,  0,  0,  0,  0,  0,  0,  0, 20, 20, 20 }, // \"
  {  0,  0, 18, 18, 18, 63, 18, 18, 63, 18, 18,  0 }, // #
  {  0,  8, 28, 42, 10, 10, 12, 24, 40, 42, 28,  8 }, // $
  {  0,  0,  6, 73, 41, 22,  8, 52, 74, 73, 48,  0 }, // %
  {  0, 12, 18, 18, 12, 25, 37, 34, 34, 29,  0,  0 }, // &
  { 12, 12, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, // '
  {  0,  6,  8,  8, 16, 16, 16, 16, 16,  8,  8,  6 }, // (
  {  0, 48,  8,  8,  4,  4,  4,  4,  4,  8,  8, 48 }, //)
  {  0,  0,  0,  0,  0,  0,  8, 42, 20, 42,  8,  0 }, // *
  {  0,  0,  0,  8,  8,  8,127,  8,  8,  8,  0,  0 }, // +
  {  0, 24, 12, 12,  0,  0,  0,  0,  0,  0,  0,  0 }, // ,
  {  0,  0,  0,  0,  0,  0,127,  0,  0,  0,  0,  0 }, // -
  {  0,  0, 24, 24,  0,  0,  0,  0,  0,  0,  0,  0 }, // .
  {  0, 32, 32, 16, 16,  8,  8,  8,  4,  4,  2,  2 }, // /
  {  0,  0, 28, 34, 34, 34, 34, 34, 34, 34, 28,  0 }, // 0
  {  0,  0,  8,  8,  8,  8,  8,  8, 40, 24,  8,  0 }, // 1
  {  0,  0, 62, 32, 16,  8,  4,  2,  2, 34, 28,  0 }, // 2
  {  0,  0, 28, 34,  2,  2, 12,  2,  2, 34, 28,  0 }, // 3
  {  0,  0,  4,  4,  4,126, 68, 36, 20, 12,  4,  0 }, // 4
  {  0,  0, 28, 34,  2,  2,  2, 60, 32, 32, 62,  0 }, // 5
  {  0,  0, 28, 34, 34, 34, 60, 32, 32, 34, 28,  0 }, // 6
  {  0,  0, 16, 16, 16,  8,  8,  4,  2,  2, 62,  0 }, // 7
  {  0,  0, 28, 34, 34, 34, 28, 34, 34, 34, 28,  0 }, // 8
  {  0,  0, 28, 34,  2,  2, 30, 34, 34, 34, 28,  0 }, // 9
  {  0,  0, 24, 24,  0,  0,  0, 24, 24,  0,  0,  0 }, // :
  {  0, 48, 24, 24,  0,  0,  0, 24, 24,  0,  0,  0 }, // ;
  {  0,  0,  0,  2,  4,  8, 16,  8,  4,  2,  0,  0 }, // <
  {  0,  0,  0,  0,  0,127,  0,127,  0,  0,  0,  0 }, // =
  {  0,  0,  0, 16,  8,  4,  2,  4,  8, 16,  0,  0 }, // >
  {  0,  0, 16, 16,  0, 16, 28,  2,  2,  2, 60,  0 }, // ?
  {  0,  0, 28, 32, 73, 86, 82, 82, 78, 34, 28,  0 }, // @
  {  0,  0, 33, 33, 33, 63, 18, 18, 18, 12, 12,  0 }, // A
  {  0,  0, 60, 34, 34, 34, 60, 34, 34, 34, 60,  0 }, // B
  {  0,  0, 14, 16, 32, 32, 32, 32, 32, 18, 14,  0 }, // C
  {  0,  0, 56, 36, 34, 34, 34, 34, 34, 36, 56,  0 }, // D
  {  0,  0, 62, 32, 32, 32, 60, 32, 32, 32, 62,  0 }, // E
  {  0,  0, 16, 16, 16, 16, 30, 16, 16, 16, 30,  0 }, // F
  {  0,  0, 14, 18, 34, 34, 32, 32, 32, 18, 14,  0 }, // G
  {  0,  0, 34, 34, 34, 34, 62, 34, 34, 34, 34,  0 }, // H
  {  0,  0, 62,  8,  8,  8,  8,  8,  8,  8, 62,  0 }, // I
  {  0,  0,112,  8,  8,  8,  8,  8,  8,  8, 62,  0 }, // J
  {  0,  0, 33, 33, 34, 36, 56, 40, 36, 34, 33,  0 }, // K
  {  0,  0, 30, 16, 16, 16, 16, 16, 16, 16, 16,  0 }, // L
  {  0,  0, 33, 33, 33, 45, 45, 45, 51, 51, 33,  0 }, // M
  {  0,  0, 34, 34, 38, 38, 42, 42, 50, 50, 34,  0 }, // N
  {  0,  0, 12, 18, 33, 33, 33, 33, 33, 18, 12,  0 }, // O
  {  0,  0, 32, 32, 32, 60, 34, 34, 34, 34, 60,  0 }, // P
  {  3,  6, 12, 18, 33, 33, 33, 33, 33, 18, 12,  0 }, // Q
  {  0,  0, 34, 34, 34, 36, 60, 34, 34, 34, 60,  0 }, // R
  {  0,  0, 60,  2,  2,  6, 28, 48, 32, 32, 30,  0 }, // S
  {  0,  0,  8,  8,  8,  8,  8,  8,  8,  8,127,  0 }, // T
  {  0,  0, 28, 34, 34, 34, 34, 34, 34, 34, 34,  0 }, // U
  {  0,  0, 12, 12, 18, 18, 18, 33, 33, 33, 33,  0 }, // V
  {  0,  0, 34, 34, 34, 54, 85, 73, 73, 73, 65,  0 }, // W
  {  0,  0, 34, 34, 20, 20,  8, 20, 20, 34, 34,  0 }, // X
  {  0,  0,  8,  8,  8,  8, 20, 20, 34, 34, 34,  0 }, // Y
  {  0,  0, 62, 32, 16, 16,  8,  4,  4,  2, 62,  0 }, // Z
  {  0, 14,  8,  8,  8,  8,  8,  8,  8,  8,  8, 14 }, // [
  {  0,  2,  2,  4,  4,  8,  8,  8, 16, 16, 32, 32 }, // [backslash]
  {  0, 56,  8,  8,  8,  8,  8,  8,  8,  8,  8, 56 }, // ]
  {  0,  0,  0,  0,  0, 34, 34, 20, 20,  8,  8,  0 }, // ^
  {  0,127,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, // _
  {  0,  0,  0,  0,  0,  0,  0,  0,  0, 24, 24, 12 }, // `
  {  0,  0, 29, 34, 34, 30,  2, 34, 28,  0,  0,  0 }, // a
  {  0,  0, 60, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // b
  {  0,  0, 14, 16, 32, 32, 32, 16, 14,  0,  0,  0 }, // c
  {  0,  0, 26, 38, 34, 34, 34, 34, 30,  2,  2,  2 }, // d
  {  0,  0, 28, 34, 32, 62, 34, 34, 28,  0,  0,  0 }, // e
  {  0,  0, 16, 16, 16, 16, 16, 16, 62, 16, 16, 14 }, // f
  { 28,  2,  2, 26, 38, 34, 34, 34, 30,  0,  0,  0 }, // g
  {  0,  0, 34, 34, 34, 34, 34, 50, 44, 32, 32, 32 }, // h
  {  0,  0,  8,  8,  8,  8,  8,  8, 56,  0,  8,  8 }, // i
  { 56,  4,  4,  4,  4,  4,  4,  4, 60,  0,  4,  4 }, // j
  {  0,  0, 33, 34, 36, 56, 40, 36, 34, 32, 32, 32 }, // k
  {  0,  0,  8,  8,  8,  8,  8,  8,  8,  8,  8, 56 }, // l
  {  0,  0, 73, 73, 73, 73, 73,109, 82,  0,  0,  0 }, // m
  {  0,  0, 34, 34, 34, 34, 34, 50, 44,  0,  0,  0 }, // n
  {  0,  0, 28, 34, 34, 34, 34, 34, 28,  0,  0,  0 }, // o
  { 32, 32, 60, 34, 34, 34, 34, 50, 44,  0,  0,  0 }, // p
  {  2,  2, 26, 38, 34, 34, 34, 34, 30,  0,  0,  0 }, // q
  {  0,  0, 16, 16, 16, 16, 16, 24, 22,  0,  0,  0 }, // r
  {  0,  0, 60,  2,  2, 28, 32, 32, 30,  0,  0,  0 }, // s
  {  0,  0, 14, 16, 16, 16, 16, 16, 62, 16, 16,  0 }, // t
  {  0,  0, 26, 38, 34, 34, 34, 34, 34,  0,  0,  0 }, // u
  {  0,  0,  8,  8, 20, 20, 34, 34, 34,  0,  0,  0 }, // v
  {  0,  0, 34, 34, 34, 85, 73, 73, 65,  0,  0,  0 }, // w
  {  0,  0, 34, 34, 20,  8, 20, 34, 34,  0,  0,  0 }, // x
  { 48, 16,  8,  8, 20, 20, 34, 34, 34,  0,  0,  0 }, // y
  {  0,  0, 62, 32, 16,  8,  4,  2, 62,  0,  0,  0 }, // z
  {  0,  6,  8,  8,  8,  4, 24,  4,  8,  8,  8,  6 }, // {
  {  0,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8 }, // |
  {  0, 48,  8,  8,  8, 16, 12, 16,  8,  8,  8, 48 }, // }
  {  0,  0,  0,  0,  0,  0, 78, 57,  0,  0,  0,  0 }  // ~
};

static void
printString(const char * s)
{
  size_t i,n;
  n = strlen(s);
  for (i = 0; i < n; i++)
    glBitmap(8, 12, 0.0, 2.0, 10.0, 0.0, fps2dfont[s[i] - 32]);
}

static void
Draw2DString(const char * str, SbVec2s glsize, SbVec2f position)
{
  // Store GL state.
  glPushAttrib(GL_ENABLE_BIT|GL_CURRENT_BIT);

  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, glsize[0], 0.0, glsize[1], -1, 1);

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  glColor3f(0.0, 0.0, 0.0);
  glRasterPos2f(position[0] + 1, position[1]);
  printString(str);
  glRasterPos2f(position[0] - 1, position[1]);
  printString(str);
  glRasterPos2f(position[0], position[1] + 1);
  printString(str);
  glRasterPos2f(position[0], position[1] - 1);
  printString(str);

  glColor3f(1.0, 1.0, 0.0);
  glRasterPos2f(position[0], position[1]);
  printString(str);

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // restore default value

  glPopAttrib();
}


// FIXME: the following is just a temporary hack to enable the FPS
// counter. We should really write a proper interface against it, so
// applications can set up feedback loops to control scene complexity
// and get a nice and steady maximum framerate, for instance.
//
// For anyone who want to execute that task, check what TGS has done
// first. If their API is fine, use the same approach.
//
// 20001124 mortene.

// Draw a text string showing the current frame-per-seconds value in
// the lower left corner of the OpenGL canvas (after recording
// information needed to calculate the fps).
//
// The methods for recording FPS values are Coin extensions, not
// available in the original Open Inventor API.
//
// The two displayed values can be explained as follows:
//
// The first number is the time it takes for the SoGLRenderAction to
// traverse the scene graph (displayed as Hz / FPS). The second is the
// interval between each time SoGLRenderAction::apply() is invoked
// (i.e. the "actual" rendering rate, as experienced by the user).
//
// The first number is mainly useful just for internal debugging
// purposes.
//
// The second number will always be <= to the first, because it will
// also include the time stalling on glFlush() upon releasing the
// OpenGL context after traversal (glFlush() stalls until the GPU
// completes all OpenGL commands), and any application-code processing
// in between rendering.
//
// There is by the way a useful "trick" to improve application
// performance implied in that last paragraph above: if
// application-code processing is interleaved between the completion
// of the SoGLRenderAction traversal and the release of the OpenGL
// context, application-code is likely to run on the CPU in parallel
// with GPU processing OpenGL commands.
//
// FIXME: the above optimization trick should be documented in the
// visible API docs somewhere, with code to show how to do it. The
// example code would probably involve making an application-specific
// viewer, and overriding actualRedraw()? Or can it be done by using a
// callback mechanism somewhere? Ask pederb. 20031009 mortene.
//
// \sa resetFrameCounter(), addFrametime()
void
So@Gui@ViewerP::recordFPS(const double rendertime)
{
  const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER");
  if ( !env ) {
    COIN_SHOW_FPS_COUNTER = UNINITIALIZED_ENVVAR;
  } else {
    COIN_SHOW_FPS_COUNTER = atoi(env);
  }

#if 0
  // disabled to make fps-couter dynamically adjustable
  if (COIN_SHOW_FPS_COUNTER == UNINITIALIZED_ENVVAR) {
    const char * env = SoAny::si()->getenv("COIN_SHOW_FPS_COUNTER");
    COIN_SHOW_FPS_COUNTER = env ? atoi(env) : 0;
  }
#endif

  if (COIN_SHOW_FPS_COUNTER > 0) {
    SbVec2f fps = this->addFrametime(rendertime);

    char buffer[64];
    int nr = sprintf(buffer, "%.1f/%.1f fps", fps[0], fps[1]);
    assert(nr < 64);
    Draw2DString(buffer, PUBLIC(this)->getGLSize(), SbVec2f(10, 10));
  }
}

// *************************************************************************

SO@GUI@_OBJECT_ABSTRACT_SOURCE(So@Gui@Viewer);

// *************************************************************************

/*!
  \enum So@Gui@Viewer::Type

  Hints about what context the viewer will be used in.  Usually not
  very interesting for the application programmer, it doesn't matter
  much which value is used for the viewer type.  This "feature" of the
  viewer is included just to be compatible with the old SGI Inventor
  API.
*/
/*!
  \var So@Gui@Viewer::Type So@Gui@Viewer::BROWSER

  If a user-supplied scene graph passed into the setSceneGraph()
  function does not contain a camera, setting the viewer type to
  BROWSER will make the viewer in that case automatically set up a
  camera outside the scene, as part of the viewer's private and hidden
  "supergraph".
*/
/*!
  \var So@Gui@Viewer::Type So@Gui@Viewer::EDITOR

  If a user-supplied scene graph passed into the setSceneGraph()
  function does not contain a camera, setting the viewer type to
  EDITOR will make the viewer in that case automatically set up a
  camera \e in the user-supplied scene.

  So if you want to avoid having the So@Gui@Viewer class muck about
  with your supplied scene graph, set the type-flag to
  So@Gui@Viewer::BROWSER instead, which makes an inserted camera node
  go into the viewer's own "wrapper" scene graph instead.
*/

/*!
  \enum So@Gui@Viewer::DrawType

  Contains valid values for the first argument to the
  So@Gui@Viewer::setDrawStyle() call. Decides the effect of the second
  argument.

  \sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawStyle
*/
/*!
  \var So@Gui@Viewer::DrawType So@Gui@Viewer::STILL

  If this value is passed as the first argument of
  So@Gui@Viewer::setDrawStyle(), the second argument decides which
  draw style to use when the viewer camera is standing still in the
  same position with the same orientation -- i.e. when the end user is
  \e not interacting with the scene camera.
*/
/*!
  \var So@Gui@Viewer::DrawType So@Gui@Viewer::INTERACTIVE

  If this value is passed as the first argument of
  So@Gui@Viewer::setDrawStyle(), the second argument decides which
  draw style to use when the end user is interacting with the scene
  camera, causing continuous animation redraws.
*/

/*!
  \enum So@Gui@Viewer::DrawStyle

  Decides draw style for a scene with either a still camera or an
  animating camera.

  \sa So@Gui@Viewer::setDrawStyle(), So@Gui@Viewer::DrawType
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_AS_IS

  Normal rendering, draws all scene geometry in its original style.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_HIDDEN_LINE

  Draw scene in "hidden line" mode: that is, as wireframe with no
  "see-through".

  Note that this is actually an expensive way to render, as the scene
  must be rendered twice to achieve the effect of hiding lines behind
  the invisible geometry.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_WIREFRAME_OVERLAY

  Render the scene as normal, but overlay a set of lines showing the
  contours of all polygons.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_NO_TEXTURE

  Render scene without textures.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_COMPLEXITY

  Render all "complex" shape types with low complexity to improve
  rendering performance.

  "Complex shapes" in this context includes spheres, cones, cylinder,
  NURBS surfaces, and others which are tessellated to polygons before
  being rendered.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LINE

  View all polygon geometry in wireframe mode.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_POINT

  Render only the vertex positions of the geometry.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_BBOX

  View the scene's bounding boxes, instead of rendering the full
  geometry.

  A very efficient way of optimizing rendering performance for scenes
  with high primitive counts while moving the camera about is to set
  this mode for the So@Gui@Viewer::INTERACTIVE DrawType.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_LINE

  Render as wireframe and don't bother with getting them rendered
  correctly in depth.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_LOW_RES_POINT

  Render as vertex points and don't bother with getting them rendered
  correctly in depth.
*/
/*!
  \var So@Gui@Viewer::DrawStyle So@Gui@Viewer::VIEW_SAME_AS_STILL

  Always render a scene with an animating camera (i.e.
  So@Gui@Viewer::INTERACTIVE DrawType) in the same manner as scene
  with a still camera.
*/

/*!
  \enum So@Gui@Viewer::BufferType
  Set of valid values for So@Gui@Viewer::setBufferingType().
*/
/*!
  \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_SINGLE
  Change underlying OpenGL canvas to be single buffered.
*/
/*!
  \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_DOUBLE
  Change underlying OpenGL canvas to be double buffered.
*/
/*!
  \var So@Gui@Viewer::BufferType So@Gui@Viewer::BUFFER_INTERACTIVE

  Set up so animation rendering is done in a double buffered OpenGL
  canvas, but ordinary rendering happens directly in the front buffer.

  This mode can be useful with absurdly large scenes, as the rendering
  will \e visibly progress, and one will avoid having the end user
  wonder why nothing is happening while the scene is rendered to the
  back buffer in the default So@Gui@Viewer::BUFFER_DOUBLE mode.
*/

// *************************************************************************

/*
  Return the parent node in the scene graph of the given \a node.
  NB: this is just a quick and dirty thing for often executed code,
  and doesn't cover cases where nodes have multiple parents.
*/

SoGroup *
So@Gui@ViewerP::getParentOfNode(SoNode * root, SoNode * node) const
{
  assert(node && root && "called with null argument");

  const SbBool oldsearch = SoBaseKit::isSearchingChildren();
  SoBaseKit::setSearchingChildren(TRUE);

  this->searchaction->reset();
  this->searchaction->setSearchingAll(TRUE);
  this->searchaction->setNode(node);
  this->searchaction->apply(root);

  SoPath * p = this->searchaction->getPath();

  SoGroup * parent = NULL;
  if (p) {
    parent = (SoGroup *) ((SoFullPath *)p)->getNodeFromTail(1);
    assert(parent && "couldn't find parent");
  }

  this->searchaction->reset();

  SoBaseKit::setSearchingChildren(oldsearch);

  return parent;
}

// *************************************************************************

/*!
  Constructor. \a parent, \a name and \a embed are passed on to
  So@Gui@RenderArea, so see the documentation for our parent
  constructor for for more information on those.

  The \a t type setting hints about what context the viewer will be
  used in.  Usually not very interesting for the application
  programmer, but if you want to make sure the So@Gui@Viewer class
  doesn't muck about with your supplied scene graph, set the type-flag
  to So@Gui@Viewer::BROWSER.  (This "feature" of the viewer is
  included just to be compatible with the old SGI Inventor API.)

  The \a build flag decides whether or not to delay building the
  widgets / window which is going to make up the components of the
  viewer.
*/
So@Gui@Viewer::So@Gui@Viewer(@WIDGET@ parent,
                             const char * name,
                             SbBool embed,
                             So@Gui@Viewer::Type t,
                             SbBool build)
  : inherited(parent, name, embed, TRUE, TRUE, FALSE)
{
  PRIVATE(this) = new So@Gui@ViewerP(this);

  // initialization of protected data
  PRIVATE(this)->type = t;
  PRIVATE(this)->viewingflag = TRUE;
  PRIVATE(this)->altdown = FALSE;
  PRIVATE(this)->camera = NULL;
  PRIVATE(this)->scenegraph = NULL;

  // initialization of internal data
  PRIVATE(this)->cursoron = TRUE;
  PRIVATE(this)->localsetbuffertype = FALSE;

  PRIVATE(this)->cameratype = SoPerspectiveCamera::getClassTypeId();
  PRIVATE(this)->buffertype = this->isDoubleBuffer() ? BUFFER_DOUBLE : BUFFER_SINGLE;

  PRIVATE(this)->interactionstartCallbacks = new SoCallbackList;
  PRIVATE(this)->interactionendCallbacks = new SoCallbackList;
  PRIVATE(this)->interactionnesting = 0;

  PRIVATE(this)->seekdistance = 50.0f;
  PRIVATE(this)->seekdistanceabs = FALSE;
  PRIVATE(this)->seektopoint = TRUE;
  PRIVATE(this)->seekperiod = 2.0f;
  PRIVATE(this)->inseekmode = FALSE;
  PRIVATE(this)->seeksensor = new SoTimerSensor(So@Gui@ViewerP::seeksensorCB, this);

  PRIVATE(this)->sceneroot = PRIVATE(this)->createSuperScene();
  PRIVATE(this)->sceneroot->ref();

  PRIVATE(this)->drawstyles[STILL] = VIEW_AS_IS;
  PRIVATE(this)->drawstyles[INTERACTIVE] = VIEW_SAME_AS_STILL;

  this->addStartCallback(So@Gui@ViewerP::interactivestartCB);
  this->addFinishCallback(So@Gui@ViewerP::interactiveendCB);

  PRIVATE(this)->adjustclipplanes = TRUE;
  PRIVATE(this)->autoclipbboxaction = NULL;

  PRIVATE(this)->stereoviewing = FALSE;
  PRIVATE(this)->stereooffset = 0.1f;

  PRIVATE(this)->wireframeoverlaycolor = SbColor(1.0f, 0.0f, 0.0f);

  if (build) {
    this->setClassName("So@Gui@Viewer");
    @WIDGET@ widget = this->buildWidget(this->getParentWidget());
    this->setBaseWidget(widget);
  }

  PRIVATE(this)->resetFrameCounter();
}

// *************************************************************************

/*!
  Destructor.
*/

So@Gui@Viewer::~So@Gui@Viewer()
{
  delete PRIVATE(this)->autoclipbboxaction;

  delete PRIVATE(this)->interactionstartCallbacks;
  delete PRIVATE(this)->interactionendCallbacks;

  delete PRIVATE(this)->seeksensor;

  if (PRIVATE(this)->scenegraph) this->setSceneGraph(NULL);
  if (PRIVATE(this)->superimpositions != NULL) {
    while ( PRIVATE(this)->superimpositions->getLength() > 0 ) {
      SoNode * node = (SoNode *) (*PRIVATE(this)->superimpositions)[0];
      this->removeSuperimposition(node);
    }
  }
  PRIVATE(this)->sceneroot->unref();
  delete PRIVATE(this);
}

// *************************************************************************

// Note: the following function documentation block will also be used
// for all the miscellaneous viewer subclasses, so keep it general.
/*!
  Set the camera we want the viewer to manipulate when interacting with
  the viewer controls.

  The camera passed in as an argument to this method \e must already
  be part of the viewer's scene graph.  You do \e not inject viewpoint
  cameras to the viewer with this method.

  You should rather insert a camera into the scene graph first (if
  necessary, often one will be present already), then register it as
  the camera used by the viewer controls with this method.

  If the application code doesn't explicitly set up a camera through
  this method, the viewer will automatically scan through the
  scene graph to find a camera to use. If no camera is available in the
  scene graph at all, it will set up its own camera.

  \sa getCamera()
*/
void
So@Gui@Viewer::setCamera(SoCamera * cam)
{
  if (PRIVATE(this)->camera) {
    // remove the camera from the super scene graph if we inserted a camera there
    int idx = PRIVATE(this)->sceneroot->findChild(PRIVATE(this)->camera);
    if (idx >= 0) {
      PRIVATE(this)->sceneroot->removeChild(idx);
    }
    PRIVATE(this)->camera->unref();
  }
  
  if (cam) {
    cam->ref();
    PRIVATE(this)->cameratype = cam->getTypeId();
  }

  PRIVATE(this)->camera = cam;

  this->saveHomePosition();
}

// *************************************************************************

/*!
  Returns the camera currently used by the viewer for the user's main
  viewpoint.

  It \e is possible that this function returns \c NULL, for instance
  if there's no scene graph present in the viewer.  (This is mostly
  meant as a note for developers extending the So@Gui@ library, as
  application programmers usually controls if and when a viewer
  contains a scene graph, and therefore know in advance if this method
  will return a valid camera pointer.)

  \sa setCamera()
*/
SoCamera *
So@Gui@Viewer::getCamera(void) const
{
  // This impossible to miss reminder was inserted so we don't
  // accidentally let an So* v2 slip out the door without fixing this
  // API design flaw. 20030903 mortene.
#if (SO@GUI@_MAJOR_VERSION == 2)
#error This is a reminder: when jumping to version 2 of an So* toolkit, the So@Gui@Viewer::getCamera() method should be made virtual.
#endif // version = 2

  return PRIVATE(this)->camera;
}

// *************************************************************************

/*!
  When the viewer has to make its own camera as a result of the graph
  passed to setSceneGraph() not containing any camera nodes, this call
  can be made in advance to decide which type the camera will be of.

  Default is to use an SoPerspectiveCamera.

  If this method is called when there is a scene graph and a camera
  already set up, it will delete the old camera and set up a camera
  with the new type if the \a t type is different from that of the
  current camera.

  \sa getCameraType()
*/

void
So@Gui@Viewer::setCameraType(SoType t)
{
  if (PRIVATE(this)->camera && 
      !PRIVATE(this)->camera->isOfType(SoPerspectiveCamera::getClassTypeId()) &&
      !PRIVATE(this)->camera->isOfType(SoOrthographicCamera::getClassTypeId())) {
#if SO@GUI@_DEBUG
    SoDebugError::postWarning("So@Gui@Viewer::setCameraType",
                              "Only SoPerspectiveCamera and SoOrthographicCamera is supported.");
#endif // SO@GUI_DEBUG 
    return;
  }


  SoType perspectivetype = SoPerspectiveCamera::getClassTypeId();
  SoType orthotype = SoOrthographicCamera::getClassTypeId();
  SbBool oldisperspective = PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype);
  SbBool newisperspective = t.isDerivedFrom(perspectivetype);

  if ((oldisperspective && newisperspective) ||
      (!oldisperspective && !newisperspective)) // Same old, same old..
    return;

  if (SO@GUI@_DEBUG) {
    SbBool valid = TRUE;
    if (t == SoType::badType()) valid = FALSE;
    if (valid) {
      valid = FALSE;
      if (newisperspective) valid = TRUE;
      if (t.isDerivedFrom(orthotype)) valid = TRUE;
    }

    if (!valid) {
      SoDebugError::post("So@Gui@Viewer::setCameraType",
                         "not a valid camera type: '%s'",
                         t == SoType::badType() ?
                         "badType" : t.getName().getString());
      return;
    }
  }

  SoCamera * currentcam = PRIVATE(this)->camera;

  if (currentcam == NULL) {
    // A camera has not been set up for the scene yet, so just store
    // the type and short-cut the rest of this function.
    PRIVATE(this)->cameratype = t;
    return;
  }

  SoCamera * newcamera = (SoCamera *)t.createInstance();

  // Transfer and convert values from one camera type to the other.
  if (newisperspective) {
    So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)currentcam,
                                             (SoPerspectiveCamera *)newcamera);
  }
  else {
    So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)currentcam,
                                             (SoOrthographicCamera *)newcamera);
  }

  SoGroup * cameraparent =
    PRIVATE(this)->getParentOfNode(PRIVATE(this)->sceneroot, currentcam);
  if (cameraparent) { cameraparent->replaceChild(currentcam, newcamera); }
  else {
    // camera not actually present in the scene graph, so just NULL
    // and void.
    newcamera->ref();
    newcamera->unref();
    newcamera = NULL;

    // Yes, this can "legally" happen, if e.g. the camera is taken out
    // of the scene graph by the app programmer, and no new camera was
    // set with setCamera() -- but this is a quite odd thing to do, so
    // we warn about this for now.
    SoDebugError::postWarning("So@Gui@Viewer::setCameraType",
                              "Could not find the current camera in the "
                              "scene graph, for some odd reason.");
  }

  // The setCamera() invokation below will set the saved "home"
  // position of the camera to the current camera position. We make
  // no attempt to avoid this, as it would involve nasty hacks, and
  // it shouldn't really matter.

  this->setCamera(newcamera); // This will set PRIVATE(this)->cameratype.
}

// *************************************************************************

/*!
  Returns camera type which will be used when the viewer has to make its
  own camera.

  Note that this call does \e not return the current camera type, as one
  might expect. Use getCamera() and SoType::getTypeId() for that inquiry.

  \sa setCameraType()
*/

SoType
So@Gui@Viewer::getCameraType(void) const
{
  return PRIVATE(this)->cameratype;
}

// *************************************************************************

/*!
  Reposition the current camera so we can see the complete scene.
*/
void
So@Gui@Viewer::viewAll(void)
{
  SoCamera * cam = PRIVATE(this)->camera;
  if (cam && PRIVATE(this)->scenegraph) {
    cam->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion());
  }
}

// *************************************************************************

/*!
  Store the current camera settings for later retrieval with
  resetToHomePosition().

  \sa resetToHomePosition()
*/
void
So@Gui@Viewer::saveHomePosition(void)
{
  if (! PRIVATE(this)->camera) return; // probably a scene-less viewer

  // We use SoType::createInstance() to store a copy of the camera,
  // not just assuming it's either a perspective or an orthographic
  // camera.

  SoType t = PRIVATE(this)->camera->getTypeId();
  assert(t.isDerivedFrom(SoNode::getClassTypeId()));
  assert(t.canCreateInstance());

  if (PRIVATE(this)->storedcamera) { PRIVATE(this)->storedcamera->unref(); }

  PRIVATE(this)->storedcamera = (SoNode *)t.createInstance();
  PRIVATE(this)->storedcamera->ref();

  // We copy the field data directly, instead of using
  // SoFieldContainer::copyContents(), as that has one problematic
  // side-effect: the new camera node used for storing the data would
  // also get the *name* of the old camera, which would overwrite the
  // old name->ptr entry of the global dictionary behind
  // SoNode::getByName(). This can cause surprising and hard to find
  // bugs for app programmers, for instance when using
  // SoNode::getByName() to get at a camera loaded from an iv-file.
  PRIVATE(this)->storedcamera->copyFieldValues(PRIVATE(this)->camera);
}

// *************************************************************************

/*!
  Restore the saved camera settings.

  \sa saveHomePosition()
*/
void
So@Gui@Viewer::resetToHomePosition(void)
{
  if (!PRIVATE(this)->camera) { return; } // probably a scene-less viewer
  if (!PRIVATE(this)->storedcamera) { return; }
  
  SoType t = PRIVATE(this)->camera->getTypeId();
  SoType s = PRIVATE(this)->storedcamera->getTypeId();

  // most common case
  if (t == s) {
    // We copy the field data directly, instead of using
    // SoFieldContainer::copyContents(), for the reason described in
    // detail in So@Gui@Viewer::saveHomePosition().
    PRIVATE(this)->camera->copyFieldValues(PRIVATE(this)->storedcamera);
  }
  // handle common case #1
  else if (t == SoOrthographicCamera::getClassTypeId() &&
           s == SoPerspectiveCamera::getClassTypeId()) {
    So@Gui@ViewerP::convertPerspective2Ortho((SoPerspectiveCamera *)PRIVATE(this)->storedcamera,
                                             (SoOrthographicCamera *)PRIVATE(this)->camera);
  }
  // handle common case #2
  else if (t == SoPerspectiveCamera::getClassTypeId() &&
           s == SoOrthographicCamera::getClassTypeId()) {
    So@Gui@ViewerP::convertOrtho2Perspective((SoOrthographicCamera *)PRIVATE(this)->storedcamera,
                                             (SoPerspectiveCamera *)PRIVATE(this)->camera);
  }
  // otherwise, cameras have changed in ways we don't understand since
  // the last saveHomePosition() invokation, and so we're just going
  // to ignore the reset request
}

// *************************************************************************

/*!
  Turn the camera headlight on or off.

  Default is to have a headlight turned on.

  \sa isHeadlight(), getHeadlight()
*/

void
So@Gui@Viewer::setHeadlight(SbBool on)
{
  PRIVATE(this)->headlight->on = on;
}

// *************************************************************************

/*!
  Returns status of the viewer headlight, whether it is on or off.

  \sa setHeadlight(), getHeadlight()
*/

SbBool
So@Gui@Viewer::isHeadlight(void) const
{
  return PRIVATE(this)->headlight->on.getValue();
}

// *************************************************************************

/*!
  Returns the a pointer to the directional light node which is the
  viewer headlight.

  The fields of the node is available for user editing.

  \sa isHeadlight(), setHeadlight()
*/

SoDirectionalLight *
So@Gui@Viewer::getHeadlight(void) const
{
  return PRIVATE(this)->headlight;
}

// *************************************************************************

/*!
  Set up a drawing style. The \a type argument specifies if the given
  \a style should be interpreted as the draw style during animation or
  when the camera is static.

  Default values for the drawing style is to render the scene "as is"
  in both still mode and while the camera is moving.

  See the documentation for the \a DrawType and \a DrawStyle for more
  information.

  \sa getDrawStyle()
*/
void
So@Gui@Viewer::setDrawStyle(So@Gui@Viewer::DrawType type,
                            So@Gui@Viewer::DrawStyle style)
{
  if (SO@GUI@_DEBUG) {
    if ((type != STILL) && (type != INTERACTIVE)) {
      SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle",
                                "unknown drawstyle type setting 0x%x", type);
      return;
    }
  }

  if (style == this->getDrawStyle(type)) {
    if (SO@GUI@_DEBUG && 0) { // debug
      SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle",
                                "drawstyle for type 0x%02x already 0x%02x",
                                type, style);
    }
    return;
  }

  PRIVATE(this)->drawstyles[type] = style;
  PRIVATE(this)->changeDrawStyle(PRIVATE(this)->currentDrawStyle());
}

// *************************************************************************

/*!
  Return current draw styles for the given type (\a STILL or
  \a INTERACTIVE).

  \sa setDrawStyle()
*/

So@Gui@Viewer::DrawStyle
So@Gui@Viewer::getDrawStyle(const So@Gui@Viewer::DrawType type) const
{
  if (SO@GUI@_DEBUG) {
    if ((type != STILL) && (type != INTERACTIVE)) {
      SoDebugError::postWarning("So@Gui@Viewer::setDrawStyle",
                                "unknown drawstyle type setting 0x%x", type);
      return PRIVATE(this)->drawstyles[STILL];
    }
  }

  return PRIVATE(this)->drawstyles[type];
}

// *************************************************************************

/*!
  Set the viewer's buffer type. Available types are \c
  So@Gui@Viewer::BUFFER_SINGLE, \c So@Gui@Viewer::BUFFER_DOUBLE and \c
  So@Gui@Viewer::BUFFER_INTERACTIVE.

  (With a buffer type of \c So@Gui@Viewer::BUFFER_INTERACTIVE, the
  viewer will render with double buffering during user interaction and
  with single buffering otherwise.)

  Default is \c So@Gui@Viewer::BUFFER_DOUBLE.

  \sa getBufferingType()
*/

void
So@Gui@Viewer::setBufferingType(So@Gui@Viewer::BufferType type)
{
  if (type == PRIVATE(this)->buffertype) return;

  if (type != BUFFER_SINGLE &&
      type != BUFFER_DOUBLE &&
      type != BUFFER_INTERACTIVE) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::setBufferingType",
                                "unknown buffer type 0x%x", type);
    }
    return;
  }

  PRIVATE(this)->buffertype = type;

  PRIVATE(this)->localsetbuffertype = TRUE;
  inherited::setDoubleBuffer(type == BUFFER_DOUBLE);
  PRIVATE(this)->localsetbuffertype = FALSE;
}

// *************************************************************************

/*!
  Return the viewer's buffer type.

  \sa setBufferingType()
*/

So@Gui@Viewer::BufferType
So@Gui@Viewer::getBufferingType(void) const
{
  return PRIVATE(this)->buffertype;
}

// *************************************************************************

// Note: this documentation for setViewing() will also be used for all
// the miscellaneous viewer subclasses, so keep it general.
/*!
  Set view mode.

  If the view mode is on, user events will be caught and used to
  influence the camera position / orientation. If view mode is off,
  all events in the viewer canvas (like for instance keypresses or
  mouse clicks and -movements) will be passed along to the scene graph.

  Default is to have the view mode active.

  \sa isViewing()
*/
void
So@Gui@Viewer::setViewing(SbBool enable)
{
  if (PRIVATE(this)->viewingflag == enable) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::setViewing",
                                "unnecessary called");
    }
    return;
  }

  PRIVATE(this)->viewingflag = enable;

  // Turn off the selection indicators when we go back from picking
  // mode into viewing mode.
  if (PRIVATE(this)->viewingflag) {
    SoGLRenderAction * action = this->getGLRenderAction();
    if (action != NULL)
      SoLocateHighlight::turnOffCurrentHighlight(action);
  }
}

// *************************************************************************

/*!
  Return state of view mode.

  \c TRUE means that the mode of the viewer is set such that user
  interaction with the mouse is used to modify the position and
  orientation of the camera.

  \sa setViewing()
*/
SbBool
So@Gui@Viewer::isViewing(void) const
{
  return PRIVATE(this)->viewingflag;
}

// *************************************************************************

/*!
  Set whether or not the mouse cursor representation should be visible
  in the viewer canvas.

  Default value is on.

  \sa isCursorEnabled()
*/

void
So@Gui@Viewer::setCursorEnabled(SbBool on)
{
  PRIVATE(this)->cursoron = on;
}

// *************************************************************************

/*!
  Returns visibility status of mouse cursor.

  \sa setCursorEnabled()
*/

SbBool
So@Gui@Viewer::isCursorEnabled(void) const
{
  return PRIVATE(this)->cursoron;
}

// *************************************************************************

/*!
  Turn on or off continuous automatic adjustments of the near and far
  clipping planes.

  If on, the distance from the camera position to the near and far
  planes will be calculated to be a "best fit" around the geometry in
  the scene, to maximize the "stretch" of values for the visible
  geometry in the z-buffer. This is important, as z-buffer resolution
  is usually limited enough that one will quickly see flickering in
  the rasterization of close polygons upon lousy utilization of the
  z-buffer.

  Automatic calculations of near and far clip planes are on as
  default.

  For better control over what happens in boundary conditions (for
  instance when the distance between near and far planes get very far,
  or if geometry gets very close to the camera position), it is
  possible to use the So@Gui@Viewer::setAutoClippingStrategy() method
  to fine-tune the near/far clipping plane settings.

  On a major note, be aware that turning auto-updating of near and far
  clip planes \e off have a potentially serious detrimental effect on
  performance, due to an important side effect: updating the near and
  far clip planes triggers an SoGetBoundingBoxAction to traverse the
  scene graph, which causes bounding boxes to be calculated and stored
  in caches. The bounding box caches are then used by the
  SoGLRenderAction traversal for view frustum culling operations. With
  no bounding box caches, the rendering will not do culling, which can
  cause much worse performance. Kongsberg Oil & Gas Technologies are
  working on correcting this problem properly from within the Coin
  library.

  On a minor note, be aware that notifications will be temporarily
  turned off for the scene's SoCamera when changing the near and far
  clipping planes (which is done right before each redraw). This is
  done to avoid notifications being sent through the scene graph right
  before rendering, as that causes some latency. It is mentioned here
  in case you have any client code which for some reason needs to
  sense all changes to the scene camera. This is however unlikely, so
  you can very probably ignore this.

  \sa getAutoClipping()
*/

void
So@Gui@Viewer::setAutoClipping(SbBool enable)
{
  if (SO@GUI@_DEBUG) {
    if (PRIVATE(this)->adjustclipplanes == enable) {
      SoDebugError::postWarning("So@Gui@Viewer::setAutoClipping",
                                "unnecessary called");
      return;
    }
  }

  PRIVATE(this)->adjustclipplanes = enable;
  if (enable) { this->scheduleRedraw(); }
}

/*!
  Set the strategy used for automatic updates of the distances to the
  near and far clipping planes.

  When auto clipping is enabled, the near plane distance is calculated
  so that it is just in front of the scene bounding box. If this near
  plane is behind or very close to the projection point, one of the
  following strategies will be used to calculate the new clipping
  plane.

  The VARIABLE_NEAR_PLANE strategy considers the number of z buffer
  bits available for the current OpenGL context, and uses \a value to
  calculate the number of bits that is lost because of the far/near
  ratio. \a value should be in the range [0.0, 1.0]. A higher \a value
  will increase the z-buffer precision, but also push the near plane
  further away from the projection point.
  
  The CONSTANT_NEAR_PLANE strategy simply sets the near plane to 
  \a value. If \a value at some point approaches the far clipping
  plane distance, the near plane distance will be set to far plane 
  distance divided by 5000.0.

  The default strategy is VARIABLE_NEAR_PLANE.


  It is also possible to register a callback method \a cb, which will
  then be invoked after the near and far clipping planes have been
  calculated by the So@Gui@Viewer code. The callback can then adjust
  the values for the distance to the near and far planes to exactly
  match the needs of the application (for instance at specific parts
  in the scene), to limit the distance to either plane, or whatever
  else needs to be controlled.

  The signature of the So@Gui@AutoClippingCB callback must match:
  \code
  SbVec2f myfunc(void * data, const SbVec2f & nearfar);
  \endcode

  The first argument is the \a cbuserdata passed in along with the
  callback function pointer itself (i.e. the callback function's
  closure). The second argument are the near and far clipping plane
  distances from the camera position, as calculated internally by the
  viewer, including "slack".

  The function callback can then modify the near and far clipping
  plane distances to what will \e actually be used by the
  viewer. These values will then be used unmodified for the viewer's
  camera.

  This is a good way of dynamically modifying the near and far
  distances such that they at all times exactly matches the specific
  layout of the application scene, for instance with regard to the
  trade-off between z-buffer resolution and how early geometry is
  clipped at the near plane (or at the far plane).

  Note that the internal near/far calculations should be good enough
  for the vast majority of scenes. Application programmers should only
  need to set up their own adjustments upon "unusual" scenes, like for
  instance scenes with a large world space, but where one would still
  like to be able to get up extremely close on details in some parts
  of the scene.


  \sa setAutoClipping() 
*/
void 
So@Gui@Viewer::setAutoClippingStrategy(const AutoClippingStrategy strategy,
                                       const float value,
                                       So@Gui@AutoClippingCB * cb,
                                       void * cbuserdata)
{
  PRIVATE(this)->autoclipstrategy = strategy;
  PRIVATE(this)->autoclipvalue = value;
  PRIVATE(this)->autoclipcb = cb;
  PRIVATE(this)->autoclipuserdata = cbuserdata;

  if (PRIVATE(this)->autoclipstrategy == VARIABLE_NEAR_PLANE) {
    // normalize the value so that the near plane isn't too near or
    // too far from the projection point.  FIXME: calibrate this
    // normalization, pederb, 2002-04-25
    float v = So@Gui@Clamp(value, 0.0f, 1.0f); // just in case
    v *= 0.8f;
    v += 0.1f; // v will be in range [0.1, 0.9]

    PRIVATE(this)->autoclipvalue = v;
  }
  if (PRIVATE(this)->adjustclipplanes) {
    this->scheduleRedraw();
  }
}

// *************************************************************************

/*!
  Return value of the automatic near/far clip plane adjustment indicator.

  \sa setAutoClipping()
*/

SbBool
So@Gui@Viewer::isAutoClipping(void) const
{
  return PRIVATE(this)->adjustclipplanes;
}

// *************************************************************************

/*!
  Turn stereo viewing on or off.

  Note: this function is being obsoleted, you should use the
  setStereoType() function instead.

  Coin does "correct" stereo rendering, using the method known as
  "parallel axis asymmetric frustum perspective projection". For more
  information, see this link:

    http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/

  \sa isStereoViewing(), setStereoType()
*/

void
So@Gui@Viewer::setStereoViewing(SbBool enable)
{
  PRIVATE(this)->stereoviewing = enable;
  this->scheduleRedraw();
}

/*!
  Returns a boolean indicating whether or not we're in stereo viewing
  mode.

  NOTE: in the original InventorXt API, this method was virtual.  It is not
  virtual here.

  \sa setStereoViewing(), getStereoType()
*/

SbBool
So@Gui@Viewer::isStereoViewing(void) const
{
  return PRIVATE(this)->stereoviewing;
}

// *************************************************************************

/*!
  \enum So@Gui@Viewer::StereoType

  Contains list of supported stereo rendering techniques.

  \sa So@Gui@Viewer::setStereoType()
*/
/*!
  \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_NONE

  Use monoscopic rendering.
*/
/*!
  \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_ANAGLYPH

  Render stereo by superimposing two images of the same scene, but with
  different color filters over the left and right view (or "eye").

  This is a way of rendering stereo which works on any display, using
  color-filter glasses. Such glasses are usually cheap and easy to
  come by.

  \sa setAnaglyphStereoColorMasks()
*/
/*!
  \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_QUADBUFFER

  Render stereo by using OpenGL quad-buffers. This is the most common
  interface for stereo rendering for more expensive hardware devices,
  such as shutter glasses and polarized glasses.

  The well known Crystal Eyes glasses are commonly used with this type
  of stereo display.
*/
/*!
  \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_ROWS

  Interleaving / interlacing rows from the left and right eye is
  another stereo rendering method requiring special hardware. One
  example of a provider of shutter glasses working with interleaved
  glasses is VRex:

    http://www.vrex.com/
*/
/*!
  \var So@Gui@Viewer::StereoType So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS

  Basically the same technique as So@Gui@Viewer::STEREO_INTERLEAVED_ROWS, but
  vertical lines are interleaved / interlaced, instead of
  horizontal lines.
*/

/*!
  Set up stereo rendering.

  Coin does "correct" stereo rendering, using the method known as
  "parallel axis asymmetric frustum perspective projection". For more
  information, see this link:

    http://astronomy.swin.edu.au/~pbourke/opengl/stereogl/


  Note: it is preferred that one uses this function for control of
  which type of stereo rendering to use, instead of the older
  So@Gui@Viewer::setStereoViewing() and
  So@Gui@GLWidget::setQuadBufferStereo() functions.

  The default is to do monoscopic rendering, i.e. the default
  So@Gui@Viewer::StereoType value is So@Gui@Viewer::STEREO_NONE.

  \sa So@Gui@Viewer::StereoType, SoCamera::setStereoAdjustment
  \since So@Gui@ 1.2
*/
SbBool
So@Gui@Viewer::setStereoType(So@Gui@Viewer::StereoType s)
{
  if (s == this->getStereoType()) { return TRUE; }

  // We need to know this to keep compatibility with older client
  // code, which controlled stereo rendering with setStereoViewing()
  // and setQuadBufferStereo() only.
  PRIVATE(this)->stereotypesetexplicit = TRUE;

  switch (s) {
  case So@Gui@Viewer::STEREO_NONE:
    this->setQuadBufferStereo(FALSE);
    this->setStereoViewing(FALSE);
    break;

  case So@Gui@Viewer::STEREO_ANAGLYPH:
    this->setStereoViewing(TRUE);
    this->setQuadBufferStereo(FALSE);
    break;

  case So@Gui@Viewer::STEREO_QUADBUFFER:
    this->setStereoViewing(TRUE);
    this->setQuadBufferStereo(TRUE);

    // Check, in case GL quad buffers not supported with the driver
    // config:
    if (!this->isQuadBufferStereo()) {
      this->setStereoViewing(FALSE);
      return FALSE;
    }
    break;

  case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
  case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
    this->setStereoViewing(TRUE);
    this->setQuadBufferStereo(FALSE);
    this->setStencilBuffer(TRUE);

    // Check, in case GL stencil buffers not supported with the driver
    // config:
    if (!this->getStencilBuffer()) {
      this->setStereoViewing(FALSE);
      return FALSE;
    }
    break;

  default:
    assert(FALSE); break;
  }

  PRIVATE(this)->stereotype = s;
  return TRUE;
}

/*!
  Returns the current type of stereo rendering used (or
  So@Gui@Viewer::STEREO_NONE if monoscopic).
*/
So@Gui@Viewer::StereoType
So@Gui@Viewer::getStereoType(void) const
{
  // Stereo can be set up without using setStereoType() through the
  // older functions setStereoViewing() and setQuadBufferStereo(), so
  // we need to check for this separately.
  if (!PRIVATE(this)->stereotypesetexplicit) {
    if (this->isQuadBufferStereo()) {
      PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_QUADBUFFER;
    }
    else if (this->isStereoViewing()) {
      PRIVATE(this)->stereotype = So@Gui@Viewer::STEREO_ANAGLYPH;
    }
  }

  return PRIVATE(this)->stereotype;
}

// *************************************************************************

/*!
  If display is configured to render in anaglyph stereo, this function
  can be used to control which filter is used for each eye.

  The default filters are red (i.e. color vector [TRUE,FALSE,FALSE])
  for the left eye, and cyan (color vector [FALSE,TRUE,TRUE]) for the
  right eye.

  \sa So@Gui@Viewer::StereoType, setStereoType()
*/
void
So@Gui@Viewer::setAnaglyphStereoColorMasks(const SbBool left[3], const SbBool right[3])
{
  for (unsigned int i = 0; i < 3; i++) {
    PRIVATE(this)->stereoanaglyphmask[0][i] = left[i];
    PRIVATE(this)->stereoanaglyphmask[1][i] = right[i];
  }

  this->scheduleRedraw();
}

/*!
  Returns color masks for left and right eye filters in anaglyph
  stereo.

  \sa setAnaglyphStereoColorMasks()
*/
void
So@Gui@Viewer::getAnaglyphStereoColorMasks(SbBool left[3], SbBool right[3])
{
  for (unsigned int i = 0; i < 3; i++) {
    left[i] = PRIVATE(this)->stereoanaglyphmask[0][i];
    right[i] = PRIVATE(this)->stereoanaglyphmask[1][i];
  }
}

// *************************************************************************

/*!
  Set the offset between the two viewpoints when in stereo mode.
  Default value is 0.1.

  NOTE: In the original InventorXt API, this method was not virtual.

  \sa getStereoOffset()
*/
void
So@Gui@Viewer::setStereoOffset(const float dist)
{
  PRIVATE(this)->stereooffset = dist;
  this->scheduleRedraw();
}

/*!
  Return the offset distance between the two viewpoints when in stereo
  mode.

  \sa setStereoOffset()
*/
float
So@Gui@Viewer::getStereoOffset(void) const
{
  return PRIVATE(this)->stereooffset;
}

// *************************************************************************

/*!
  Toggle between seeking to a point or seeking to an object.

  Default is to seek to a point.

  \sa isDetailSeek()
*/

void
So@Gui@Viewer::setDetailSeek(const SbBool on)
{
  if (SO@GUI@_DEBUG) {
    if (PRIVATE(this)->seektopoint == on) {
      SoDebugError::postWarning("So@Gui@Viewer::setDetailSeek",
                                "unnecessary called");
      return;
    }
  }

  PRIVATE(this)->seektopoint = on;
}

// *************************************************************************

/*!
  Returns a value indicating whether or not seeks will be performed
  to the exact point of picking or just towards the picked object.

  \sa setDetailSeek()
*/

SbBool
So@Gui@Viewer::isDetailSeek(void) const
{
  return PRIVATE(this)->seektopoint;
}

// *************************************************************************

/*!
  Set the duration of animating the camera repositioning
  after a successful seek. Call with \a seconds equal to \a 0.0 to make
  the camera jump immediately to the correct spot.

  Default value is 2 seconds.

  \sa getSeekTime()
*/

void
So@Gui@Viewer::setSeekTime(const float seconds)
{
  if (seconds < 0.0f) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::setSeekTime",
                                "an attempt was made to set a negative seek "
                                "time duration");
    }
    return;
  }
  PRIVATE(this)->seekperiod = seconds;
}

// *************************************************************************

/*!
  Returns the camera repositioning duration following a seek action.

  \sa setSeekTime()
*/

float
So@Gui@Viewer::getSeekTime(void) const
{
  return PRIVATE(this)->seekperiod;
}

// *************************************************************************

/*!
  Add a function to call when user interaction with the scene starts.

  \sa removeStartCallback(), addFinishCallback()
*/

void
So@Gui@Viewer::addStartCallback(So@Gui@ViewerCB * func, void * data)
{
  PRIVATE(this)->interactionstartCallbacks->addCallback((SoCallbackListCB *)func, data);
}

/*!
  Remove one of the functions which has been set up to be called when user
  interaction with the scene starts.

  \sa addStartCallback(), removeFinishCallback()
*/

void
So@Gui@Viewer::removeStartCallback(So@Gui@ViewerCB * func, void * data)
{
  PRIVATE(this)->interactionstartCallbacks->removeCallback((SoCallbackListCB *)func,
                                                  data);
}

// *************************************************************************

/*!
  Add a function to call when user interaction with the scene ends.

  \sa removeFinishCallback(), addStartCallback()
*/

void
So@Gui@Viewer::addFinishCallback(So@Gui@ViewerCB * func, void * data)
{
  PRIVATE(this)->interactionendCallbacks->addCallback((SoCallbackListCB *)func, data);
}

/*!
  Remove one of the functions which has been set up to be called when user
  interaction with the scene ends.

  \sa addFinishCallback(), removeStartCallback()
*/

void
So@Gui@Viewer::removeFinishCallback(So@Gui@ViewerCB * func, void * data)
{
  PRIVATE(this)->interactionendCallbacks->removeCallback((SoCallbackListCB *)func,
                                                data);
}

// *************************************************************************

/*!
  Set the color of the overlay wireframe to \a color.

  \sa getWireframeOverlayColor()
*/
void So@Gui@Viewer::setWireframeOverlayColor(const SbColor & color)
{
  PRIVATE(this)->wireframeoverlaycolor = color;
  this->scheduleRedraw();
}

// *************************************************************************

/*!
  Returns the current color of the overlay wireframe. The default
  color is [1,0,0], i.e. pure red.

  \sa setWireframeOverlayColor()
*/
const SbColor &So@Gui@Viewer::getWireframeOverlayColor(void) const
{
  return PRIVATE(this)->wireframeoverlaycolor;
}

// *************************************************************************

/*!
  Overloaded to update the local bufferingtype variable.

  \sa setBufferingType(), getBufferingType()
*/

void
So@Gui@Viewer::setDoubleBuffer(const SbBool on)
{
  if (!PRIVATE(this)->localsetbuffertype)
    PRIVATE(this)->buffertype = on ? BUFFER_DOUBLE : BUFFER_SINGLE;

  inherited::setDoubleBuffer(on);
}

// *************************************************************************

/*!
  Give the viewer a scene graph to render and interact with. Overridden
  from parent class so the viewer can add its own nodes to control
  rendering in different styles, rendering with a headlight, etc.

  The \a root node will be inserted under the \e viewer's root node,
  which also covers the nodes necessary to implement the different
  preferences drawing style settings.

  If no camera is part of the scene graph under \a root, one will
  automatically be instantiated and added. You can get a reference to
  this camera by using the So@Gui@Viewer::getCamera() method.

  \sa getSceneGraph(), setCameraType()
*/

void
So@Gui@Viewer::setSceneGraph(SoNode * root)
{
  if ((root != NULL) && (root == PRIVATE(this)->scenegraph)) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::setSceneGraph",
                                "called with the same root as already set");
    }
    return;
  }

  // If the So@Gui@RenderArea hasn't yet set up its pointer to the
  // So@Gui@Viewer "viewer root" (i.e. the viewer-generated root above
  // the user-supplied root), do that first.
  if (!inherited::getSceneGraph())
    inherited::setSceneGraph(PRIVATE(this)->sceneroot);

  if (PRIVATE(this)->scenegraph) {
    if (this->getCamera())
      this->setCamera(NULL);
    // Release the old user-supplied graph.
    PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph);
    // old: PRIVATE(this)->sceneroot->removeChild(PRIVATE(this)->scenegraph);
  }

  PRIVATE(this)->scenegraph = root;
  if (!root) return;

  PRIVATE(this)->usersceneroot->addChild(PRIVATE(this)->scenegraph);

  // Search for a camera in the user-supplied scenegraph.

  SbBool oldsearch = SoBaseKit::isSearchingChildren();
  SoBaseKit::setSearchingChildren(TRUE);

  PRIVATE(this)->searchaction->reset();
  PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId());
  PRIVATE(this)->searchaction->apply(PRIVATE(this)->scenegraph);

  SoBaseKit::setSearchingChildren(oldsearch);

  SoCamera * scenecamera = NULL;
  if ( PRIVATE(this)->searchaction->getPath() != NULL ) {
    SoFullPath * fullpath =
      (SoFullPath *) PRIVATE(this)->searchaction->getPath();
    scenecamera = (SoCamera *)fullpath->getTail();
  }

#if 0 // debug
  SoDebugError::postInfo("So@Gui@Viewer::setSceneGraph",
                         "camera %sfound in graph",
                         scenecamera ? "" : "not ");
#endif // debug

  // Make our own camera if none was available.
  if (!scenecamera) {
    if (SoGuiViewpointWrapper::hasViewpoints(root)) {
      scenecamera = new SoGuiViewpointWrapper;
      PRIVATE(this)->cameratype = SoGuiViewpointWrapper::getClassTypeId();
      ((SoGuiViewpointWrapper*)scenecamera)->setSceneGraph(root);
    }
    else {
      scenecamera = (SoCamera *) PRIVATE(this)->cameratype.createInstance();
    }
    
    // If type==BROWSER, camera should be inserted in the private
    // viewer "supergraph", if it's equal to EDITOR it should be
    // inserted in the user-supplied scene graph.
    if (PRIVATE(this)->type == So@Gui@Viewer::BROWSER) {
      PRIVATE(this)->sceneroot->insertChild(scenecamera, 1);
    }
    else { // PRIVATE(this)->type == So@Gui@Viewer::EDITOR
      if (PRIVATE(this)->scenegraph->isOfType(SoGroup::getClassTypeId())) {
        // At the uppermost leftmost position in the user-supplied
        // scene graph.
        ((SoGroup *)PRIVATE(this)->scenegraph)->insertChild(scenecamera, 0);
      }
      else {
        // Make an extra depth level to fit the camera node into the
        // user-scene graph.
        SoGroup * g = new SoGroup;
        g->addChild(scenecamera);
        g->addChild(PRIVATE(this)->scenegraph);
        PRIVATE(this)->usersceneroot->removeChild(PRIVATE(this)->scenegraph);
        PRIVATE(this)->usersceneroot->addChild(g);
        PRIVATE(this)->scenegraph = g;
      }
    }
    if (PRIVATE(this)->cameratype != SoGuiViewpointWrapper::getClassTypeId()) {
      scenecamera->viewAll(PRIVATE(this)->scenegraph, this->getViewportRegion());
    }
  }

  this->setCamera(scenecamera);
}

// *************************************************************************

// doc in super
SoNode *
So@Gui@Viewer::getSceneGraph(void)
{
  // Overloaded from parent class to return the root of the scene
  // graph set by the user, without the extras added by the viewer to
  // control rendering.
  return PRIVATE(this)->scenegraph;
}

// *************************************************************************

// Note: the following function documentation block will also be used
// for all the miscellaneous viewer subclasses, so keep it general.
/*!
  Put the viewer in or out of "waiting-to-seek" mode.

  If the user performs a mouse button click when the viewer is in
  "waiting-to-seek" mode, the camera will be repositioned so the
  camera focal point lies on the point of the geometry under the mouse
  cursor.

  \sa isSeekMode(), setDetailSeek()
*/
void
So@Gui@Viewer::setSeekMode(SbBool enable)
{
  if (SO@GUI@_DEBUG) {
    // User might have switched mode during seek, so if enable==FALSE,
    // isViewing() is irrelevant.
    if (enable) { assert(this->isViewing()); }
  }

  if (!enable && PRIVATE(this)->seeksensor->isScheduled()) {
    PRIVATE(this)->seeksensor->unschedule();
    this->interactiveCountDec();
  }

  PRIVATE(this)->inseekmode = enable;
}

// *************************************************************************

/*!
  Return a flag which indicates whether or not the viewer is in
  "waiting-to-seek" mode.

  (The actual animated translation will not occur until the end user
  really \e starts the seek operation, typically by clicking with the
  left mouse button.)

  \sa setSeekMode()
*/
SbBool
So@Gui@Viewer::isSeekMode(void) const
{
  return PRIVATE(this)->inseekmode;
}

// *************************************************************************

/*!
  Call this method to initiate a seek action towards the 3D
  intersection of the scene and the ray from the screen coordinate's
  point and in the same direction as the camera is pointing.

  Returns \c TRUE if the ray from the \a screenpos position intersect
  with any parts of the onscreen geometry, otherwise \c FALSE.
*/
SbBool
So@Gui@Viewer::seekToPoint(const SbVec2s screenpos)
{
  if (! PRIVATE(this)->camera)
    return FALSE;

  SoRayPickAction rpaction(this->getViewportRegion());
  rpaction.setPoint(screenpos);
  rpaction.setRadius(2);
  rpaction.apply(PRIVATE(this)->sceneroot);

  SoPickedPoint * picked = rpaction.getPickedPoint();
  if (!picked) {
    // FIXME: this inc seems bogus, but is needed now due to buggy
    // code in for instance the examinerviewer
    // processSoEvent(). 20020510 mortene.
#if 1
    this->interactiveCountInc(); // decremented in setSeekMode(FALSE)
#endif // FIXME
    this->setSeekMode(FALSE);
    return FALSE;
  }

  SbVec3f hitpoint;
  if (PRIVATE(this)->seektopoint) {
    hitpoint = picked->getPoint();
  } 
  else {
    SoGetBoundingBoxAction bbaction(this->getViewportRegion());
    bbaction.apply(picked->getPath());
    SbBox3f bbox = bbaction.getBoundingBox();
    hitpoint = bbox.getCenter();
  }

  this->seekToPoint(hitpoint);
  return TRUE;
}

/*!
  Call this method to initiate a seek action towards the given 3D world
  coordinate point in the scene, \a scenepos.

  \since So@Gui@ 1.3.0
*/
void
So@Gui@Viewer::seekToPoint(const SbVec3f & scenepos)
{
  SbVec3f hitpoint(scenepos);

  PRIVATE(this)->camerastartposition = PRIVATE(this)->camera->position.getValue();
  PRIVATE(this)->camerastartorient = PRIVATE(this)->camera->orientation.getValue();

  // move point to the camera coordinate system, consider
  // transformations before camera in the scene graph
  SbMatrix cameramatrix, camerainverse;
  PRIVATE(this)->getCameraCoordinateSystem(PRIVATE(this)->camera,
                                           PRIVATE(this)->sceneroot,
                                           cameramatrix,
                                           camerainverse);
  camerainverse.multVecMatrix(hitpoint, hitpoint);

  float fd = PRIVATE(this)->seekdistance;
  if (!PRIVATE(this)->seekdistanceabs)
    fd *= (hitpoint - PRIVATE(this)->camera->position.getValue()).length()/100.0f;
  PRIVATE(this)->camera->focalDistance = fd;

  SbVec3f dir = hitpoint - PRIVATE(this)->camerastartposition;
  dir.normalize();

  // find a rotation that rotates current camera direction into new
  // camera direction.
  SbVec3f olddir;
  PRIVATE(this)->camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), olddir);
  SbRotation diffrot(olddir, dir);
  PRIVATE(this)->cameraendposition = hitpoint - fd * dir;
  PRIVATE(this)->cameraendorient = PRIVATE(this)->camera->orientation.getValue() * diffrot;

  // Subclasses that want another cameraendorient than what is
  // computed here should override this function and set the desired
  // orientation there
  this->computeSeekFinalOrientation();

  if (PRIVATE(this)->seeksensor->isScheduled()) {
    PRIVATE(this)->seeksensor->unschedule();
    this->interactiveCountDec();
  }

  PRIVATE(this)->seeksensor->setBaseTime(SbTime::getTimeOfDay());
  PRIVATE(this)->seeksensor->schedule();
  this->interactiveCountInc();
}

// *************************************************************************

void
So@Gui@ViewerP::setStereoEye(SoCamera * thecamera,
                             const So@Gui@ViewerP::Eye eye,
                             So@Gui@ViewerP::StereoData & s) const
{
#ifdef HAVE_SOCAMERA_SETSTEREOMODE

  // SoCamera::setStereoMode() is a fairly recent addition to Coin and
  // TGS Inventor. If available, camera eye setup is quite
  // straightforward.

  if (eye == So@Gui@ViewerP::LEFT) {
    thecamera->setStereoAdjustment(PUBLIC(this)->getStereoOffset());
    thecamera->setStereoMode(SoCamera::LEFT_VIEW);
  }
  else if (eye == So@Gui@ViewerP::RIGHT) {
    thecamera->setStereoMode(SoCamera::RIGHT_VIEW);
  }
  else {
    assert(eye == So@Gui@ViewerP::RESTORE);

    thecamera->setStereoMode(SoCamera::MONOSCOPIC);
  }

#else // ! HAVE_SOCAMERA_SETSTEREOMODE

  // To support older versions of Coin, and SGI/TGS Inventor, we also
  // provide "manual" tuning of the camera left/right eye split.

  if (eye == So@Gui@ViewerP::LEFT) {
    s.camerapos = thecamera->position.getValue();
    s.cameradir.setValue(0.0f, 0.0f, -1.0f);
    s.offsetvec.setValue(1.0f, 0.0f, 0.0f);
    s.offset = PUBLIC(this)->getStereoOffset() * 0.5f;
    s.camerarot = thecamera->orientation.getValue();
    s.camerarot.multVec(s.cameradir, s.cameradir);
    s.camerarot.multVec(s.offsetvec, s.offsetvec);
    s.focalpoint = s.camerapos + s.cameradir * thecamera->focalDistance.getValue();

    s.nodenotify = thecamera->isNotifyEnabled();
    s.positionnotify = thecamera->position.isNotifyEnabled();
    s.orientationnotify = thecamera->orientation.isNotifyEnabled();
    // turn off notification to avoid redraws
    thecamera->enableNotify(FALSE);
    thecamera->position.enableNotify(FALSE);
    thecamera->orientation.enableNotify(FALSE);

    thecamera->position = s.camerapos - s.offsetvec * s.offset;
    SbVec3f dir = s.focalpoint - thecamera->position.getValue();
    SbRotation rot(s.cameradir, dir);
    thecamera->orientation = s.camerarot * rot;
  }
  else if (eye == So@Gui@ViewerP::RIGHT) {
    thecamera->position = s.camerapos + s.offsetvec * s.offset;
    SbVec3f dir = s.focalpoint - thecamera->position.getValue();
    SbRotation rot(s.cameradir, dir);
    thecamera->orientation = s.camerarot * rot;
  }
  else {
    assert(eye == So@Gui@ViewerP::RESTORE);

    thecamera->position = s.camerapos;
    thecamera->orientation = s.camerarot;
    thecamera->position.enableNotify(s.positionnotify);
    thecamera->orientation.enableNotify(s.orientationnotify);
    thecamera->enableNotify(s.nodenotify);
  }

#endif // ! HAVE_SOCAMERA_SETSTEREOMODE
}

void
So@Gui@ViewerP::initStencilBufferForInterleavedStereo(void)
{
  const SbViewportRegion & currentvp = PUBLIC(this)->getViewportRegion();
  if (this->stereostencilmaskvp == currentvp) { return; } // the common case

  So@Gui@Viewer::StereoType s = PUBLIC(this)->getStereoType();
  assert((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) ||
         (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS));

  // Find out whether or not we need to regenerate the mask data.
  SbBool allocnewmask = (this->stereostencilmask == NULL);

  const SbVec2s neworigin = currentvp.getViewportOriginPixels();
  const SbVec2s newsize = currentvp.getViewportSizePixels();

  const SbVec2s oldorigin = this->stereostencilmaskvp.getViewportOriginPixels();
  const SbVec2s oldsize = this->stereostencilmaskvp.getViewportSizePixels();

  allocnewmask = allocnewmask ||
    ((oldsize[0] + 7) / 8 * oldsize[1]) < ((newsize[0] + 7) / 8 * newsize[1]);

  const SbBool fillmask = allocnewmask || (this->stereostenciltype != s) ||
    ((s == So@Gui@Viewer::STEREO_INTERLEAVED_ROWS) && (oldsize[0] != newsize[0]));

  const SbBool layoutchange = !(this->stereostencilmaskvp == currentvp);

  const short bytewidth = (newsize[0] + 7) / 8;

  if (allocnewmask) {
    delete[] this->stereostencilmask;
    this->stereostencilmask = new GLubyte[bytewidth * newsize[1]];
  }

  this->stereostencilmaskvp = currentvp;

  if (fillmask) {
    GLubyte * mask = this->stereostencilmask;

    if (s == So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS) {
      // alternating columns of 0's and 1's
      (void)memset(mask, 0x55, bytewidth * newsize[1]);
    }
    else {
      // alternating rows of 0's and 1's
      for (short h=0; h < newsize[1]; h++) {
        const GLubyte fill = (h % 2) ? 0xff : 0x00;
        (void)memset(mask + (h * bytewidth), fill, bytewidth);
      }
    }

    this->stereostenciltype = s;
  }

  if (layoutchange) {
    glClearStencil(0x0);

    glClear(GL_STENCIL_BUFFER_BIT);
    glStencilFunc(GL_ALWAYS, GL_REPLACE, GL_REPLACE);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glViewport(neworigin[0], neworigin[1], newsize[0], newsize[1]);

    glOrtho(0, newsize[0], 0, newsize[1], -1.0f, 1.0f);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // FIXME: I've noticed a problem with this approach. If there is
    // something in the window system obscuring part of the canvas
    // while this is called (as could e.g. happen with a size
    // indicator, as with the Sawfish window manager), the stencil
    // mask will not be set up for that part. 20041019 mortene.
    //
    // UPDATE 20041019 mortene: discussed this with pederb, and we
    // believe this may be due to a bug in either the OpenGL driver
    // (Nvidia 61.11, Linux) or window system or manager (Sawfish,
    // XFree86 v4.1.0.1). Should test on other systems to see if they
    // show the same artifact.

    glRasterPos2f(0, 0);
    glDrawPixels(newsize[0], newsize[1], GL_STENCIL_INDEX, GL_BITMAP,
                 this->stereostencilmask);

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
  }
}

// Documented in superclass. Overridden from parent class to be able
// to do the necessary two-pass rendering e.g. if the drawing style is
// hidden line.
void
So@Gui@Viewer::actualRedraw(void)
{
  SbTime redrawtime = SbTime::getTimeOfDay();
  const SbBool clearcol = this->isClearBeforeRender();
  const SbBool clearz = this->isClearZBufferBeforeRender();
  const So@Gui@Viewer::StereoType stereotype = this->getStereoType();

  if (stereotype != So@Gui@Viewer::STEREO_NONE) {
    SbColor bgcol;
    float bgalpha;
#if HAVE_SOSCENEMANAGER_GETBACKGROUNDCOLORRGBA
    const SbColorRGBA bkgcol = this->getSceneManager()->getBackgroundColorRGBA();
    bgcol[0] = bkgcol[0]; bgcol[1] = bkgcol[1]; bgcol[2] = bkgcol[2]; bgalpha = bkgcol[3];
#else
    bgcol = this->getSceneManager()->getBackgroundColor();
    bgalpha = 0.0f;
#endif
    SoCamera * camera = this->getCamera();
    So@Gui@ViewerP::StereoData tmpstorage;

    // Render left eye:

    PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::LEFT, tmpstorage);

    switch (stereotype) {
    case So@Gui@Viewer::STEREO_ANAGLYPH:
      glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT);
      glClearColor(bgcol[0], bgcol[1], bgcol[2], bgalpha);
      glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
      glColorMask(PRIVATE(this)->stereoanaglyphmask[0][0] ? GL_TRUE : GL_FALSE,
                  PRIVATE(this)->stereoanaglyphmask[0][1] ? GL_TRUE : GL_FALSE,
                  PRIVATE(this)->stereoanaglyphmask[0][2] ? GL_TRUE : GL_FALSE,
                  GL_TRUE);
      PRIVATE(this)->reallyRedraw(FALSE, FALSE);
      break;
    case So@Gui@Viewer::STEREO_QUADBUFFER:
      glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_LEFT : GL_FRONT_LEFT);
      PRIVATE(this)->reallyRedraw(clearcol, clearz);
      break;
    case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
    case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
      PRIVATE(this)->initStencilBufferForInterleavedStereo();
      glEnable(GL_STENCIL_TEST);
      // immutable data in the stencil buffer
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
      glStencilFunc(GL_EQUAL, 0x1, 0x1);
      PRIVATE(this)->reallyRedraw(clearcol, clearz);
      break;

    default: assert(FALSE); break;
    }

    // Render right eye:

    PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RIGHT, tmpstorage);

    switch (stereotype) {
    case So@Gui@Viewer::STEREO_ANAGLYPH:
      glColorMask(PRIVATE(this)->stereoanaglyphmask[1][0] ? GL_TRUE : GL_FALSE,
                  PRIVATE(this)->stereoanaglyphmask[1][1] ? GL_TRUE : GL_FALSE,
                  PRIVATE(this)->stereoanaglyphmask[1][2] ? GL_TRUE : GL_FALSE,
                  GL_TRUE);
      PRIVATE(this)->reallyRedraw(FALSE, TRUE);
      break;
    case So@Gui@Viewer::STEREO_QUADBUFFER:
      glDrawBuffer(this->isDoubleBuffer() ? GL_BACK_RIGHT : GL_FRONT_RIGHT);
      PRIVATE(this)->reallyRedraw(clearcol, clearz);
      break;
    case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
    case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
      glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
      PRIVATE(this)->reallyRedraw(FALSE, FALSE);
      break;
    default: assert(FALSE); break;
    }

    // Clean-up, post-rendering:

    PRIVATE(this)->setStereoEye(camera, So@Gui@ViewerP::RESTORE, tmpstorage);

    switch (stereotype) {
    case So@Gui@Viewer::STEREO_ANAGLYPH:
      glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // restore GL color mask
      break;
    case So@Gui@Viewer::STEREO_QUADBUFFER:
      glDrawBuffer(this->isDoubleBuffer() ? GL_BACK : GL_FRONT);
      break;
    case So@Gui@Viewer::STEREO_INTERLEAVED_ROWS:
    case So@Gui@Viewer::STEREO_INTERLEAVED_COLUMNS:
      // FIXME: restore old val. 20040618 mortene.
      glDisable(GL_STENCIL_TEST);
      break;
    default: assert(FALSE); break;
    }
  }
  else { // No stereo:
    PRIVATE(this)->reallyRedraw(clearcol, clearz);
  }

  if (PRIVATE(this)->superimpositions != NULL) {
    SoGLRenderAction * raaction = this->getSceneManager()->getGLRenderAction();
    SbBool first = TRUE;
    SbBool zWasEnabled = FALSE;
    for (int i = 0; i < PRIVATE(this)->superimpositions->getLength(); i++) {
      if (PRIVATE(this)->superimpositionsenabled[i] != FALSE) {
	if (first) {
	  // save Z buffer state and disable
          zWasEnabled = glIsEnabled(GL_DEPTH_TEST) ? TRUE : FALSE;
	  glDisable(GL_DEPTH_TEST);
          first = FALSE;
	}
	SoNode * scene = (SoNode *) (*PRIVATE(this)->superimpositions)[i];
	raaction->apply(scene);
      }
    }
    if (!first && zWasEnabled) glEnable(GL_DEPTH_TEST);
  }

  redrawtime = SbTime::getTimeOfDay() - redrawtime;
  PRIVATE(this)->recordFPS(redrawtime.getValue());
}

// *************************************************************************

/*!
  To be able to trigger callback functions when user interaction starts
  and/or stops, we need to keep track of the viewer state (i.e. are we in
  still mode or in animation mode?).

  So@Gui@Viewer automatically adds callbacks to switch between still and
  moving draw style, and to switch between single/double buffer when
  the buffer type is \a INTERACTIVE.

  \sa interactiveCountDec(), getInteractiveCount()
  \sa addStartCallback(), addFinishCallback()
  \sa removeStartCallback(), removeFinishCallback()
  \sa setDrawStyle(), setBufferingType()
*/

void
So@Gui@Viewer::interactiveCountInc(void)
{
  // Catch problems with missing interactiveCountDec() calls.
  assert(PRIVATE(this)->interactionnesting < 100);

  if (++(PRIVATE(this)->interactionnesting) == 1) {
    PRIVATE(this)->interactionstartCallbacks->invokeCallbacks(this);
    PRIVATE(this)->resetFrameCounter();
  }

#if 0 // debug
  SoDebugError::postInfo("So@Gui@Viewer::interactiveCountInc", "%d -> %d",
                         PRIVATE(this)->interactionnesting - 1,
                         PRIVATE(this)->interactionnesting);
#endif // debug
}

// *************************************************************************

/*!
  To be able to trigger callback functions when user interaction starts
  and/or stops, we need to keep track of the viewer state (i.e. are we in
  still mode or in animation mode?).

  So@Gui@Viewer automatically adds callbacks to switch between still and
  moving draw style, and to switch between single/double buffer when
  the buffer type is \a INTERACTIVE.

  \sa interactiveCountInc(), getInteractiveCount()
  \sa addStartCallback(), addFinishCallback()
  \sa removeStartCallback(), removeFinishCallback()
  \sa setDrawStyle(), setBufferingType()
*/

void
So@Gui@Viewer::interactiveCountDec(void)
{
  // FIXME: The UI toolkits may cause the interactionnesting to go
  // below zero by triggering press and release events in different
  // widgets. mariusbu 20010709.

  // FIXME: just to clarify; this is due to programming mistakes on
  // our behalf and should be cleaned up. We're using a simple
  // work-around / ignore strategy for now, though, as getting this
  // 100% correct is hard (there are so many possible ways of user
  // interaction with a viewer canvas) and the end-user will usually
  // not notice any problems at all. So that's why we are using a
  // warning instead of an assert(). 20010815 mortene.

  // FIXME: here's one known way to trigger the bug: hit "s" in the
  // examinerviewer in EXAMINE mode, then while seeking hit ESC to put
  // the viewer in INTERACT mode. When the seek is completed, the
  // count will become -1.  20010912 mortene.

  // FIXME: and another one (tested with SoXt): click and hold LMB in
  // the canvas while in INTERACT mode, then hit 'Esc' to switch to
  // EXAMINE mode, then release LMB. 20020325 mortene.

  if (SO@GUI@_DEBUG) {
    if (PRIVATE(this)->interactionnesting <= 0) {
      SoDebugError::postWarning("So@Gui@Viewer::interactiveCountDec",
                                "interaction count nesting went below zero. "
                                "This is due to an internal So@Gui@ bug.");
    }
  }

  if (--(PRIVATE(this)->interactionnesting) <= 0) {
    PRIVATE(this)->interactionendCallbacks->invokeCallbacks(this);
    PRIVATE(this)->interactionnesting = 0;
  }
}

// *************************************************************************

/*!
  Return current interaction count nesting. If equal to zero, the viewer
  is in animation mode, otherwise the camera is still.

  \sa interactiveCountInc(), interactiveCountDec()
*/

int
So@Gui@Viewer::getInteractiveCount(void) const
{
  return PRIVATE(this)->interactionnesting;
}

// *************************************************************************

/*!
  Set the value used for calculating how close the camera and intersection
  hit point should be made at the end of a seek operation.

  The value can be interpreted as an absolute value in the given world
  unit (which typically is meters) or as a percentage value of the
  distance between the camera starting position and the intersection
  hit point. This can be controlled through the
  setSeekValueAsPercentage() method. It is as default used as an
  absolute value.

  Default value is 50 (absolute distance or percent).

  \sa getSeekDistance(), setSeekValueAsPercentage(), setSeekTime()
*/

void
So@Gui@Viewer::setSeekDistance(const float distance)
{
  if (distance <= 0.0f) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::setSeekDistance",
                                "invalid seek distance value: %f",
                                distance);
    }
    return;
  }
  PRIVATE(this)->seekdistance = distance;
}

// *************************************************************************

/*!
  Returns the current seek distance. Value given as an absolute scalar
  length or as a percentage value of the original distance between
  the hit point and the camera starting position.

  \sa setSeekDistance(), isSeekValueAsPercentage()
*/

float
So@Gui@Viewer::getSeekDistance(void) const
{
  return PRIVATE(this)->seekdistance;
}

// *************************************************************************

/*!
  Control whether or not the seek distance value should be interpreted as
  a percentage value or as an absolute distance. See documentation on
  setSeekDistance() for more information.

  \sa setSeekDistance(), isSeekValueAsPercentage()
*/

void
So@Gui@Viewer::setSeekValueAsPercentage(const SbBool on)
{
  if (SO@GUI@_DEBUG) {
    if ((on && this->isSeekValuePercentage()) ||
        (!on && !this->isSeekValuePercentage())) {
      SoDebugError::postWarning("So@Gui@Viewer::setSeekDistanceAsPercentage",
                                "unnecessary called, value already %s",
                                on ? "on" : "off");
      return;
    }
  }

  PRIVATE(this)->seekdistanceabs = on ? FALSE : TRUE;
}

// *************************************************************************

/*!
  Returns an boolean which indicates if the seek distance value from
  getSeekDistance() should be interpreted as a percentage value or
  as an absolute value.

  \sa setSeekValuePercentage(), getSeekDistance()
*/

SbBool
So@Gui@Viewer::isSeekValuePercentage(void) const
{
  return PRIVATE(this)->seekdistanceabs ? FALSE : TRUE;
}

// ************************************************************************

/*!  
  This method can be overridden in subclasses if the final
  orientation of the camera after a seek should be something other
  than what is computed in So@Gui@Viewer::seekToPoint(const SbVec3f &
  scenepos)
 */
void
So@Gui@Viewer::computeSeekFinalOrientation(void)
{
  
}

// *************************************************************************

/*!
  If the current camera is of perspective type, switch to
  orthographic, and vice versa.

  Automatically calls So@Gui@Viewer::setCameraType() so the change
  will immediately take place.
*/
void
So@Gui@Viewer::toggleCameraType(void)
{
  SoType perspectivetype = SoPerspectiveCamera::getClassTypeId();
  SoType orthotype = SoOrthographicCamera::getClassTypeId();
  this->setCameraType(PRIVATE(this)->cameratype.isDerivedFrom(perspectivetype)
                      ? orthotype : perspectivetype);
}

// ************************************************************************

/*!
  Copies the settings of \a camera into our current camera.  Cameras
  must be of the same class type.
 */
void
So@Gui@Viewer::changeCameraValues(// virtual, protected
  SoCamera * camera)
{
  assert(camera != NULL);

  SoCamera * cam = this->getCamera();
  if (!cam) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues",
                                "no current camera in the scene graph");
    }
    return;
  }
  if (cam->getTypeId() != camera->getTypeId()) {
    if (SO@GUI@_DEBUG) {
      SoDebugError::postWarning("So@Gui@Viewer::changeCameraValues",
                                "tried to copy data from camera of "
                                "different type");
    }
    return;
  }

  cam->copyFieldValues(camera, FALSE);
}

// *************************************************************************

// doc in super
void
So@Gui@Viewer::sizeChanged(const SbVec2s & size)
{
  inherited::sizeChanged(size);
}

// *************************************************************************

// Documented in superclass.
SbBool
So@Gui@Viewer::processSoEvent(const SoEvent * const event)
{
  const SoType type(event->getTypeId());
  const SoKeyboardEvent * keyevent = NULL;

  if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
    keyevent = (SoKeyboardEvent *) event;
    switch (keyevent->getKey()) {
    
    // the ESC key switches between view and interact mode
    case SoKeyboardEvent::ESCAPE:
      if (keyevent->getState() == SoButtonEvent::DOWN) {
        this->setViewing(this->isViewing() ? FALSE : TRUE);
        return TRUE;
      }
      break;

    // Let the end-user toggle between camera-interaction mode
    // ("viewing") and scene graph-interaction mode with ALT key(s).
    case SoKeyboardEvent::LEFT_ALT:
    case SoKeyboardEvent::RIGHT_ALT:
      if (!this->isViewing() && (keyevent->getState() == SoButtonEvent::DOWN)) {
        PRIVATE(this)->altdown = TRUE;
        this->setViewing(TRUE);
        return TRUE;
      }
      else if (PRIVATE(this)->altdown && (keyevent->getState() == SoButtonEvent::UP)) {
        this->setViewing(FALSE);
        PRIVATE(this)->altdown = FALSE;
        return TRUE;
      }
      break;
    default:
      break;
    }
  }

  // If not viewing, break off further handling and pass the event on
  // to the So@Gui@RenderArea, which will pass it on to the
  // scene graph.
  if (!this->isViewing()) { return inherited::processSoEvent(event); }


  if (keyevent && (keyevent->getState() == SoButtonEvent::DOWN)) {
    switch (keyevent->getKey()) {
    case SoKeyboardEvent::S:
      this->setSeekMode(this->isSeekMode() ? FALSE : TRUE);
      return TRUE;
    case SoKeyboardEvent::HOME:
      this->resetToHomePosition();
      return TRUE;
    case SoKeyboardEvent::LEFT_ARROW:
      PRIVATE(this)->moveCameraScreen(SbVec2f(-0.1f, 0.0f));
      return TRUE;
    case SoKeyboardEvent::UP_ARROW:
      PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, 0.1f));
      return TRUE;
    case SoKeyboardEvent::RIGHT_ARROW:
      PRIVATE(this)->moveCameraScreen(SbVec2f(0.1f, 0.0f));
      return TRUE;
    case SoKeyboardEvent::DOWN_ARROW:
      PRIVATE(this)->moveCameraScreen(SbVec2f(0.0f, -0.1f));
      return TRUE;
    default:
      break;
    }
  }

  if (this->isSeekMode()) {
    if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
      SoMouseButtonEvent * const e = (SoMouseButtonEvent *) event;
      if (e->getButton() == SoMouseButtonEvent::BUTTON1) {
        if (e->getState() == SoButtonEvent::DOWN) {
          this->seekToPoint(e->getPosition());
        }
        else {
          // We got an LMB UP-event while in seek-mode, and we just
          // swallow the event.
        }
        return TRUE;
      }
    }
  }

  return FALSE;
}

// *************************************************************************

/*!
  This method is for setting up a superimposed scene graph on top
  of the viewer scene graph.  It will be used for adding spin-rotation
  coordinate systems, fly-viewer speed indicators and similar things.

  This method is not part of the original InventorXt API.
*/

void
So@Gui@Viewer::addSuperimposition(SoNode * scene)
{
  if (PRIVATE(this)->superimpositions == NULL)
    PRIVATE(this)->superimpositions = new SbPList;
  assert(scene != NULL);
  scene->ref();
  PRIVATE(this)->searchaction->reset();
  PRIVATE(this)->searchaction->setType(SoCamera::getClassTypeId());
  PRIVATE(this)->searchaction->setInterest(SoSearchAction::FIRST);
  PRIVATE(this)->searchaction->apply(scene);
  if (PRIVATE(this)->searchaction->getPath() == NULL) {
    // FIXME: set up default environment if there is no camera in the
    // superimposition scene - or not...
    if (SO@GUI@_DEBUG) {
      SoDebugError::postInfo("So@Gui@Viewer::addSuperimposition",
                             "cameraless superimpositions are not "
                             "supported");
    }
    scene->unrefNoDelete();
    return;
  }
  PRIVATE(this)->superimpositions->append(scene);
  PRIVATE(this)->superimpositionsenabled.append(TRUE);
}

// *************************************************************************

/*!
  This method is not part of the original InventorXt API.
*/

void
So@Gui@Viewer::removeSuperimposition(SoNode * scene)
{
  assert(scene);
  int idx = -1;
  if (PRIVATE(this)->superimpositions == NULL) goto error;
  idx = PRIVATE(this)->superimpositions->find(scene);
  if (idx == -1) goto error;
  assert(PRIVATE(this)->superimpositions != NULL);
  PRIVATE(this)->superimpositions->remove(idx);
  PRIVATE(this)->superimpositionsenabled.remove(idx);
  scene->unref();
  return;

 error:
  if (SO@GUI@_DEBUG) {
    SoDebugError::post("So@Gui@Viewer::removeSuperimposition",
                       "no such superimposition");
  }
  return;
}

// *************************************************************************

/*!
  This method sets whether the superimposed scene graph should be traversed
  or not.

  This method is not part of the original InventorXt API.
*/

void
So@Gui@Viewer::setSuperimpositionEnabled(SoNode * scene,
                                         const SbBool enable)
{
  int idx = -1;
  if (PRIVATE(this)->superimpositions == NULL) goto error;
  idx = PRIVATE(this)->superimpositions->find(scene);
  if (idx == -1) goto error;
  PRIVATE(this)->superimpositionsenabled[idx] = enable;
  return;

 error:
  if (SO@GUI@_DEBUG) {
    SoDebugError::post("So@Gui@Viewer::setSuperimpositionEnabled",
                       "no such superimposition");
  }
  return;
}

// *************************************************************************

/*!
  This method returns whether the superimposed scene is rendered or not.

  This method is not part of the original InventorXt API.
*/

SbBool
So@Gui@Viewer::getSuperimpositionEnabled(SoNode * scene) const
{
  int idx = -1;
  if (PRIVATE(this)->superimpositions == NULL) goto error;
  idx = PRIVATE(this)->superimpositions->find(scene);
  if (idx == -1) goto error;
  return PRIVATE(this)->superimpositionsenabled[idx];

 error:
  if (SO@GUI@_DEBUG) {
    SoDebugError::post("So@Gui@Viewer::getSuperimpositionEnabled",
                       "no such superimposition");
  }
  return FALSE;
}

// *************************************************************************

#undef PRIVATE
#undef PUBLIC

