/*
 *
 *  Copyright (C) 2000 Silicon Graphics, Inc.  All Rights Reserved. 
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  Further, this software is distributed without any warranty that it is
 *  free of the rightful claim of any third person regarding infringement
 *  or the like.  Any license provided herein, whether implied or
 *  otherwise, applies only to this software file.  Patent licenses, if
 *  any, provided herein do not apply to combinations of this program with
 *  other software, or any other product whatsoever.
 * 
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 *  Mountain View, CA  94043, or:
 * 
 *  http://www.sgi.com 
 * 
 *  For further information regarding this notice, see: 
 * 
 *  http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */

/*
 * Copyright (C) 1990,91   Silicon Graphics, Inc.
 *
 _______________________________________________________________________
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 |
 |   $Revision: 1.1.1.1 $
 |
 |   Classes:
 |	SoHandleBoxDragger
 |
 |   Author(s): Paul Isaacs, David Mott
 |
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 _______________________________________________________________________
 */


#include <cstdio>
#include <Inventor/SoDB.h>
#include <Inventor/sensors/SoFieldSensor.h>
#include <Inventor/events/SoEvent.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/projectors/SbPlaneProjector.h>
#include <Inventor/projectors/SbLineProjector.h>
#include <Inventor/actions/SoHandleEventAction.h>
#include <Inventor/draggers/SoHandleBoxDragger.h>
#include <Inventor/nodes/SoSurroundScale.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoRotation.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSwitch.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/nodes/SoTransform.h>
#include <GL/gl.h>

#include "geom/SoHandleBoxDraggerGeom.h"


SO_KIT_SOURCE(SoHandleBoxDragger);


////////////////////////////////////////////////////////////////////////
//
// Description:
//    Constructor
//
SoHandleBoxDragger::SoHandleBoxDragger()
//
////////////////////////////////////////////////////////////////////////
{
    SO_KIT_CONSTRUCTOR(SoHandleBoxDragger);

    isBuiltIn = TRUE;

    // Don't create this by default. It's only really put into use
    // if this dragger is put inside a manipulator.
    SO_KIT_ADD_CATALOG_ENTRY(surroundScale, SoSurroundScale, 
				TRUE, topSeparator, geomSeparator,TRUE);

    // These parts will all go under the geomSeparator, for efficient 
    // rendering
    SO_KIT_ADD_CATALOG_ENTRY(drawStyle, SoDrawStyle, TRUE,
				geomSeparator, ,FALSE);

    SO_KIT_ADD_CATALOG_ENTRY(translator1Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(translator1, SoSeparator, TRUE,
				translator1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator1Active, SoSeparator, TRUE,
				translator1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator2Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(translator2, SoSeparator, TRUE,
				translator2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator2Active, SoSeparator, TRUE,
				translator2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator3Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(translator3, SoSeparator, TRUE,
				translator3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator3Active, SoSeparator, TRUE,
				translator3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator4Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(translator4, SoSeparator, TRUE,
				translator4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator4Active, SoSeparator, TRUE,
				translator4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator5Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(translator5, SoSeparator, TRUE,
				translator5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator5Active, SoSeparator, TRUE,
				translator5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator6Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(translator6, SoSeparator, TRUE,
				translator6Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator6Active, SoSeparator, TRUE,
				translator6Switch, ,TRUE);

    SO_KIT_ADD_CATALOG_ENTRY(extruder1Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder1, SoSeparator, TRUE,
				extruder1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder1Active, SoSeparator, TRUE,
				extruder1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder2Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder2, SoSeparator, TRUE,
				extruder2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder2Active, SoSeparator, TRUE,
				extruder2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder3Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder3, SoSeparator, TRUE,
				extruder3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder3Active, SoSeparator, TRUE,
				extruder3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder4Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder4, SoSeparator, TRUE,
				extruder4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder4Active, SoSeparator, TRUE,
				extruder4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder5Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder5, SoSeparator, TRUE,
				extruder5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder5Active, SoSeparator, TRUE,
				extruder5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder6Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder6, SoSeparator, TRUE,
				extruder6Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(extruder6Active, SoSeparator, TRUE,
				extruder6Switch, ,TRUE);

    SO_KIT_ADD_CATALOG_ENTRY(uniform1Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform1, SoSeparator, TRUE,
				uniform1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform1Active, SoSeparator, TRUE,
				uniform1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform2Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform2, SoSeparator, TRUE,
				uniform2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform2Active, SoSeparator, TRUE,
				uniform2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform3Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform3, SoSeparator, TRUE,
				uniform3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform3Active, SoSeparator, TRUE,
				uniform3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform4Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform4, SoSeparator, TRUE,
				uniform4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform4Active, SoSeparator, TRUE,
				uniform4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform5Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform5, SoSeparator, TRUE,
				uniform5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform5Active, SoSeparator, TRUE,
				uniform5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform6Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform6, SoSeparator, TRUE,
				uniform6Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform6Active, SoSeparator, TRUE,
				uniform6Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform7Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform7, SoSeparator, TRUE,
				uniform7Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform7Active, SoSeparator, TRUE,
				uniform7Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform8Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform8, SoSeparator, TRUE,
				uniform8Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(uniform8Active, SoSeparator, TRUE,
				uniform8Switch, ,TRUE);

    SO_KIT_ADD_CATALOG_ENTRY(arrowTranslation, SoTranslation, 
				TRUE, geomSeparator, ,FALSE);

    SO_KIT_ADD_CATALOG_ENTRY(arrow1Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow1, SoSeparator, TRUE,
				arrow1Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow2Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow2, SoSeparator, TRUE,
				arrow2Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow3Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow3, SoSeparator, TRUE,
				arrow3Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow4Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow4, SoSeparator, TRUE,
				arrow4Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow5Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow5, SoSeparator, TRUE,
				arrow5Switch, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow6Switch, SoSwitch, TRUE,
				geomSeparator, ,FALSE);
    SO_KIT_ADD_CATALOG_ENTRY(arrow6, SoSeparator, TRUE,
				arrow6Switch, ,TRUE);

    // read geometry for shared parts
    if (SO_KIT_IS_FIRST_INSTANCE())
	readDefaultParts("handleBoxDragger.iv", geomBuffer, sizeof(geomBuffer));

    SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0));
    SO_KIT_ADD_FIELD(scaleFactor, (1.0, 1.0, 1.0));

    SO_KIT_INIT_INSTANCE();

    // Set up the drawStyle node
    SoDrawStyle *ds = (SoDrawStyle *) getAnyPart( "drawStyle", TRUE );
    ds->style.setValue(SoDrawStyle::LINES);

    // translation box pieces.
  setPartAsDefault("translator1",     "handleBoxTranslator1");
  setPartAsDefault("translator1Active","handleBoxTranslator1Active");
  setPartAsDefault("translator2",     "handleBoxTranslator2");
  setPartAsDefault("translator2Active","handleBoxTranslator2Active");
  setPartAsDefault("translator3",     "handleBoxTranslator3");
  setPartAsDefault("translator3Active","handleBoxTranslator3Active");
  setPartAsDefault("translator4",     "handleBoxTranslator4");
  setPartAsDefault("translator4Active","handleBoxTranslator4Active");
  setPartAsDefault("translator5",     "handleBoxTranslator5");
  setPartAsDefault("translator5Active","handleBoxTranslator5Active");
  setPartAsDefault("translator6",     "handleBoxTranslator6");
  setPartAsDefault("translator6Active","handleBoxTranslator6Active");

    // face scale pieces.
    setPartAsDefault("extruder1",      "handleBoxExtruder1");
    setPartAsDefault("extruder1Active","handleBoxExtruder1Active");
    setPartAsDefault("extruder2",      "handleBoxExtruder2");
    setPartAsDefault("extruder2Active","handleBoxExtruder2Active");
    setPartAsDefault("extruder3",      "handleBoxExtruder3");
    setPartAsDefault("extruder3Active","handleBoxExtruder3Active");
    setPartAsDefault("extruder4",      "handleBoxExtruder4");
    setPartAsDefault("extruder4Active","handleBoxExtruder4Active");
    setPartAsDefault("extruder5",      "handleBoxExtruder5");
    setPartAsDefault("extruder5Active","handleBoxExtruder5Active");
    setPartAsDefault("extruder6",      "handleBoxExtruder6");
    setPartAsDefault("extruder6Active","handleBoxExtruder6Active");

    // uniform scale pieces.
    setPartAsDefault("uniform1",      "handleBoxUniform1");
    setPartAsDefault("uniform1Active","handleBoxUniform1Active");
    setPartAsDefault("uniform2",      "handleBoxUniform2");
    setPartAsDefault("uniform2Active","handleBoxUniform2Active");
    setPartAsDefault("uniform3",      "handleBoxUniform3");
    setPartAsDefault("uniform3Active","handleBoxUniform3Active");
    setPartAsDefault("uniform4",      "handleBoxUniform4");
    setPartAsDefault("uniform4Active","handleBoxUniform4Active");
    setPartAsDefault("uniform5",      "handleBoxUniform5");
    setPartAsDefault("uniform5Active","handleBoxUniform5Active");
    setPartAsDefault("uniform6",      "handleBoxUniform6");
    setPartAsDefault("uniform6Active","handleBoxUniform6Active");
    setPartAsDefault("uniform7",      "handleBoxUniform7");
    setPartAsDefault("uniform7Active","handleBoxUniform7Active");
    setPartAsDefault("uniform8",      "handleBoxUniform8");
    setPartAsDefault("uniform8Active","handleBoxUniform8Active");

    setAllPartsActive(FALSE);

    // Arrow Feedback for translation.
    setAnyPart("arrowTranslation", new SoTranslation );

    setPartAsDefault("arrow1",      "handleBoxArrow1");
    setPartAsDefault("arrow2",      "handleBoxArrow2");
    setPartAsDefault("arrow3",      "handleBoxArrow3");
    setPartAsDefault("arrow4",      "handleBoxArrow4");
    setPartAsDefault("arrow5",      "handleBoxArrow5");
    setPartAsDefault("arrow6",      "handleBoxArrow6");

    setSwitchValue( arrow1Switch.getValue(), SO_SWITCH_NONE );
    setSwitchValue( arrow2Switch.getValue(), SO_SWITCH_NONE );
    setSwitchValue( arrow3Switch.getValue(), SO_SWITCH_NONE );
    setSwitchValue( arrow4Switch.getValue(), SO_SWITCH_NONE );
    setSwitchValue( arrow5Switch.getValue(), SO_SWITCH_NONE );
    setSwitchValue( arrow6Switch.getValue(), SO_SWITCH_NONE );

    motionPlaneProj = new SbPlaneProjector;
    motionLineProj = new SbLineProjector;

    // by default, handlebox is a bit bigger than the bounding box
//OBSOLETE    setDraggerScale( SbVec3f( 1.2, 1.2, 1.2 ) );

    currentState = INACTIVE;
    restartState = INACTIVE;


    // add the callbacks to perform the dragging.
    addStartCallback(  &SoHandleBoxDragger::startCB );
    addMotionCallback( &SoHandleBoxDragger::motionCB );
    addFinishCallback(   &SoHandleBoxDragger::finishCB );

    // add the callback to update things each time a meta key changes.
    addOtherEventCallback(   &SoHandleBoxDragger::metaKeyChangeCB );

    // Update the rotation and scale fields when the motionMatrix is set.
    addValueChangedCallback( &SoHandleBoxDragger::valueChangedCB );

    // Updates the motionMatrix when the translationFactor field is set.
    translFieldSensor 
        = new SoFieldSensor( &SoHandleBoxDragger::fieldSensorCB, this);
    translFieldSensor->setPriority( 0 );

    // Updates the motionMatrix when the scaleFactor field is set.
    scaleFieldSensor 
	= new SoFieldSensor( &SoHandleBoxDragger::fieldSensorCB, this);
    scaleFieldSensor->setPriority( 0 );

    setUpConnections( TRUE, TRUE );
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Destructor
//
SoHandleBoxDragger::~SoHandleBoxDragger()
//
////////////////////////////////////////////////////////////////////////
{
    delete motionPlaneProj;
    delete motionLineProj;
    if (translFieldSensor )
	delete translFieldSensor; 
    if (scaleFieldSensor )
	delete scaleFieldSensor; 
}

//    detach/attach any sensors, callbacks, and/or field connections.
//    Called by:            start/end of SoBaseKit::readInstance
//    and on new copy by:   start/end of SoBaseKit::copy.
//    Classes that redefine must call setUpConnections(TRUE,TRUE) 
//    at end of constructor.
//    Returns the state of the node when this was called.
SbBool
SoHandleBoxDragger::setUpConnections( SbBool onOff, SbBool doItAlways )
{
    if ( !doItAlways && connectionsSetUp == onOff)
	return onOff;

    if ( onOff ) {

	// We connect AFTER base class.
	SoDragger::setUpConnections( onOff, FALSE );

	// Call the sensor CBs to make things are up-to-date.
	fieldSensorCB( this, NULL );

	// Connect the field sensors
	if (translFieldSensor->getAttachedField() != &translation)
	    translFieldSensor->attach( &translation );
	if (scaleFieldSensor->getAttachedField() != &scaleFactor)
	    scaleFieldSensor->attach( &scaleFactor );
    }
    else {

	// We disconnect BEFORE base class.

	// Disconnect the field sensors.
	if (translFieldSensor->getAttachedField())
	    translFieldSensor->detach();
	if (scaleFieldSensor->getAttachedField())
	    scaleFieldSensor->detach();

	SoDragger::setUpConnections( onOff, FALSE );
    }

    return !(connectionsSetUp = onOff);
}


////////////////////////////////////////////////////////////////////////
//
// Description:
//    Get set to begin dragging.
//
// Use: private
//
void
SoHandleBoxDragger::dragStart()
//
////////////////////////////////////////////////////////////////////////
{

    const SoPath *pickPath = getPickPath();

    // Invalidate surroundScale if it exists.
    SoSurroundScale *ss = SO_CHECK_PART(this, "surroundScale", SoSurroundScale);
    if (ss != NULL)
	ss->invalidate();

    // This gives us an opportunity for a part to start a new gesture
    // after releasing and re-grabbing in the metaKey callback.
    currentState = restartState;

    translateDir = -1;

    // Make a note of which modifier keys are down.
    altDown  = getEvent()->wasAltDown();
    ctlDown  = getEvent()->wasCtrlDown();
    shftDown = getEvent()->wasShiftDown();

    // See which subGraph of the handle box was hit to determine operation

    if ( FALSE ) {
	// Are we scaling geometry only?
	currentState = SCALE_GEOM_ONLY;
    }

    // Note: we won't even bother if the restartState was not INACTIVE.
    if ( currentState == INACTIVE && pickPath != NULL ) {

	// Try finding a part that was picked.

	// TRANSLATIONS
	if (      pickPath->containsNode( translator1Switch.getValue() ))
	    currentState = TOP_TRANSLATE;
	else if ( pickPath->containsNode( translator2Switch.getValue() ))
	    currentState = BOT_TRANSLATE;
	else if ( pickPath->containsNode( translator3Switch.getValue() ))
	    currentState = LFT_TRANSLATE;
	else if ( pickPath->containsNode( translator4Switch.getValue() ))
	    currentState = RIT_TRANSLATE;
	else if ( pickPath->containsNode( translator5Switch.getValue() ))
	    currentState = FNT_TRANSLATE;
	else if ( pickPath->containsNode( translator6Switch.getValue() ))
	    currentState = BAK_TRANSLATE;

	// 1-d SCALING
	else if ( pickPath->containsNode( extruder1Switch.getValue() ))
	    currentState = TOP_Y_SCALE;
	else if ( pickPath->containsNode( extruder2Switch.getValue() ))
	    currentState = BOT_Y_SCALE;
	else if ( pickPath->containsNode( extruder3Switch.getValue() ))
	    currentState = LFT_X_SCALE;
	else if ( pickPath->containsNode( extruder4Switch.getValue() ))
	    currentState = RIT_X_SCALE;
	else if ( pickPath->containsNode( extruder5Switch.getValue() ))
	    currentState = FNT_Z_SCALE;
	else if ( pickPath->containsNode( extruder6Switch.getValue() ))
	    currentState = BAK_Z_SCALE;

	// 3-d SCALING
	else if ( pickPath->containsNode( uniform1Switch.getValue() ))
	    currentState = PX_PY_PZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform2Switch.getValue() ))
	    currentState = PX_PY_NZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform3Switch.getValue() ))
	    currentState = PX_NY_PZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform4Switch.getValue() ))
	    currentState = PX_NY_NZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform5Switch.getValue() ))
	    currentState = NX_PY_PZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform6Switch.getValue() ))
	    currentState = NX_PY_NZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform7Switch.getValue() ))
	    currentState = NX_NY_PZ_3D_SCALE;
	else if ( pickPath->containsNode( uniform8Switch.getValue() ))
	    currentState = NX_NY_NZ_3D_SCALE;
    }

    if (currentState == INACTIVE ) {

	// Try looking at the surrogate part paths...

	// Surrogate part paths...
	if ( getSurrogatePartPickedName() ==  "translator1" )
	    currentState = TOP_TRANSLATE;
	else if ( getSurrogatePartPickedName() ==  "translator2" )
	    currentState = BOT_TRANSLATE;
	else if ( getSurrogatePartPickedName() ==  "translator3" )
	    currentState = LFT_TRANSLATE;
	else if ( getSurrogatePartPickedName() ==  "translator4" )
	    currentState = RIT_TRANSLATE;
	else if ( getSurrogatePartPickedName() ==  "translator5" )
	    currentState = FNT_TRANSLATE;
	else if ( getSurrogatePartPickedName() ==  "translator6" )
	    currentState = BAK_TRANSLATE;
	else if ( getSurrogatePartPickedName() ==  "extruder1" )
	    currentState = TOP_Y_SCALE;
	else if ( getSurrogatePartPickedName() ==  "extruder2" )
	    currentState = BOT_Y_SCALE;
	else if ( getSurrogatePartPickedName() ==  "extruder3" )
	    currentState = LFT_X_SCALE;
	else if ( getSurrogatePartPickedName() ==  "extruder4" )
	    currentState = RIT_X_SCALE;
	else if ( getSurrogatePartPickedName() ==  "extruder5" )
	    currentState = FNT_Z_SCALE;
	else if ( getSurrogatePartPickedName() ==  "extruder6" )
	    currentState = BAK_Z_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform1" )
	    currentState = PX_PY_PZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform2" )
	    currentState = PX_PY_NZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform3" )
	    currentState = PX_NY_PZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform4" )
	    currentState = PX_NY_NZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform5" )
	    currentState = NX_PY_PZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform6" )
	    currentState = NX_PY_NZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform7" )
	    currentState = NX_NY_PZ_3D_SCALE;
	else if ( getSurrogatePartPickedName() ==  "uniform8" )
	    currentState = NX_NY_NZ_3D_SCALE;
    }

    setHighlights();

    switch( currentState ) {
	case RIT_TRANSLATE:
	case LFT_TRANSLATE:
	case TOP_TRANSLATE:
	case BOT_TRANSLATE:
	case FNT_TRANSLATE:
	case BAK_TRANSLATE:
	    translateInit();
	    break;
	case PX_PY_PZ_3D_SCALE:
	case PX_PY_NZ_3D_SCALE:
	case PX_NY_PZ_3D_SCALE:
	case PX_NY_NZ_3D_SCALE:
	case NX_PY_PZ_3D_SCALE:
	case NX_PY_NZ_3D_SCALE:
	case NX_NY_PZ_3D_SCALE:
	case NX_NY_NZ_3D_SCALE:
	    uniformScaleInit();
	    break;
	case RIT_X_SCALE:
	case LFT_X_SCALE:
	case TOP_Y_SCALE:
	case BOT_Y_SCALE:
	case FNT_Z_SCALE:
	case BAK_Z_SCALE:
	    faceScaleInit();
	    break;
	case SCALE_GEOM_ONLY:
	    scaleGeomSizeOnlyInit(); 
	    break;
	case INACTIVE:
	default:
	    break;
    }
}


////////////////////////////////////////////////////////////////////////
//
// Description:
//    Get set to begin translating.
//
// Use: private
//
SbBool
SoHandleBoxDragger::translateInit()
//
////////////////////////////////////////////////////////////////////////
{
    // This is the point we'll use if a metaKey callback makes us re-start.
	worldRestartPt = getWorldStartingPoint();

    // Establish the projector line in working space.
    // Working space is space after "surroundScale"

	SbMatrix workSpaceToLocal, localToWorkSpace;
	getPartToLocalMatrix("surroundScale",workSpaceToLocal,localToWorkSpace);
	SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
	worldSpaceToWork.multRight( localToWorkSpace );
	SbVec3f startWorkHit;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startWorkHit);

	// do different things depending on what you hit...
	SbVec3f workSpaceNormal;

	switch( currentState ) {
	    case RIT_TRANSLATE:
	    case LFT_TRANSLATE:
		workSpaceNormal.setValue(  1,  0,  0);
		break;
	    case TOP_TRANSLATE:
	    case BOT_TRANSLATE:
		workSpaceNormal.setValue(  0,  1,  0);
		break;
	    case FNT_TRANSLATE:
	    case BAK_TRANSLATE:
		workSpaceNormal.setValue(  0,  0,  1);
		break;
	    default:
		return FALSE;
	}

    // At this point, the translation direction (used for 1-D translating)
    // is undefined.
	translateDir = -1;

    // For calculating motion within the plane.
    motionPlaneProj->setPlane(SbPlane(workSpaceNormal,startWorkHit));

    // If alt key is down, we also need a projector perpendicular to plane.
    if ( ctlDown )
	motionLineProj->setLine(
	    SbLine(startWorkHit, startWorkHit + workSpaceNormal));

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Get set to begin scaling, based on input from a knob placed on a 
//    face of the handleBox.
//    This implies a one dimensional scaling.
//
// Use: private
//
SbBool
SoHandleBoxDragger::faceScaleInit()
//
////////////////////////////////////////////////////////////////////////
{
    // This is the point we'll use if a metaKey callback makes us re-start.
	worldRestartPt = getWorldStartingPoint();

    // Establish the projector line in working space.
    // Working space is space after "surroundScale"

	SbMatrix workSpaceToLocal, localToWorkSpace;
	getPartToLocalMatrix("surroundScale",workSpaceToLocal,localToWorkSpace);
	SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
	worldSpaceToWork.multRight( localToWorkSpace );
	SbVec3f startWorkHit;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startWorkHit);

	// do different things depending on what you hit...
	SbVec3f axisPt2;

	switch( currentState ) {
	    case RIT_X_SCALE:
	    case LFT_X_SCALE:
		axisPt2 = startWorkHit + SbVec3f(  1,  0,  0);
		break;
	    case TOP_Y_SCALE:
	    case BOT_Y_SCALE:
		axisPt2 = startWorkHit + SbVec3f(  0,  1,  0);
		break;
	    case FNT_Z_SCALE:
	    case BAK_Z_SCALE:
		axisPt2 = startWorkHit + SbVec3f(  0,  0,  1);
		break;
	    default:
		return FALSE;
	}

	motionLineProj->setLine( SbLine( startWorkHit, axisPt2 ) );

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Get set to begin scaling, based on input from a knob placed on a 
//    vertex of the handleBox.
//    This implies a one dimensional scaling.
//
// Use: private
//
SbBool
SoHandleBoxDragger::uniformScaleInit()
//
////////////////////////////////////////////////////////////////////////
{
    // This is the point we'll use if a metaKey callback makes us re-start.
	worldRestartPt = getWorldStartingPoint();

    // Establish the projector line in working space.
    // Working space is space after "surroundScale"

	SbMatrix workSpaceToLocal, localToWorkSpace;
	getPartToLocalMatrix("surroundScale",workSpaceToLocal,localToWorkSpace);
	SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
	worldSpaceToWork.multRight( localToWorkSpace );
	SbVec3f startWorkHit;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startWorkHit);

    // create a line between the box center and the point that was hit
	motionLineProj->setLine( SbLine( SbVec3f(0,0,0),startWorkHit ) );

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Get set to begin scaling only the Geometry, based on
//    scaling along a line connecting (0,0,0) to the point originally hit.
//
//
// Use: private
//
SbBool
SoHandleBoxDragger::scaleGeomSizeOnlyInit()
//
////////////////////////////////////////////////////////////////////////
{
    // This is the point we'll use if a metaKey callback makes us re-start.
	worldRestartPt = getWorldStartingPoint();

    // Establish the projector line in working space.
    // Working space is space after "surroundScale"

	SbMatrix workSpaceToLocal, localToWorkSpace;
	getPartToLocalMatrix("surroundScale",workSpaceToLocal,localToWorkSpace);
	SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
	worldSpaceToWork.multRight( localToWorkSpace );
	SbVec3f startWorkHit;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startWorkHit);

    // the motion line connects the origin with the point that was hit
	motionLineProj->setLine( SbLine( SbVec3f(0,0,0),startWorkHit));

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Drag the handlebox based on locater motion.
//    This routine just ships the work to translateDrag or 
//    scaleDrag.
//
// Use: private
//
void
SoHandleBoxDragger::drag()
//
////////////////////////////////////////////////////////////////////////
{
    switch( currentState ) {
	case RIT_TRANSLATE:
	case LFT_TRANSLATE:
	case TOP_TRANSLATE:
	case BOT_TRANSLATE:
	case FNT_TRANSLATE:
	case BAK_TRANSLATE:
	    translateDrag();
	    break;
	case PX_PY_PZ_3D_SCALE:
	case PX_PY_NZ_3D_SCALE:
	case PX_NY_PZ_3D_SCALE:
	case PX_NY_NZ_3D_SCALE:
	case NX_PY_PZ_3D_SCALE:
	case NX_PY_NZ_3D_SCALE:
	case NX_NY_PZ_3D_SCALE:
	case NX_NY_NZ_3D_SCALE:

	case RIT_X_SCALE:
	case LFT_X_SCALE:
	case TOP_Y_SCALE:
	case BOT_Y_SCALE:
	case FNT_Z_SCALE:
	case BAK_Z_SCALE:
	    scaleDrag();
	    break;
	case SCALE_GEOM_ONLY:
	    scaleGeomSizeOnlyDrag(); 
	    break;
	case INACTIVE:
	default:
	    break;
    }
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Translate the handlebox and object based on locater motion.
//
// Use: private
//
SbBool
SoHandleBoxDragger::translateDrag()
//
////////////////////////////////////////////////////////////////////////
{
    SbMatrix workSpaceToLocal, localToWorkSpace;
    getPartToLocalMatrix("surroundScale", workSpaceToLocal, localToWorkSpace);
    SbMatrix workSpaceToWorld = getLocalToWorldMatrix();
    workSpaceToWorld.multLeft( workSpaceToLocal );
    SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
    worldSpaceToWork.multRight( localToWorkSpace );

    // Get startHitPt in workspace.
	SbVec3f startHitPt;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startHitPt);

    // Calculate intersection with plane projector.
    // Find mouse hit on plane currently in use...
	motionPlaneProj->setViewVolume( getViewVolume() );
	motionPlaneProj->setWorkingSpace( workSpaceToWorld );
	SbVec3f workSpacePlanePt
	    = motionPlaneProj->project(getNormalizedLocaterPosition());

    // If we need to start a new gesture, we'll carry on from this pt.
	workSpaceToWorld.multVecMatrix( workSpacePlanePt, worldRestartPt);

    // Get newHitPt in workSpace.
	SbVec3f  newHitPt;

    // If CONTROL key down, our 'newHitPt' must be taken from the
    // line projector instead.
    if ( !ctlDown ) {
        newHitPt = workSpacePlanePt;
    }
    else { 
	// If modify key is down, then we are moving PERPENDICULAR to the 
	// motion plane.  
	motionLineProj->setViewVolume( getViewVolume() );
	motionLineProj->setWorkingSpace( workSpaceToWorld );
	newHitPt 
	    = motionLineProj->project( getNormalizedLocaterPosition());
    }

    // find the difference between current and beginning intersections.
    SbVec3f  workSpaceMotion;
    workSpaceMotion = newHitPt - startHitPt;

    if ( !shftDown || ctlDown )
					// constrainKey not down, or we are 
				 	// doing perpendicular (CONTROL) motion.
				 	// Clear the 1-D translation dir.
	translateDir = -1;
    else {
	// the constrain key is pressed. This means 1-D translation.
	if ( translateDir == -1 ) {
	    // The 1-D direction is not defined.  Calculate it
	    // based on which direction got the maximum locater motion.
	    if ( isAdequateConstraintMotion() ) {
		if (    fabs( workSpaceMotion[0]) > fabs( workSpaceMotion[1]) 
		     && fabs( workSpaceMotion[0]) > fabs( workSpaceMotion[2]) )
		    translateDir = 0;
		else if (fabs( workSpaceMotion[1]) > fabs( workSpaceMotion[2]) )
		    translateDir = 1;
		else 
		    translateDir = 2;
		// We'll need to display different translation arrows.
    		setFeedbackArrows();
	    }
	    else {
		// Not ready to pick a direction yet. Don't move.
		return FALSE;
	    }
	}
	// get the projection of 'workSpaceMotion' onto the preferred axis.
	SbVec3f constrainedMotion(0,0,0);
	constrainedMotion[translateDir] = workSpaceMotion[translateDir];
	workSpaceMotion = constrainedMotion;
    }

    // Append this to the startMotionMatrix, which we saved at the beginning
    // of the drag, to find the current motion matrix.
    // We need to send our matrix that converts from workSpace to localSpace.
	setMotionMatrix( appendTranslation( getStartMotionMatrix(), workSpaceMotion,
			     &workSpaceToLocal ) );

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Scale the handlebox and object based on locater motion.
//
// Use: private
//
SbBool
SoHandleBoxDragger::scaleDrag()
//
////////////////////////////////////////////////////////////////////////
{
    // Get your spaces straight...
	SbMatrix workSpaceToLocal, localToWorkSpace;
	getPartToLocalMatrix("surroundScale",workSpaceToLocal,localToWorkSpace);
	SbMatrix workSpaceToWorld = getLocalToWorldMatrix();
	workSpaceToWorld.multLeft( workSpaceToLocal );
	SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
	worldSpaceToWork.multRight( localToWorkSpace );

    // Get startHitPt in workspace.
	SbVec3f startHitPt;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startHitPt);

    // determine the center for scaling...
	SbVec3f  scaleCenter;
	if ( !ctlDown ) {
	    // In this case, always want to scale towards center.
	    switch( currentState ) {
		case PX_PY_PZ_3D_SCALE:
		case PX_PY_NZ_3D_SCALE:
		case PX_NY_PZ_3D_SCALE:
		case PX_NY_NZ_3D_SCALE:
		case NX_PY_PZ_3D_SCALE:
		case NX_PY_NZ_3D_SCALE:
		case NX_NY_PZ_3D_SCALE:
		case NX_NY_NZ_3D_SCALE:
		case SCALE_GEOM_ONLY:
	    	    scaleCenter.setValue(0, 0, 0);
		    break;

		case RIT_X_SCALE:
		case LFT_X_SCALE:
		    scaleCenter.setValue(  0,  startHitPt[1], startHitPt[2]);
		    break;
		case TOP_Y_SCALE:
		case BOT_Y_SCALE:
		    scaleCenter.setValue(  startHitPt[0],  0, startHitPt[2]);
		    break;
		case FNT_Z_SCALE:
		case BAK_Z_SCALE:
		    scaleCenter.setValue(  startHitPt[0], startHitPt[1],  0);
		    break;
		    
                default:
                    break;
	    }
	}
	else {  // With alt key down, edge being pulled should move and 
		// leave the opposite edge standing still.

	    switch( currentState ) {
		case PX_PY_PZ_3D_SCALE:
		    scaleCenter.setValue( -1, -1, -1 );
		    break;
		case PX_PY_NZ_3D_SCALE:
		    scaleCenter.setValue( -1, -1,  1 );
		    break;
		case PX_NY_PZ_3D_SCALE:
		    scaleCenter.setValue( -1,  1, -1 );
		    break;
		case PX_NY_NZ_3D_SCALE:
		    scaleCenter.setValue( -1,  1,  1 );
		    break;
		case NX_PY_PZ_3D_SCALE:
		    scaleCenter.setValue(  1, -1, -1 );
		    break;
		case NX_PY_NZ_3D_SCALE:
		    scaleCenter.setValue(  1, -1,  1 );
		    break;
		case NX_NY_PZ_3D_SCALE:
		    scaleCenter.setValue(  1,  1, -1 );
		    break;
		case NX_NY_NZ_3D_SCALE:
		    scaleCenter.setValue(  1,  1,  1 );
		    break;

		case RIT_X_SCALE:
		    scaleCenter.setValue( -1,  startHitPt[1], startHitPt[2]);
		    break;
		case LFT_X_SCALE:
		    scaleCenter.setValue(  1,  startHitPt[1], startHitPt[2]);
		    break;
		case TOP_Y_SCALE:
		    scaleCenter.setValue(  startHitPt[0], -1, startHitPt[2]);
		    break;
		case BOT_Y_SCALE:
		    scaleCenter.setValue(  startHitPt[0],  1, startHitPt[2]);
		    break;
		case FNT_Z_SCALE:
		    scaleCenter.setValue(  startHitPt[0], startHitPt[1], -1);
		    break;
		case BAK_Z_SCALE:
		    scaleCenter.setValue(  startHitPt[0], startHitPt[1],  1);
		    break;
		case SCALE_GEOM_ONLY:
		    // This mode always scales about origin.
		    scaleCenter.setValue( 0, 0, 0);
		    break;
                
                default:
                    break;
	    }
	}

    // Set up projector. 
	motionLineProj->setViewVolume( getViewVolume() );
	motionLineProj->setWorkingSpace( workSpaceToWorld  );
    // We need to re-establish the motion line, because the line should always
    // pass through the scale center.
	motionLineProj->setLine( SbLine( scaleCenter, startHitPt ) );

    // Get newHitPt in workSpace.
	SbVec3f  newHitPt;
	newHitPt 
	    = motionLineProj->project(getNormalizedLocaterPosition());

    // Save the final point in world space.
    // We'll need it if we hit a meta-key to start off the next gesture.
	workSpaceToWorld.multVecMatrix( newHitPt, worldRestartPt);


#define TINY 0.00001

    // set delta to be the proportionate change in distance from 
    // the scaleCenter in each direction.
    SbVec3f oldDiff = startHitPt  - scaleCenter;
    SbVec3f newDiff = newHitPt - scaleCenter;
    // If either vector is close to zero, then leave delta at 1.0
    SbVec3f delta( 1.0, 1.0, 1.0 );
    int ind;
    for ( ind = 0; ind < 3; ind++ ) {
	if ((fabs(newDiff[ind]) > TINY) && (fabs(oldDiff[ind]) > TINY))
	    delta[ind] = newDiff[ind] / oldDiff[ind];
    }
#undef TINY

    // Make sure the scale doesn't go below getMinScale()
	for (ind = 0; ind < 3; ind++ )
	    if ( delta[ind] < getMinScale() )
		delta[ind] = getMinScale();

    // Append this to the startMotionMatrix, which we saved at the beginning
    // of the drag, to find the current motion matrix.
    // We need to send our matrix that converts from workSpace to localSpace.
	setMotionMatrix( appendScale( getStartMotionMatrix(), 
			    delta, scaleCenter, &workSpaceToLocal ) );
    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Scale up only the geometry of the handlebox, by intersecting
//    an imaginary sphere around the center of the handlebox
//
// Use: private
//
SbBool
SoHandleBoxDragger::scaleGeomSizeOnlyDrag()
//
////////////////////////////////////////////////////////////////////////
{
    SbMatrix workSpaceToLocal, localToWorkSpace;
    getPartToLocalMatrix( "surroundScale", workSpaceToLocal, localToWorkSpace);
    SbMatrix workSpaceToWorld = getLocalToWorldMatrix();
    workSpaceToWorld.multLeft( workSpaceToLocal );
    SbMatrix worldSpaceToWork = getWorldToLocalMatrix();
    worldSpaceToWork.multRight( localToWorkSpace );

    // Get startHitPt in workspace.
	SbVec3f startHitPt;
	worldSpaceToWork.multVecMatrix(getWorldStartingPoint(), startHitPt);

    // Get newHitPt in workSpace.
	SbVec3f  newHitPt;
	motionLineProj->setViewVolume( getViewVolume() );
	motionLineProj->setWorkingSpace( workSpaceToWorld  );
	newHitPt 
	    = motionLineProj->project(getNormalizedLocaterPosition());

    // Save the final point in world space.
    // We'll need it if we hit a meta-key to start off the next gesture.
	workSpaceToWorld.multVecMatrix( newHitPt, worldRestartPt);

    // Find the oldRadius and the new radius
	SbVec3f  origin(0,0,0);
	float oldRadius, newRadius;
	oldRadius = (startHitPt - origin).length();
	newRadius = (newHitPt - origin).length();

    // Find the change in scale.
	float delta = 1.0;
	if ( newRadius != 0.0 && oldRadius != 0.0 ) {
	    delta = newRadius / oldRadius;
	}

//OBSOLETE        SbVec3f relScale = getDraggerScale();
//OBSOLETE        relScale *= delta;
//OBSOLETE        setDraggerScale( relScale );

    return TRUE;
}

//
////////////////////////////////////////////////////////////////////////
// keypress/release callback functions
//
// These assure that the proper changes to the highlights, 
// currentState, and projectors are made
//
////////////////////////////////////////////////////////////////////////
//

void
SoHandleBoxDragger::metaKeyChangeCB( void *, SoDragger *inDragger)
{
    SoHandleBoxDragger  *hb = (SoHandleBoxDragger *) inDragger;
    SoHandleEventAction *ha = hb->getHandleEventAction();

    //[1] Only do this if we are grabbing events
    if ( ha->getGrabber() != hb )
	return;

    //[2] We only want key press or release events.
    const SoEvent *event = hb->getEvent();
    if ( !SO_KEY_PRESS_EVENT(event, ANY) && !SO_KEY_RELEASE_EVENT(event, ANY))
	return;

    //[3] Is the key constrain, modify, or control?
    const SoKeyboardEvent *ke = (const SoKeyboardEvent *) event;
    SoKeyboardEvent::Key key = ke->getKey();

    if ( key == SoKeyboardEvent::LEFT_CONTROL  ||
	      key == SoKeyboardEvent::RIGHT_CONTROL || 
	      ( ( key == SoKeyboardEvent::LEFT_SHIFT ||
	          key == SoKeyboardEvent::RIGHT_SHIFT ) &&
	        ( hb->currentState == RIT_TRANSLATE ||
	          hb->currentState == LFT_TRANSLATE ||
	          hb->currentState == TOP_TRANSLATE ||
	          hb->currentState == BOT_TRANSLATE ||
	          hb->currentState == FNT_TRANSLATE ||
	          hb->currentState == BAK_TRANSLATE ))) {

	// We want to end the old gesture and start a new one.

	// [A] Release the grabber. This ends the gesture and calls all 
	//     finishCallbacks (on parent dragger, too, if we're registered)
	//     Remember the state so you can restart with it after releasing.
	    State savedState = hb->currentState;
	    ha->releaseGrabber();

	// [B] Set the starting point to be our saved worldRestartPoint
	    hb->restartState = savedState;
	    hb->setStartingPoint( hb->worldRestartPt );

	// [C] Become the grabber again. This begins a new gesture and calls all
	//     startCallbacks (parent dragger, too).  Info like viewVolume, 
	//     viewportRegion, handleEventAction, and tempPathToThis 
	//     is still valid.
	    ha->setGrabber(hb);

	// [D] set handled
	    ha->setHandled();
    }
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Finish dragging.
//
// Use: private
//
void
SoHandleBoxDragger::dragFinish()
//
////////////////////////////////////////////////////////////////////////
{
    currentState = INACTIVE;
    restartState = INACTIVE;
    setHighlights();

    // Invalidate surroundScale if it exists.
    SoSurroundScale *ss = SO_CHECK_PART(this, "surroundScale", SoSurroundScale);
    if (ss != NULL)
	ss->invalidate();
}; 


////////////////////////////////////////////////////////////////////////
//
// Use: private
//
void
SoHandleBoxDragger::highlightAxisForScale( SoHandleBoxDragger::WhichAxis axis )
//
////////////////////////////////////////////////////////////////////////
{
    switch ( axis ) {
	case POSY:
	    setSwitchValue( extruder1Switch.getValue(), 1 );
	    if ( !ctlDown )
		setSwitchValue( extruder2Switch.getValue(), 1 );
	    break;
	case NEGY:
	    setSwitchValue( extruder2Switch.getValue(), 1 );
	    if ( !ctlDown )
		setSwitchValue( extruder1Switch.getValue(), 1 );
	    break;
	case NEGX:
	    setSwitchValue( extruder3Switch.getValue(), 1 );
	    if ( !ctlDown )
		setSwitchValue( extruder4Switch.getValue(), 1 );
	    break;
	case POSX:
	    setSwitchValue( extruder4Switch.getValue(), 1 );
	    if ( !ctlDown )
		setSwitchValue( extruder3Switch.getValue(), 1 );
	    break;
	case POSZ:
	    setSwitchValue( extruder5Switch.getValue(), 1 );
	    if ( !ctlDown )
		setSwitchValue( extruder6Switch.getValue(), 1 );
	    break;
	case NEGZ:
	    setSwitchValue( extruder6Switch.getValue(), 1 );
	    if ( !ctlDown )
		setSwitchValue( extruder5Switch.getValue(), 1 );
	    break;
    }
}

////////////////////////////////////////////////////////////////////////
//
// Use: protected
//
void
SoHandleBoxDragger::setAllPartsActive( SbBool onOrOff )
//
////////////////////////////////////////////////////////////////////////
{
    int sVal = (onOrOff == TRUE) ? 1 : 0;

    setSwitchValue(translator1Switch.getValue(), sVal );
    setSwitchValue(translator2Switch.getValue(), sVal );
    setSwitchValue(translator3Switch.getValue(), sVal );
    setSwitchValue(translator4Switch.getValue(), sVal );
    setSwitchValue(translator5Switch.getValue(), sVal );
    setSwitchValue(translator6Switch.getValue(), sVal );

    setSwitchValue(extruder1Switch.getValue(), sVal );
    setSwitchValue(extruder2Switch.getValue(), sVal );
    setSwitchValue(extruder3Switch.getValue(), sVal );
    setSwitchValue(extruder4Switch.getValue(), sVal );
    setSwitchValue(extruder5Switch.getValue(), sVal );
    setSwitchValue(extruder6Switch.getValue(), sVal );

    setSwitchValue(uniform1Switch.getValue(), sVal );
    setSwitchValue(uniform2Switch.getValue(), sVal );
    setSwitchValue(uniform3Switch.getValue(), sVal );
    setSwitchValue(uniform4Switch.getValue(), sVal );
    setSwitchValue(uniform5Switch.getValue(), sVal );
    setSwitchValue(uniform6Switch.getValue(), sVal );
    setSwitchValue(uniform7Switch.getValue(), sVal );
    setSwitchValue(uniform8Switch.getValue(), sVal );

}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Sets the various switch nodes to highlight the correct parts
//    of the handleBox, depending on the state.
//
// Use: private
//
void
SoHandleBoxDragger::setHighlights()
//
////////////////////////////////////////////////////////////////////////
{
    // Turn notification off temporarily so that
    // all these switches switching dont cause a zillion
    // notifies.
    enableNotify(FALSE);
    
    setFeedbackArrows();

    // Set up the drawStyle node
    SoDrawStyle *ds = (SoDrawStyle *) drawStyle.getValue();
    if ( ds == NULL ) {
        ds = (SoDrawStyle *) getAnyPart( "drawStyle", TRUE );
        ds->style.setValue(SoDrawStyle::LINES);
    }

    ds->lineWidth.setValue(1);
    ds->linePattern.setValue(0xffff);
    ds->setOverride(FALSE);

    // if switching everything on, just do it and return.
    switch ( currentState ) {
	case SCALE_GEOM_ONLY:
	    setAllPartsActive( TRUE );
	    ds->lineWidth.setValue(2);
	    ds->linePattern.setValue(0xf0f0);
	    ds->setOverride(TRUE);
   	    enableNotify(TRUE);
	    touch();
	    return;

	case RIT_TRANSLATE:
	case LFT_TRANSLATE:
	case TOP_TRANSLATE:
	case BOT_TRANSLATE:
	case FNT_TRANSLATE:
	case BAK_TRANSLATE:
	    setAllPartsActive( TRUE );
   	    enableNotify(TRUE);
	    touch();
	    return;
        default:
            break;
    }

    // The other states require some things on, some things off.
    // Start by turning everything off, then turn on what's needed.
    setAllPartsActive( FALSE );

    switch ( currentState ) {

	case PX_PY_PZ_3D_SCALE:
	    setSwitchValue( uniform1Switch.getValue(), 1 );
	    highlightAxisForScale( POSX );
	    highlightAxisForScale( POSY );
	    highlightAxisForScale( POSZ );
	    break;
	case PX_PY_NZ_3D_SCALE:
	    setSwitchValue( uniform2Switch.getValue(), 1 );
	    highlightAxisForScale( POSX );
	    highlightAxisForScale( POSY );
	    highlightAxisForScale( NEGZ );
	    break;
	case PX_NY_PZ_3D_SCALE:
	    setSwitchValue( uniform3Switch.getValue(), 1 );
	    highlightAxisForScale( POSX );
	    highlightAxisForScale( NEGY );
	    highlightAxisForScale( POSZ );
	    break;
	case PX_NY_NZ_3D_SCALE:
	    setSwitchValue( uniform4Switch.getValue(), 1 );
	    highlightAxisForScale( POSX );
	    highlightAxisForScale( NEGY );
	    highlightAxisForScale( NEGZ );
	    break;
	case NX_PY_PZ_3D_SCALE:
	    setSwitchValue( uniform5Switch.getValue(), 1 );
	    highlightAxisForScale( NEGX );
	    highlightAxisForScale( POSY );
	    highlightAxisForScale( POSZ );
	    break;
	case NX_PY_NZ_3D_SCALE:
	    setSwitchValue( uniform6Switch.getValue(), 1 );
	    highlightAxisForScale( NEGX );
	    highlightAxisForScale( POSY );
	    highlightAxisForScale( NEGZ );
	    break;
	case NX_NY_PZ_3D_SCALE:
	    setSwitchValue( uniform7Switch.getValue(), 1 );
	    highlightAxisForScale( NEGX );
	    highlightAxisForScale( NEGY );
	    highlightAxisForScale( POSZ );
	    break;
	case NX_NY_NZ_3D_SCALE:
	    setSwitchValue( uniform8Switch.getValue(), 1 );
	    highlightAxisForScale( NEGX );
	    highlightAxisForScale( NEGY );
	    highlightAxisForScale( NEGZ );
	    break;

	case RIT_X_SCALE:
	    highlightAxisForScale( POSX );
	    break;
	case LFT_X_SCALE:
	    highlightAxisForScale( NEGX );
	    break;
	case TOP_Y_SCALE:
	    highlightAxisForScale( POSY );
	    break;
	case BOT_Y_SCALE:
	    highlightAxisForScale( NEGY );
	    break;
	case FNT_Z_SCALE:
	    highlightAxisForScale( POSZ );
	    break;
	case BAK_Z_SCALE:
	    highlightAxisForScale( NEGZ );
	    break;

	case INACTIVE:
	default:
	    break;
    }

    // Turn notification back on and cause one notify
    // to eminate from the dragger
    enableNotify(TRUE);
    touch();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Sets the various switch nodes to highlight the correct parts
//    of the handleBox, depending on the state.
//
// Use: private
//
void
SoHandleBoxDragger::setFeedbackArrows()
//
////////////////////////////////////////////////////////////////////////
{
    setSwitchValue( arrow1Switch.getValue(), SO_SWITCH_NONE  );
    setSwitchValue( arrow2Switch.getValue(), SO_SWITCH_NONE  );
    setSwitchValue( arrow3Switch.getValue(), SO_SWITCH_NONE  );
    setSwitchValue( arrow4Switch.getValue(), SO_SWITCH_NONE  );
    setSwitchValue( arrow5Switch.getValue(), SO_SWITCH_NONE  );
    setSwitchValue( arrow6Switch.getValue(), SO_SWITCH_NONE  );

    SoTranslation *arrowTrans = (SoTranslation *) arrowTranslation.getValue();
    if (!arrowTrans) {
	setAnyPart("arrowTranslation", new SoTranslation );
        arrowTrans = (SoTranslation *) arrowTranslation.getValue();
    }

    // if switching everything on, just do it and return.
    if ( !ctlDown ) {
	switch ( currentState ) {
	    case RIT_TRANSLATE:
		if ( translateDir == -1 || translateDir == 1 ) {
		    setSwitchValue( arrow1Switch.getValue(), 0  );
		    setSwitchValue( arrow2Switch.getValue(), 0  );
		}
		if ( translateDir == -1 || translateDir == 2 ) {
		    setSwitchValue( arrow5Switch.getValue(), 0  );
		    setSwitchValue( arrow6Switch.getValue(), 0  );
		}
		arrowTrans->translation.setValue(SbVec3f(1,0,0));
		break;
	    case LFT_TRANSLATE:
		if ( translateDir == -1 || translateDir == 1 ) {
		    setSwitchValue( arrow1Switch.getValue(), 0  );
		    setSwitchValue( arrow2Switch.getValue(), 0  );
		}
		if ( translateDir == -1 || translateDir == 2 ) {
		    setSwitchValue( arrow5Switch.getValue(), 0  );
		    setSwitchValue( arrow6Switch.getValue(), 0  );
		}
		arrowTrans->translation.setValue(SbVec3f(-1,0,0));
		break;
	    case TOP_TRANSLATE:
		if ( translateDir == -1 || translateDir == 0 ) {
		    setSwitchValue( arrow4Switch.getValue(), 0  );
		    setSwitchValue( arrow3Switch.getValue(), 0  );
		}
		if ( translateDir == -1 || translateDir == 2 ) {
		    setSwitchValue( arrow5Switch.getValue(), 0  );
		    setSwitchValue( arrow6Switch.getValue(), 0  );
		}
		arrowTrans->translation.setValue(SbVec3f(0,1,0));
		break;
	    case BOT_TRANSLATE:
		if ( translateDir == -1 || translateDir == 0 ) {
		    setSwitchValue( arrow4Switch.getValue(), 0  );
		    setSwitchValue( arrow3Switch.getValue(), 0  );
		}
		if ( translateDir == -1 || translateDir == 2 ) {
		    setSwitchValue( arrow5Switch.getValue(), 0  );
		    setSwitchValue( arrow6Switch.getValue(), 0  );
		}
		arrowTrans->translation.setValue(SbVec3f(0,-1,0));
		break;
	    case FNT_TRANSLATE:
		if ( translateDir == -1 || translateDir == 0 ) {
		    setSwitchValue( arrow4Switch.getValue(), 0  );
		    setSwitchValue( arrow3Switch.getValue(), 0  );
		}
		if ( translateDir == -1 || translateDir == 1 ) {
		    setSwitchValue( arrow1Switch.getValue(), 0  );
		    setSwitchValue( arrow2Switch.getValue(), 0  );
		}
		arrowTrans->translation.setValue(SbVec3f(0,0,1));
		break;
	    case BAK_TRANSLATE:
		if ( translateDir == -1 || translateDir == 0 ) {
		    setSwitchValue( arrow4Switch.getValue(), 0  );
		    setSwitchValue( arrow3Switch.getValue(), 0  );
		}
		if ( translateDir == -1 || translateDir == 1 ) {
		    setSwitchValue( arrow1Switch.getValue(), 0  );
		    setSwitchValue( arrow2Switch.getValue(), 0  );
		}
		arrowTrans->translation.setValue(SbVec3f(0,0,-1));
		break;
            default:
                break;
	}
    }
    else {
	switch ( currentState ) {
	    case RIT_TRANSLATE:
		setSwitchValue( arrow4Switch.getValue(), 0  );
		setSwitchValue( arrow3Switch.getValue(), 0  );
		arrowTrans->translation.setValue(SbVec3f(1,0,0));
		break;
	    case LFT_TRANSLATE:
		setSwitchValue( arrow4Switch.getValue(), 0  );
		setSwitchValue( arrow3Switch.getValue(), 0  );
		arrowTrans->translation.setValue(SbVec3f(-1,0,0));
		break;
	    case TOP_TRANSLATE:
		setSwitchValue( arrow1Switch.getValue(), 0  );
		setSwitchValue( arrow2Switch.getValue(), 0  );
		arrowTrans->translation.setValue(SbVec3f(0,1,0));
		break;
	    case BOT_TRANSLATE:
		setSwitchValue( arrow1Switch.getValue(), 0  );
		setSwitchValue( arrow2Switch.getValue(), 0  );
		arrowTrans->translation.setValue(SbVec3f(0,-1,0));
		break;
	    case FNT_TRANSLATE:
		setSwitchValue( arrow5Switch.getValue(), 0  );
		setSwitchValue( arrow6Switch.getValue(), 0  );
		arrowTrans->translation.setValue(SbVec3f(0,0,1));
		break;
	    case BAK_TRANSLATE:
		setSwitchValue( arrow5Switch.getValue(), 0  );
		setSwitchValue( arrow6Switch.getValue(), 0  );
		arrowTrans->translation.setValue(SbVec3f(0,0,-1));
		break;
            default:
                break;

	}
    }
}

////////////////////////////////////////////////////////////////////
//  Stubs for callbacks
////////////////////////////////////////////////////////////////////
void 
SoHandleBoxDragger::startCB( void *, SoDragger *inDragger )
{
    SoHandleBoxDragger *hb = (SoHandleBoxDragger *) inDragger;
    hb->dragStart();
}

void 
SoHandleBoxDragger::motionCB( void *, SoDragger *inDragger )
{
    SoHandleBoxDragger *hb = (SoHandleBoxDragger *) inDragger;
    hb->drag();
}

void 
SoHandleBoxDragger::finishCB( void *, SoDragger *inDragger )
{
    SoHandleBoxDragger *hb = (SoHandleBoxDragger *) inDragger;
    hb->dragFinish();
}

void
SoHandleBoxDragger::valueChangedCB( void *, SoDragger *inDragger )
{
    SoHandleBoxDragger *m = (SoHandleBoxDragger *) inDragger;
    SbMatrix motMat = m->getMotionMatrix();

    SbVec3f trans, scale;
    SbRotation rot, scaleOrient;
    getTransformFast( motMat, trans, rot, scale, scaleOrient);

    // Disconnect the field sensors
    m->translFieldSensor->detach();
    m->scaleFieldSensor->detach();

    if ( m->translation.getValue() != trans )
	m->translation = trans;
    if ( m->scaleFactor.getValue() != scale )
	m->scaleFactor = scale;

    // Reconnect the field sensors
    m->translFieldSensor->attach( &(m->translation) );
    m->scaleFieldSensor->attach( &(m->scaleFactor) );
}

void
SoHandleBoxDragger::fieldSensorCB( void *inDragger, SoSensor * )
{
    SoHandleBoxDragger *dragger = (SoHandleBoxDragger *) inDragger;

    // Incorporate the new field values into the matrix 
    SbMatrix motMat = dragger->getMotionMatrix();
    dragger->workFieldsIntoTransform(motMat);

    dragger->setMotionMatrix( motMat );
}

void
SoHandleBoxDragger::setDefaultOnNonWritingFields()
{
    // These nodes may change after construction, but we still
    // don't want to write them out.
    surroundScale.setDefault(TRUE);
    drawStyle.setDefault(TRUE);
    arrowTranslation.setDefault(TRUE);

    // Call the base class...
    SoDragger::setDefaultOnNonWritingFields();
}
