/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

#include "vtkKWMouseBindings.h"

#include "vtkKWApplication.h"
#include "vtkKWEvent.h"
#include "vtkKWEventMap.h"
#include "vtkKWLabel.h"
#include "vtkKWMenu.h"
#include "vtkKWMenuButton.h"
#include "vtkObjectFactory.h"

#include "vtkKWWidgetsConfigure.h" // Needed for KWWidgets_BUILD_VTK_WIDGETS
#ifdef KWWidgets_BUILD_VTK_WIDGETS
#include "vtkKWCommonProConfigure.h" // Needed for KWCommonPro_USE_XML_RW
#endif

#ifdef KWCommonPro_USE_XML_RW
#include "XML/vtkXMLKWEventMapWriter.h"
#endif

#include <vtksys/SystemTools.hxx>

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkKWMouseBindings);
vtkCxxRevisionMacro(vtkKWMouseBindings, "$Revision: 1.31 $");

//----------------------------------------------------------------------------

#define VTK_KW_MB_BUTTON_0_LABEL       "Left"
#define VTK_KW_MB_BUTTON_1_LABEL       "Middle"
#define VTK_KW_MB_BUTTON_2_LABEL       "Right"

#define VTK_KW_MB_MODIFIER_0_LABEL     "Button Only"
#define VTK_KW_MB_MODIFIER_1_LABEL     "Button + Shift"
#define VTK_KW_MB_MODIFIER_2_LABEL     "Button + Control"

#define VTK_KW_MB_OP_WINDOWLEVEL       "WindowLevel"
#define VTK_KW_MB_OP_WINDOWLEVEL_LABEL "WW/WL"

#define VTK_KW_MB_OP_PAN               "Pan"
#define VTK_KW_MB_OP_PAN_LABEL         "Pan"

#define VTK_KW_MB_OP_ZOOM              "Zoom"
#define VTK_KW_MB_OP_ZOOM_LABEL        "Zoom"

#define VTK_KW_MB_OP_MEASURE           "Measure"
#define VTK_KW_MB_OP_MEASURE_LABEL     "Measure"

#define VTK_KW_MB_OP_ROTATE            "Rotate"
#define VTK_KW_MB_OP_ROTATE_LABEL      "Rotate"

#define VTK_KW_MB_OP_ROLL              "Roll"
#define VTK_KW_MB_OP_ROLL_LABEL        "Roll"

#define VTK_KW_MB_OP_FLYIN             "FlyIn"
#define VTK_KW_MB_OP_FLYIN_LABEL       "Fly In"

#define VTK_KW_MB_OP_FLYOUT            "FlyOut"
#define VTK_KW_MB_OP_FLYOUT_LABEL      "Fly Out"

//----------------------------------------------------------------------------
vtkKWMouseBindings::vtkKWMouseBindings()
{
  this->EventMap = 0;
  this->MouseBindingChangedEvent = vtkKWEvent::MouseBindingChangedEvent;
  this->MouseBindingChangedCommand = NULL;

  int b, m;

  // Button labels

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    this->ButtonLabels[b] = vtkKWLabel::New();
    }

  // Modifier labels

  for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
    {
    this->ModifierLabels[m] = vtkKWLabel::New();
    }

  // Menus

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      this->OperationMenus[b][m] = vtkKWMenuButton::New();
      }
    }

  // Operations

  this->AllowWindowLevel = 1;
  this->AllowPan = 1;
  this->AllowZoom = 1;
  this->AllowMeasure = 1;
  this->AllowRotate = 1;
  this->AllowRoll = 1;
  this->AllowFlyIn = 1;
  this->AllowFlyOut = 1;
}

//----------------------------------------------------------------------------
vtkKWMouseBindings::~vtkKWMouseBindings()
{
  if (this->MouseBindingChangedCommand)
    {
    delete [] this->MouseBindingChangedCommand;
    this->MouseBindingChangedCommand = NULL;
    }

  int b, m;

  // Button labels

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    if (this->ButtonLabels[b])
      {
      this->ButtonLabels[b]->Delete();
      this->ButtonLabels[b] = NULL;
      }
    }

  // Modifier labels

  for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
    {
    if (this->ModifierLabels[m])
      {
      this->ModifierLabels[m]->Delete();
      this->ModifierLabels[m] = NULL;
      }
    }

  // Menus

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      if (this->OperationMenus[b][m])
        {
        this->OperationMenus[b][m]->Delete();
        this->OperationMenus[b][m] = NULL;
        }
      }
    }
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetEventMap(vtkKWEventMap *arg)
{
  if (this->EventMap == arg)
    {
    return;
    }
  this->EventMap = arg;
  this->Modified();

  this->Update();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::CreateWidget()
{
  // Check if already created

  if (this->IsCreated())
    {
    vtkErrorMacro(<< this->GetClassName() << " already created");
    return;
    }

  // Call the superclass to create the whole widget

  this->Superclass::CreateWidget();

  this->SetReliefToFlat();
  this->SetBorderWidth(0);
  this->SetHighlightThickness(0);

  ostrstream tk_cmd;
  int b, m;

  // Create the buttons labels
  
  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    this->ButtonLabels[b]->SetParent(this);
    this->ButtonLabels[b]->Create();
    tk_cmd << "grid " << this->ButtonLabels[b]->GetWidgetName()
           << " -padx 1 -pady 1 -sticky nsw -column 0 -row " << b + 1 << endl;
    }

  this->ButtonLabels[0]->SetText(VTK_KW_MB_BUTTON_0_LABEL);
  this->ButtonLabels[1]->SetText(VTK_KW_MB_BUTTON_1_LABEL);
  this->ButtonLabels[2]->SetText(VTK_KW_MB_BUTTON_2_LABEL);

  // Create the modifiers labels
  
  for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
    {
    this->ModifierLabels[m]->SetParent(this);
    this->ModifierLabels[m]->Create();
    this->ModifierLabels[m]->SetWidth(20);
    tk_cmd << "grid " << this->ModifierLabels[m]->GetWidgetName()
           << " -padx 1 -pady 1 -sticky news -row 0 -column " << m + 1 << endl
           << "grid columnconfigure " << this->GetWidgetName() << " " 
           << m + 1 << " -weight 1" << endl;
    }

  this->ModifierLabels[0]->SetText(VTK_KW_MB_MODIFIER_0_LABEL);
  this->ModifierLabels[1]->SetText(VTK_KW_MB_MODIFIER_1_LABEL);
  this->ModifierLabels[2]->SetText(VTK_KW_MB_MODIFIER_2_LABEL);

  // Create the menus
  
  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    // Also set the button help string

    vtksys_stl::string button_label = vtksys::SystemTools::LowerCase(
      this->ButtonLabels[b]->GetText());
    ostrstream button_help_str;
    button_help_str << "Control the action associated with the " 
                    << button_label.c_str() << " mouse button." << ends;
    this->ButtonLabels[b]->SetBalloonHelpString(button_help_str.str());
    button_help_str.rdbuf()->freeze(0);
      
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      // Also set the modifer help string

      vtksys_stl::string modifier_label = vtksys::SystemTools::LowerCase(
        this->ModifierLabels[m]->GetText());
      if (b == 0)
        {
        ostrstream modifier_help_str;
        modifier_help_str << "Control the action associated with a mouse " 
                          << modifier_label.c_str() << "." << ends;
        this->ModifierLabels[m]->SetBalloonHelpString(modifier_help_str.str());
        modifier_help_str.rdbuf()->freeze(0);
        }

      // Create the menu

      this->OperationMenus[b][m]->SetParent(this);
      this->OperationMenus[b][m]->Create();
      tk_cmd << "grid " << this->OperationMenus[b][m]->GetWidgetName()
             << " -padx 1 -pady 1 -sticky news -row " << b + 1 
             << " -column " << m + 1 << endl;

      // Also set the menu help string

      ostrstream menu_help_str;
      menu_help_str << "Control the action associated with the " 
       << button_label.c_str() << " " << modifier_label.c_str() << "." << ends;
      this->OperationMenus[b][m]->SetBalloonHelpString(menu_help_str.str());
      menu_help_str.rdbuf()->freeze(0);
      }
    }

  // Pack

  tk_cmd << ends;
  this->Script(tk_cmd.str());
  tk_cmd.rdbuf()->freeze(0);

  // Update the menus

  this->UpdateMenus();

  // Update enable state
  
  this->UpdateEnableState();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::Update()
{
  this->UpdateEnableState();

  int b, m;

  // For each button/modifier combination, find the current action
  // and update the corresponding menu

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      vtkKWMenuButton *menu = this->OperationMenus[b][m];
      if (!menu)
        {
        continue;
        }

      if (!this->EventMap)
        {
        menu->SetEnabled(0);
        continue;
        }

      const char *action = this->EventMap->FindMouseAction(b, m);
      if (!action)
        {
        continue;
        }

      if (this->AllowWindowLevel && !strcmp(action, VTK_KW_MB_OP_WINDOWLEVEL))
        {
        menu->SetValue(VTK_KW_MB_OP_WINDOWLEVEL_LABEL);
        }
      else if (this->AllowPan && !strcmp(action, VTK_KW_MB_OP_PAN))
        {
        menu->SetValue(VTK_KW_MB_OP_PAN_LABEL);
        }
      else if (this->AllowZoom && !strcmp(action, VTK_KW_MB_OP_ZOOM))
        {
        menu->SetValue(VTK_KW_MB_OP_ZOOM_LABEL);
        }
      else if (this->AllowMeasure && !strcmp(action, VTK_KW_MB_OP_MEASURE))
        {
        menu->SetValue(VTK_KW_MB_OP_MEASURE_LABEL);
        }
      else if (this->AllowRotate && !strcmp(action, VTK_KW_MB_OP_ROTATE))
        {
        menu->SetValue(VTK_KW_MB_OP_ROTATE_LABEL);
        }
      else if (this->AllowRoll && !strcmp(action, VTK_KW_MB_OP_ROLL))
        {
        menu->SetValue(VTK_KW_MB_OP_ROLL_LABEL);
        }
      else if (this->AllowFlyIn && !strcmp(action, VTK_KW_MB_OP_FLYIN))
        {
        menu->SetValue(VTK_KW_MB_OP_FLYIN_LABEL);
        }
      else if (this->AllowFlyOut && !strcmp(action, VTK_KW_MB_OP_FLYOUT))
        {
        menu->SetValue(VTK_KW_MB_OP_FLYOUT_LABEL);
        }
      }
    }

  if (!this->EventMap)
    {
    for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
      {
      this->ButtonLabels[b]->SetEnabled(0);
      }
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      this->ModifierLabels[m]->SetEnabled(0);
      }
    }
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::UpdateMenus()
{
  if (!this->IsCreated())
    {
    return;
    }

  // Update the menus according to what operations are allowed or not

  int b, m;

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      vtkKWMenuButton *menubutton = this->OperationMenus[b][m];
      if (!menubutton)
        {
        continue;
        }

      menubutton->GetMenu()->DeleteAllItems();

      ostrstream callback_str;
      callback_str << "MouseOperationCallback " << b <<" " << m << " " << ends;

      if (this->AllowWindowLevel)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_WINDOWLEVEL << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_WINDOWLEVEL_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowPan)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_PAN << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_PAN_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowZoom)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_ZOOM << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_ZOOM_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowMeasure)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_MEASURE << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_MEASURE_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowRotate)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_ROTATE << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_ROTATE_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowRoll)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_ROLL << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_ROLL_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowFlyIn)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_FLYIN << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_FLYIN_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      if (this->AllowFlyOut)
        {
        ostrstream str;
        str << callback_str.str() << VTK_KW_MB_OP_FLYOUT << ends;
        menubutton->GetMenu()->AddRadioButton(
          VTK_KW_MB_OP_FLYOUT_LABEL, this, str.str());
        str.rdbuf()->freeze(0);
        }

      callback_str.rdbuf()->freeze(0);
      }
    }

  // Now update the menu choices

  this->Update();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::MouseOperationCallback(int button, 
                                                int modifier, 
                                                char *action)
{
  if (!this->EventMap)
    {
    return;
    }

  if (this->EventMap->FindMouseAction(button, modifier))
    {
    this->EventMap->SetMouseEvent(button, modifier, action);
    }
  else
    {
    this->EventMap->AddMouseEvent(button, modifier, action);
    }

  // This event will (for example) be processed by vtkKWWindow and 
  // propagated to synchronize the event map of similar widgets.

#ifdef KWCommonPro_USE_XML_RW
  ostrstream event;

  vtkXMLKWEventMapWriter *xmlw = vtkXMLKWEventMapWriter::New();
  xmlw->SetObject(this->EventMap);
  xmlw->OutputMouseEventsOn();
  xmlw->OutputKeyEventsOff();
  xmlw->OutputKeySymEventsOff();
  xmlw->WriteToStream(event);
  xmlw->Delete();

  event << ends;

  this->InvokeEvent(this->MouseBindingChangedEvent, event.str());
  event.rdbuf()->freeze(0);
#else
  this->InvokeEvent(this->MouseBindingChangedEvent, NULL);
#endif
  
  // Or sync can also be achieved by just calling a user-defined callback

  if (this->MouseBindingChangedCommand && *this->MouseBindingChangedCommand &&
      this->IsCreated())
    {
    this->Script("eval {%s %s}",
                 this->MouseBindingChangedCommand, this->GetTclName());
    }
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetMouseBindingChangedCommand(vtkKWObject *object,
                                                       const char *method)
{
  this->SetObjectMethodCommand(
    &this->MouseBindingChangedCommand, object, method);
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::UpdateEnableState()
{
  this->Superclass::UpdateEnableState();

  int b, m;

  // Button labels

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    this->PropagateEnableState(this->ButtonLabels[b]);
    }

  // Modifier labels

  for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
    {
    this->PropagateEnableState(this->ModifierLabels[m]);
    }

  // Menus

  for (b = 0; b < VTK_KW_MOUSE_BINDINGS_NB_BUTTONS; b++)
    {
    for (m = 0; m < VTK_KW_MOUSE_BINDINGS_NB_MODIFIERS; m++)
      {
      this->PropagateEnableState(this->OperationMenus[b][m]);
      }
    }
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowWindowLevel(int arg)
{
  if (this->AllowWindowLevel == arg)
    {
    return;
    }

  this->AllowWindowLevel = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowPan(int arg)
{
  if (this->AllowPan == arg)
    {
    return;
    }

  this->AllowPan = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowZoom(int arg)
{
  if (this->AllowZoom == arg)
    {
    return;
    }

  this->AllowZoom = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowMeasure(int arg)
{
  if (this->AllowMeasure == arg)
    {
    return;
    }

  this->AllowMeasure = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowRotate(int arg)
{
  if (this->AllowRotate == arg)
    {
    return;
    }

  this->AllowRotate = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowRoll(int arg)
{
  if (this->AllowRoll == arg)
    {
    return;
    }

  this->AllowRoll = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowFlyIn(int arg)
{
  if (this->AllowFlyIn == arg)
    {
    return;
    }

  this->AllowFlyIn = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::SetAllowFlyOut(int arg)
{
  if (this->AllowFlyOut == arg)
    {
    return;
    }

  this->AllowFlyOut = arg;
  this->Modified();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::Allow2DOperationsOnly()
{
  this->AllowWindowLevelOn();
  this->AllowPanOn();
  this->AllowZoomOn();
  this->AllowMeasureOn();

  this->AllowRotateOff();
  this->AllowRollOff();
  this->AllowFlyInOff();
  this->AllowFlyOutOff();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::Allow3DOperationsOnly()
{
  this->AllowWindowLevelOff();
  this->AllowPanOn();
  this->AllowZoomOn();
  this->AllowMeasureOff();

  this->AllowRotateOn();
  this->AllowRollOn();
  this->AllowFlyInOn();
  this->AllowFlyOutOn();

  this->UpdateMenus();
}

//----------------------------------------------------------------------------
void vtkKWMouseBindings::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);

  os << indent << "EventMap: " << this->EventMap << endl;
  os << indent << "MouseBindingChangedEvent: " 
     << this->MouseBindingChangedEvent << endl;

  os << indent << "AllowWindowLevel: " 
     << (this->AllowWindowLevel ? "On" : "Off") << endl;
  os << indent << "AllowPan: " 
     << (this->AllowPan ? "On" : "Off") << endl;
  os << indent << "AllowZoom: " 
     << (this->AllowZoom ? "On" : "Off") << endl;
  os << indent << "AllowMeasure: " 
     << (this->AllowMeasure ? "On" : "Off") << endl;
  os << indent << "AllowRotate: " 
     << (this->AllowRotate ? "On" : "Off") << endl;
  os << indent << "AllowRoll: " 
     << (this->AllowRoll ? "On" : "Off") << endl;
  os << indent << "AllowFlyIn: " 
     << (this->AllowFlyIn ? "On" : "Off") << endl;
  os << indent << "AllowFlyOut: " 
     << (this->AllowFlyOut ? "On" : "Off") << endl;
}
