// @(#)root/x3d:$Name:  $:$Id: TViewerX3D.cxx,v 1.9 2004/06/01 14:36:41 rdm Exp $
// Author: Rene Brun   05/09/99
/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// TViewerX3D                                                           //
//                                                                      //
// C++ interface to the X3D viewer                                      //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

#include "TViewerX3D.h"
#include "TAtt3D.h"
#include "X3DBuffer.h"
#include "TVirtualPad.h"
#include "TView.h"
#include "TMath.h"
#include "TROOT.h"
#include "TClass.h"

#include "TRootHelpDialog.h"
#include "TGClient.h"
#include "TGCanvas.h"
#include "TGMenu.h"
#include "TGWidget.h"
#include "TGMsgBox.h"
#include "TVirtualX.h"

#include "HelpText.h"

const char gHelpX3DViewer[] = "\
     PRESS \n\
     \tw\t--- wireframe mode\n\
     \te\t--- hidden line mode\n\
     \tr\t--- hidden surface mode\n\
     \tu\t--- move object down\n\
     \ti\t--- move object up\n\
     \to\t--- toggle controls style\n\
     \ts\t--- toggle stereo display\n\
     \td\t--- toggle blue stereo view\n\
     \tf\t--- toggle double buffer\n\
     \th\t--- move object right\n\
     \tj\t--- move object forward\n\
     \tk\t--- move object backward\n\
     \tl\t--- move object left\n\
     \tx a\t--- rotate about x\n\
     \ty b\t--- rotate about y\n\
     \tz c\t--- rotate about z\n\
     \t1 2 3\t--- autorotate about x\n\
     \t4 5 6\t--- autorotate about y\n\
     \t7 8 9\t--- autorotate about z\n\
     \t[ ] { }\t--- adjust focus\n\n\
     HOLD the left mouse button and MOVE mouse to ROTATE object\n\n\
";


extern "C" {
  Window_t x3d_main(Float_t *longitude, Float_t *latitude, Float_t *psi,
                    Option_t *option, Window_t parent);
  void     x3d_set_display(Display_t display);
  int      x3d_dispatch_event(Handle_t event);
  void     x3d_update();
  void     x3d_get_position(Float_t *longitude, Float_t *latitude, Float_t *psi);
  int      x3d_exec_command(int px, int py, char command);
  void     x3d_terminate();
}


// Canvas menu command ids
enum EX3DViewerCommands {
   kFileNewViewer,
   kFileSave,
   kFileSaveAs,
   kFilePrint,
   kFileCloseViewer,

   kHelpAbout,
   kHelpOnViewer
};

Bool_t TViewerX3D::fgActive = kFALSE;


//////////////////////////////////////////////////////////////////////////
//                                                                      //
// TX3DContainer                                                        //
//                                                                      //
// Utility class used by TViewerX3D. The TX3DContainer is the frame     //
// embedded in the TGCanvas widget. The X3D graphics goes into this     //
// frame. This class is used to enable input events on this graphics    //
// frame and forward the events to X3D.                                 //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

class TX3DContainer : public TGCompositeFrame {
private:
   TViewerX3D  *fViewer;    // pointer back to viewer imp
public:
   TX3DContainer(TViewerX3D *c, Window_t id, const TGWindow *parent);

   Bool_t  HandleButton(Event_t *ev)
                { x3d_dispatch_event(gVirtualX->GetNativeEvent());
                  return fViewer->HandleContainerButton(ev); }
   Bool_t  HandleConfigureNotify(Event_t *ev)
                { TGFrame::HandleConfigureNotify(ev);
                  return x3d_dispatch_event(gVirtualX->GetNativeEvent()); }
   Bool_t  HandleKey(Event_t *)
                { return x3d_dispatch_event(gVirtualX->GetNativeEvent()); }
   Bool_t  HandleMotion(Event_t *)
                { return x3d_dispatch_event(gVirtualX->GetNativeEvent()); }
   Bool_t  HandleExpose(Event_t *)
                { return x3d_dispatch_event(gVirtualX->GetNativeEvent()); }
   Bool_t  HandleColormapChange(Event_t *)
                { return x3d_dispatch_event(gVirtualX->GetNativeEvent()); }
};

//______________________________________________________________________________
TX3DContainer::TX3DContainer(TViewerX3D *c, Window_t id, const TGWindow *p)
   : TGCompositeFrame(gClient, id, p)
{
   // Create a canvas container.

   fViewer = c;

   /* already done in x3d InitDisplay()
   gVirtualX->GrabButton(fId, kAnyButton, kAnyModifier,
                    kButtonPressMask | kButtonReleaseMask,
                    kNone, kNone);

   gVirtualX->SelectInput(fId, kKeyPressMask | kExposureMask | kPointerMotionMask |
                     kStructureNotifyMask);
   */
}

ClassImp(TViewerX3D)

//______________________________________________________________________________
TViewerX3D::TViewerX3D(TVirtualPad *pad, Option_t *option, const char *title,
                       UInt_t width, UInt_t height)
   : TGMainFrame(gClient->GetRoot(), width, height)
{
   // Create ROOT X3D viewer.

   fPad = 0;
   if (fgActive) {
      Int_t retval;
      new TGMsgBox(gClient->GetRoot(), gClient->GetRoot(),
                   "X3D Viewer Warning", "Can have only one X3D viewer active",
                   kMBIconExclamation, kMBOk, &retval);
      return;
   }

   fPad    = pad;
   fOption = option;
   fX3DWin = 0;

   CreateViewer(title);
   if (!fX3DWin) return;

   Resize(width, height);

   x3d_update();

   fgActive = kTRUE;
}

//______________________________________________________________________________
TViewerX3D::TViewerX3D(TVirtualPad *pad, Option_t *option, const char *title,
                       Int_t x, Int_t y, UInt_t width, UInt_t height)
   : TGMainFrame(gClient->GetRoot(), width, height)
{
   // Create ROOT X3D viewer.

   fPad = 0;
   if (fgActive) {
      Int_t retval;
      new TGMsgBox(gClient->GetRoot(), gClient->GetRoot(),
                   "X3D Viewer", "Can have only one X3D viewer active",
                   kMBIconExclamation, kMBOk, &retval);
      return;
   }

   fPad    = pad;
   fOption = option;
   fX3DWin = 0;

   CreateViewer(title);
   if (!fX3DWin) return;

   MoveResize(x, y, width, height);
   SetWMPosition(x, y);

   x3d_update();

   fgActive = kTRUE;
}

//______________________________________________________________________________
TViewerX3D::~TViewerX3D()
{
   // Delete ROOT X3D viewer.

   if (!fPad) return;

   DeleteX3DWindow();

   delete fContainer;
   delete fCanvas;
   delete fFileMenu;
   delete fHelpMenu;
   delete fMenuBar;
   delete fMenuBarLayout;
   delete fMenuBarItemLayout;
   delete fMenuBarHelpLayout;
   delete fCanvasLayout;

   fgActive = kFALSE;
}

//______________________________________________________________________________
void TViewerX3D::CreateViewer(const char *name)
{
   // Create the actual canvas.

   // Create menus
   fFileMenu = new TGPopupMenu(fClient->GetRoot());
   fFileMenu->AddEntry("&New Viewer",         kFileNewViewer);
   fFileMenu->AddSeparator();
   fFileMenu->AddEntry("Save",                kFileSave);
   fFileMenu->AddEntry("Save As...",          kFileSaveAs);
   fFileMenu->AddSeparator();
   fFileMenu->AddEntry("&Print...",           kFilePrint);
   fFileMenu->AddSeparator();
   fFileMenu->AddEntry("&Close Viewer",       kFileCloseViewer);

   //fFileMenu->DefaultEntry(kFileNewViewer);
   fFileMenu->DisableEntry(kFileNewViewer);
   fFileMenu->DisableEntry(kFileSave);
   fFileMenu->DisableEntry(kFileSaveAs);
   fFileMenu->DisableEntry(kFilePrint);

   fHelpMenu = new TGPopupMenu(fClient->GetRoot());
   fHelpMenu->AddEntry("&About ROOT...",           kHelpAbout);
   fHelpMenu->AddSeparator();
   fHelpMenu->AddEntry("Help On X3D Viewer...", kHelpOnViewer);

   // This main frame will process the menu commands
   fFileMenu->Associate(this);
   fHelpMenu->Associate(this);

   // Create menubar layout hints
   fMenuBarLayout = new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 0, 0, 1, 1);
   fMenuBarItemLayout = new TGLayoutHints(kLHintsTop | kLHintsLeft, 0, 4, 0, 0);
   fMenuBarHelpLayout = new TGLayoutHints(kLHintsTop | kLHintsRight);

   // Create menubar
   fMenuBar = new TGMenuBar(this, 1, 1, kHorizontalFrame);
   fMenuBar->AddPopup("&File",    fFileMenu,    fMenuBarItemLayout);
   fMenuBar->AddPopup("&Help",    fHelpMenu,    fMenuBarHelpLayout);

   AddFrame(fMenuBar, fMenuBarLayout);

   // Create canvas and canvas container that will host the ROOT graphics
   fCanvas = new TGCanvas(this, GetWidth()+4, GetHeight()+4,
                          kSunkenFrame | kDoubleBorder);
   InitX3DWindow();
   if (!fX3DWin) {
      fContainer    = 0;
      fCanvasLayout = 0;
      return;
   }
   fContainer = new TX3DContainer(this, fX3DWin, fCanvas->GetViewPort());
   fCanvas->SetContainer(fContainer);
   fCanvasLayout = new TGLayoutHints(kLHintsExpandX | kLHintsExpandY);
   AddFrame(fCanvas, fCanvasLayout);

   // Misc

   SetWindowName(name);
   SetIconName(name);
   SetClassHints("X3DViewer", "X3DViewer");

   SetMWMHints(kMWMDecorAll, kMWMFuncAll, kMWMInputModeless);

   MapSubwindows();

   // we need to use GetDefaultSize() to initialize the layout algorithm...
   Resize(GetDefaultSize());

   Show();
}

//______________________________________________________________________________
Int_t TViewerX3D::ExecCommand(Int_t px, Int_t py, char command)
{
// This function may be called from a script to animate an X3D picture
// px, py  mouse position
//command = 0       --- move to px,py
//        = w       --- wireframe mode
//        = e       --- hidden line mode
//        = r       --- hidden surface mode
//        = u       --- move object down
//        = i       --- move object up
//        = o       --- toggle controls style
//        = s       --- toggle stereo display
//        = d       --- toggle blue stereo view
//        = f       --- toggle double buffer
//        = h       --- move object right
//        = j       --- move object forward
//        = k       --- move object backward
//        = l       --- move object left
//        = x a     --- rotate about x
//        = y b     --- rotate about y
//        = z c     --- rotate about z
//        = 1 2 3   --- autorotate about x
//        = 4 5 6   --- autorotate about y
//        = 7 8 9   --- autorotate about z
//        = [ ] { } --- adjust focus
// Example:
/*
{
   gSystem->Load("libX3d");
   TCanvas *c1 = new TCanvas("c1");
   TFile *f = new TFile("hsimple.root");
   TTree *ntuple = (TTree*)f->Get("ntuple");
   ntuple->SetMarkerColor(kYellow);
   ntuple->Draw("px:py:pz");
   TViewerX3D *x3d = new TViewerX3D(c1,"");
   for (Int_t i=0;i<500;i++) {
      Int_t px = i%500;
      Int_t py = (2*i)%200;
      x3d->ExecCommand(px,py,0);  //rotate
      if (i%20 >10) x3d->ExecCommand(px,py,'j'); //zoom
      if (i%20 <10) x3d->ExecCommand(px,py,'k'); //unzoom
   }
}
*/

   return x3d_exec_command(px,py,command);
}

//______________________________________________________________________________
void TViewerX3D::GetPosition(Float_t &longitude, Float_t &latitude, Float_t &psi)
{

   x3d_get_position(&longitude, &latitude, &psi);
}

//______________________________________________________________________________
void TViewerX3D::InitX3DWindow()
{
   // Setup geometry and initialize X3D.

   TObject *obj;
   char x3dopt[32];

   TView *view = fPad->GetView();
   if (!view) {
      Error("InitX3DWindow", "view is not set");
      return;
   }

   gSize3D.numPoints = 0;
   gSize3D.numSegs   = 0;
   gSize3D.numPolys  = 0;

   TObjLink *lnk = fPad->GetListOfPrimitives()->FirstLink();
   while (lnk) {
      obj = lnk->GetObject();
      TAtt3D *att;
#ifdef R__INTEL_COMPILER
      // bug in dynamic_cast !!
      if ((att = (TAtt3D*)obj->IsA()->DynamicCast(TAtt3D::Class(), obj)))
#else
      if ((att = dynamic_cast<TAtt3D*>(obj)))
#endif
         att->Sizeof3D();
      lnk = lnk->Next();
   }

   printf("Total size of x3d primitives:\n");
   printf("     gSize3D.numPoints= %d\n",gSize3D.numPoints);
   printf("     gSize3D.numSegs  = %d\n",gSize3D.numSegs);
   printf("     gSize3D.numPolys = %d\n",gSize3D.numPolys);

   if (!AllocateX3DBuffer()) {
      Error("InitX3DWindow", "x3d buffer allocation failure");
      return;
   }

   lnk = fPad->GetListOfPrimitives()->FirstLink();
   while (lnk) {
      obj = lnk->GetObject();
      if (obj->InheritsFrom(TAtt3D::Class())) {
         strcpy(x3dopt,"x3d");
         strcat(x3dopt,fOption.Data());
         obj->Paint(x3dopt);
      }
      lnk = lnk->Next();
   }

   const Float_t kPI = Float_t (TMath::Pi());

   Float_t longitude_rad = ( 90 + view->GetLongitude()) * kPI/180.0;
   Float_t  latitude_rad = (-90 + view->GetLatitude() ) * kPI/180.0;
   Float_t       psi_rad = (      view->GetPsi()      ) * kPI/180.0;

   // Call 'x3d' package
   x3d_set_display(gVirtualX->GetDisplay());
   fX3DWin = (Window_t) x3d_main(&longitude_rad, &latitude_rad, &psi_rad,
                                 fOption.Data(), fCanvas->GetViewPort()->GetId());
}

//______________________________________________________________________________
void TViewerX3D::DeleteX3DWindow()
{
   // Close X3D window.

   x3d_terminate();
}

//______________________________________________________________________________
void TViewerX3D::CloseWindow()
{
   // In case viewer is closed via WM we get here.

   DeleteWindow();
}

//______________________________________________________________________________
void TViewerX3D::Update()
{
   // Update X3D viewer.

   x3d_update();
}

//______________________________________________________________________________
Bool_t TViewerX3D::ProcessMessage(Long_t msg, Long_t parm1, Long_t)
{
   // Handle menu and other command generated by the user.

   TRootHelpDialog *hd;

   switch (GET_MSG(msg)) {

      case kC_COMMAND:

         switch (GET_SUBMSG(msg)) {

            case kCM_BUTTON:
            case kCM_MENU:

               switch (parm1) {
                  // Handle File menu items...
                  case kFileNewViewer:
                     if (fPad) fPad->x3d();
                     break;
                  case kFileSave:
                  case kFileSaveAs:
                  case kFilePrint:
                     break;
                  case kFileCloseViewer:
                     SendCloseMessage();
                     break;

                  // Handle Help menu items...
                  case kHelpAbout:
                     {
                        char str[32];
                        sprintf(str, "About ROOT %s...", gROOT->GetVersion());
                        hd = new TRootHelpDialog(this, str, 600, 400);
                        hd->SetText(gHelpAbout);
                        hd->Popup();
                     }
                     break;
                  case kHelpOnViewer:
                     hd = new TRootHelpDialog(this, "Help on X3D Viewer...", 600, 400);
                     hd->SetText(gHelpX3DViewer);
                     hd->Popup();
                     break;
               }
            default:
               break;
         }
      default:
         break;
   }
   return kTRUE;
}

//______________________________________________________________________________
Bool_t TViewerX3D::HandleContainerButton(Event_t *ev)
{
   // After button release get current position and update associated pad.

   if (ev->fType == kButtonRelease) {
      Float_t longitude_rad;
      Float_t latitude_rad;
      Float_t psi_rad;
      const Float_t kPI = Float_t (TMath::Pi());

      x3d_get_position(&longitude_rad, &latitude_rad, &psi_rad);

      Int_t irep;

      Float_t longitude_deg = longitude_rad * 180.0/kPI - 90;
      Float_t  latitude_deg = latitude_rad  * 180.0/kPI + 90;
      Float_t       psi_deg = psi_rad       * 180.0/kPI;

      fPad->GetView()->SetView(longitude_deg, latitude_deg, psi_deg, irep);

      fPad->SetPhi(-90 - longitude_deg);
      fPad->SetTheta(90 - latitude_deg);

      fPad->Modified(kTRUE);
      fPad->Update();
   }
   return kTRUE;
}
