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

Copyright (c) 1998-2003 Kitware Inc. 469 Clifton Corporate Parkway,
Clifton Park, NY, 12065, USA.
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 Kitware nor the names of any contributors may be used
   to endorse or promote products derived from this software without specific
   prior written permission.

 * Modified source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

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 AUTHORS 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.

=========================================================================*/
#include "vtkKWThumbWheel.h"

#include "vtkKWApplication.h"
#include "vtkKWEntry.h"
#include "vtkKWLabel.h"
#include "vtkKWPushButton.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"

#define VTK_KW_TW_BORDER_SIZE      2
#define VTK_KW_TW_MIN_SIZE_NOTCHES 2
#define VTK_KW_TW_MIN_WIDTH        7
#define VTK_KW_TW_MIN_HEIGHT       7
#define VTK_KW_TW_NL_REFRESH_RATE  50
#define VTK_KW_TW_INDICATOR_SIZE   5

#define VTK_KW_TW_CLAMP_UCHAR_MACRO(var) if (var < 0) {var = 0;} else if (var > 255) {var = 255;}

// ---------------------------------------------------------------------------
vtkStandardNewMacro( vtkKWThumbWheel );
vtkCxxRevisionMacro(vtkKWThumbWheel, "1.9");

// ---------------------------------------------------------------------------
int vtkKWThumbWheelCommand(ClientData cd, 
                           Tcl_Interp *interp,
                           int argc, char *argv[]);

// ---------------------------------------------------------------------------
/* 
 * This part was generated by ImageConvert from image:
 *    arrow.png (zlib, base64)
 */
#define image_arrow_width         4
#define image_arrow_height        7
#define image_arrow_pixel_size    4
#define image_arrow_buffer_length 40

static unsigned char image_arrow[] = 
  "eNpjYGD4z4AK/jOgiv1HE/uPB+PSDwcAlQUP8Q==";

// ---------------------------------------------------------------------------
vtkKWThumbWheel::vtkKWThumbWheel()
{
  this->Value                      = 0;
  this->Resolution                 = 1;
  this->NonLinearMaximumMultiplier = 20;
  this->ThumbWheelWidth            = 80;
  this->ThumbWheelHeight           = 16;
  this->ResizeThumbWheel           = 1;
  this->SizeOfNotches              = 4;
  this->LinearThreshold            = 0.05;
  this->MinimumValue               = 0.0;
  this->ClampMinimumValue          = 0;
  this->MaximumValue               = 0.0;
  this->ClampMaximumValue          = 0;

  this->InteractionModes[0]        = VTK_KW_TW_MODE_LINEAR_MOTION;
  this->InteractionModes[1]        = VTK_KW_TW_MODE_NONLINEAR_MOTION;
  this->InteractionModes[2]        = VTK_KW_TW_MODE_TOGGLE_CENTER_INDICATOR;

  this->ThumbWheelPositionIndicatorColor[0] = 0.91;
  this->ThumbWheelPositionIndicatorColor[1] = 0.41;
  this->ThumbWheelPositionIndicatorColor[2] = 0.22;

  this->DisplayLabel               = 0;
  this->DisplayEntry               = 0;
  this->DisplayEntryAndLabelOnTop  = 1;
  this->DisplayThumbWheelPositionIndicator = 1;
  this->DisplayThumbWheelCenterIndicator = 0;
  this->PopupMode                  = 0;
  this->ExpandEntry                = 0;

  this->Command         = NULL;
  this->StartCommand    = NULL;
  this->EndCommand      = NULL;
  this->EntryCommand    = NULL;

  this->ThumbWheel      = vtkKWWidget::New();
  this->Entry           = NULL;
  this->Label           = NULL;
  this->TopLevel        = NULL;
  this->PopupPushButton = NULL;

  this->CommandFunction = vtkKWThumbWheelCommand;
  this->EntryResolution = 0;
  this->State           = vtkKWThumbWheel::Idle;

  this->ThumbWheelShift = 0.0;
  this->InInvokeCommand = 0;
}

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

  if (this->StartCommand)
    {
    delete [] this->StartCommand;
    this->StartCommand = NULL;
    }

  if (this->EndCommand)
    {
    delete [] this->EndCommand;
    this->EndCommand = NULL;
    }

  if (this->EntryCommand)
    {
    delete [] this->EntryCommand;
    this->EntryCommand = NULL;
    }

  if (this->ThumbWheel)
    {
    this->ThumbWheel->Delete();
    this->ThumbWheel = NULL;
    }

  if (this->Entry)
    {
    this->Entry->Delete();
    this->Entry = NULL;
    }

  if (this->Label)
    {
    this->Label->Delete();
    this->Label = NULL;
    }

  if (this->TopLevel)
    {
    this->TopLevel->Delete();
    this->TopLevel = NULL;
    }

  if (this->PopupPushButton)
    {
    this->PopupPushButton->Delete();
    this->PopupPushButton = NULL;
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::Create(vtkKWApplication *app, 
                             const char * vtkNotUsed(args))
{
  const char *wname;

  // Set the application

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

  this->SetApplication(app);

  // Create the frame that will embend all widgets

  wname = this->GetWidgetName();
  this->Script("frame %s -bd 0", wname);

  // If we need the thumbwheel to popup, create the toplevel and the pushbutton

  if (this->PopupMode)
    {
    this->TopLevel = vtkKWWidget::New();
    this->TopLevel->Create(
      app, "toplevel", "-bg black -bd 2 -relief flat");
    this->Script("wm overrideredirect %s 1", 
                 this->TopLevel->GetWidgetName());
    this->Script("wm withdraw %s", 
                 this->TopLevel->GetWidgetName());

    this->PopupPushButton = vtkKWPushButton::New();
    this->PopupPushButton->SetParent(this);
    this->PopupPushButton->Create(app, "-padx 0 -pady 0");

    this->PopupPushButton->SetImageOption(image_arrow, 
                                          image_arrow_width, 
                                          image_arrow_height, 
                                          image_arrow_pixel_size,
                                          image_arrow_buffer_length);

    this->ThumbWheel->SetParent(this->TopLevel);
    }
  else
    {
    this->ThumbWheel->SetParent(this);
    }

  // Create the scale

  ostrstream params;
  params << " -bd " << VTK_KW_TW_BORDER_SIZE 
         << " -highlightthickness 0"
         << " -relief sunken" << ends;
  this->ThumbWheel->Create(app, "label", params.str());
  params.rdbuf()->freeze(0);

  this->UpdateThumbWheelImage();
  this->Bind();
  this->PackWidget();

  // Update enable state

  this->UpdateEnableState();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetValue(float arg)
{
  if (this->Value == arg)
    {
    return;
    }

  this->Value = arg;
  if (this->ClampMinimumValue && this->Value < this->MinimumValue)
    {
    this->Value = this->MinimumValue;
    }
  if (this->ClampMaximumValue && this->Value > this->MaximumValue)
    {
    this->Value = this->MaximumValue;
    }

  this->Modified();

  if (this->Entry && this->Entry->IsCreated())
    {
    this->Entry->SetValue(this->Value, this->EntryResolution);
    }
  
  this->InvokeCommand();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetResolution(float arg)
{
  if (this->Resolution == arg)
    {
    return;
    }

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

  if (this->Entry && this->Entry->IsCreated())
    {
    this->UpdateEntryResolution();
    this->Entry->SetValue(this->Value, this->EntryResolution);
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetThumbWheelWidth(int arg)
{
  if (this->ThumbWheelWidth == arg)
    {
    return;
    }

  this->ThumbWheelWidth = 
    (arg < VTK_KW_TW_MIN_WIDTH ? VTK_KW_TW_MIN_WIDTH : arg);
  this->Modified();

  this->UpdateThumbWheelImage();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetThumbWheelHeight(int arg)
{
  if (this->ThumbWheelHeight == arg)
    {
    return;
    }

  this->ThumbWheelHeight = 
    (arg < VTK_KW_TW_MIN_HEIGHT ? VTK_KW_TW_MIN_HEIGHT : arg);
  this->Modified();

  this->UpdateThumbWheelImage();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetResizeThumbWheel(int arg)
{
  if (this->ResizeThumbWheel == arg)
    {
    return;
    }

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

  this->Bind();
  this->PackWidget();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetDisplayThumbWheelCenterIndicator(int arg)
{
  if (this->DisplayThumbWheelCenterIndicator == arg)
    {
    return;
    }

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

  this->UpdateThumbWheelImage();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::ToggleDisplayThumbWheelCenterIndicator()
{
  if (this->DisplayThumbWheelCenterIndicator)
    {
    this->DisplayThumbWheelCenterIndicatorOff();
    }
  else
    {
    this->DisplayThumbWheelCenterIndicatorOn();
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::ResizeThumbWheelCallback()
{
  this->Script("concat [winfo width %s] [winfo height %s]",
               this->ThumbWheel->GetWidgetName(),
               this->ThumbWheel->GetWidgetName());
  
  int tw, th;
  sscanf(this->Application->GetMainInterp()->result, "%d %d", &tw, &th);

  // Remove the border size (-bd)

  tw -= VTK_KW_TW_BORDER_SIZE * 2;
  th -= VTK_KW_TW_BORDER_SIZE * 2;
  
  if (this->ThumbWheelWidth == tw && this->ThumbWheelHeight == th)
    {
    return;
    }

  this->ThumbWheelWidth =
    (tw < VTK_KW_TW_MIN_WIDTH ? VTK_KW_TW_MIN_WIDTH : tw);
  this->ThumbWheelHeight =
    (th < VTK_KW_TW_MIN_HEIGHT ? VTK_KW_TW_MIN_HEIGHT : th);

  this->UpdateThumbWheelImage();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetSizeOfNotches(float arg)
{
  if (this->SizeOfNotches == arg)
    {
    return;
    }

  this->SizeOfNotches = 
    (arg < VTK_KW_TW_MIN_SIZE_NOTCHES ? VTK_KW_TW_MIN_SIZE_NOTCHES : arg);
  this->Modified();

  this->UpdateThumbWheelImage();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::CreateEntry()
{
  if (this->Entry && this->Entry->IsCreated())
    {
    return;
    }

  this->Entry = vtkKWEntry::New();
  this->Entry->SetParent(this);
  this->Entry->Create(this->Application, "-width 7");
  this->Entry->SetEnabled(this->Enabled);
  this->UpdateEntryResolution();
  this->Entry->SetValue(this->GetValue(), this->EntryResolution);
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetDisplayEntry(int arg)
{
  if (this->DisplayEntry == arg)
    {
    return;
    }

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

  if (this->DisplayEntry && !this->Entry)
    {
    this->CreateEntry();
    }

  this->Bind();
  this->PackWidget();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::CreateLabel()
{
  if (this->Label && this->Label->IsCreated())
    {
    return;
    }

  this->Label = vtkKWLabel::New();
  this->Label->SetParent(this);
  this->Label->Create(this->Application, "");
  this->Label->SetEnabled(this->Enabled);
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetDisplayLabel(int arg)
{
  if (this->DisplayLabel == arg)
    {
    return;
    }

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

  if (this->DisplayLabel && !this->Label)
    {
    this->CreateLabel();
    }

  this->Bind();
  this->PackWidget();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetLabel(const char *label)
{
  if (!this->Label)
    {
    this->CreateLabel();
    }

  this->Label->SetLabel(label);
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetDisplayEntryAndLabelOnTop(int arg)
{
  if (this->DisplayEntryAndLabelOnTop == arg)
    {
    return;
    }

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

  this->PackWidget();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetExpandEntry(int arg)
{
  if (this->ExpandEntry == arg)
    {
    return;
    }

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

  this->PackWidget();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetInteractionMode(int mode, int arg)
{
  if (mode < 0 || mode > 2 || this->InteractionModes[mode] == arg)
    {
    return;
    }

  this->InteractionModes[mode] = arg;
  if (this->InteractionModes[mode] < VTK_KW_TW_MODE_NONE)
    {
    this->InteractionModes[mode] = VTK_KW_TW_MODE_NONE;
    }
  else if (this->InteractionModes[mode] > VTK_KW_TW_MODE_TOGGLE_CENTER_INDICATOR)
    {
    this->InteractionModes[mode] = VTK_KW_TW_MODE_TOGGLE_CENTER_INDICATOR;
    }
  this->Modified();

  this->Bind();
}

// ---------------------------------------------------------------------------
int vtkKWThumbWheel::GetInteractionMode(int mode)
{
  if (mode < 0 || mode > 2)
    {
    return VTK_KW_TW_MODE_NONE;
    }
  return InteractionModes[mode];
}

//----------------------------------------------------------------------------
char *vtkKWThumbWheel::GetInteractionModeAsString(int mode)
{
  if (mode < 0 || mode > 2)
    {
    return (char *)"Invalid mode";
    }

  switch (this->InteractionModes[mode])
    {
    case VTK_KW_TW_MODE_NONE:
      return (char *)"None";
      break;
    case VTK_KW_TW_MODE_LINEAR_MOTION:
      return (char *)"Linear";
      break;
    case VTK_KW_TW_MODE_NONLINEAR_MOTION:
      return (char *)"NonLinear";
      break;
    case VTK_KW_TW_MODE_TOGGLE_CENTER_INDICATOR:
      return (char *)"ToggleCenterIndicator";
      break;
    default:
      return (char *)"Unknown";
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::PackWidget()
{
  if (this->ThumbWheel && this->ThumbWheel->IsCreated())
    {
    if (this->DisplayEntryAndLabelOnTop && !this->PopupMode)
      {
      this->Script("pack %s -side bottom -fill x -expand %d -pady 0 -padx 0",
                   this->ThumbWheel->GetWidgetName(),
                   this->ResizeThumbWheel);
      }
    else
      {
      this->Script("pack %s -side left -fill x -expand %d -pady 0 -padx 0",
                   this->ThumbWheel->GetWidgetName(),
                   this->ResizeThumbWheel);
      }
    }

  if (this->Label && this->Label->IsCreated())
    {
    this->Script("pack forget %s", this->Label->GetWidgetName());
    if (this->DisplayLabel)
      {
      if (this->DisplayEntryAndLabelOnTop || this->PopupMode)
        {
        this->Script("pack %s -side left -padx 0 -fill y", 
                     this->Label->GetWidgetName());
        }
      else
        {
        this->Script("pack %s -side left -padx 0 -fill y -before %s", 
                     this->Label->GetWidgetName(), 
                     this->ThumbWheel->GetWidgetName());
        }
      }
    }

  if (this->Entry && this->Entry->IsCreated())
    {
    this->Script("pack forget %s", this->Entry->GetWidgetName());
    if (this->DisplayEntry)
      {
      if (this->PopupMode)
        {
        this->Script("pack %s -side left -padx 0 %s", 
                     this->Entry->GetWidgetName(),
                     (this->ExpandEntry ? "-fill both -expand t" : "-fill y"));
        }
      else
        {
        if (this->DisplayEntryAndLabelOnTop)
          {
          this->Script("pack %s -side right -padx 0 -fill y", 
                       this->Entry->GetWidgetName());
          }
        else
          {
          this->Script("pack %s -side right -padx 0 -fill y -after %s", 
                       this->Entry->GetWidgetName(), 
                       this->ThumbWheel->GetWidgetName());
          }
        }
      }
    }

  if (this->PopupMode && 
      this->PopupPushButton && this->PopupPushButton->IsCreated())
    {
    this->Script("pack forget %s", this->PopupPushButton->GetWidgetName());
    if (this->DisplayLabel || this->DisplayEntry)
      {
      this->Script("pack %s -side left -padx 1 -fill y -ipadx 1 -after %s", 
                   this->PopupPushButton->GetWidgetName(),
                   (this->Entry ? this->Entry->GetWidgetName() 
                                : this->Label->GetWidgetName()));
      }
    else
      {
      this->Script("pack %s -side left -padx 1 -fill y -ipadx 1", 
                   this->PopupPushButton->GetWidgetName());
      }
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::Bind()
{
  if (this->ThumbWheel && this->ThumbWheel->IsCreated())
    {
    this->Script("bind %s <ButtonRelease> {%s StopMotionCallback}",
                 this->ThumbWheel->GetWidgetName(), this->GetTclName());

    // Interaction modes

    int i;
    for (i = 0 ; i < 3; i++)
      {
      int b = i + 1;
      switch (this->InteractionModes[i])
        {
        case VTK_KW_TW_MODE_LINEAR_MOTION:
          this->Script("bind %s <Button-%d> {%s StartLinearMotionCallback}",
                       this->ThumbWheel->GetWidgetName(), b, this->GetTclName());
          this->Script("bind %s <B%d-Motion> {%s PerformLinearMotionCallback}",
                       this->ThumbWheel->GetWidgetName(), b, this->GetTclName());
          break;
        case VTK_KW_TW_MODE_NONLINEAR_MOTION:
          this->Script("bind %s <Button-%d> {%s StartNonLinearMotionCallback}",
                       this->ThumbWheel->GetWidgetName(), b, this->GetTclName());
          this->Script("bind %s <B%d-Motion> {}", 
                       this->ThumbWheel->GetWidgetName(), b);
          break;
        case VTK_KW_TW_MODE_TOGGLE_CENTER_INDICATOR:
          this->Script("bind %s <Button-%d> {%s ToggleDisplayThumbWheelCenterIndicator}",
                       this->ThumbWheel->GetWidgetName(), b, this->GetTclName());
          this->Script("bind %s <B%d-Motion> {}", 
                       this->ThumbWheel->GetWidgetName(), b);
          break;
        default:
          this->Script("bind %s <Button-%d> {}", 
                       this->ThumbWheel->GetWidgetName(), b);
          this->Script("bind %s <B%d-Motion> {}", 
                       this->ThumbWheel->GetWidgetName(), b);
        }
      }

    // Auto resize

    if (this->ResizeThumbWheel)
      {
      this->Script("bind %s <Configure> {%s ResizeThumbWheelCallback}",
                   this->ThumbWheel->GetWidgetName(), this->GetTclName());
      }
    else
      {
      this->Script("bind %s <Configure> {}", this->ThumbWheel->GetWidgetName());
      }

    // If in popup mode, leaving the toplevel will withdraw it, unless 
    // the user is interacting with the scale.

    if (this->PopupMode &&
        this->TopLevel && this->TopLevel->IsCreated())
      {
      this->Script("bind %s <Leave> {%s WithdrawPopupCallback}",
                   this->TopLevel->GetWidgetName(), this->GetTclName());
      int i;
      for (i = 0 ; i < 3; i++)
        {
        this->Script("bind %s <Button-%d> {+bind %s <Leave> {}}",
                     this->ThumbWheel->GetWidgetName(),
                     i + 1,
                     this->TopLevel->GetWidgetName());
        }
      this->Script("bind %s <ButtonRelease> "
                   "{+bind %s <Leave> {%s WithdrawPopupCallback}}",
                   this->ThumbWheel->GetWidgetName(), 
                   this->TopLevel->GetWidgetName(), 
                   this->GetTclName());
      }
    }

  if (this->Entry && this->Entry->IsCreated())
    {
    this->Script("bind %s <Return> {%s EntryCallback}",
                 this->Entry->GetWidgetName(), this->GetTclName());
    this->Script("bind %s <FocusOut> {%s EntryCallback}",
                 this->Entry->GetWidgetName(), this->GetTclName());
    }

  if (this->PopupMode && 
      this->PopupPushButton && this->PopupPushButton->IsCreated())
    {
    this->Script("bind %s <ButtonPress> {%s DisplayPopupCallback}",
                 this->PopupPushButton->GetWidgetName(), this->GetTclName());
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::UnBind()
{
  if (this->ThumbWheel && this->ThumbWheel->IsCreated())
    {
    this->Script("bind %s <ButtonPress> {}", this->ThumbWheel->GetWidgetName());
    this->Script("bind %s <ButtonRelease> {}",this->ThumbWheel->GetWidgetName());

    int i;
    for (i = 0 ; i < 3; i++)
      {
      this->Script("bind %s <Button-%d> {}", 
                   this->ThumbWheel->GetWidgetName(), i + 1);
      this->Script("bind %s <B%d-Motion> {}", 
                   this->ThumbWheel->GetWidgetName(), i + 1);
      }

    this->Script("bind %s <Configure> {}", this->ThumbWheel->GetWidgetName());
    }

  if (this->Entry && this->Entry->IsCreated())
    {
    this->Script("bind %s <Return> {}", this->Entry->GetWidgetName());
    this->Script("bind %s <FocusOut> {}", this->Entry->GetWidgetName());
    }

  if (this->PopupMode && 
      this->PopupPushButton && this->PopupPushButton->IsCreated())
    {
    this->Script("bind %s <ButtonPress> {}", 
                 this->PopupPushButton->GetWidgetName());
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::DisplayPopupCallback()
{
  if (!this->PopupMode ||
      !this->PopupPushButton || !this->PopupPushButton->IsCreated() ||
      !this->TopLevel || !this->TopLevel->IsCreated() ||
      !this->ThumbWheel || !this->ThumbWheel->IsCreated())
    {
    return;
    }

  // Get the position of the mouse, the position and size of the push button,
  // the size of the scale.

  this->Script("concat "
               " [winfo pointerx %s] [winfo pointery %s]" 
               " [winfo rooty %s] [winfo height %s]"
               " [winfo width %s] [winfo height %s]",
               this->GetWidgetName(), this->GetWidgetName(),
               this->PopupPushButton->GetWidgetName(), 
               this->PopupPushButton->GetWidgetName(),
               this->ThumbWheel->GetWidgetName(),
               this->ThumbWheel->GetWidgetName());
  
  int x, y, py, ph, tw, th;
  sscanf(this->Application->GetMainInterp()->result, 
         "%d %d %d %d %d %d", 
         &x, &y, &py, &ph, &tw, &th);
 
  // Place the scale so that the slider is coincident with the x mouse position
  // and just below the push button
  
  x -= tw / 2;

  if (py <= y && y <= (py + ph -1))
    {
    y = py + ph - 3;
    }
  else
    {
    y -= th / 2;
    }

  this->Script("wm geometry %s +%d+%d",
               this->TopLevel->GetWidgetName(), x, y);
  this->Script("update");
  this->Script("wm deiconify %s", 
               this->TopLevel->GetWidgetName());
  this->Script("raise %s", 
               this->TopLevel->GetWidgetName());
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::WithdrawPopupCallback()
{
  if (!this->PopupMode ||
      !this->TopLevel || !this->TopLevel->IsCreated() ||
      !this->ThumbWheel || !this->ThumbWheel->IsCreated())
    {
    return;
    }

  // Withdraw the popup

  this->Script("wm withdraw %s", this->TopLevel->GetWidgetName());
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::UpdateEntryResolution()
{
  if (fabs(this->Resolution) >= 1.0)
    {
    this->EntryResolution = 0;
    }
  else if (this->IsCreated())
    {
    // Trick here: use the 'expr' Tcl command to display the shortest
    // representation of the floating point number this->Resolution.
    // sprintf would be of no help here.
    const char *res = this->Script("expr %f", fabs(this->Resolution));
    const char *pos = strchr(res, '.');
    if (pos)
      {
      this->EntryResolution = static_cast<int>(strlen(res)) - (pos - res) - 1;
      }
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::EntryCallback()
{
  float value = this->Entry->GetValueAsFloat();
  float old_value = this->GetValue();
  this->SetValue(value);

  if (value != old_value)
    {
    this->InvokeEntryCommand();
    }
}

// ---------------------------------------------------------------------------
// Return normalized mouse position relative to the thumbwheel.
// i.e. if the mouse is "inside" the thumbwheel, the range is [0.0, 1.0]
// (< 0.0 if position < lower limit , > 1.0 if position > upper limit)
float vtkKWThumbWheel::GetMousePositionInThumbWheel()
{
  this->Script("concat [winfo pointerx %s] [winfo rootx %s]",
               this->GetWidgetName(), this->ThumbWheel->GetWidgetName());
  
  int x, tx;
  sscanf(this->Application->GetMainInterp()->result, "%d %d", &x, &tx);

  return (float)(x - tx - VTK_KW_TW_BORDER_SIZE) / 
    (float)(this->ThumbWheelWidth - 1);
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::StartLinearMotionCallback()
{
  if (this->State == vtkKWThumbWheel::InMotion)
    {
    this->StopMotionCallback();
    }

  this->State = vtkKWThumbWheel::InMotion;

  // Save current state (mouse position, thumbwheel shift, value)

  this->StartLinearMotionState.MousePosition = 
    this->GetMousePositionInThumbWheel();
  this->StartLinearMotionState.ThumbWheelShift = this->ThumbWheelShift;
  this->StartLinearMotionState.Value = this->Value;
  this->StartLinearMotionState.InPerform = 0;

  // If the position indicator has to be shown, drawing the wheel
  
  if (this->DisplayThumbWheelPositionIndicator)
    {
    this->UpdateThumbWheelImage(this->StartLinearMotionState.MousePosition);
    }

  this->InvokeStartCommand();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::PerformLinearMotionCallback()
{
  if (this->State != vtkKWThumbWheel::InMotion ||
      this->StartLinearMotionState.InPerform)
    {
    return;
    }

  this->StartLinearMotionState.InPerform = 1;

  // pos: mouse position relative to the wheel range [0.0, 1.0]
  // distance: distance between current pos and starting pos

  float pos = this->GetMousePositionInThumbWheel();
  float distance = pos - this->StartLinearMotionState.MousePosition;

  float new_value = 
    this->StartLinearMotionState.Value + 
    (distance / this->LinearThreshold) * this->Resolution;

  // Update thumbwheel aspect

  this->ThumbWheelShift = 
    this->StartLinearMotionState.ThumbWheelShift + distance;
  this->UpdateThumbWheelImage(pos);

  // If the resolution implies an integer, round the value

  if (this->EntryResolution == 0)
    {
    this->SetValue((int)(new_value));
    }
  else
    {
    this->SetValue(new_value);
    }

  this->StartLinearMotionState.InPerform = 0;
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::StartNonLinearMotionCallback()
{
  if (this->State == vtkKWThumbWheel::InMotion)
    {
    this->StopMotionCallback();
    }

  this->State = vtkKWThumbWheel::InMotion;

  // Save current state (value)

  this->StartNonLinearMotionState.Value = this->Value;
  this->StartNonLinearMotionState.Increment = 0.0;
  this->StartNonLinearMotionState.InPerform = 0;

  this->InvokeStartCommand();

  // Now perform the motion immediately

  this->PerformNonLinearMotionCallback();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::PerformNonLinearMotionCallback()
{
  if (this->State != vtkKWThumbWheel::InMotion ||
      this->StartNonLinearMotionState.InPerform)

    {
    return;
    }

  this->StartNonLinearMotionState.InPerform = 1;

  // Get current mouse position, compute "speed", update increment

  // pos:   mouse position relative to the wheel range [0.0, 1.0], clamped
  // xn:    mouse position value mapped to [-1.0, 1.0], i.e. cos()
  // angle: angle corresponding  to that xn
  // yn:    sin(angle), i.e. [0.0, 1.0]

  float pos = this->GetMousePositionInThumbWheel();
  if (pos < 0.0)
    {
    pos = 0.0;
    }
  else if (pos > 1.0)
    {
    pos = 1.0;
    }
  float xn = pos * 2.0 - 1.0;
  double yn = sin(acos(xn));
  float direction = (xn < 0.0 ? -1.0 : 1.0);

  // Compute the non-linear increment corresponding to that position, and
  // add it to the increment buffer which will be added to the value (can
  // not update value directly since it can be rounded/clamped by SetValue)

  float inc = 
    ((1.0 - yn) * (this->NonLinearMaximumMultiplier - 0.0) + 0.0) * this->Resolution;
  this->StartNonLinearMotionState.Increment += inc * direction;
  float new_value = this->StartNonLinearMotionState.Value + 
    this->StartNonLinearMotionState.Increment;

  // Update thumbwheel aspect (accelerates depending on the position)

  float shift = (0.0704321 + (1.0 - yn) * 0.00543) * direction;
  this->ThumbWheelShift += shift;
  this->UpdateThumbWheelImage(pos);

  // If the resolution implies an integer, round the value

  if (this->EntryResolution == 0)
    {
    this->SetValue((int)(new_value));
    }
  else
    {
    this->SetValue(new_value);
    }
  
  // Check if we are still in motion later (asynchronously)

  this->Script("after %d {%s PerformNonLinearMotionCallback}", 
               VTK_KW_TW_NL_REFRESH_RATE,
               this->GetTclName());

  this->StartNonLinearMotionState.InPerform = 0;
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::StopMotionCallback()
{
  this->State = vtkKWThumbWheel::Idle;

  // If the position indicator was shown, hide it by redrawing the wheel
  
  if (this->DisplayThumbWheelPositionIndicator)
    {
    this->UpdateThumbWheelImage();
    }

  this->InvokeEndCommand();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::InvokeCommand()
{
  if (!this->Command || this->InInvokeCommand)
    {
    return;
    }
  this->InInvokeCommand = 1;

  this->Script("eval %s", this->Command);

  this->InInvokeCommand = 0;
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::InvokeStartCommand()
{
  if (this->StartCommand)
    {
    this->Script("eval %s", this->StartCommand);
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::InvokeEndCommand()
{
  if (this->EndCommand)
    {
    this->Script("eval %s", this->EndCommand);
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::InvokeEntryCommand()
{
  if (this->EntryCommand)
    {
    this->Script("eval %s",this->EntryCommand);
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetCommand(vtkKWObject* Object, 
                                 const char *CommandString)
{
  if (this->Command)
    {
    delete [] this->Command;
    }

  if (!Object)
    {
    return;
    }

  ostrstream command;
  command << Object->GetTclName() << " " << CommandString << ends;
  this->Command = command.str();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetStartCommand(vtkKWObject* Object, 
                                      const char * MethodAndArgString)
{
  if (this->StartCommand)
    {
    delete [] this->StartCommand;
    }

  if (!Object)
    {
    return;
    }

  ostrstream command;
  command << Object->GetTclName() << " " << MethodAndArgString << ends;
  this->StartCommand = command.str();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetEndCommand(vtkKWObject* Object, 
                                    const char * MethodAndArgString)
{
  if (this->EndCommand)
    {
    delete [] this->EndCommand;
    }

  if (!Object)
    {
    return;
    }

  ostrstream command;
  command << Object->GetTclName() << " " << MethodAndArgString << ends;
  this->EndCommand = command.str();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetEntryCommand(vtkKWObject* Object, 
                                      const char * MethodAndArgString)
{
  if (this->EntryCommand)
    {
    delete [] this->EntryCommand;
    }

  if (!Object)
    {
    return;
    }

  ostrstream command;
  command << Object->GetTclName() << " " << MethodAndArgString << ends;
  this->EntryCommand = command.str();
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetBalloonHelpString(const char *string)
{
  // Interaction modes

  ostrstream modes;
  modes << string << " (";

  int i;
  for (i = 0 ; i < 3; i++)
    {
    if (this->InteractionModes[i] == VTK_KW_TW_MODE_NONE)
      {
      continue;
      }
    switch (i)
      {
      case 0:
        modes << "left";
        break;
      case 1:
        modes << "middle";
        break;
      case 2:
        modes << "right";
        break;
      }

    modes << " button: ";

    switch (this->InteractionModes[i])
      {
      case VTK_KW_TW_MODE_LINEAR_MOTION:
        modes << "linear";
        break;
      case VTK_KW_TW_MODE_NONLINEAR_MOTION:
        modes << "non-linear";
        break;
      case VTK_KW_TW_MODE_TOGGLE_CENTER_INDICATOR:
        modes << "toggle center indicator";
        break;
      default:
        modes << "unknown";
      }
    
    if ((i == 0 && (this->InteractionModes[1] != VTK_KW_TW_MODE_NONE ||
                    this->InteractionModes[2] != VTK_KW_TW_MODE_NONE)) ||
        (i == 1 && this->InteractionModes[2] != VTK_KW_TW_MODE_NONE))
      {
      modes << ", ";
      }
    }

  modes << ")" << ends;
  this->ThumbWheel->SetBalloonHelpString(modes.str());
  modes.rdbuf()->freeze(0);

  if (this->Entry)
    {
    this->Entry->SetBalloonHelpString(string);
    }

  if (this->Label)
    {
    this->Label->SetBalloonHelpString(string);
    }

  if (this->PopupMode && this->PopupPushButton)
    {
    ostrstream temp;
    temp << string << " (press this button to display a thumbwheel)" << ends;
    this->PopupPushButton->SetBalloonHelpString(temp.str());
    temp.rdbuf()->freeze(0);
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::SetBalloonHelpJustification(int j)
{
  this->ThumbWheel->SetBalloonHelpJustification(j);

  if (this->Entry)
    {
    this->Entry->SetBalloonHelpJustification(j);
    }

  if (this->Label)
    {
    this->Label->SetBalloonHelpJustification(j);
    }

  if (this->PopupMode && this->PopupPushButton)
    {
    this->PopupPushButton->SetBalloonHelpJustification(j);
    }
}

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

  if (this->Entry)
    {
    this->Entry->SetEnabled(this->Enabled);
    }

  if (this->Label)
    {
    this->Label->SetEnabled(this->Enabled);
    }

  if (this->ThumbWheel)
    {
    this->ThumbWheel->SetEnabled(this->Enabled);
    }

  if (this->PopupPushButton)
    {
    this->PopupPushButton->SetEnabled(this->Enabled);
    }
}

// ---------------------------------------------------------------------------
void vtkKWThumbWheel::UpdateThumbWheelImage(float pos)
{
  // Show position indicator ? Compute range

  int posx_start = 0, posx_end = 0;
  float indicator_hsv[3];
  if (this->DisplayThumbWheelPositionIndicator && 
      this->State == vtkKWThumbWheel::InMotion)
    {
    int posx = (int)(pos * (float)(this->ThumbWheelWidth - 1));
    posx_start = posx - (int)floor((double)(VTK_KW_TW_INDICATOR_SIZE - 1) * 0.5);
    posx_end = posx + (int)floor((double)(VTK_KW_TW_INDICATOR_SIZE) * 0.5);
    if (posx_start < 0)
      {
      posx_end -= posx_start;
      posx_start = 0;
      }
    if (posx_end > (this->ThumbWheelWidth - 1))
      {
      posx_start -= (posx_end - (this->ThumbWheelWidth - 1));
      posx_end = this->ThumbWheelWidth - 1;
      }
    vtkMath::RGBToHSV(this->ThumbWheelPositionIndicatorColor, indicator_hsv);
    }

  double width2 = 0.5 * (double)(this->ThumbWheelWidth - 1);

  // gray_global_shift: added to gray values (used to darken or lighten aspect)
  // gray_relief_shift: added to gray values to provide the "notch" relief/shadow

  int gray_global_shift = -20;
  int gray_relief_shift = 35;

  int img_pixel_size = 3;
  int img_row_size = img_pixel_size * this->ThumbWheelWidth;
  int img_buffer_size = img_row_size * this->ThumbWheelHeight;

  // notch_size: size of a notch given the number of notches in a half-wheel

  double notch_size = 
    1.0 / ((double)this->ThumbWheelWidth / (double)this->SizeOfNotches);
  
  int last_notch = 0;
  int relief_flag = 0;

  // Allocate buffer for the whole wheel

  unsigned char *img_buffer = new unsigned char[img_buffer_size];

    // img_ptr_s0: 1st row = gray - shadow * 2 (i.e. dark shadow)
    // img_ptr_s1: 2nd row = gray - shadow     (i.e. ligh shadow)
    // img_ptr:    3rd row = gray              (i.e. normal aspect)

  unsigned char *img_ptr_s0 = img_buffer;
  unsigned char *img_ptr_s1 = img_ptr_s0 + img_row_size;
  unsigned char *img_ptr    = img_ptr_s1 + img_row_size;

  int x;
  for (x = 0; x < this->ThumbWheelWidth; x++)
    {
    // xn:     x value mapped to [-1.0, 1.0], i.e. cos()
    // angle:  angle corresponding  to that xn
    // yn:     sin(angle), i.e. [0.0, 1.0]
    // anglen: normalized angle (+ shift), i.e. mapped to [0.0, 1.0]
    // notch:  index of the current notch given the current angle

    double xn = -1.0 + (double)x / width2;
    double angle = acos(xn);
    double yn = sin(angle);
    double anglen = fmod(angle / 3.14159265358979 + this->ThumbWheelShift, 1.0);
    int notch = (int)(floor(anglen / notch_size));

    // gray:    the gray value for the current pixel in the notch
    // gray_s0: gray + dark shadow
    // gray_s1: gray + light shadow

    int gray = (int)(255.0 * yn + gray_global_shift);

    // If the current x position is a transition from one notch to
    // the other, add some "relief" by first (relief_flag == 0) casting 
    // a shadow (i.e. gray - gray_relief_shift), then (relief_flag == 1)
    // casting a highlight (i.e. gray + gray_relief_shift)

    if (x == 0)
      {
      last_notch = notch;
      }
    else if (notch != last_notch) 
      {
      if (relief_flag == 0)
        {
        gray -= gray_relief_shift;
        relief_flag = 1;
        } 
      else 
        {
        gray += gray_relief_shift;
        relief_flag = 0;
        last_notch = notch;
        }
      }

    // Now the dark and light shadow for the first and second rows of the wheel

    int gray_s0 = gray - gray_relief_shift * 2;
    int gray_s1 = gray - gray_relief_shift;

      // Clamp and assign

    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray);
    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray_s0);
    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray_s1);

    if (this->DisplayThumbWheelPositionIndicator && 
        this->State == vtkKWThumbWheel::InMotion &&
        x >= posx_start && x <= posx_end)
      {
      float r, g, b;

      vtkMath::HSVToRGB(indicator_hsv[0], indicator_hsv[1], (float)gray / 255.0,
                        &r, &g, &b);
      *img_ptr++ = (int)(r * 255.0);
      *img_ptr++ = (int)(g * 255.0);
      *img_ptr++ = (int)(b * 255.0);

      vtkMath::HSVToRGB(indicator_hsv[0], indicator_hsv[1], (float)gray_s0/255.0,
                        &r, &g, &b);
      *img_ptr_s0++ = (int)(r * 255.0);
      *img_ptr_s0++ = (int)(g * 255.0);
      *img_ptr_s0++ = (int)(b * 255.0);

      vtkMath::HSVToRGB(indicator_hsv[0], indicator_hsv[1], (float)gray_s1/255.0,
                        &r, &g, &b);
      *img_ptr_s1++ = (int)(r * 255.0);
      *img_ptr_s1++ = (int)(g * 255.0);
      *img_ptr_s1++ = (int)(b * 255.0);

      }
    else
      {
      *img_ptr++ = gray; *img_ptr++ = gray; *img_ptr++ = gray;

      *img_ptr_s0++ = gray_s0; *img_ptr_s0++ = gray_s0; *img_ptr_s0++ = gray_s0;

      *img_ptr_s1++ = gray_s1; *img_ptr_s1++ = gray_s1; *img_ptr_s1++ = gray_s1;
      }
    }

  // Copy the 1st row to the last row, then 2nd row to last-1 row, so
  // that we got symetrical vertical shadow

  memcpy(img_buffer + img_buffer_size - img_row_size, 
         img_ptr_s0 - img_row_size, img_row_size);

  memcpy(img_buffer + img_buffer_size - img_row_size * 2,
         img_ptr_s1 - img_row_size, img_row_size);

  // Copy the 3rd row to the remaining rows (the body of the wheel)

  int y;
  for (y = 3; y < this->ThumbWheelHeight - 2; y++)
    {
    memcpy(img_ptr, img_ptr_s1, img_row_size);
    img_ptr += img_row_size;
    }

  // Now add a center indicator

  #define VTK_KW_TW_INDICATOR_WIDTH 7
  #define VTK_KW_TW_INDICATOR_HEIGHT 6

  static int indicator[VTK_KW_TW_INDICATOR_WIDTH*VTK_KW_TW_INDICATOR_HEIGHT] = {
    -1, -1, -1,  0, -1, -1, -1,
    -1, -1,  0,  3,  0, -1, -1,
    -1,  0,  3,  2,  1,  0, -1,
     0,  3,  2,  2,  2,  1,  0,
     0,  1,  1,  1,  1,  1,  0,
     0,  0,  0,  0,  0,  0,  0};

  if (this->DisplayThumbWheelCenterIndicator)
    {
    int gray[4];
    gray[2] = 240 + gray_global_shift;
    gray[3] = (int)(gray[2] + 1.7 * gray_relief_shift);
    gray[1] = (int)(gray[2] - 1.7 * gray_relief_shift);
    gray[0] = (int)(gray[2] - 3.4 * gray_relief_shift);

    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray[0]);
    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray[1]);
    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray[2]);
    VTK_KW_TW_CLAMP_UCHAR_MACRO(gray[3]);

    int show_up_indicator = 
      (this->ThumbWheelHeight >= VTK_KW_TW_INDICATOR_HEIGHT * 2 + 2);

    int distance_to_indicator = (int)floor(
      0.5 * (this->ThumbWheelWidth-1-VTK_KW_TW_INDICATOR_WIDTH))*img_pixel_size;

    img_ptr = img_buffer + img_buffer_size 
      - img_row_size * VTK_KW_TW_INDICATOR_HEIGHT + distance_to_indicator;

    int *indicator_ptr = indicator;
    
    unsigned char *img_ptr2 = 0;
    if (show_up_indicator)
      {
      img_ptr2 = img_buffer 
        + img_row_size * (VTK_KW_TW_INDICATOR_HEIGHT-1) + distance_to_indicator;
      }

    for (y = 0; y < VTK_KW_TW_INDICATOR_HEIGHT; y++)
      {
      if (show_up_indicator)
        {
        for (x = 0; x < VTK_KW_TW_INDICATOR_WIDTH; x++)
          {
          if (*indicator_ptr >= 0)
            {
            img_ptr[0] = img_ptr[1] = img_ptr[2] =
              img_ptr2[0] = img_ptr2[1] = img_ptr2[2] = gray[*indicator_ptr];
            }
          img_ptr += img_pixel_size;
          img_ptr2 += img_pixel_size;
          indicator_ptr++;
          }
        img_ptr += img_row_size - VTK_KW_TW_INDICATOR_WIDTH * img_pixel_size;
        img_ptr2 += -img_row_size - VTK_KW_TW_INDICATOR_WIDTH * img_pixel_size;
        }
      else
        {
        for (x = 0; x < VTK_KW_TW_INDICATOR_WIDTH; x++)
          {
          if (*indicator_ptr >= 0)
            {
            img_ptr[0] = img_ptr[1] = img_ptr[2] = gray[*indicator_ptr];
            }
          img_ptr += img_pixel_size;
          indicator_ptr++;
          }
        img_ptr += img_row_size - VTK_KW_TW_INDICATOR_WIDTH * img_pixel_size;
        }
      }
    }

  // Update the Tk photo

  this->ThumbWheel->SetImageOption(img_buffer,
                                   this->ThumbWheelWidth, 
                                   this->ThumbWheelHeight, 
                                   img_pixel_size);

  delete [] img_buffer;
}

//----------------------------------------------------------------------------
void vtkKWThumbWheel::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << indent << "Value: " << this->Value << endl;
  os << indent << "MinimumValue: " << this->MinimumValue << endl;
  os << indent << "MaximumValue: " << this->MaximumValue << endl;
  os << indent << "ClampMinimumValue: " 
     << (this->ClampMinimumValue ? "On" : "Off") << endl;
  os << indent << "ClampMaximumValue: " 
     << (this->ClampMaximumValue ? "On" : "Off") << endl;
  os << indent << "Resolution: " << this->Resolution << endl;
  os << indent << "NonLinearMaximumMultiplier: " << this->NonLinearMaximumMultiplier << endl;
  os << indent << "ThumbWheelWidth: " << this->ThumbWheelWidth << endl;
  os << indent << "ThumbWheelHeight: " << this->ThumbWheelHeight << endl;
  os << indent << "SizeOfNotches: " << this->SizeOfNotches << endl;
  os << indent << "LinearThreshold: " << this->LinearThreshold << endl;
  os << indent << "ThumbWheelPositionIndicatorColor: (" 
     << this->ThumbWheelPositionIndicatorColor[0] << ", "
     << this->ThumbWheelPositionIndicatorColor[1] << ", "
     << this->ThumbWheelPositionIndicatorColor[2] << ")\n";
  os << indent << "ResizeThumbWheel: " 
     << (this->ResizeThumbWheel ? "On" : "Off") << endl;
  int i;
  for (i = 0; i < 3; i++)
    {
    os << indent << "InteractionMode[" << i << "]: " 
       << this->GetInteractionModeAsString(i) << endl;
    }
  os << indent << "DisplayLabel: " 
     << (this->DisplayLabel ? "On" : "Off") << endl;
  os << indent << "DisplayEntry: " 
     << (this->DisplayEntry ? "On" : "Off") << endl;
  os << indent << "DisplayEntryAndLabelOnTop: " 
     << (this->DisplayEntryAndLabelOnTop ? "On" : "Off") << endl;
  os << indent << "DisplayThumbWheelPositionIndicator: " 
     << (this->DisplayThumbWheelPositionIndicator ? "On" : "Off") << endl;
  os << indent << "DisplayThumbWheelCenterIndicator: " 
     << (this->DisplayThumbWheelCenterIndicator ? "On" : "Off") << endl;
  os << indent << "PupupMode: " 
     << (this->PopupMode ? "On" : "Off") << endl;
  os << indent << "ExpandEntry: " 
     << (this->ExpandEntry ? "On" : "Off") << endl;
  os << indent << "Label: " << this->Label << endl;
  os << indent << "Entry: " << this->Entry << endl;
  os << indent << "PopupPushButton: " << this->PopupPushButton << endl;
}

