//<copyright>
// 
// Copyright (c) 1994,95,96
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// 
//</copyright>

//<file>
//
// Name:        scenewin.C
//
// Purpose:     implementation of class SceneWindow
//
// Created:     26 Jan 94   Michael Pichler
//
// Changed:     26 Jan 96   Michael Pichler
//
// $Id: scenewin.C,v 1.24 1996/01/30 12:25:59 mpichler Exp $
//
//</file>



#include "scenewin.h"

#include "camera.h"
#include "material.h"
#include "geomobj.h"

#include "gecontext.h"

#include "stranslate.h"
#include "themenus.h"
#include "hgptrhand.h"
#include "vecutil.h"

#include <ge3d/ge3d.h>
#include <hyperg/hyperg/message.h>
#include <hyperg/hyperg/verbose.h>
#include <hyperg/widgets/about.h>
#include <hyperg/widgets/cursors.h>
#include <hyperg/widgets/menus.h>
#include <hyperg/widgets/progrind.h>
#include <hyperg/widgets/textb.h>

#include  <hyperg/utils/hgraster.h>
//#include <InterViews/tiff.h>

#include <InterViews/action.h>
#include <InterViews/background.h>
#include <InterViews/bitmap.h>
#include <InterViews/color.h>
#include <InterViews/deck.h>
#include <InterViews/event.h>
#include <InterViews/layout.h>
#include <InterViews/patch.h>
#include <InterViews/raster.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/window.h>

#include <Dispatch/iocallback.h>
#include <Dispatch/dispatcher.h>

#include <IV-look/kit.h>
#include <X11/keysym.h>

#include <OS/string.h>
#include <string.h>

#include <IV-X11/xdisplay.h>
#include <IV-X11/xwindow.h>
#include <IV-X11/xevent.h>
#ifdef SPACEBALL
# include <IV-X11/Xdefs.h>
# include <SPW_Input.h>
# include <IV-X11/Xundefs.h>
#endif

#include <iostream.h>
#include <stdio.h>
#include <unistd.h>  /* unlink */
#include <sys/wait.h>  /* waidpid */
#ifndef IRIX
#include <stdlib.h>  /* exit */
#endif

#include <sys/time.h>  /* gettimeofday */

/* activate this if gettimeofday is not declared in in <sys/time.h> */
// use man gettimeofday to find out the correct prototype
#if defined(SUN4) || defined(SUN4_GNU) || defined(PMAX) || defined(PMAX_GNU)
extern "C" {
  int gettimeofday (struct timeval*, struct timezone*);
}
#endif
#if defined(SCO)
extern "C" {
  int gettimeofday (struct timeval*);
}
#endif

/* activate this if fsync is not declared in <unistd.h> */
#if defined(SUN4) || defined(SUN4_GNU) || defined(PMAX) || defined(PMAX_GNU)
extern "C" {
  extern int fsync(int);
}
#endif


//#define LANGUAGETEST

declareIOCallback(SceneWindow)
implementIOCallback(SceneWindow)


/*** the global language ***/

HgLanguage::Language slanguage = HgLanguage::Default;



/*** readSysClock ***/
// get current time tick in milliseconds

unsigned long readSysClock ()
{
  struct timeval tv;

#if defined(SCO)
   gettimeofday (&tv);
#else
  gettimeofday (&tv, 0);
#endif

//cerr << "Clocktick: " << tv.tv_sec << " seconds and " << tv.tv_usec << " microseconds" << endl;

  return (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}


/*** SceneAppWindow ***/

/* see configuration file gl.conf for spaceball support */


class SceneAppWindow: public ApplicationWindow
{
  public:
    SceneAppWindow (SceneWindow* scene, Glyph* body);

    // ApplicationWindow
    void bind ();

    // Window
    void set_attributes ();
    void property_notify (const Event&);

#ifdef SPACEBALL
    // Window
    void client_event (const Event&);
#endif

  private:
    SceneWindow* scene_;
    int sbpresent_;
};


SceneAppWindow::SceneAppWindow (SceneWindow* scene, Glyph* body)
: ApplicationWindow (body)
{
  scene_ = scene;
  sbpresent_ = 0;
}


void SceneAppWindow::bind ()
{
  ApplicationWindow::bind ();

#ifdef SPACEBALL
  sbpresent_ = SceneWindow::init_spaceball (this);

  DEBUGNL ("spaceball present: " << sbpresent_);
#endif

  scene_->acceptRemoteRequests (this, 1);
}


void SceneAppWindow::set_attributes ()
{
  ApplicationWindow::set_attributes ();

  // require PropertyNotify events
  Window::rep ()->xattrs_.event_mask |= PropertyChangeMask;
}


void SceneAppWindow::property_notify (const Event& e)
{
  scene_->propertyRemoteRequest (this, e);
}


#ifdef SPACEBALL
void SceneAppWindow::client_event (const Event& e)
{
  // spaceball events
  if (sbpresent_)
    scene_->ihandler ()->spaceball (e);
}
#endif



/*** ge_Object ***/
// Glyph for drawing with ge3d (the body of GEContext)


class ge_Object: public Glyph
{
  public:
    ge_Object (SceneWindow* scene)
    { scene_ = scene;
    }

    virtual void request (Requisition&) const;                       // request my desired geometry
    virtual void allocate (Canvas*, const Allocation&, Extension&);  // told my actual geometry
    virtual void draw (Canvas*, const Allocation&) const;            // draw myself

  private:
    SceneWindow* scene_;

};  // ge_Object


void ge_Object::request (Requisition& req) const
// specify my desired geometry (in points)
{
//  cout << "ge_Object::request" << endl ;

  Requirement rx (640, fil, 320, 0);
  Requirement ry (480, fil, 240, 0);

  req.require (Dimension_X, rx);
  req.require (Dimension_Y, ry);

} // request


void ge_Object::allocate (Canvas* c, const Allocation& a, Extension& e)
{ // told my actual allocation
  e.merge (c, a);

//   cout << "ge_Object::allocate" << endl ;
// 
//   //cout << "  c = " << (long)c << endl ;
//   cout << "- size: (" << c->width () << ", " << c->height () << ")" << endl;
//   cout << "- position: (" << a.left () << ", " << a.bottom () << "), (" 
//        << a.right () << ", " << a.top () << ")" << endl;

} // allocate


void ge_Object::draw (Canvas*, const Allocation& a) const
{ // draw the scene
//DEBUG ("[-draw3d-]");

//cerr << "[draw]";

  // damaged region was already determined by gecontext
  scene_->drawAll (a);  // draw scene and UI parts (for fly, fly to)

} // draw



/*** SceneWindow ***/

SceneWindow::SceneWindow (Session* session, const char* wintitle)
: Scene3D ()
{
  navmode_ = -1;  // undefined
  themenus_ = 0;
  progrind_ = 0;

  // internal default values (overridden by Xdefaults)
  anchormotion_ = 1;  // default: enable motion
  velocitycontrol_ = 1;  // default: control speed
  selectionmode_ = SelectObject;  // default: select objects
  measuretime_ = 0;  // default: no framerate feedback

  gecontext_ = 0;
  hg3dinput_ = 0;
  appwin_ = 0;

  feedbacktime_ = 1000000.0;  // ms (1 s)
  feedbackhandler_ = new IOCallback(SceneWindow) (this, &SceneWindow::doEndFeedback);

  makeappwin (session, wintitle);

  setdefaults ();

// must be done by user of this class
// appwin_->map ();
}


SceneWindow::~SceneWindow ()
{
  delete feedbackhandler_;
}


void SceneWindow::clear ()
{
  if (themenus_)
  {
    themenus_->clearViewpoints ();
    // clear error messages
    TextBrowser* errbrowser = themenus_->errorBrowser ();
    if (errbrowser)
      errbrowser->clearText ();
    // might call themenus_->sceneChanged ();
  }

  Scene3D::clear ();
}


void SceneWindow::redraw ()
{
  if (gecontext_)
    gecontext_->redraw (data () && (currentMode () != ge3d_wireframe));
}


void SceneWindow::reset ()
{
  if (hg3dinput_)
    hg3dinput_->reset ();
  redraw ();
}


void SceneWindow::setCamera (const point3D& position, const point3D& lookat, const point3D* up)
{
  Camera* cam = getCamera ();
  if (!cam)
    return;

  cam->setposlookup (position, lookat, up);
  storeCamera ();

  // no redraw done here
}


void SceneWindow::registerCamera (
  const char* name, QvPerspectiveCamera* pcam, QvOrthographicCamera* ocam
)
{
  if (themenus_)
    themenus_->addViewpoint (name, pcam, ocam);
}


/* unbind no longer necessary with InterViews patch */
// void SceneWindow::unmap ()
// {
//   if (appwin_ && gecontext_)
//   {
//     appwin_->unmap ();
//     gecontext_->unbind ();
//     appwin_->unbind ();  
//   }
// }


int SceneWindow::init_spaceball (Window* win)
{
#ifdef SPACEBALL
  // check if spaceball is present
  XDisplay* xdpy = win->rep ()->display_->rep ()->display_;
  XWindow xwin = win->rep ()->xwindow_;

  int rval = SPW_InputCheckForSpaceball (xdpy, xwin, "");
  if (rval)  // flush driver copyright message
    fflush (stdout);

  return rval;
#else
  return 0;
#endif
}



void SceneWindow::drawAll (const Allocation& a)
{
  // should only be called by gecontext
  // to ensure proper initialisation of 3D graphics

  static unsigned long milliseconds = 0;

  if (measuretime_ && !milliseconds)  // first time measurement
    milliseconds = readSysClock ();


  unsigned slowdraw = !interact () && interactRelevant ();

  if (slowdraw)
    gecontext_->pushCursor (HgCursors::instance ()->hourglass ());

  draw ();  // scene itself

  // draw UI parts (for fly, fly to)
  if (hg3dinput_)
    hg3dinput_->drawui (a);

  if (slowdraw)
    gecontext_->popCursor ();

  if (measuretime_ && progrind_)
  {
    unsigned long newtime = readSysClock ();
    milliseconds = newtime - milliseconds;
    int fps = milliseconds ? int (1000 / milliseconds) : 1000;
    // do not bother on more than 100 fps (usually clock inaccuracy)
    char framerate [128];
    sprintf (framerate,
      "%s: %4.2f s (%s%d %s)\n",
      STranslate::str (STranslate::StatuslineLASTFRAME),
      milliseconds / 1000.0,
      fps > 100 ? "> " : "", fps > 100 ? 100 : fps,
      STranslate::str (STranslate::StatuslineFPS)
    );
    progrind_->readyMessage (framerate);
    milliseconds = newtime;
  }
} // drawAll


void SceneWindow::updateLanguage ()
{
  if (themenus_)
    themenus_->updateLanguage ();
  if (progrind_)
  { progrind_->readyMessage (STranslate::str (STranslate::ProgressREADY));
    showNumFaces ();  // update status information
  }
}


void SceneWindow::showNumFaces ()
{
  if (!progrind_)
    return;

  char numstr [32];
  char msgstring [128];

  // show number of faces (polygons) when reading a new scene (or changing language)
  unsigned long numpolys = getNumFaces ();
  unsigned long numprims = getNumPrimitives ();
  const char* format = formatName ();

  *msgstring = '\0';
  if (format)
    strcat (msgstring, format);
  if (numpolys)
  {
    sprintf (numstr, ", %ld ", numpolys);
    strcat (msgstring, numstr);
    strcat (msgstring, STranslate::str (STranslate::StatuslinePOLYGONS));
  }
  if (numprims)
  {
    sprintf (numstr, ", %ld ", numprims);
    strcat (msgstring, numstr);
    strcat (msgstring, STranslate::str (STranslate::StatuslinePRIMITIVES));
  }

  progrind_->readyMessage (msgstring, 1);  // show scene type/size in right field
}


void SceneWindow::setNavigationMode (int newmode)
{
  int oldmode = navmode_;
  if (newmode == oldmode)
    return;
  navmode_ = newmode;

  // redraw gecontext if old or new mode does ui drawings
  if (gecontext_ && hg3dinput_)
    if (hg3dinput_->reset () || modeDrawsUI (oldmode) || modeDrawsUI (newmode))
      redraw ();
  // TODO: no redraw necessary on change from flip to walk

  // the set of displayed buttons does never change, which simpliefies much
}


void SceneWindow::navigationHint (int smode)
{
  // only hints are flip and the walk "family"
  if (smode == Scene3D::nav_flip)
  { DEBUGNL ("navigationHint: flip");
    if (navmode_ != NavMode::flip_obj)
      themenus_->navigationMode (NavMode::flip_obj);
  }
  else // walk
  { DEBUGNL ("navigationHint: walk");
    if (navmode_ == NavMode::flip_obj)
      themenus_->navigationMode (NavMode::heads_up);
  }
}


// showFramerate
// set time and framerate feedback

void SceneWindow::showFramerate (int flag)
{
  measuretime_ = flag;
  if (!flag)
    showTitle ();
}


// activateAnchor
// activates the current selected anchor

void SceneWindow::activateAnchor ()
{
  QvWWWAnchor* anchor = selectedAnchor ();  // VRML
  if (anchor)
  {
    beginFeedback ();
    int mustredraw = followLink (anchor);
    if (mustredraw)
      redraw ();
    endFeedback ();
  }

  GeometricObject* selobj = selectedObj ();
  if (!selobj)
    return;

  const SourceAnchor* sanchor = selobj->getCurrentAnchor ();  // SDF
  if (!sanchor)
    return;

  beginFeedback ();

  int mustredraw = followLink (selobj, sanchor);
  // feedback (busy cursor) now done by followLink

  if (mustredraw)  // redraw if new scene was loaded; no change of anchor highlighting
    redraw ();

  endFeedback ();

} // activateAnchor


void SceneWindow::beginFeedback ()
{
  if (gecontext_)
    gecontext_->setCursor (HgCursors::instance ()->hourglass ());
}


void SceneWindow::endFeedback ()
{
  if (gecontext_)
    gecontext_->resetCursor ();
}


void SceneWindow::statusMessage (const char* hint)
{
  if (progrind_)
    progrind_->readyMessage (hint);
}


void SceneWindow::showTitle ()
{
  if (progrind_)
    progrind_->readyMessage (title_.string ());
}


void SceneWindow::giveNavigationHint ()  // short help line
{
  if (!progrind_)
    return;

  const char* str = 0;

  switch (navmode_)
  {
    case NavMode::flip_obj:
      str = STranslate::str (STranslate::NavHintFLIPOBJECT);
    break;
    case NavMode::walk_around:
      str = STranslate::str (STranslate::NavHintWALK);
    break;
    case NavMode::fly_1:
      str = STranslate::str (STranslate::NavHintFLY);
    break;
    case NavMode::fly_2:
      str = STranslate::str (STranslate::NavHintFLYTO);
    break;
    case NavMode::heads_up:
      str = STranslate::str (STranslate::NavHintHEADSUP);
    break;
  }
  progrind_->readyMessage (str);
}


// handleKeystroke
// gets and handles keystrokes from scene window (and soon from application widow too)

void SceneWindow::handleKeystroke (const Event& e)
{
  if (!themenus_)
    return;

  unsigned long keysym = e.keysym ();
//   char key = '\0';
//   e.mapkey (&key, 1);

  int ctrl = e.control_is_down ();
  int shift = e.shift_is_down ();

  if (keysym >= XK_1 && keysym <= XK_9)  // '1'..'9'
  {
    themenus_->selectViewpoint (keysym - XK_1);  // assumed to be contiguous
  }
  else
  switch (keysym)  // cursor and function keys
  {
    // (letters)

    case XK_a:
      if (ctrl)
        themenus_->toggleAntialiasing ();
    break;

    case XK_b:
      if (!ctrl)
        back ();
    break;

    case XK_f:
      if (ctrl)
        themenus_->displayMode (ge3d_flat_shading, shift);
      else
        forward ();
    break;

    case XK_h:
      if (ctrl)
        themenus_->displayMode (ge3d_hidden_line, shift);
      else if (shift)
        hold ();
      else
        history ();
    break;

    case XK_l:
      themenus_->levelView ();
    break;

    case XK_r:
      themenus_->resetView ();
    break;

    case XK_s:
      if (ctrl)
        themenus_->displayMode (ge3d_smooth_shading, shift);
    break;

    case XK_t:
      if (ctrl && !shift)
        themenus_->displayMode (ge3d_texturing, 0);
    break;

    case XK_u:
      if (ctrl)
        themenus_->displayMode (Scene3D::same_mode, 1);
      else
        themenus_->untiltView ();
    break;

    case XK_w:
      if (ctrl)
        themenus_->displayMode (ge3d_wireframe, shift);
    break;

    case XK_x:
      if (ctrl)
        Session::instance ()->quit ();
    break;

    // (special keys)

    case XK_Delete:
      deleteSourceAnchor ();
    break;

    // case XK_Tab:
    // nextAnchor
    // break;

    case XK_Return:
      activateAnchor ();
    break;

    // (function keys)

    case XK_F1:
      showHelp ();
    break;
    case XK_F2:
      themenus_->saveOriginalScene ();
    break;
    case XK_F3:
      themenus_->openFile ();
    break;
    case XK_F4:
      themenus_->navigationMode (NavMode::flip_obj);
    break;
    case XK_F5:
      themenus_->navigationMode (NavMode::walk_around);
    break;
    case XK_F6:
      themenus_->navigationMode (NavMode::fly_1);
    break;
    case XK_F7:
      themenus_->navigationMode (NavMode::fly_2);
    break;
    case XK_F8:
      themenus_->navigationMode (NavMode::heads_up);
    break;
    case XK_F9:
      themenus_->toggleShowAnchors ();
    break;

  } // switch keysym

} // handleKeystroke



// *** overridden virtual functions of Scene3D ***


// readSceneFILE (FILE*)
// caller responsible for closing file
// does decompression and calles readSceneU

#define COMPRESSION_MAGIC 0x1f

int SceneWindow::readSceneFILE (FILE* file)
{
  // check for gzipped/compressed files
  int chr = getc (file);

  // normal data
  if (chr != COMPRESSION_MAGIC)
  {
    ungetc (chr, file);
    return readSceneU (file);
  }

  // handle compressed data
  chr = getc (file);  // compression method

  if (! (chr == 0x8b || chr == 0x9d))  // gzip or compress
  { HgMessage::error ("bad input file: unknown compression method (supported: gzip, compress)");
    return 1;  // error
  }

  // copy the file onto a temporary file
  // TODO: omit this step if possible (i.e. the data comes already from a file)
  // TODO: respect tempdir resource (use e.g. tempname of hgmpeg.C)
  char tempfilename[256];
  tmpnam (tempfilename);
  DEBUGNL ("copying compressed input to temporary file " << tempfilename);
  FILE* tempfile = fopen (tempfilename, "w");
  if (!tempfile)
  { HgMessage::error ("could not create temp file - cannot apply gunzip");
    return 1;  // error
  }

  // copy input to temporary file
  putc (COMPRESSION_MAGIC, tempfile);  // the two chars already read from file
  putc (chr, tempfile);

  const int copybufsize = 4096;
  char copybuf [copybufsize];
  while ((chr = fread (copybuf, 1, copybufsize, file)) > 0)
    fwrite (copybuf, 1, chr, tempfile);

  // ensure data is written to disk
  fflush (tempfile);
  fsync (fileno (tempfile));
  fclose (tempfile);

  DEBUGNL ("temp file " << tempfilename << " written");

#define CLEANUP unlink (tempfilename);

  // run gunzip on the file
#if 1
  // had troubles with popen on ALPHA_GNU, so do its work by hand:
  int fds[2];
  if (pipe (fds) < 0)
  { HgMessage::error ("could not establish pipe to run gunzip");
    CLEANUP
    return 1;
  }

  DEBUGNL ("forking to call gunzip");
  int pid = fork ();
  if (pid < 0)
  { HgMessage::error ("fork to run gunzip failed");
    CLEANUP
    return 1;
  }
  else if (pid == 0)  // child 
  { DEBUGNL ("child process: executing gunzip");
    ::close (fds[0]) ;
    ::dup2 (fds[1], STDOUT_FILENO);
    execlp ("gunzip", "gunzip", "-c", tempfilename, 0);  // no return if successful
    HgMessage::error ("could not start gunzip");
    exit (1);
  }
  // else: parent

  DEBUGNL ("parent process: reading stdout of child");
  ::close (fds[1]);
  FILE* readfile = fdopen (fds[0], "r");
  if (!readfile)
  { HgMessage::error ("could not read output of gunzip");
    CLEANUP
    return 1;
  }

  // do not remove the following two lines (necessary on ALPHA_GNU)
  chr = getc (readfile);
  ungetc (chr, readfile);

  int rval = readSceneU (readfile);

  fclose (readfile);
  int childstatus;
  waitpid (pid, &childstatus, 0);  // prevent zombie

#else

  char commandline [256];
  sprintf (commandline, "gunzip -c %s", tempfilename);
  DEBUGNL ("piped command: " << commandline);

  FILE* subprocess = popen (commandline, "r");
  if (!subprocess)
  { HgMessage::error ("decompressing file with \"gunzip -c\" failed.");
    CLEANUP
    return 1;  // error
  }

  // read the uncompressed file through a pipe
  int rval = readSceneU (subprocess);

  pclose (subprocess);

#endif  /* fork/popen */

  DEBUGNL ("removing temporary file" << tempfilename);
  unlink (tempfilename);  // delete tempfile

  return rval;

} // readSceneFILE


// readSceneU (FILE*)
// read uncompressed scene data (only called by readSceneFILE)
// caller responsible for closing file

int SceneWindow::readSceneU (FILE* file)
{
  int rval;

  if (progrind_ && gecontext_)  // should always be true
  {
    progrind_->workingState ();
    gecontext_->setCursor (HgCursors::instance ()->animatedBusy ());
    rval = Scene3D::readSceneFILE (file);
    gecontext_->resetCursor ();
    progrind_->readyState ();
    showNumFaces ();
  }
  else
    rval = Scene3D::readSceneFILE (file);

  if (themenus_)
    themenus_->sceneChanged ();

  return rval;
} // readSceneU


// int SceneWindow::readInlineVRML (QvWWWInline* node, const char* filename)
// {
// // TODO: uncompress input and call parent function

//   return Scene3D::readInlineVRML (node, file);

// } // readInlineVRML


void SceneWindow::errorMessage (const char* msg) const
{
  if (!themenus_)  // should not happen
  { Scene3D::errorMessage (msg);
    return;
  }

  TextBrowser* errbrowser = themenus_->errorBrowserInstance ();

//cerr << "*** Message for error browser: " << msg;

  errbrowser->appendText (msg);  // may consist of multiple lines
}



// read a texture image from a file

void SceneWindow::loadTextureFile (Material* mat, const char* filename)
{
  if (!mat || !filename || !*filename)
    return;

  DEBUGNL ("Scene: loading texture file " << filename);

  if (progrind_)
  { progrind_->workingMessage (STranslate::str (STranslate::ProgressREADINGTEXTURE));
    progrind_->workingState ();
  }

  // Raster* image = TIFFRaster::load (filename);

  Raster* image;
  Bitmap* bitmap;

  HgRaster::load (filename, image, bitmap, nil, false);  // dithering flag irrelevant (only used on drawing)
  Resource::ref (image);
  Resource::ref (bitmap);

  if (!image)  // currently only able to handle images (no bitmaps)
  { cerr << "Harmony Scene Viewer: error on loading texture image " << filename << endl;
    mat->texhandle (0);  // failure on read
  }
  else
  {
    // cerr << "got an image with size (" << image->pwidth () << " x " << image->pheight () << ")" << endl;
/*
const unsigned char* pixel = image->getRGBarray () + 51320 * 3;
cerr << "pixel at (120, 200) (counted from 0, index 51320): ("
     << (int) pixel [0] << ", " << (int) pixel [1] << ", " << (int) pixel [2] << ")" << endl;
*/
    int th = ge3dCreateTexture (
      (int) image->pwidth (), (int) image->pheight (),
      image->getRGBarray (), ge3d_ubyte_RGB_TB);
    mat->texhandle (th);
    image->freeRGBarray ();
  }

  Resource::unref (image);
  Resource::unref (bitmap);

  redraw ();

  if (progrind_)
    progrind_->readyState ();

} // loadTextureFile


void SceneWindow::selectionChanged ()
{
  if (themenus_)
    themenus_->selectionChanged ();
}


void SceneWindow::progress (float p, int id)  // update progress bar
{
  // id as defined in scene3d.h - scene itself should not know about class STranslate
  // transformation table to id of STranslate (be sure to match order!)
  static const STranslate::SStrSym stranID [progr_unchanged] =
  { STranslate::ProgressREADINGACTORS,
    STranslate::ProgressREADINGPOSITIONS,
    STranslate::ProgressREADINGCAMERAS,
    STranslate::ProgressREADINGMATERIALS,
    STranslate::ProgressREADINGLIGHTS,
    STranslate::ProgressPROCESSCAMERAS,
    STranslate::ProgressREADINGOBJECTS,
    STranslate::ProgressREADINGVRML
  };

  if (progrind_ && gecontext_)
  { gecontext_->setCursor (HgCursors::instance ()->animatedBusy ());
    if (id < progr_unchanged)
      progrind_->workingMessage (STranslate::str (stranID [id]));
    progrind_->setProgress (p);  // immediate redraw
  }
} // progress



/*** language tests ***/

#ifdef LANGUAGETEST
class LangAction: public Action
{
  public:
    LangAction::LangAction (HgLanguage::Language lang)
    { lang_ = lang; }

    void execute ()
    { slanguage = lang_;
      SceneMenus::update ();
    }

  private:
    HgLanguage::Language lang_;
};
#endif



/*** About3dObject ***/
/*
class About3dObject: public Glyph
{
  public:
    virtual void allocate (Canvas*, const Allocation&, Extension&);
    virtual void draw (Canvas*, const Allocation&) const;
};


void About3dObject::allocate (Canvas* c, const Allocation& a, Extension& e)
{
  e.set (c, a);
}


void About3dObject::draw (Canvas*, const Allocation&) const
{
  static point3D position = { 0, 0, 1 };
  static point3D lookat = { 0, 0, 0 };
  static point3D vertexlist [] =
  { { -1, -1, 0 }, { 1, -1, 0 }, { 1, 1, 0 }, { -1, 1, 0} };
  static colorRGB colorlist [] =
  { { 0, 1, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, { 0, 0, 1 } };

  ge3d_clearscreen ();
  ge3d_setmode (ge3d_smooth_shading);

  ge3d_ortho_cam (position, lookat, 2, 2, 0, 2);
  ge3dShadedPolygon (4, vertexlist, colorlist);
}


class ShowAbout3dObject: public Action
{
  public:
    void execute ();
};


void ShowAbout3dObject::execute ()
{
  ApplicationWindow* win3d = new ApplicationWindow ( 
    new GEContext (
      new About3dObject ()
    )
  );

  win3d->map ();
}
*/
/*
static void setabout3d (WidgetKit& kit, const LayoutKit& layout)
{
  Glyph* about3d =
    layout.flexible (
      kit.push_button ("3D!", new ShowAbout3dObject ())
    );

  PolyGlyph* glyphs = new PolyGlyph ();
  glyphs->append (about3d);
  About::setGlyphs (glyphs);
}
*/


/*** makeappwin ***/

void SceneWindow::makeappwin (Session* session, const char* wintitle)
{
  WidgetKit& kit = *WidgetKit::instance ();
  const LayoutKit& layout = *LayoutKit::instance ();
  Style* style = kit.style ();

  // *** language ***

  String langstr;
  if (style->find_attribute ("language", langstr))
  {
    //cerr << "language attribute: " << langstr.string () << endl;
    RString langrstr (langstr.string ());
    RString langword;
    int index = 0;
    while (langrstr.gWordChar (index, '|', langword))
    {
      //cerr << "language word: " << langword << endl;
      HgLanguage::Language lres = HgLanguage::nameToEnum (langword, HgLanguage::NumLanguages);
      if (lres != HgLanguage::NumLanguages)
      {
        slanguage = lres;  // found supported language
        break;
      }
    }
  }
  //cerr << "language set to: " << slanguage << endl;

  // *** window visual ***
  if (GEContext::implementationHints () & GEContext::impl_requiresTrueColor)
  { String name = "visual";
    String value = "TrueColor";
    style->attribute (name, value);
  }

  // *** window title ***
  session->style ()->attribute ("name", wintitle);
  session->style ()->attribute ("iconName", "VRweb");

  // be careful: segmentation fault when calling any GL function before window is mapped
  // ge3d_init_ ();


  // *** buttons (navigation modes) ***
/*
  WidgetKit& olkit = *(new OLKit);  // used for buttons (look better)
  olkit.style (kit.style ());  // inherit style (Harmony.Scene)
  // bad: olkit makes itself kit.instance [WidgetKitImpl::kit_]
*/

  // the buttons themselves are managed by SceneMenus
  Patch* buttonpatch = new Patch (nil);


  // *** progress indicator ***
  static float fieldwidths [] = { 0.7, 0.3 };

  progrind_ = new ProgressIndicator (
    slanguage,
    2,  // 2 fields
    fieldwidths,
    0, 0
  );
  progrind_->readyMessage (STranslate::str (STranslate::ProgressREADY));


  // *** glwindow (with input handler) ***

  ge_Object* geobject = new ge_Object (this);

  hg3dinput_ = new HG3dInputHandler (
    geobject,
    style,
    this
  );

  gecontext_ = new GEContext (hg3dinput_);

  hg3dinput_->context (gecontext_);  // for redraw

  // cerr << "window told by gecontext: " << gecontext_->window () << endl;
  // NULL, because set in allocate (and not in constructor)!


  // *** application window ***

  Patch* patchformenus = new Patch (nil);

  appwin_ = new SceneAppWindow (
    this,
    new Background (
      layout.vbox (
        patchformenus,
        kit.outset_frame (layout.margin (buttonpatch, 1.0)),
        gecontext_,
        progrind_
#ifdef LANGUAGETEST
, layout.hbox (
  kit.push_button ("english", new LangAction (HgLanguage::English)),
  kit.push_button ("german",  new LangAction (HgLanguage::German)),
  kit.push_button ("styrian",  new LangAction (HgLanguage::Styrian)),
  layout.hglue ()
)
#endif
      ),
      kit.background ()
    )
  ); // appwin

  appwin_->style (style);  // use style "Scene"

  // highlighting colours are now managed by scene itself
  // use X attribute pointerColor to change color of cursor


  // *** the menus ***

  themenus_ = new SceneMenus (
    patchformenus, buttonpatch, this, gecontext_, hg3dinput_, &kit, &layout
  );
  themenus_->createMenus ();
  themenus_->createButtons ();
  themenus_->updateDialogs ();

} // makeappwin



static int getDrawingMode (const char* str, int def)
{
  // little helper to get a drawing mode from a string
  if (!str || !*str)
    return def;

  if (strstr (str, "wire"))             // wireframe
    return ge3d_wireframe;
  if (strstr (str, "hidden"))           // hidden line
    return ge3d_hidden_line;
  if (strstr (str, "flat"))             // flat shading
    return ge3d_flat_shading;
  if (strstr (str, "smooth"))           // smooth shading
    return ge3d_smooth_shading;
  if (strstr (str, "textur"))           // texturing
    return ge3d_texturing;
  if (strstr (str, "same"))             // same mode
    return Scene3D::same_mode;

  return def;

} // getDrawingMode



/*** setdefaults ***/

void SceneWindow::setdefaults ()
{
  // load Xdefaults to override internal default values

  WidgetKit& kit = *WidgetKit::instance ();
  Style* style = kit.style ();

  double val_double;
  String strval;

  if (!themenus_)
    return;

  // how "true" is "true colour"? not true enough for X! Consider
  // requiring #BBBBBB as background - XParseColor will treat it as
  // #BB00BB00BB00 (!) and return the next possible "true" colour,
  // which is #BABABABABABA.

  kit.begin_style ("3D");  // 3D.background
  {
    float r, g, b;
    kit.background ()->intensities (r, g, b);  // on default display
    // DEBUGNL ("setting background to (" << r << ", " << g << ", " << b << ")");

    initRGB (col_background, r, g, b);
    ge3dBackgroundColor (&col_background);
  }
  kit.end_style ();  // "3D"

  int slow = GEContext::implementationHints () & GEContext::impl_slow;

  strval = "";
  style->find_attribute ("drawmode", strval);  // drawing mode
  themenus_->displayMode (getDrawingMode (strval.string (),
    slow ? ge3d_flat_shading : ge3d_smooth_shading), 0);

  strval = "";
  style->find_attribute ("intdrawmode", strval);  // interactive drawing mode
  themenus_->displayMode (getDrawingMode (strval.string (),
    slow ? (int) ge3d_wireframe : (int) Scene3D::same_mode), 1);

  if (style->value_is_on ("antialiasing") && !themenus_->antialiasing ())
    themenus_->toggleAntialiasing ();

  if (style->find_attribute ("minBrightnessAnchors", val_double))
    Scene3D::anchors_minbrightness = val_double;

  if (style->find_attribute ("maxBrightnessNonAnchors", val_double))
    Scene3D::nonanch_maxbrightness = val_double;

  if (style->find_attribute ("activateFeedbackTime", val_double))
    feedbacktime_ = val_double * 1000000.0;  // ms

  if (style->find_attribute ("URL", strval))
    currentURL (strval.string ());

  if (style->find_attribute ("helpDir", strval))
    *helpdir_ = strval.string ();

  useMosaic (style->value_is_on ("mosaic"));

  themenus_->setCollisionDetection (style->value_is_on ("collisionDetection"));
  if (style->find_attribute ("collisionDistance", val_double))
    Camera::collisionDistance_ = val_double;

  // navigation constants
  HG3dInputHandler::loadXdefaults (style);

} // setdefaults
