// 

/**************************************************************************\
 * 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.
\**************************************************************************/

/**************************************************************************\
 *
 *  A WORD OF ADVICE
 *
 *  It is fruitless to modify the contents of the SoQtRenderArea.cpp file
 *  because it is autogenerated by configure from the SoGuiRenderArea.cpp.in
 *  file which you will find in the src/Inventor/Qt/common/ directory.
 *  Do your modifications to that file instead.
 *
\**************************************************************************/

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

/*!
  \class SoQtRenderArea Inventor/Qt/SoQtRenderArea.h
  \brief The SoQtRenderArea class adds scene graph handling and event management.

  \ingroup components viewers

  The SoQtRenderArea class is a component that adds scene graph
  management and input device event handling to the SoQtGLWidget
  component.

  The class has many convenient methods for controlling aspects of the
  rendering, like for instance transparency, aliasing and for
  scheduling of redraws.

  Native toolkit events are caught by SoQtRenderArea components,
  translated to Coin SoEvent instances and passed on to the
  scene graph, in case the user is doing interactive operations on for
  instance Coin geometry draggers.


  SoQtRenderArea is the first non-abstract component in its
  inheritance hierarchy that you can use directly from client
  application code to set up a scene graph viewer canvas.

  For an SoQtRenderArea component to properly display your
  scene graph, it must contain an SoCamera-derived node and at least
  one SoLight-derived light source node.

  Here's a complete, stand-alone example on how to set up an
  SoQtRenderArea with a scene graph:

  \code
  #include <Inventor/Qt/SoQt.h>
  #include <Inventor/Qt/SoQtRenderArea.h>
  
  #include <Inventor/nodes/SoCube.h>
  #include <Inventor/nodes/SoRotor.h>
  #include <Inventor/nodes/SoArray.h>
  #include <Inventor/nodes/SoDirectionalLight.h>
  #include <Inventor/nodes/SoPerspectiveCamera.h>
  #include <Inventor/nodes/SoSeparator.h>
  
  // Set up a simple scene graph, just for demonstration purposes.
  static SoSeparator *
  get_scene_graph(void)
  {
    SoSeparator * root = new SoSeparator;
  
    SoGroup * group = new SoGroup;
  
    SoRotor * rotor = new SoRotor;
    rotor->rotation = SbRotation(SbVec3f(0.2, 0.5, 0.9), M_PI/4.0);
    group->addChild(rotor);
  
    SoCube * cube = new SoCube;
    group->addChild(cube);
  
    SoArray * array = new SoArray;
    array->origin = SoArray::CENTER;
    array->addChild(group);
    array->numElements1 = 2;
    array->numElements2 = 2;
    array->separation1 = SbVec3f(4, 0, 0);
    array->separation2 = SbVec3f(0, 4, 0);
  
    root->addChild(array);
    return root;
  }
  
  int
  main(int argc, char ** argv)
  {
    QWidget* window = SoQt::init(argv[0]);
  
    SoSeparator * root = new SoSeparator;
    root->ref();
  
    SoPerspectiveCamera * camera;
    root->addChild(camera = new SoPerspectiveCamera);
  
    root->addChild(new SoDirectionalLight);
  
    SoSeparator * userroot = get_scene_graph();
    root->addChild(userroot);
  
    SoQtRenderArea * renderarea = new SoQtRenderArea(window);
    camera->viewAll(userroot, renderarea->getViewportRegion());
    renderarea->setSceneGraph(root);
    renderarea->setBackgroundColor(SbColor(0.0f, 0.2f, 0.3f));
    if (argc > 1) {
      renderarea->setTitle(argv[1]);
      renderarea->setIconTitle(argv[1]);
    }
    renderarea->show();
  
    SoQt::show(window);
    SoQt::mainLoop();
  
    delete renderarea;
    root->unref();
  
    return 0;
  }
  \endcode
*/

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

#include <Inventor/Qt/SoQtRenderArea.h>

#include <cstring> // strchr()

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

#if SOQT_DEBUG // For the "soinfo" debugging backdoor.
#include <qapplication.h>
#endif // SOQT_DEBUG

#include <Inventor/Qt/common/gl.h> // glDrawBuffer()

#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/actions/SoWriteAction.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/misc/SoBasic.h>
#include <Inventor/nodekits/SoBaseKit.h>
#include <Inventor/nodes/SoCamera.h>
#include <Inventor/nodes/SoSelection.h>
#include <Inventor/SoOffscreenRenderer.h>

#include <soqtdefs.h>
#include <Inventor/Qt/SoQtBasic.h>
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/devices/SoQtKeyboard.h>
#include <Inventor/Qt/devices/SoQtMouse.h>
#ifdef HAVE_JOYSTICK_LINUX
#include <Inventor/Qt/devices/SoQtLinuxJoystick.h>
#endif // HAVE_JOYSTICK_LINUX
#ifdef HAVE_SPACENAV_SUPPORT
#include <Inventor/Qt/devices/SoQtSpacenavDevice.h>
#endif // HAVE_SPACENAV_SUPPORT 

#include <Inventor/Qt/SoQtGLWidgetP.h>
#include <Inventor/Qt/SoAny.h>

#define RENDERAREA_DEBUG_REDRAWS 0

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

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

SOQT_OBJECT_SOURCE(SoQtRenderArea);

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

#ifndef DOXYGEN_SKIP_THIS

class SoQtRenderAreaP {
public:

  SoQtRenderAreaP(class SoQtRenderArea * pub);
  ~SoQtRenderAreaP(void);

  SbBool clear;
  SbBool clearZBuffer;
  SbBool clearOverlay;

  SoSceneManager * normalManager;
  SoSceneManager * overlayManager;

  SbColor * normalColormap;
  int normalColormapSize;
  int normalColormapStart;
  SbColor * overlayColormap;
  int overlayColormapSize;
  int overlayColormapStart;

  SbPList * devicelist;

  struct {
    SoQtKeyboard * keyboard;
    SoQtMouse * mouse;
#ifdef HAVE_SPACENAV_SUPPORT
    SoQtSpacenavDevice * spacenav;
#endif // HAVE_SPACENAV_SUPPORT
  } devices;

  SbBool autoRedraw;

  void replaceSoSelectionMonitor(SoSelection * newsel, SoSelection * oldsel) const;
  SoSelection * normalselection;
  SoSelection * overlayselection;

  static const int GL_DEFAULT_MODE;

  void constructor(SbBool mouseInput, SbBool keyboardInput, SbBool build);
  static void renderCB(void * user, SoSceneManager * manager);
  static void selection_redraw_cb(void * data, SoSelection * sel);
  void setDevicesWindowSize(const SbVec2s size);

  // OpenGL info-window hack.
  enum { NONE, OPENGL, INVENTOR, TOOLKIT, DUMPSCENEGRAPH, DUMPCAMERAS, OFFSCREENGRAB };
  int checkMagicSequences(const char c);
  void showOpenGLDriverInformation(void);
  void showInventorInformation(void);
  void showToolkitInformation(void);
  void dumpScenegraph(void);
  void dumpCameras(void);
  void offScreenGrab(void);

  SbBool invokeAppCB(QEvent* event);
  const SoEvent * getSoEvent(QEvent* event);

  SoQtRenderAreaEventCB * appeventhandler;
  void * appeventhandlerdata;

private:
  SoQtRenderArea * pub; // public interface class
  SbString currentinput; // For the OpenGL info-window hack.
};

const int SoQtRenderAreaP::GL_DEFAULT_MODE = (SO_GL_RGB |
                                                 SO_GL_ZBUFFER |
                                                 SO_GL_DOUBLE );

#if SOQT_DEBUG && defined(__COIN__)
// Disabled when compiling against SGI / TGS Inventor, as we're using
// our Coin-specific extension SbString::sprintf() a lot.
#define DEBUGGING_EGGS 1
#endif // SOQT_DEBUG && __COIN__

// Note: assumes a valid current OpenGL context.
void
SoQtRenderAreaP::showOpenGLDriverInformation(void)
{
#if DEBUGGING_EGGS
  const GLubyte * vendor = glGetString(GL_VENDOR);
  const GLubyte * renderer = glGetString(GL_RENDERER);
  const GLubyte * version = glGetString(GL_VERSION);
  const GLubyte * extensions = glGetString(GL_EXTENSIONS);

  SbString info = "GL_VENDOR: \""; info += (const char *)vendor; info += "\"\n";
  info += "GL_RENDERER: \""; info += (const char *)renderer; info += "\"\n";
  info += "GL_VERSION: \""; info += (const char *)version; info += "\"\n";
  info += "GL_EXTENSIONS: \"\n   ";
    
  SbString exts = (const char *)extensions;
  const char * p;
  int count = 0;
  // (the extra parentheses in the while-expression kills a gcc warning)
  while ((p = strchr(exts.getString(), ' '))) {
    const char * start = exts.getString();
    info += exts.getSubString(0, int(p - start));
    exts.deleteSubString(0, int(p - start));
    count++;
    if (count == 4) { // number of extensions listed on each line
      info += "\n   ";
      count = 0;
    }
  }
  if (exts.getLength() > 0) { info += "\n   "; info += exts; }
  info += "\"\n";

  // FIXME: should also show available GLX / WGL / AGL
  // extensions. 20020802 mortene.

  // Misc implementation info
  {
    SbVec2f range;
    float granularity;
    PUBLIC(this)->getPointSizeLimits(range, granularity);

    SbString s;
    s.sprintf("glPointSize(): range=[%f, %f], granularity=%f\n",
              range[0], range[1], granularity);
    info += s;


    PUBLIC(this)->getLineWidthLimits(range, granularity);

    s.sprintf("glLineWidth(): range=[%f, %f], granularity=%f\n",
              range[0], range[1], granularity);
    info += s;

    GLint depthbits[1];
    glGetIntegerv(GL_DEPTH_BITS, depthbits);
    s.sprintf("GL_DEPTH_BITS==%d\n", depthbits[0]);
    info += s;

    GLint colbits[4];
    glGetIntegerv(GL_RED_BITS, &colbits[0]);
    glGetIntegerv(GL_GREEN_BITS, &colbits[1]);
    glGetIntegerv(GL_BLUE_BITS, &colbits[2]);
    glGetIntegerv(GL_ALPHA_BITS, &colbits[3]);
    s.sprintf("GL_[RED|GREEN|BLUE|ALPHA]_BITS==[%d, %d, %d, %d]\n",
              colbits[0], colbits[1], colbits[2], colbits[3]);
    info += s;

    GLint accumbits[4];
    glGetIntegerv(GL_ACCUM_RED_BITS, &accumbits[0]);
    glGetIntegerv(GL_ACCUM_GREEN_BITS, &accumbits[1]);
    glGetIntegerv(GL_ACCUM_BLUE_BITS, &accumbits[2]);
    glGetIntegerv(GL_ACCUM_ALPHA_BITS, &accumbits[3]);
    s.sprintf("GL_ACCUM_[RED|GREEN|BLUE|ALPHA]_BITS==[%d, %d, %d, %d]\n",
              accumbits[0], accumbits[1], accumbits[2], accumbits[3]);
    info += s;

    GLint stencilbits;
    glGetIntegerv(GL_STENCIL_BITS, &stencilbits);
    s.sprintf("GL_STENCIL_BITS==%d\n", stencilbits);
    info += s;

    GLint maxdims[2];
    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxdims);
    s.sprintf("GL_MAX_VIEWPORT_DIMS==<%d, %d>\n", maxdims[0], maxdims[1]);
    info += s;

    GLint texdim;
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texdim);
    s.sprintf("GL_MAX_TEXTURE_SIZE==%d\n", texdim);
    info += s;

    GLint maxlights;
    glGetIntegerv(GL_MAX_LIGHTS, &maxlights);
    s.sprintf("GL_MAX_LIGHTS==%d\n", maxlights);
    info += s;

    GLint maxplanes;
    glGetIntegerv(GL_MAX_CLIP_PLANES, &maxplanes);
    s.sprintf("GL_MAX_CLIP_PLANES==%d\n", maxplanes);
    info += s;

    // FIXME: other implementation specifics to print are
    //
    //  * maximum stack depths (attribute, modelview matrix, name,
    //    projection matrix, texture matrix)
    //
    //  * max display list nesting
    //
    //  * max 3D texture size (needs specific extension?)
    //
    // 20020802 mortene.
  }

  SbString s;
  s.sprintf("\n"
            "Rendering is %sdirect.\n",
            SoGuiGLWidgetP::isDirectRendering(PUBLIC(this)) ? "" : "in");
  info += s;

  SoQt::createSimpleErrorDialog(NULL, "OpenGL driver information",
                                    info.getString());
#endif // DEBUGGING_EGGS
}

void
SoQtRenderAreaP::showInventorInformation(void)
{
#if DEBUGGING_EGGS
  SbString info;
  info.sprintf("%s\n", SoDB::getVersion());

  // Display calculated maximum resolution of SbTime::getTimeOfDay().
  {
    const double DURATION = 0.2;  // in seconds
    SbTime current = SbTime::getTimeOfDay();
    SbTime end(current + DURATION);
    SbTime last = current;
    unsigned int ticks = 0;
    do {
      current = SbTime::getTimeOfDay();
      if (current.getValue() != last.getValue()) { ticks++; last = current; }
    } while (current < end);
    SbString s;
    s.sprintf("\nSbTime::getTimeOfDay() resolution: ~ %d Hz\n",
              (int)(((double)ticks) / DURATION));
    info += s;
  }

  // FIXME: dump list of available node classes? 20010927 mortene.
  SoQt::createSimpleErrorDialog(NULL, "Inventor implementation info",
                                    info.getString());

#endif // DEBUGGING_EGGS
}

void
SoQtRenderAreaP::showToolkitInformation(void)
{
#if DEBUGGING_EGGS
  SbString info = "SoQt version "; info += SOQT_VERSION; info += "\n";
#if SOQT_MAKE_DLL
  info += "Built as Microsoft Windows DLL.\n";
#endif // !SOQT_MAKE_DLL

  // FIXME: include information about the underlying toolkit library,
  // if possible, like we do for Qt below (i.e. Gtk version, Motif
  // implementation, Microsoft Windows version, ...).  20010927 mortene.

#ifdef SOQT_INTERNAL
  // Qt implementation info.
  {
    SbString s;
    s.sprintf("\nQt version: %s\n", qVersion());
    info += s;
  }
#endif // SOQT_INTERNAL

  // FIXME: information about DLL path(s) (both for the SoQt and
  // Coin/Inventor library) would be _extremely_ useful for debugging
  // "remote" applications, as application programmers (including
  // ourselves) tend to spread those files around misc disk drive
  // directories -- especially on Microsoft Windows platforms.  Mismatches for
  // runtime binding and link-time binding then causes bugs which are
  // impossible to make sense of.
  //
  // I don't know if any platforms have enough introspection
  // functionality to enable us to do this, though. Should
  // investigate. (update: GetModuleHandle() looks like the place to
  // start looking in the Win32 API.)
  //
  // 20010927 mortene.

  // OpenGL canvas settings.
  {
    SbString s;
    s.sprintf("\nCurrent OpenGL canvas:\n"
              "         %sbuffer\n"
              "         drawing to %sbuffer\n"
              "         %s rendering%s\n"
              "         %s mode\n"
              "         with%s overlay planes\n",
              PUBLIC(this)->isDoubleBuffer() ? "double" : "single",
              PUBLIC(this)->isDrawToFrontBufferEnable() ? "front" : "back",
              PUBLIC(this)->isStereoBuffer() ? "stereo" : "mono",
              PUBLIC(this)->isQuadBufferStereo() ? " (OpenGL quadbuffer)" : "",
              PUBLIC(this)->isRGBMode() ? "RGB" : "colorindex",
              PUBLIC(this)->isOverlayRender() ? "" : "out");

    // FIXME: information about the native OpenGL widget format?
    // 20010927 mortene.

    info += s;
  }

  // Underlying Inventor implementation.
  {
    SbString s;
    s.sprintf("\nInventor implementation: %s\n", SoDB::getVersion());
    info += s;
  }

  SoQt::createSimpleErrorDialog(NULL, "SoQt implementation info",
                                    info.getString());
#endif // DEBUGGING_EGGS
}

void
SoQtRenderAreaP::dumpScenegraph(void)
{
#ifdef DEBUGGING_EGGS
  SoOutput out;
  SbString filename = SbTime::getTimeOfDay().format();
  filename += "-dump.iv";
  SbBool ok = out.openFile(filename.getString());
  if (!ok) {
    SoDebugError::post("SoQtRenderAreaP::dumpScenegraph",
                       "couldn't open file '%s'", filename.getString());
    return;
  }
  SoWriteAction wa(&out);
  wa.apply(this->normalManager->getSceneGraph());
  SoDebugError::postInfo("SoQtRenderAreaP::dumpScenegraph",
                         "dumped scene graph to '%s'", filename.getString());
#endif // DEBUGGING_EGGS
}

void
SoQtRenderAreaP::dumpCameras(void)
{
#ifdef DEBUGGING_EGGS
  const SbBool kitsearch = SoBaseKit::isSearchingChildren();
  SoBaseKit::setSearchingChildren(TRUE);

  SoSearchAction search;
  search.setType(SoCamera::getClassTypeId());
  search.setInterest(SoSearchAction::ALL);
  search.setSearchingAll(TRUE);
  search.apply(this->normalManager->getSceneGraph());

  SoBaseKit::setSearchingChildren(kitsearch);

  const SoPathList & pl = search.getPaths();
  const unsigned int numcams = pl.getLength();
  SoDebugError::postInfo("SoQtRenderAreaP::dumpCameras",
                         "Number of cameras in scene graph: %d",
                         numcams);

  for (unsigned int i = 0; i < numcams; i++) {
    const SoPath * p = pl[i];
    SoNode * n = p->getTail();
    assert(n->isOfType(SoCamera::getClassTypeId()));
    SoCamera * cam = (SoCamera *)n;

    const SbVec3f pos = cam->position.getValue();
    const SbRotation rot = cam->orientation.getValue();
    SbVec3f axis;
    float angle;
    rot.getValue(axis, angle);

    SoDebugError::postInfo("SoQtRenderAreaP::dumpCameras",
                           "type==%s, name=='%s', position==<%f, %f, %f>, "
                           "orientation-rotation==<%f, %f, %f>--%f",
                           cam->getTypeId().getName().getString(),
                           cam->getName().getString(),
                           pos[0], pos[1], pos[2],
                           axis[0], axis[1], axis[2], angle);
  }
#endif // DEBUGGING_EGGS
}

/*
  Behaviour controlled by environment variables
    COIN_SOGRAB_GEOMETRY  (maximum geometry - on-screen aspect is preserved)
    COIN_SOGRAB_FILENAME  (filename template - can use %d to insert counter)

  Examples:
    export COIN_SOGRAB_GEOMETRY=1024x768
    export COIN_SOGRAB_FILENAME=c:\\grab%03d.png

*/

void
SoQtRenderAreaP::offScreenGrab(void)
{
#ifdef DEBUGGING_EGGS
  static int maxwidth = -1;
  static int maxheight = -1;
  static int counter = 0;
  static const char fallback_ext[] = ".rgb";
  static const char fallback_name[] = "coingrab%03d.rgb";

  /*
    FIXME:

    - schedule a regular render-pass and hook into the render
      pipe-line to check all the interesting GL context features that
      might need to be enabled for the offscreen renderer context.
      Then set up the offscreen renderer context to match those
      features, so the rendering becomes the same.
    - create a clone of the on-screen renderaction to get the same
      kind of custom rendering.
    - check if we might be able to hook up the on-screen pre-render
      callback to the offscreen renderer as well, if any point.
    - disable accidentally enabled seek mode again (from typing 'osgrab').

    20050606 larsa.
  */

  counter++;
  if ( maxwidth <= 0 ) {
    const char * env = 
      SoAny::si()->getenv("COIN_SOGRAB_GEOMETRY");
    if ( env ) {
      sscanf(env, "%dx%d", &maxwidth, &maxheight);
    }
    if ( (maxwidth <= 0) || !env ) {
      SbVec2s vp = PUBLIC(this)->getViewportRegion().getWindowSize();
      maxwidth = vp[0];
      maxheight = vp[1];
    }
  }

  if ( maxwidth <= 0 || maxheight <= 0 ) {
    SoDebugError::post("SoQtRenderAreaP::offScreenGrab",
                       "invalid geometry: %dx%d", maxwidth, maxheight);
    return;
  }
  SbVec2s vp = PUBLIC(this)->getViewportRegion().getWindowSize();

  const char * filenametpl =
    SoAny::si()->getenv("COIN_SOGRAB_FILENAME");
  if ( !filenametpl ) filenametpl = fallback_name;

  SbString filename;
  filename.sprintf(filenametpl, counter);
  const char * ext = strrchr(filename.getString(), '.');
  if ( !ext ) ext = fallback_ext;
  ext++;

  SbVec2s osvp(maxwidth, maxheight);
  if ( vp[0] > maxwidth || vp[1] > maxheight ||
       (vp[0] < maxwidth && vp[1] < maxheight) ) {
    float onscaspect = float(vp[0]) / float(vp[1]);
    float offscaspect = float(maxwidth) / float(maxheight);

    osvp[1] = maxheight;
    osvp[0] = short(maxheight * onscaspect);
    if ( osvp[0] > maxwidth ) {
      osvp[0] = maxwidth;
      osvp[1] = short(maxwidth * (1.0f / onscaspect));
    }
  }

  SoOffscreenRenderer os(osvp);
  if ( !os.render(PUBLIC(this)->getSceneManager()->getSceneGraph()) ) {
    return;
  }

  SbBool written = FALSE;
  if (strcmp(ext, "rgb") == 0) {
    written = os.writeToRGB(filename.getString());
  }
  else {
    written = os.writeToFile(filename, ext);
  }

  if (written) {
    SoDebugError::postInfo("SoQtRenderAreaP::offScreenGrab",
                           "wrote image #%d, %dx%d as '%s'",
                           counter, osvp[0], osvp[1],
                           filename.getString());
  }
  else {
    SoDebugError::post("SoQtRenderAreaP::offScreenGrab",
                       "tried to write image '%s', but failed for unknown "
                       "reason",
                       filename.getString());
  }
#endif // DEBUGGING_EGGS
}

int
SoQtRenderAreaP::checkMagicSequences(const char c)
{
#if DEBUGGING_EGGS
  this->currentinput += c;

  if (0) { // handy for debugging keyboard handling
    SoDebugError::postInfo("SoQtRenderAreaP::checkMagicSequences",
                           "'%s'", this->currentinput.getString());
  }

  const int cl = this->currentinput.getLength();

  static const char * keyseq[] = {
    "glinfo", "ivinfo", "soinfo", "dumpiv", "cameras", "osgrab"
  };
  static const int id[] = {
    SoQtRenderAreaP::OPENGL,
    SoQtRenderAreaP::INVENTOR,
    SoQtRenderAreaP::TOOLKIT,
    SoQtRenderAreaP::DUMPSCENEGRAPH,
    SoQtRenderAreaP::DUMPCAMERAS,
    SoQtRenderAreaP::OFFSCREENGRAB
  };

  for (unsigned int i = 0; i < (sizeof(keyseq) / sizeof(keyseq[0])); i++) {
    const int ml = (int)strlen(keyseq[i]);
    if (cl >= ml && this->currentinput.getSubString(cl - ml) == keyseq[i]) {
      return id[i];
    }
  }

  // Limit memory usage.
  if (cl > 1024) { this->currentinput = ""; }
#endif // DEBUGGING_EGGS

  return SoQtRenderAreaP::NONE;
}

// This method sets the window size data in all the connected device
// classes.
void
SoQtRenderAreaP::setDevicesWindowSize(const SbVec2s size)
{
  if (!this->devicelist) return;
  const int num = this->devicelist->getLength();
  for (int i = 0; i < num; i++)
    ((SoQtDevice *)(*this->devicelist)[i])->setWindowSize(size);
}

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

void
SoQtRenderAreaP::renderCB(void * closure, SoSceneManager * manager)
{
  assert(closure && manager);
  SoQtRenderArea * thisptr = (SoQtRenderArea *) closure;
  if (manager == PRIVATE(thisptr)->normalManager) {
    thisptr->render();
  } else if (manager == PRIVATE(thisptr)->overlayManager) {
    thisptr->renderOverlay();
  } else {
#if SOQT_DEBUG
    SoDebugError::post("SoQtRenderAreaP::renderCB",
                       "invoked for unknown SoSceneManager (%p)", manager);
#endif // SOQT_DEBUG
    return;
  }
}

// Callback for automatic redraw on SoSelection changes.
void
SoQtRenderAreaP::selection_redraw_cb(void * closure, SoSelection * sel)
{
  SoQtRenderArea * ra = (SoQtRenderArea *) closure;
  if (sel == PRIVATE(ra)->normalselection)
    ra->scheduleRedraw();
  else if (sel == PRIVATE(ra)->overlayselection)
    ra->scheduleOverlayRedraw();
  else
    assert(0 && "callback on unknown SoSelection node");
}

// Private class constructor.
SoQtRenderAreaP::SoQtRenderAreaP(SoQtRenderArea * api)
{
  PUBLIC(this) = api;

  this->normalManager = new SoSceneManager;
  this->overlayManager = new SoSceneManager;

  this->normalColormap = NULL;
  this->normalColormapSize = 0;
  this->overlayColormap = NULL;
  this->overlayColormapSize = 0;

  this->clear = TRUE;
  this->clearZBuffer = TRUE;
  this->clearOverlay = TRUE;
  this->autoRedraw = TRUE;

  this->normalselection = NULL;
  this->overlayselection = NULL;

  this->devices.mouse = NULL;
  this->devices.keyboard = NULL;
#ifdef HAVE_SPACENAV_SUPPORT
  this->devices.spacenav = NULL;
#endif // HAVE_SPACENAV_SUPPORT

}

// Private class destructor.
SoQtRenderAreaP::~SoQtRenderAreaP()
{
  delete this->normalManager;
  delete this->overlayManager;
  delete [] this->normalColormap;
  delete [] this->overlayColormap;
}

// Common code for all constructors.
void
SoQtRenderAreaP::constructor(SbBool mouseInput,
                                SbBool keyboardInput,
                                SbBool build)
{
  this->normalManager->setRenderCallback(SoQtRenderAreaP::renderCB, PUBLIC(this));
  this->normalManager->activate();
  this->overlayManager->setRenderCallback(SoQtRenderAreaP::renderCB, PUBLIC(this));
  this->overlayManager->activate();
  // FIXME: what is this magic number doing here - shouldn't we use
  // SoGLCacheContextElement::getUniqueCacheContext() for Coin, and
  // magic numbers just for SGI / TGS Inventor?
  //
  // On a side note: won't this code fail if we construct several
  // SoQtRenderArea instances with overlays? They will all use
  // cachecontext==1 for their SoGLRenderAction instances -- is that
  // kosher?
  //
  // 20010831 mortene.
  this->overlayManager->getGLRenderAction()->setCacheContext(1);

  this->appeventhandler = NULL;
  this->appeventhandlerdata = NULL;

  this->devicelist = new SbPList;

  if (mouseInput) {
    this->devices.mouse = new SoQtMouse;
    PUBLIC(this)->registerDevice(this->devices.mouse);
  }

  if (keyboardInput) {
    this->devices.keyboard = new SoQtKeyboard;
    PUBLIC(this)->registerDevice(this->devices.keyboard);
  }

  if (! build) return;
  PUBLIC(this)->setClassName("SoQtRenderArea");
  QWidget* glarea = PUBLIC(this)->buildWidget(PUBLIC(this)->getParentWidget());
  PUBLIC(this)->setBaseWidget(glarea);
  PUBLIC(this)->setSize(SbVec2s(400, 400));
}

// This method invokes the application event handler, if one is set.
SbBool
SoQtRenderAreaP::invokeAppCB(QEvent* event)
{
  if (this->appeventhandler != NULL)
    return this->appeventhandler(this->appeventhandlerdata, event);
  return FALSE;
}

// This method returns an SoEvent * corresponding to the given \a
// event, or \c NULL if there are none.
const SoEvent *
SoQtRenderAreaP::getSoEvent(QEvent* event)
{
  if (!this->devicelist)
    return (SoEvent *) NULL;

  const SoEvent * soevent = NULL;
  const int num = this->devicelist->getLength();
  for (int i = 0; (i < num) && (soevent == NULL); i++)
    soevent = ((SoQtDevice *)(*this->devicelist)[i])->translateEvent(event);

  return soevent;
}

#endif // DOXYGEN_SKIP_THIS

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

/*!
  Public constructor.
*/
SoQtRenderArea::SoQtRenderArea(QWidget* parent,
                                     const char * name,
                                     SbBool embed,
                                     SbBool mouseInput,
                                     SbBool keyboardInput)
  : inherited(parent, name, embed, SoQtRenderAreaP::GL_DEFAULT_MODE, FALSE)
{
  PRIVATE(this) = new SoQtRenderAreaP(this);
  PRIVATE(this)->constructor(mouseInput, keyboardInput, TRUE);
}

/*!
  Protected constructor used by derived classes.
*/
SoQtRenderArea::SoQtRenderArea(QWidget* parent,
                                     const char * name,
                                     SbBool embed,
                                     SbBool mouseInput,
                                     SbBool keyboardInput,
                                     SbBool build)
  : inherited(parent, name, embed, SoQtRenderAreaP::GL_DEFAULT_MODE, FALSE)
{
  PRIVATE(this) = new SoQtRenderAreaP(this);
  PRIVATE(this)->constructor(mouseInput, keyboardInput, build);
}

/*!
  Destructor.
*/
SoQtRenderArea::~SoQtRenderArea()
{
  // Clean out any callbacks we may have registered with SoSelection
  // nodes.
  this->redrawOverlayOnSelectionChange(NULL);
  this->redrawOnSelectionChange(NULL);

  for (int i = PRIVATE(this)->devicelist->getLength() - 1; i >= 0; i--) {
    SoQtDevice * device = (SoQtDevice *) ((*PRIVATE(this)->devicelist)[i]);
    this->unregisterDevice(device);
    delete device;
  }
  delete PRIVATE(this)->devicelist;
  delete PRIVATE(this);
}

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

/*!
  This method adds \a device to the list of devices handling events
  for this component.
*/
void
SoQtRenderArea::registerDevice(SoQtDevice * device)
{
  int idx = PRIVATE(this)->devicelist->find(device);
  if (idx != -1) {
#if SOQT_DEBUG
    SoDebugError::postWarning("SoQtRenderArea::registerDevice",
                              "device already registered");
#endif // SOQT_DEBUG
    return;
  }

  PRIVATE(this)->devicelist->append(device);
  QWidget* w = this->getGLWidget();
  if (w != NULL) {
#ifdef __COIN_SOXT__
    device->enable(w, (SoXtEventHandler *) &SoQtGLWidget::eventHandler, (void *)this);
#else
    device->enable(w, &SoQtGLWidgetP::eventHandler, (void *)this);
#endif
    device->setWindowSize(this->getGLSize());
  }
}

/*!
  This method removes \a device from the list of devices handling
  events for this component.
*/
void
SoQtRenderArea::unregisterDevice(SoQtDevice * device)
{
  assert(PRIVATE(this)->devicelist != NULL);
  const int idx = PRIVATE(this)->devicelist->find(device);
  if (idx == -1) {
#if SOQT_DEBUG
    SoDebugError::post("SoQtRenderArea::unregisterDevice",
                       "tried to remove nonexisting device");
#endif // SOQT_DEBUG
    return;
  }

  PRIVATE(this)->devicelist->remove(idx);
  QWidget* w = this->getGLWidget();
  if (w != NULL) { device->disable(w, NULL, NULL); }
}

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

// Documented in superclass.
void
SoQtRenderArea::afterRealizeHook(void)
{
  inherited::afterRealizeHook();

#ifdef HAVE_JOYSTICK_LINUX
  if (SoQtLinuxJoystick::exists())
    this->registerDevice(new SoQtLinuxJoystick);
#endif // HAVE_JOYSTICK_LINUX

#ifdef HAVE_SPACENAV_SUPPORT
  PRIVATE(this)->devices.spacenav = new SoQtSpacenavDevice;
  this->registerDevice(PRIVATE(this)->devices.spacenav);
#endif // HAVE_SPACENAV_SUPPORT
}

/*!
  This method sets the scene graph to be rendered in the normal bitmap
  planes.

  \sa getSceneGraph(), setOverlaySceneGraph()
*/
void
SoQtRenderArea::setSceneGraph(SoNode * scene)
{
  PRIVATE(this)->normalManager->setSceneGraph(scene);
}

/*!
  This method returns a reference to the scene graph root node as set
  by the user.

  \sa SoQtRenderArea::getSceneManager()
*/
SoNode *
SoQtRenderArea::getSceneGraph(void)
{
  return PRIVATE(this)->normalManager->getSceneGraph();
}

/*!
  This method sets the scene graph to render for the overlay bitmap
  planes.

  It will automatically take care of setting up overplay planes in the
  OpenGL canvas if the OpenGL hardware and driver support it.

  Important note: not all graphics hardware and / or drivers for
  graphics hardware support overlay planes, so application programmers
  are advised to find some other way of accomplishing what they want
  to do before resorting to using overlay planes.  Using overlay
  planes will in practice severely limit the portability of
  applications which depend on them being available.

  \sa setSceneGraph(), getOverlaySceneGraph()
*/
void
SoQtRenderArea::setOverlaySceneGraph(SoNode * scene)
{
  SoNode * oldroot = this->getOverlaySceneGraph();
  PRIVATE(this)->overlayManager->setSceneGraph(scene);

  if (!oldroot && scene) { this->setOverlayRender(TRUE); }
  else if (oldroot && !scene) { this->setOverlayRender(FALSE); }
}

/*!
  This method returns the scene graph for the overlay scene.
*/
SoNode *
SoQtRenderArea::getOverlaySceneGraph(void)
{
  return PRIVATE(this)->overlayManager->getSceneGraph();
}

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

/*!
  This method sets the background color of the scene.
*/
void
SoQtRenderArea::setBackgroundColor(const SbColor & color)
{
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->setBackgroundColor(color);
  this->scheduleRedraw();
}

/*!
  This method returns the background color for the scene.
*/
const SbColor &
SoQtRenderArea::getBackgroundColor(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getBackgroundColor();
}

#if HAVE_SOSCENEMANAGER_GETBACKGROUNDCOLORRGBA

/*!
  This method sets the RGBA background color of the scene.
*/
void
SoQtRenderArea::setBackgroundColorRGBA(const SbColorRGBA & color)
{
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->setBackgroundColorRGBA(color);
  this->scheduleRedraw();
}

/*!
  This method returns the RGBA background color for the scene.
*/
const SbColorRGBA &
SoQtRenderArea::getBackgroundColorRGBA(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getBackgroundColorRGBA();
}

#endif

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

/*!
  This method sets the index of the background color for the scene.
*/
void
SoQtRenderArea::setBackgroundIndex(int idx)
{
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->setBackgroundIndex(idx);
  this->scheduleRedraw();
}

/*!
  This method returns the index of the background color for the scene.
*/
int
SoQtRenderArea::getBackgroundIndex(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getBackgroundIndex();
}

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

/*!
  This method sets the index of the background for the overlay scene.
*/
void
SoQtRenderArea::setOverlayBackgroundIndex(int idx)
{
  assert(PRIVATE(this)->overlayManager != NULL);
  PRIVATE(this)->overlayManager->setBackgroundIndex(idx);
  this->scheduleOverlayRedraw();
}

/*!
  This method returns the index of the background for the overlay scene.
*/
int
SoQtRenderArea::getOverlayBackgroundIndex(void) const
{
  assert(PRIVATE(this)->overlayManager != NULL);
  return PRIVATE(this)->overlayManager->getBackgroundIndex();
}

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

/*!
  This method sets the colormap for the scene.
*/
void
SoQtRenderArea::setColorMap(int start, int num, const SbColor * colors)
{
  delete [] PRIVATE(this)->normalColormap;
  PRIVATE(this)->normalColormapStart = start;
  PRIVATE(this)->normalColormapSize = num;
  PRIVATE(this)->normalColormap = new SbColor [ num ];
  for (int i = 0; i < num; i++)
    PRIVATE(this)->normalColormap[i] = colors[i];
  this->scheduleRedraw();
}

/*!
  This method sets the colormap for the overlay scene.
*/
void
SoQtRenderArea::setOverlayColorMap(int start, int num,
                                      const SbColor * colors)
{
  delete [] PRIVATE(this)->overlayColormap;
  PRIVATE(this)->overlayColormapStart = start;
  PRIVATE(this)->overlayColormapSize = num;
  PRIVATE(this)->overlayColormap = new SbColor [ num ];
  for (int i = 0; i < num; i++) {
    PRIVATE(this)->overlayColormap[i] = colors[i];
  }
  this->scheduleOverlayRedraw();
}

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

/*!
  This method sets the viewport region.
*/
void
SoQtRenderArea::setViewportRegion(const SbViewportRegion & region)
{
  if (region.getWindowSize()[0] == -1) return;

#if SOQT_DEBUG && 0 // debug
  SoDebugError::postInfo("SoQtRenderArea::setViewportRegion",
                         "size=<%d, %d>",
                         region.getWindowSize()[0],
                         region.getWindowSize()[1]);
#endif // debug

  PRIVATE(this)->normalManager->setViewportRegion(region);
  PRIVATE(this)->overlayManager->setViewportRegion(region);
  this->scheduleRedraw();
}

/*!
  This method returns the viewport region.
*/
const SbViewportRegion &
SoQtRenderArea::getViewportRegion(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getGLRenderAction()->getViewportRegion();
}

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

/*!
  This method sets the transparency type to be used for the scene.
*/
void
SoQtRenderArea::setTransparencyType(SoGLRenderAction::TransparencyType type)
{
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->getGLRenderAction()->setTransparencyType(type);
  PRIVATE(this)->overlayManager->getGLRenderAction()->setTransparencyType(type);
  this->scheduleRedraw();
}

/*!
  This method returns the transparency type used for the scene.
*/
SoGLRenderAction::TransparencyType
SoQtRenderArea::getTransparencyType(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getGLRenderAction()->getTransparencyType();
}

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

/*!
  This method sets the antialiasing used for the scene.

  The \a smoothing flag signifies whether or not line and point
  aliasing should be turned on. See documentation of
  SoGLRenderAction::setSmoothing(), which will be called from this
  function.

  \a numPasses gives the number of re-renderings to do of the scene,
  blending together the results from slight "jitters" of the camera
  view into the OpenGL accumulation buffer. For further information,
  see documentation of SoGLRenderAction::setNumPasses() and
  SoQtGLWidget::setAccumulationBuffer().
*/
void
SoQtRenderArea::setAntialiasing(SbBool smoothing, int numPasses)
{
  // FIXME: is this really necessary? I think we should either ignore
  // the call or store values for later migration if the scene manager
  // instance(s) haven't been made yet. 20010331 mortene.
  assert(PRIVATE(this)->normalManager != NULL);

  // Instead of piping the call further to
  // SoSceneManager::setAntialiasing(), we duplicate the code found in
  // that function. The reason for this is that we want to work around
  // a bug found in SGI Inventor, where they define the
  // setAntialiasing() method, but doesn't actually implement it.  So
  // we don't use it to avoid a linker error for those compiling So*
  // libraries on top of the older SGI Inventor versions with this
  // bug.
  //
  // We should perhaps throw in a configure check for the
  // SoSceneManager::setAntialiasing() method and only activate this
  // code when actually needed?
  //                                                   mortene@sim.no
  enum { MGRS = 2 };
  SoSceneManager * mgrs[MGRS] = { PRIVATE(this)->normalManager,
                                  PRIVATE(this)->overlayManager };
  for (int i=0; i < MGRS; i++) {
    SoGLRenderAction * glra = mgrs[i]->getGLRenderAction();
    if (glra) {
      glra->setSmoothing(smoothing);
      glra->setNumPasses(numPasses);
    }
  }

  this->scheduleRedraw();
}

/*!
  This method returns the antialiasing used for the scene.
*/
void
SoQtRenderArea::getAntialiasing(SbBool & smoothing, int & numPasses) const
{
  // FIXME: there's an API design flaw here, as it is assumed that the
  // antialiasing setting for the render action in the "normal"
  // rendering context always matches what is the case for the
  // render action in the overlay manager. This is not necessarily
  // true. Could be solved by an additional argument to
  // getAntialiasing(): a boolean indicator on whether or not we want
  // the overlay context with a default value (false) to keep API
  // compatibility. 20010331 mortene.

  assert(PRIVATE(this)->normalManager != NULL);

  // About why we don't use SoSceneManager::getAntialiasing()
  // directly, see comment in SoGuiRenderArea::setAntiAliasing().
  SoGLRenderAction * glra = PRIVATE(this)->normalManager->getGLRenderAction();
  smoothing = glra->isSmoothing();
  numPasses = glra->getNumPasses();
}

/*!
  This method sets whether the render buffer should be cleared before
  rendering.

  The first argument specifies whether or not to clear out the pixels
  in the buffer, the second argument specifies whether or not the
  z-buffer values should be cleared between renderings.

  Setting the first argument to \c FALSE can for instance be used when
  you want to clear out the buffer yourself, for instance by drawing a
  background image "under" the 3D scene rendered by Coin / Inventor.
*/
void
SoQtRenderArea::setClearBeforeRender(SbBool enable, SbBool zbEnable)
{
  PRIVATE(this)->clear = enable;
  PRIVATE(this)->clearZBuffer = zbEnable;

  this->scheduleRedraw();
}

/*!
  This method returns whether the render buffer is cleared before each
  render.
*/
SbBool
SoQtRenderArea::isClearBeforeRender(void) const
{
  return PRIVATE(this)->clear;
}

/*!
  This method returns whether the render buffer's z-buffer is cleared
  before each render.
*/
SbBool
SoQtRenderArea::isClearZBufferBeforeRender(void) const
{
  return PRIVATE(this)->clearZBuffer;
}

/*!
  This method sets whether the overlay render buffer should be cleared
  before each render or not.
*/
void
SoQtRenderArea::setClearBeforeOverlayRender(SbBool enable)
{
  PRIVATE(this)->clearOverlay = enable;
  this->scheduleOverlayRedraw();
}

/*!
  This method returns whether the overlay render buffer is cleared
  before each redraw or not.
*/
SbBool
SoQtRenderArea::isClearBeforeOverlayRender(void) const
{
  return PRIVATE(this)->clearOverlay;
}

/*!
  This method sets whether redrawing should be handled automatically
  or not when data in the scene graph changes.

  The default setting causes the render area to automatically trigger a
  redraw of the scene graph contents.
*/
void
SoQtRenderArea::setAutoRedraw(SbBool enable)
{
  if (enable) {
    PRIVATE(this)->normalManager->activate();
    PRIVATE(this)->overlayManager->activate();
  }
  else {
    PRIVATE(this)->normalManager->deactivate();
    PRIVATE(this)->overlayManager->deactivate();
  }
  PRIVATE(this)->autoRedraw = enable;
}

/*!
  This method returns whether redrawing is handled automatically 
  not.
*/
SbBool
SoQtRenderArea::isAutoRedraw(void) const
{
  return PRIVATE(this)->autoRedraw;
}

/*!
  This method sets the redraw priority.
*/
void
SoQtRenderArea::setRedrawPriority(uint32_t priority)
{
  PRIVATE(this)->normalManager->setRedrawPriority(priority);
  PRIVATE(this)->overlayManager->setRedrawPriority(priority);
}

/*!
  This method returns the redraw priority.
*/
uint32_t
SoQtRenderArea::getRedrawPriority(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getRedrawPriority();
}

/*!
  This function returns the default redraw priority.
*/
uint32_t
SoQtRenderArea::getDefaultRedrawPriority(void)
{
  return SoSceneManager::getDefaultRedrawPriority();
}

/*!
  This method causes the immediate rendering of the scene, by calling
  SoQtRenderArea::redraw().
*/
void
SoQtRenderArea::render(void)
{
  this->redraw();
}

/*!
  This method renders the overlay scene.
*/
void
SoQtRenderArea::renderOverlay(void)
{
  this->redrawOverlay();
}

/*!
  This method schedules a redraw to happen at a later time (when the
  application has processed its other events first).
*/
void
SoQtRenderArea::scheduleRedraw(void)
{
#if SOQT_DEBUG && RENDERAREA_DEBUG_REDRAWS // debug
  SoDebugError::postInfo("SoQtRenderArea::scheduleRedraw",
                         "invoked");
#endif // debug
  
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->scheduleRedraw(); // Redraw when idle.
}

/*!
  This method schedules a redraw of the overlay scene.
*/
void
SoQtRenderArea::scheduleOverlayRedraw(void)
{
  assert(PRIVATE(this)->overlayManager != NULL);
  PRIVATE(this)->overlayManager->scheduleRedraw(); // Redraw when idle.
}

#ifndef DOXYGEN_SKIP_THIS
void
SoQtRenderAreaP::replaceSoSelectionMonitor(SoSelection * newsel,
                                              SoSelection * oldsel) const
{
  if (newsel) { newsel->ref(); }

  if (oldsel) {
    oldsel->removeChangeCallback(SoQtRenderAreaP::selection_redraw_cb,
                                 PUBLIC(this));
    oldsel->unref();
  }

  if (newsel) {
    newsel->addChangeCallback(SoQtRenderAreaP::selection_redraw_cb,
                              PUBLIC(this));
  }
}
#endif // DOXYGEN_SKIP_THIS

// FIXME: the interface below is badly designed, see the comment in
// the function documentation. 20001002 mortene.

/*!
  Do automatic redraw of the scene graph when a selection under the
  SoSelection node is changed.

  Pass \c NULL to deactivate.

  (Only one SoSelection node can be monitored at any given time. This
  is obviously a rather silly design flaw. We choose to match the
  original Inventor API here, but this will probably change in the
  next major revision of the library.)
*/
void
SoQtRenderArea::redrawOnSelectionChange(SoSelection * selection)
{
  PRIVATE(this)->replaceSoSelectionMonitor(selection, PRIVATE(this)->normalselection);
  PRIVATE(this)->normalselection = selection;
}

/*!
  Do automatic redraw of the scene graph in the overlay planes when a
  selection under the SoSelection node is changed.

  Pass \c NULL to deactivate.

  \sa SoQtRenderArea::redrawOnSelectionChange()
*/
void
SoQtRenderArea::redrawOverlayOnSelectionChange(SoSelection * selection)
{
  PRIVATE(this)->replaceSoSelectionMonitor(selection, PRIVATE(this)->overlayselection);
  PRIVATE(this)->overlayselection = selection;
}

/*!
  This method sets the render area event callback.
*/
void
SoQtRenderArea::setEventCallback(SoQtRenderAreaEventCB * func,
                                    void * user)
{
  PRIVATE(this)->appeventhandler = func;
  PRIVATE(this)->appeventhandlerdata = user;
}

/*!
  This method sets the normal scene SoSceneManager object.

  The previously set scene manager is deleted, and there is no way to
  currently avoid that.  This might change in the future.
*/
void
SoQtRenderArea::setSceneManager(SoSceneManager * manager)
{
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->setRenderCallback(NULL, NULL);

  // NOTE: Although deleting the previous scene manager here is correct
  // behaviour from a compatibility-POV, I think it is ugly and should
  // be addressed in some other way if possible. It is also inconsistent
  // with overlay scene manager management.  20061015 larsa
  delete PRIVATE(this)->normalManager;

  PRIVATE(this)->normalManager = manager;
  if (PRIVATE(this)->normalManager) {
    PRIVATE(this)->normalManager->setSize(this->getGLSize());
  }
}

/*!
  This method returns the normal scene SoSceneManager object.

  Having a reference to the SoSceneManager instance is useful for
  getting at the \e real root node of the rendering scene graph,
  including camera, headlight and miscellaneous draw style nodes.  The
  getSceneGraph() method will only return the \e user scene graph for
  SoQtRenderArea subclass SoQtViewer and further subclasses. The
  reason this is not always what you want is because certain actions
  (like the SoRayPickAction) needs to traverse a valid camera if it
  should work as expected.

  If you need to get a pointer to the \e real root node use this
  method to get the SoSceneManager instance reference used by the
  SoQtRenderArea, then use SoSceneManager::getSceneGraph() to get
  the root node Coin uses for rendering.
*/
SoSceneManager *
SoQtRenderArea::getSceneManager(void) const
{
  return PRIVATE(this)->normalManager;
}

/*!
  This method sets the overlay scene SoSceneManager object.

  The previously set scene manager is not freed and will leak unless
  the user frees it.
*/
void
SoQtRenderArea::setOverlaySceneManager(SoSceneManager * manager)
{
  PRIVATE(this)->overlayManager = manager;
  if (PRIVATE(this)->overlayManager) {
    PRIVATE(this)->overlayManager->setSize(this->getGLSize());
  }
}

/*!
  This method returns the overlay scene SoSceneManager object.
*/
SoSceneManager *
SoQtRenderArea::getOverlaySceneManager(void) const
{
  return PRIVATE(this)->overlayManager;
}

/*!
  This method sets the SoGLRenderAction object for the normal scene.
*/
void
SoQtRenderArea::setGLRenderAction(SoGLRenderAction * action)
{
  assert(PRIVATE(this)->normalManager != NULL);
  PRIVATE(this)->normalManager->setGLRenderAction(action);
  // Force an update of the SoGLRenderAction to the correct
  // updatearea, aspectratio, etc.
  this->sizeChanged(this->getSize());
}

/*!
  This method returns the SoGLRenderAction object for the normal scene.
*/
SoGLRenderAction *
SoQtRenderArea::getGLRenderAction(void) const
{
  assert(PRIVATE(this)->normalManager != NULL);
  return PRIVATE(this)->normalManager->getGLRenderAction();
}

/*!
  This method sets the SoGLRenderAction object for rendering the
  overlay scene graph.
*/
void
SoQtRenderArea::setOverlayGLRenderAction(SoGLRenderAction * action)
{
  assert(PRIVATE(this)->overlayManager != NULL);
  PRIVATE(this)->overlayManager->setGLRenderAction(action);
}

/*!
  This method returns the SoGLRenderAction object for the overlay scene
  graph.
*/
SoGLRenderAction *
SoQtRenderArea::getOverlayGLRenderAction(void) const
{
  assert(PRIVATE(this)->overlayManager != NULL);
  return PRIVATE(this)->overlayManager->getGLRenderAction();
}

/*!
  This method is called from the render() method and takes care of
  setting up the context for OpenGL rendering (by making the OpenGL
  canvas the current context and specifying either the front or back
  buffer for rendering, depending on whether we're in single buffer or
  double buffer mode).

  After setting up the OpenGL context, it calls actualRedraw() for the
  actual scene graph rendering to take place.

  Finally, the OpenGL buffers are either swapped back-to-front (for
  double buffering) or flushed (for single buffering), and our OpenGL
  context is unlocked.

  The application programmer may override this method if extreme
  low-level control of the rendering process is necessary. Usually,
  you should be able to get away with overriding actualRedraw() for
  special cases, though.
*/
void
SoQtRenderArea::redraw(void)
{
#if SOQT_DEBUG && RENDERAREA_DEBUG_REDRAWS // debug
  SoDebugError::postInfo("SoQtRenderArea::redraw",
                         "start (isVisible=%s waitForExpose=%s)",
                         this->isVisible() ? "TRUE" : "FALSE",
                         this->waitForExpose ? "TRUE" : "FALSE");
#endif // debug

  if (! this->isVisible() || !this->hasNormalGLArea() || this->waitForExpose)
    return;
  this->glLockNormal(); // this makes the GL context "current"

  SbBool drawfront =
    ! this->isDoubleBuffer() ||
    this->isDrawToFrontBufferEnable();

  glDrawBuffer(drawfront ? GL_FRONT : GL_BACK);

  this->actualRedraw();

  if (!drawfront) { this->glSwapBuffers(); }
  else { this->glFlushBuffer(); }
  this->glUnlockNormal();

#if SOQT_DEBUG && RENDERAREA_DEBUG_REDRAWS // debug
  SoDebugError::postInfo("SoQtRenderArea::render", "done");
#endif // debug
}

// Note: the following function documentation block will also be used
// for all the miscellaneous viewer subclasses, so keep it general.
/*!
  This method instantly redraws the normal (non-overlay) scene graph by
  calling SoSceneManager::render().

  Subclasses may override this method to add their own rendering
  before or after Coin renders its scene graph.

  The following is a complete example that demonstrates one way of
  adding both a background image and foreground (overlay) geometry to
  the "normal" rendering:

  \code
  // This example shows how to put a permanent background image on
  // your viewer canvas, below the 3D graphics, plus overlay
  // foreground geometry.  Written by mortene.
  // Copyright Kongsberg Oil & Gas Technologies 2002.
  
  // *************************************************************************
  
  #include <Inventor/Qt/SoQt.h>
  #include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
  #include <Inventor/nodes/SoBaseColor.h>
  #include <Inventor/nodes/SoCone.h>
  #include <Inventor/nodes/SoCube.h>
  #include <Inventor/nodes/SoImage.h>
  #include <Inventor/nodes/SoLightModel.h>
  #include <Inventor/nodes/SoOrthographicCamera.h>
  #include <Inventor/nodes/SoRotationXYZ.h>
  #include <Inventor/nodes/SoSeparator.h>
  #include <Inventor/nodes/SoTranslation.h>
  
  #include <GL/gl.h>
  
  // *************************************************************************
  
  class MyExaminerViewer : public SoQtExaminerViewer {
  
  public:
    MyExaminerViewer(QWidget* parent, const char * filename);
    ~MyExaminerViewer();
  
  protected:
    virtual void actualRedraw(void);
  
  private:
    SoSeparator * bckgroundroot;
    SoSeparator * foregroundroot;
    SoRotationXYZ * arrowrotation;
  };
  
  MyExaminerViewer::MyExaminerViewer(QWidget* parent, const char * filename)
    : SoQtExaminerViewer(parent)
  {
    // Coin should not clear the pixel-buffer, so the background image
    // is not removed.
    this->setClearBeforeRender(FALSE, TRUE);
  
  
    // Set up background scene graph with image in it.
  
    this->bckgroundroot = new SoSeparator;
    this->bckgroundroot->ref();
  
    SoOrthographicCamera * cam = new SoOrthographicCamera;
    cam->position = SbVec3f(0, 0, 1);
    cam->height = 1;
    // SoImage will be at z==0.0.
    cam->nearDistance = 0.5;
    cam->farDistance = 1.5;
  
    SoImage * img = new SoImage;
    img->vertAlignment = SoImage::HALF;
    img->horAlignment = SoImage::CENTER;
    img->filename = filename;
  
    this->bckgroundroot->addChild(cam);
    this->bckgroundroot->addChild(img);
  
    // Set up foreground, overlaid scene graph.
  
    this->foregroundroot = new SoSeparator;
    this->foregroundroot->ref();
  
    SoLightModel * lm = new SoLightModel;
    lm->model = SoLightModel::BASE_COLOR;
  
    SoBaseColor * bc = new SoBaseColor;
    bc->rgb = SbColor(1, 1, 0);
  
    cam = new SoOrthographicCamera;
    cam->position = SbVec3f(0, 0, 5);
    cam->height = 10;
    cam->nearDistance = 0;
    cam->farDistance = 10;
  
    const double ARROWSIZE = 2.0;
  
    SoTranslation * posit = new SoTranslation;
    posit->translation = SbVec3f(-2.5 * ARROWSIZE, 1.5 * ARROWSIZE, 0);
  
    arrowrotation = new SoRotationXYZ;
    arrowrotation->axis = SoRotationXYZ::Z;
  
    SoTranslation * offset = new SoTranslation;
    offset->translation = SbVec3f(ARROWSIZE/2.0, 0, 0);
  
    SoCube * cube = new SoCube;
    cube->width = ARROWSIZE;
    cube->height = ARROWSIZE/15.0;
  
    this->foregroundroot->addChild(cam);
    this->foregroundroot->addChild(lm);
    this->foregroundroot->addChild(bc);
    this->foregroundroot->addChild(posit);
    this->foregroundroot->addChild(arrowrotation);
    this->foregroundroot->addChild(offset);
    this->foregroundroot->addChild(cube);
  }
  
  MyExaminerViewer::~MyExaminerViewer()
  {
    this->bckgroundroot->unref();
    this->foregroundroot->unref();
  }
  
  void
  MyExaminerViewer::actualRedraw(void)
  {
    // Must set up the OpenGL viewport manually, as upon resize
    // operations, Coin won't set it up until the SoGLRenderAction is
    // applied again. And since we need to do glClear() before applying
    // the action..
    const SbViewportRegion vp = this->getViewportRegion();
    SbVec2s origin = vp.getViewportOriginPixels();
    SbVec2s size = vp.getViewportSizePixels();
    glViewport(origin[0], origin[1], size[0], size[1]);
  
    const SbColor col = this->getBackgroundColor();
    glClearColor(col[0], col[1], col[2], 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
    // Render our scene graph with the image.
    SoGLRenderAction * glra = this->getGLRenderAction();
    glra->apply(this->bckgroundroot);
  
  
    // Render normal scene graph.
    SoQtExaminerViewer::actualRedraw();
  
  
    // Increase arrow angle with 1/1000 degrees every frame.
    arrowrotation->angle = arrowrotation->angle.getValue() + (0.001 / M_PI * 180);
    // Render overlay front scene graph.
    glClear(GL_DEPTH_BUFFER_BIT);
    glra->apply(this->foregroundroot);
  }
  
  // *************************************************************************
  
  int
  main(int argc, char ** argv)
  {
    if (argc != 2) {
      (void)fprintf(stderr, "\n\n\tUsage: %s <image-filename>\n\n", argv[0]);
      exit(1);
    }
  
    QWidget* window = SoQt::init(argv[0]);
  
    MyExaminerViewer * viewer = new MyExaminerViewer(window, argv[1]);
  
    viewer->setSceneGraph(new SoCone);
    viewer->show();
  
    SoQt::show(window);
    SoQt::mainLoop();
  
    delete viewer;
    return 0;
  }
  
  // *************************************************************************
  \endcode
*/
void
SoQtRenderArea::actualRedraw(void)
{
  assert(PRIVATE(this)->normalManager != NULL);
  if ( !this->isVisible() ) return;
  PRIVATE(this)->normalManager->render(PRIVATE(this)->clear, PRIVATE(this)->clearZBuffer);
}

/*!
  This method redraws the overlay scene.
*/
void
SoQtRenderArea::redrawOverlay(void)
{
  if (!this->isVisible() || this->waitForExpose || !this->hasOverlayGLArea()) {
    return;
  }
  
  this->glLockOverlay();
  this->actualOverlayRedraw();
  this->glFlushBuffer();
  this->glUnlockOverlay();
}

/*!
  This method renders the overlay scene.
*/
void
SoQtRenderArea::actualOverlayRedraw(void)
{
  assert(PRIVATE(this)->overlayManager != NULL);
  if (! this->isVisible()) return;
  PRIVATE(this)->overlayManager->render(PRIVATE(this)->clearOverlay,
                                        PRIVATE(this)->clearZBuffer);
}

/*!
  This method is invoked to initialize the normal graphics.
*/
void
SoQtRenderArea::initGraphic(void)
{
  SoSceneManager * mgr = this->getSceneManager();
  if (mgr) {
    mgr->reinitialize();
    mgr->setRGBMode(this->isRGBMode());

    SoGLRenderAction * renderaction = mgr->getGLRenderAction();
    renderaction->setCacheContext(SoAny::si()->getSharedCacheContextId(this));
    SbBool isdirect = SoGuiGLWidgetP::isDirectRendering(this);
    renderaction->setRenderingIsRemote(!isdirect);
  }
  // FIXME: if not RGB mode, load colormap. pederb.

  inherited::initGraphic();
}

/*!
  This method is invoked to initialize the overlay graphics.
*/
void
SoQtRenderArea::initOverlayGraphic(void)
{
  SoSceneManager * mgr = this->getOverlaySceneManager();
  if (mgr) {
    mgr->reinitialize();
    // FIXME: does overlays really _have_ to be in color index mode?
    // 20020916 mortene.
    mgr->setRGBMode(FALSE);

    SoGLRenderAction * renderaction = mgr->getGLRenderAction();
    SbBool isdirect = SoGuiGLWidgetP::isDirectRendering(this);
    renderaction->setRenderingIsRemote(!isdirect);
    // FIXME: shouldn't we also setCacheContext() on the render action?
    // 20020916 mortene.
  }
  // FIXME: if not RGB mode, load colormap. pederb.

  // FIXME: shouldn't we do inherited::initOverlayGraphic() ? 20010831 mortene.
}

// doc in super
void
SoQtRenderArea::sizeChanged(const SbVec2s & size)
{
#if SOQT_DEBUG && 0
  SoDebugError::postInfo("SoQtRenderArea::sizeChanged",
                          "invoked, <%d, %d>", size[0], size[1]);
#endif // SOQT_DEBUG

  SbVec2s newsize(size);

  if (size[0] == -1)
    return;

  assert(PRIVATE(this)->normalManager != NULL);
  assert(PRIVATE(this)->overlayManager != NULL);

  // Workaround for a bug in Qt/Mac 3.1.0 and 3.1.1 (which has been
  // confirmed fixed in 3.1.2):
  // 
  // If the OpenGL context overlaps with the QSizeGrip widget
  // (generated by default), resizing does not work any more. The
  // workaround is to leave 15 pixels at the lower border of the
  // window blank...

#if defined Q_OS_MAC && ((QT_VERSION == 0x030100) || (QT_VERSION == 0x030101))

  // Environment variable to override Qt/Mac 3.1.x workarounds.
  const char * forcenoresizeworkaround =
    SoAny::si()->getenv("FORCE_NO_QTMAC_31_RESIZE_WORKAROUND");
  if (!forcenoresizeworkaround || (atoi(forcenoresizeworkaround) == 0)) {

    if (this->getTypeId() == SoQtRenderArea::getClassTypeId()) { 
      // SoQtRenderArea used as standalone component
      newsize -= SbVec2s(0, 15);

      // spit out a warning that this is a Qt/Mac bug, not an SoQt problem
      const char * env = SoAny::si()->getenv("SOQT_NO_QTMAC_BUG_WARNINGS");
      if (!env || !atoi(env)) {        
        SoDebugError::postWarning("SoQtRenderArea::sizeChanged",
                                  "\nThis version of Qt/Mac contains a bug "
                                  "that makes it necessary to leave the\n"
                                  "lowermost 15 pixels of the viewer window "
                                  "blank. Set the environment variable\n"
                                  "FORCE_NO_QTMAC_31_RESIZE_WORKAROUND=1 to "
                                  "override this workaround. \n"
                                  "You can turn off warnings about Qt/Mac "
                                  "bugs permanently by setting \n"
                                  "SOQT_NO_QTMAC_BUG_WARNINGS=1.\n");
      } 
    } 
  }

#endif

  this->setGLSize(newsize);
  const SbVec2s glsize = this->getGLSize();


#if SOQT_DEBUG && 0
  SoDebugError::postInfo("SoQtRenderArea::sizeChanged",
                          "glsize==<%d, %d>", glsize[0], glsize[1]);
#endif // SOQT_DEBUG

  if (glsize[0] <= 0 || glsize[1] <= 0)
    return;

  this->setViewportRegion(SbViewportRegion(glsize));
  PRIVATE(this)->setDevicesWindowSize(glsize);

  // FIXME: aren't both these calls unnecessary as we set the full
  // viewportregion a few lines above this one? 20020103 mortene.
  PRIVATE(this)->normalManager->setWindowSize(glsize);
  PRIVATE(this)->normalManager->setSize(glsize);

  // FIXME: aren't both these calls unnecessary as we set the full
  // viewportregion a few lines above this one? 20020103 mortene.
  PRIVATE(this)->overlayManager->setWindowSize(glsize);
  PRIVATE(this)->overlayManager->setSize(glsize);

  inherited::sizeChanged(glsize);

  // this->scheduleRedraw(); // already done through setViewportRegion()
}

// Documented in superclass.
void
SoQtRenderArea::widgetChanged(QWidget* widget)
{
  PRIVATE(this)->normalManager->reinitialize();
  PRIVATE(this)->overlayManager->reinitialize();
  // FIXME: colorindex mode not supported yet, so this has no
  // effect. 20001121 mortene.
#if 0
  PRIVATE(this)->normalManager->setRGBMode(this->isRGBMode());
  PRIVATE(this)->overlayManager->setRGBMode(this->isRGBMode());
#endif

  // FIXME: should also walk through all registered devices and do a
  // disable() on the old widget and enable() on the new one.
  // 20001121 mortene.
}

// Documented in superclass.
QWidget*
SoQtRenderArea::buildWidget(QWidget* parent)
{
  QWidget* widget = inherited::buildWidget(parent);

  if (PRIVATE(this)->devicelist != NULL) {
    const int num = PRIVATE(this)->devicelist->getLength();
    for (int i = 0; i < num; i++) {
      SoQtDevice * device = (SoQtDevice *) (*PRIVATE(this)->devicelist)[i];
#ifdef __COIN_SOXT__
      device->enable(this->getGLWidget(),
                     (SoXtEventHandler *) &SoQtGLWidget::eventHandler,
                     (void *) this);
#else
      device->enable(this->getGLWidget(),
                     &SoQtGLWidgetP::eventHandler, (void *) this);
#endif
    }
  }

  return widget;
}

// Documented in superclass.
const char *
SoQtRenderArea::getDefaultWidgetName(void) const
{
  static const char defaultWidgetName[] = "SoQtWidgetName";
  return defaultWidgetName;
}

// Documented in superclass.
const char *
SoQtRenderArea::getDefaultTitle(void) const
{
  static const char defaultTitle[] = "Qt RenderArea";
  return defaultTitle;
}

// Documented in superclass.
const char *
SoQtRenderArea::getDefaultIconTitle(void) const
{
  static const char defaultIconTitle[] = "Qt RenderArea";
  return defaultIconTitle;
}

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

/*!
  Toolkit-native events are tried to convert to Coin-generic events
  in the SoQtRenderArea::processEvent() method.  If this succeeds,
  they are forwarded to this method.

  This is a virtual method, and is overridden in its subclasses to
  catch events of particular interest to the viewer classes, for
  instance.

  Return \c TRUE if the event was processed. If not it should be
  passed on further up in the inheritance hierarchy by the caller.
  This last point is extremely important to take note of if you are
  expanding the toolkit with your own viewer class.

  This method is not part of the original SGI InventorXt API. Note
  that you can still override the toolkit-native processEvent() method
  instead of this "generic" method.
*/
SbBool
SoQtRenderArea::processSoEvent(const SoEvent * const event)
{
  if (PRIVATE(this)->overlayManager->processEvent(event)) { return TRUE; }
  if (PRIVATE(this)->normalManager->processEvent(event)) { return TRUE; }
  return FALSE;
}

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

/*!
  Overrides SoQtGLWidget::processEvent() to try to convert
  toolkit-native events to Coin-generic events.  If this succeeds, the
  generic SoEvent is forwarded to SoQtRenderArea::processSoEvent().
 */
void
SoQtRenderArea::processEvent(QEvent* event)
{
  // FIXME: This method is not reentrant, but certain GUI toolkits may
  // run the event-loop recursively with events injected while processing
  // other events. We must detect when this happens, and queue up the
  // event for delayed processing after the primary event is done processing.
  // This means event instances can not be static and rewritten in the
  // device handlers like they are currently...  20051013 larsa

  if (PRIVATE(this)->invokeAppCB(event)) { return; }

  const SoEvent * soevent = PRIVATE(this)->getSoEvent(event);

  if (soevent != NULL) {
#if SOQT_DEBUG || defined(DEBUGGING_EGGS)
    // Undocumented feature: there are several "magic" sequences of
    // keys when tapped into the rendering canvas which'll pop up a
    // dialog box with information about that particular feature.
    //
    // See code comments behind "case" statements below for which
    // sequences are available so far.

    if (soevent->isOfType(SoKeyboardEvent::getClassTypeId())) {
      SoKeyboardEvent * ke = (SoKeyboardEvent *)soevent;
      if (ke->getState() == SoButtonEvent::UP) {
        char c = ke->getPrintableCharacter();
        switch (PRIVATE(this)->checkMagicSequences(c)) {
        case SoQtRenderAreaP::NONE:
          break;
        case SoQtRenderAreaP::OPENGL:  // "glinfo"
          this->glLockNormal();
          PRIVATE(this)->showOpenGLDriverInformation();
          this->glUnlockNormal();
          break;
        case SoQtRenderAreaP::INVENTOR:  // "ivinfo"
          PRIVATE(this)->showInventorInformation();
          break;
        case SoQtRenderAreaP::TOOLKIT:  // "soinfo"
          PRIVATE(this)->showToolkitInformation();
          break;
        case SoQtRenderAreaP::DUMPSCENEGRAPH:  // "dumpiv"
          PRIVATE(this)->dumpScenegraph();
          break;
        case SoQtRenderAreaP::DUMPCAMERAS:  // "cameras"
          PRIVATE(this)->dumpCameras();
          break;
        case SoQtRenderAreaP::OFFSCREENGRAB:  // "osgrab"
          PRIVATE(this)->offScreenGrab();
          break;
        default:
          assert(FALSE && "unknown debug key sequence");
          break;
        }
      }
    }
#endif // SOQT_DEBUG

    SbBool processed = this->processSoEvent(soevent);
    if (processed) return;
  }

  inherited::processEvent(event);
}

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

// doc from parent
SbBool 
SoQtRenderArea::glScheduleRedraw(void)
{
  this->scheduleRedraw();
  if (this->hasOverlayGLArea() && this->getOverlaySceneGraph()) {
    this->scheduleOverlayRedraw();
  }
  return TRUE;
}

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

/*!
  This method posts and processes an SoEvent object to the
  SoQtRenderArea-based component and returns the result value from the
  event handler.  This is a synchronous operation.
*/
SbBool
SoQtRenderArea::sendSoEvent(const SoEvent * event)
{
  return this->processSoEvent(event);
}

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

#undef PRIVATE
#undef PUBLIC

