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

//<file>
//
// Name:        dialogs.C
//
// Purpose:     implementation of dialogs for the scene viewer
//              interface integrated in class SceneMenus (shared states/flags with menus)
//
// Created:     19 Jan 1996   Michael Pichler
//
// Changed:     25 Jan 1996   Michael Pichler
//
// $Id: dialogs.C,v 1.2 1996/01/30 12:24:21 mpichler Exp $
//
//</file>



#include "themenus.h"

#include "scenewin.h"
#include "stranslate.h"
#include "gecontext.h"
#include "hgptrhand.h"
#include "camera.h"
#include "delhand.h"
#include "compdate.h"

#include <hyperg/widgets/about.h>
#include <hyperg/widgets/adjvalue.h>
#include <hyperg/widgets/coldlg.h>
#include <hyperg/widgets/cursors.h>
#include <hyperg/widgets/fchooser.h>
#include <hyperg/widgets/fieldb.h>
#include <hyperg/widgets/fslider.h>
#include <hyperg/widgets/glyphutil.h>
#include <hyperg/widgets/menus.h>
#include <hyperg/widgets/msgbox.h>
#include <hyperg/widgets/table.h>
#include <hyperg/widgets/textb.h>

#include <ge3d/ge3d.h>

#include <hyperg/hyperg/verbose.h>

#include <InterViews/background.h>
#include <InterViews/label.h>
#include <InterViews/layout.h>
#include <InterViews/observe.h>
#include <InterViews/patch.h>
#include <InterViews/style.h>
#include <InterViews/target.h>
#include <InterViews/window.h>
#include <IV-look/kit.h>
#include <IV-look/menu.h>
#include <IV-X11/xwindow.h>

#include <math.h>


#define NAVSETTINGSHELPFILE "navigate/index.html#settings"


declareActionCallback (SceneMenus)
// implementation in file themenus.C

declareActionCallback (HG3dInputHandler)
implementActionCallback (HG3dInputHandler)

declareColourDlgCallback(SceneMenus)
implementColourDlgCallback(SceneMenus)



/***** About *****/


void SceneMenus::updateAbout (WidgetKit& kit, const LayoutKit& layout)
{
  About::credits (
    layout.vbox (
      layout.hmargin (
        multiLineLabel (STranslate::str (STranslate::AboutSCENEVIEWER), About::getBigFont (), kit.foreground ()),
        0, fil, 0, 0, fil, 0
      ),
      multiLineLabel (
        "\n"
        "by Michael Pichler\n\n"
        "special thanks to: Keith Andrews, Michael Hofer,\n"
        "and the whole Harmony Development Team",
        About::getSmallFont (), kit.foreground ()
      )
    )
  );
}


void SceneMenus::toggleAbout ()
{
  if (About::ismapped ())
    About::unmap ();
  else
  {
    char version [256];
    sprintf (version, STranslate::str (STranslate::AboutVERSIONtemplate),
      GEContext::implementation (), compDate ());
    About::map (slanguage, STranslate::str (STranslate::AboutSCENEVIEWER), version);
  }
}



/***** (parser) Error Dialog *****/


void SceneMenus::buildErrorDialog ()
{
  // may rebuild the dialog on language change,
  // reusing the existant errorbrowser
  if (errorbrowser_)
    return;

  errorbrowser_ = new TextBrowser (*WidgetKit::instance ());
  errorwindow_ = new ApplicationWindow (
    layout_->vbox (
      kit_->outset_frame (
        layout_->margin (
          layout_->natural (errorbrowser_, 500, 500),
          15.0
        )
      ),
      kit_->outset_frame (
        layout_->margin (
          kit_->push_button (
            WTranslate::str (WTranslate::OK, slanguage),
            new ActionCallback(SceneMenus) (this, &SceneMenus::closeErrorDialog)
          ),
          10, fil, 0, 10, fil, 0, 5, 0, 0, 5, 0, 0
        )
      )
    )
  );

  Style* errdlgstyle = new Style (kit_->style ());
  errdlgstyle->attribute ("name", STranslate::str (STranslate::DialogTitlePARSEROUTPUT));
  errdlgstyle->attribute ("iconName", STranslate::str (STranslate::DialogTitlePARSEROUTPUT));
  errorwindow_->style (errdlgstyle);
  //errorwindow_->title (STranslate::str (STranslate::DialogTitlePARSEROUTPUT));  // only for mapped windows

  // delete handler
  SDeleteHandler* delhandler = new SDeleteHandler;
  errorwindow_->wm_delete (delhandler);

} // buildErrorDialog


void SceneMenus::toggleErrorDialog ()
{
  if (!errorwindow_)
    buildErrorDialog ();

  if (errorwindow_->is_mapped ())
    errorwindow_->unmap ();
  else
    errorwindow_->map ();
}


void SceneMenus::closeErrorDialog ()
{
  if (errorwindow_)
    errorwindow_->unmap ();
}



/***** Navigation Settings *****/


// ValueObserver: gets notified about value changes
// will be attached to Adjustable by creator
// will be destroyed together with Adjustable
// (no need for detach/disconnect mechanism)

class ValueObserver: public Observer
{
  public:
    ValueObserver (Adjustable* adj, float* val)
    { adj_ = adj;
      val_ = val;
    }
    // Observer
    void update (Observable*);

  private:
    Adjustable* adj_;
    float* val_;
};  // ValueObserver


void ValueObserver::update (Observable*)
{
  *val_ = adj_->cur_lower (Dimension_X);
}


// NavigationSettingsDialog

class NavigationSettingsDialog
{
  public:
    NavigationSettingsDialog (SceneWindow* scene, SceneMenus* menus, WidgetKit* kit, const LayoutKit* layout)
    {
      scene_ = scene;
      menus_ = menus;
      win_ = 0;
      build (kit, layout);
    }
    ~NavigationSettingsDialog ();

    void toggleWindow ();

  private:
    enum { num_sliders = 4 };

    void build (WidgetKit* kit, const LayoutKit* layout);  // create window
    void unmap ()
    { win_->unmap (); }
    void reset ();
    void cancel ();
    void navsettingsHelp ()
    { scene_->showHelp (NAVSETTINGSHELPFILE); }

    SceneWindow* scene_;
    SceneMenus* menus_;
    ApplicationWindow* win_;
    float* value_ [num_sliders];  // pointer to values to be set
    float origval_ [num_sliders];  // initial values (for reset)
    float lastval_ [num_sliders];  // values on dialog mapping (for cancel)
    AdjValue* adjval_ [num_sliders];  // adjustables
    ValueObserver* valobs_ [num_sliders];  // observers

}; // NavigationSettingsDialog


declareActionCallback (NavigationSettingsDialog)
implementActionCallback (NavigationSettingsDialog)


NavigationSettingsDialog::~NavigationSettingsDialog ()
{
  if (!win_)  // pathologic
    return;

  delete win_;  // destroy window

  for (int i = 0;  i < num_sliders;  i++)
  { delete adjval_ [i];
    delete valobs_ [i];
  }
}


void NavigationSettingsDialog::toggleWindow ()
{
  if (!win_)
    return;

  if (win_->is_mapped ())
    win_->unmap ();  // implicit OK
  else
  { // save values before mapping (for cancel)
    for (int i = 0;  i < num_sliders;  i++)
      lastval_ [i] = *value_ [i];

    win_->map ();
  }
} // toggleWindow


void NavigationSettingsDialog::reset ()
{
  if (!win_)
    return;

  // set the adjustable; actual values set via ValueObserver
  for (int i = 0;  i < num_sliders;  i++)
    adjval_ [i]->scroll_to (Dimension_X, origval_ [i]);
}


void NavigationSettingsDialog::cancel ()
{
  if (!win_)
    return;

  // see above
  for (int i = 0;  i < num_sliders;  i++)
    adjval_ [i]->scroll_to (Dimension_X, lastval_ [i]);

  win_->unmap ();
}


void NavigationSettingsDialog::build (WidgetKit* kit, const LayoutKit* layout)
{
  if (win_)
    return;

  kit->begin_style ("Settings", "Dialog");
  Style* style = kit->style ();
  const char* title = STranslate::str (STranslate::DialogTitleNAVIGATIONSETTINGS);
  style->attribute ("name", title);
  style->attribute ("iconName", title);

  // camera position and orientation not yet included, because:
  // o need 3tuples instead of single value
  // o different internal camera model (lookat vs. orientation)
  // o update and apply buttons needed
  // o will be part of VRweb edit

  Glyph* label [num_sliders];

  label [0] = kit->label (STranslate::str (STranslate::DialogStringCOLLISIONDISTANCE));
  label [1] = kit->label (STranslate::str (STranslate::DialogStringNAVSPEEDFACTOR));
  RString flyto (STranslate::str (STranslate::NavFLYTO));
  flyto += ": ";
  RString flytolabel = flyto + STranslate::str (STranslate::DialogStringZOOMSPEED);
  label [2] = kit->label (flytolabel);
  flytolabel = flyto + STranslate::str (STranslate::DialogStringROTATIONSPEED);
  label [3] = kit->label (flytolabel);
  // ass.: num_sliders == 4

  value_ [0] = &Camera::collisionDistance_;
  value_ [1] = &HG3dInputHandler::SpeedFactor_;
  value_ [2] = &HG3dInputHandler::FlyToTran_;
  value_ [3] = &HG3dInputHandler::FlyToRot_;

  adjval_ [0] = new AdjValue (0.1, 20, *value_ [0], 0.1, 2.0);
  adjval_ [1] = new AdjValue (0.1, 10, *value_ [1], 0.1, 1.0);
  adjval_ [2] = new AdjValue (0.01, 0.6, *value_ [2], 0.01, 0.25);
  adjval_ [3] = new AdjValue (0.02, 0.9, *value_ [3], 0.01, 0.25);

  FieldSlider* slider [num_sliders];

  TableGlyph* sliders = new TableGlyph (
    2, num_sliders, // columns (fix), rows (estimated)
    10, 0, 0  // hspace
  );
  int i;
  for (i = 0;  i < num_sliders;  i++)
  {
    origval_ [i] = *value_ [i];
    valobs_ [i] = new ValueObserver (adjval_ [i], value_ [i]);
    slider [i] = new FieldSlider (adjval_ [i], 2, "20.00");
    adjval_ [i]->attach (Dimension_X, valobs_ [i]);

    sliders->appendRow (
      layout->hbox (
        layout->vmargin (label [i], 2, fil, 0, 2, fil, 0),
        layout->vmargin (layout->hnatural (slider [i], 200), 2, fil, 0, 2, fil, 0)
      )
    );
  }

  Glyph* buttons = layout->hbox (
    kit->push_button (
      WTranslate::str (WTranslate::OK, slanguage),
      new ActionCallback(NavigationSettingsDialog) (this, &NavigationSettingsDialog::unmap)
    ),
    layout->hglue (),
    kit->push_button (
      WTranslate::str (WTranslate::RESET, slanguage),
      new ActionCallback(NavigationSettingsDialog) (this, &NavigationSettingsDialog::reset)
    ),
    layout->hglue (),
    kit->push_button (
      WTranslate::str (WTranslate::CANCEL, slanguage),
      new ActionCallback(NavigationSettingsDialog) (this, &NavigationSettingsDialog::cancel)
    ),
    layout->hglue (),
    kit->push_button (
      WTranslate::str (WTranslate::HELP, slanguage),
      new ActionCallback(NavigationSettingsDialog) (this, &NavigationSettingsDialog::navsettingsHelp)
    )
  );

  // focus handler
  InputHandler* inputhand = new InputHandler (
    new Background (
      new TransparentTarget (
        layout->vbox (
          layout->margin (
            sliders,
            20.0
          ),
          layout->margin (
            buttons,
            10, fil, 0, 10, fil, 0, 20, 0, 0, 0, 0, 0  // LRBT
          )
        )
      ),
      kit->background ()
    ),
    style
  );

  for (i = 0;  i < num_sliders;  i++)
    inputhand->append_input_handler (slider [i]);

  inputhand->focus (*slider);  // initial focus

  // dialog window
  win_ = new ApplicationWindow (inputhand);

  win_->style (style);
  kit->end_style ();  // Settings

  // delete handler
  SDeleteHandler* delhandler = new SDeleteHandler;
  win_->wm_delete (delhandler);  // unmaps window; should cancel instead

} // build NavigationSettingsDialog


void SceneMenus::toggleNavigationSettingsDialog ()
{
  if (!navsetdlg_)
    navsetdlg_ = new NavigationSettingsDialog (scene_, this, kit_, layout_);

  navsetdlg_->toggleWindow ();
}


void SceneMenus::destroyNavsetdlg ()
{
  delete navsetdlg_;
  navsetdlg_ = 0;
}



/***** Spaceball *****/


#ifdef SPACEBALL
static Glyph* sballbutton (
  WidgetKit* kit, const LayoutKit* layout, const char* number, Action* action,
  TelltaleState* tstate, Glyph* hspace, float hsize, const char* label)
{
  Glyph* button;
  if (tstate)
    button = WWidgetKit::palette_button (number, action, tstate);
  else
    button = kit->push_button (number, action);

  return layout->hbox (
    layout->hfixed (button, hsize),
    hspace,
    kit->label (label)
  );
}
#endif


#ifdef SPACEBALL
class ClientAppWindow: public ApplicationWindow
{
  public:
    ClientAppWindow (Glyph* body, Window* mainwin)
    : ApplicationWindow (body)
    { mainwin_ = mainwin;
    }

    // ApplicationWindow
    void bind ()
    { ApplicationWindow::bind ();
      SceneWindow::init_spaceball (this);
    }

    // Window
    void client_event (const Event& e)
    {
      if (mainwin_)
        mainwin_->client_event (e);
    }
    // redirect client events (include spaceball events)
    // to the main application window

  private:
    Window* mainwin_;
};  // ClientAppWindow
#endif


void SceneMenus::buildSpaceballDialog ()
{
#ifdef SPACEBALL
  if (sballwindow_)
    return;

  kit_->begin_style ("Spaceball", "Dialog");
  Style* style = kit_->style ();
  style->attribute ("name", "Spaceball");
  style->attribute ("iconName", "Spaceball");

  Glyph* closebutton = layout_->hmargin (
    kit_->push_button (
      WTranslate::str (WTranslate::OK, slanguage),
      new ActionCallback(SceneMenus) (this, &SceneMenus::closeSpaceballDialog)
    ),
    0, fil, 0, 0, fil, 0
  );

  style->attribute ("minimumWidth", "0");

  float margin = 15.0;  // around groups
  Glyph* hspace = layout_->hspace (10.0);  // between button and label
  Glyph* vspace = layout_->vspace (5.0);  // between items vertically

  float bsize;
  {
    Glyph* button = kit_->palette_button ("X", nil);
    Requisition requisition;
    button->request (requisition);
    kit_->begin_style ("PaletteButton", "Button");  // see WWidgetKit::palette_button
    if (kit_->style ()->value_is_on ("showLamp"))  // use natural width
      bsize = requisition.x_requirement ().natural ();
    else  // make buttons square
      bsize = requisition.y_requirement ().natural ();
    kit_->end_style ();  // "PaletteButton"
    delete button;
  }

  Glyph* trfgroup = layout_->vbox (
    sballbutton (kit_, layout_, "1", new MenuGroupCallback (this, &SceneMenus::sb_checkbuttons, sb_translate),
      sbflagstate_ [sb_translate], hspace, bsize, STranslate::str (STranslate::SpaceballTRANSLATION)),
    vspace,
    sballbutton (kit_, layout_, "2", new MenuGroupCallback (this, &SceneMenus::sb_checkbuttons, sb_rotate),
      sbflagstate_ [sb_rotate], hspace, bsize, STranslate::str (STranslate::SpaceballROTATION)),
    vspace,
    sballbutton (kit_, layout_, "3", nil,
      sbflagstate_ [sb_saf], hspace, bsize, STranslate::str (STranslate::SpaceballSINGLEAXISFILTER))
  );
      
  RString flipwalk (STranslate::str (STranslate::NavFLIPOBJECT));
  flipwalk += "/";
  flipwalk += STranslate::str (STranslate::NavWALK);
  Glyph* button4 = sballbutton (
    kit_, layout_, "4", new ActionCallback(SceneMenus) (this, &SceneMenus::toggleSballMode),
    nil, hspace, bsize, flipwalk);

  Resource::unref (sbsensfieldb_);
  sbsensfieldb_ = new FieldBrowser (kit_, style, "");
  Resource::ref (sbsensfieldb_);
  updateSensitivity ();  // set sensitivity field

  Glyph* sensgroup = layout_->vbox (
    layout_->hbox (
      kit_->label (STranslate::str (STranslate::SpaceballSENSITIVITY)),
      hspace,
      layout_->hfixed (sbsensfieldb_, 64)
    ),
    vspace,
    sballbutton (kit_, layout_, "5", new ActionCallback(SceneMenus) (this, &SceneMenus::decreaseSballSensitivity),
      nil, hspace, bsize, STranslate::str (STranslate::SpaceballDECREASE)),
    vspace,
    sballbutton (kit_, layout_, "6", new ActionCallback(SceneMenus) (this, &SceneMenus::increaseSballSensitivity),
      nil, hspace, bsize, STranslate::str (STranslate::SpaceballINCREASE)),
    vspace,
    sballbutton (kit_, layout_, "7", new ActionCallback(SceneMenus) (this, &SceneMenus::resetSballSensitivity),
      nil, hspace, bsize, STranslate::str (STranslate::SpaceballRESETSENSITIVITY))
  );

  Glyph* button8 = sballbutton (
    kit_, layout_, "8", new ActionCallback(HG3dInputHandler) (inputhand_, &HG3dInputHandler::sballRezero),
    nil, hspace, bsize, STranslate::str (STranslate::SpaceballREZERO));

  Glyph* resetbutton = sballbutton (
    kit_, layout_, "P", new ActionCallback(SceneMenus) (this, &SceneMenus::resetView),
    nil, hspace, bsize, STranslate::str (STranslate::NavigateRESETVIEW));

  Glyph* separator = kit_->outset_frame (layout_->vspace (0));

  sballwindow_ = new ClientAppWindow (
    new Background (
      layout_->vbox (
        layout_->margin (trfgroup, margin),
        separator,
        layout_->margin (button4, margin),
        separator,
        layout_->margin (sensgroup, margin),
        separator,
        layout_->margin (button8, margin),
        separator,
        layout_->margin (resetbutton, margin),
        layout_->margin (closebutton, margin)
      ),
      kit_->background ()
    ),
    scene_->appwin ()
  );

  sballwindow_->style (style);
  kit_->end_style ();  // Spaceball

  // delete handler
  SDeleteHandler* delhandler = new SDeleteHandler;
  sballwindow_->wm_delete (delhandler);
#endif
} // buildSpaceballDialog


void SceneMenus::toggleSpaceballDialog ()
{
#ifdef SPACEBALL
  if (!sballwindow_)
    buildSpaceballDialog ();

  if (sballwindow_->is_mapped ())
    sballwindow_->unmap ();
  else
    sballwindow_->map ();
#endif
}


void SceneMenus::closeSpaceballDialog ()
{
  if (sballwindow_)
    sballwindow_->unmap ();
}



/***** Colors *****/


void SceneMenus::buildColourDialog ()
{
  DEBUGNL ("SceneMenus::buildColourDialog");

  delete coldlg_;

  coldlg_ = new ColourDialog (
    slanguage,
    STranslate::str (STranslate::DialogTitleCOLOURCHOOSER),
    new ColourDlgCallback(SceneMenus) (this, &SceneMenus::applyColours)
  );

  // be sure to use same order as in applyColours and toggleColourDialog

  const colorRGB* col = &scene_->col_background;  // 0
  coldlg_->appendColourEntry (STranslate::str (STranslate::ColorBACKGROUND), col->R, col->G, col->B);
  col = &scene_->col_hudisplay;         // 1
  coldlg_->appendColourEntry (STranslate::str (STranslate::ColorHEADSUP), col->R, col->G, col->B);
  col = &scene_->col_anchorface;        // 2
  coldlg_->appendColourEntry (STranslate::str (STranslate::ColorANCHORFACES), col->R, col->G, col->B);
  col = &scene_->col_anchoredge;        // 3
  coldlg_->appendColourEntry (STranslate::str (STranslate::ColorANCHOREDGES), col->R, col->G, col->B);
  col = &scene_->col_viewinglight;      // 4
  coldlg_->appendColourEntry (STranslate::str (STranslate::ColorVIEWINGLIGHT), col->R, col->G, col->B);

} // buildColourDialog


void SceneMenus::toggleColourDialog ()
{
  if (!coldlg_)
    buildColourDialog ();
  else  // set current colours (to reflect choosen predefined colours in menu items)
  {
    // be sure to use same order as in buildColourDialog above

    const colorRGB* col = &scene_->col_background;  // 0
    coldlg_->setRGB (col->R, col->G, col->B, 0);
    col = &scene_->col_hudisplay;       // 1
    coldlg_->setRGB (col->R, col->G, col->B, 1);
    col = &scene_->col_anchorface;      // 2
    coldlg_->setRGB (col->R, col->G, col->B, 2);
    col = &scene_->col_anchoredge;      // 3
    coldlg_->setRGB (col->R, col->G, col->B, 3);
    col = &scene_->col_viewinglight;    // 4
    coldlg_->setRGB (col->R, col->G, col->B, 4);
  }

  if (coldlg_->ismapped ())
    coldlg_->unmap ();
  else
    coldlg_->map ();
} // toggleColourDialog
