text: /*
   *
   *  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:
   |	SoTranslate2Dragger
   |
   |   Author(s): Paul Isaacs
   |
   ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
   _______________________________________________________________________
   */
  
  
  #include <stdio.h>
  #include <Inventor/SbLinear.h>
  #include <Inventor/SoDB.h>
  #include <Inventor/sensors/SoFieldSensor.h>
  #include <Inventor/SoInput.h>
  #include <Inventor/SoPath.h>
  #include <Inventor/projectors/SbPlaneProjector.h>
  #include <Inventor/draggers/SoTranslate2Dragger.h>
  #include <Inventor/nodes/SoSeparator.h>
  #include <Inventor/nodes/SoSwitch.h>
  #include <Inventor/events/SoEvent.h>
  #include <Inventor/events/SoKeyboardEvent.h>
  #include <Inventor/actions/SoHandleEventAction.h>
  
  #include "geom/SoTranslate2DraggerGeom.h"
  
  
  SO_KIT_SOURCE(SoTranslate2Dragger);
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Constructor
  //
  SoTranslate2Dragger::SoTranslate2Dragger()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      SO_KIT_CONSTRUCTOR(SoTranslate2Dragger);
  
      isBuiltIn = TRUE;
  
      // Put this stuff under the geomSeparator so it will draw more
      // efficiently.
      SO_KIT_ADD_CATALOG_ENTRY(translatorSwitch, SoSwitch, TRUE,
  				geomSeparator,\x0,FALSE);
      SO_KIT_ADD_CATALOG_ENTRY(translator, SoSeparator, TRUE,
  				translatorSwitch,\x0,TRUE);
      SO_KIT_ADD_CATALOG_ENTRY(translatorActive, SoSeparator, TRUE,
  				translatorSwitch,\x0,TRUE);
      SO_KIT_ADD_CATALOG_ENTRY(feedbackSwitch, SoSwitch, TRUE,
  				geomSeparator,\x0,FALSE);
      SO_KIT_ADD_CATALOG_ENTRY(feedback, SoSeparator, TRUE,
  				feedbackSwitch,\x0,TRUE);
      SO_KIT_ADD_CATALOG_ENTRY(feedbackActive, SoSeparator, TRUE,
  				feedbackSwitch,\x0,TRUE);
      SO_KIT_ADD_CATALOG_ENTRY(axisFeedbackSwitch, SoSwitch, TRUE,
  				geomSeparator,\x0,FALSE);
      SO_KIT_ADD_CATALOG_ENTRY(xAxisFeedback, SoSeparator, TRUE,
  				axisFeedbackSwitch,\x0,TRUE);
      SO_KIT_ADD_CATALOG_ENTRY(yAxisFeedback, SoSeparator, TRUE,
  				axisFeedbackSwitch,\x0,TRUE);
  
      // read geometry for shared parts
      if (SO_KIT_IS_FIRST_INSTANCE())
  	readDefaultParts("translate2Dragger.iv", geomBuffer, sizeof(geomBuffer) );
  
      SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0));
  
      SO_KIT_INIT_INSTANCE();
  
      // create the parts...
     setPartAsDefault("translator",      "translate2Translator");
     setPartAsDefault("translatorActive","translate2TranslatorActive");
     setPartAsDefault("feedback",        "translate2Feedback");
     setPartAsDefault("feedbackActive",  "translate2FeedbackActive");
     setPartAsDefault("xAxisFeedback",  "translate2XAxisFeedback");
     setPartAsDefault("yAxisFeedback",  "translate2YAxisFeedback");
  
      // Set the switches to 0...
      setSwitchValue( translatorSwitch.getValue(), 0 );
      setSwitchValue( feedbackSwitch.getValue(), 0 );
  
      // Set the axis feedback switch to SO_SWITCH_NONE
      // They're only displayed while dragging
      setSwitchValue( axisFeedbackSwitch.getValue(), SO_SWITCH_NONE );
  
      // Create the line projector
      planeProj = new SbPlaneProjector();
  
      // add the callbacks to perform the dragging
      addStartCallback(  &SoTranslate2Dragger::startCB );
      addMotionCallback( &SoTranslate2Dragger::motionCB );
      addFinishCallback(   &SoTranslate2Dragger::finishCB );
  
      // add callback to respond to the shift key, for constraining motion
      // to a line within the plane.
      addOtherEventCallback( &SoTranslate2Dragger::metaKeyChangeCB );
  
      // Updates the translation field when the motionMatrix is set.
      addValueChangedCallback( &SoTranslate2Dragger::valueChangedCB );
  
      // Updates the motionMatrix when the translation field is set.
      fieldSensor = new SoFieldSensor( &SoTranslate2Dragger::fieldSensorCB, this);
      fieldSensor->setPriority( 0 );
  
      setUpConnections( TRUE, TRUE );
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Destructor
  //
  SoTranslate2Dragger::~SoTranslate2Dragger()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      delete planeProj;
      if (fieldSensor)
  	delete fieldSensor;
  }
  
  //    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
  SoTranslate2Dragger::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 (fieldSensor->getAttachedField() != &translation)
  	    fieldSensor->attach( &translation );
      }
      else {
  
  	// We disconnect BEFORE base class.
  
  	// Disconnect the field sensors.
  	if (fieldSensor->getAttachedField())
  	    fieldSensor->detach();
  
  	SoDragger::setUpConnections( onOff, FALSE );
      }
  
      return !(connectionsSetUp = onOff);
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Set up the highlighting, projector, and the initial hit on
  //    the dragger
  //
  // Use: private
  //
  void
  SoTranslate2Dragger::dragStart()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // Set the switches to 1...
      setSwitchValue( translatorSwitch.getValue(), 1 );
      setSwitchValue( feedbackSwitch.getValue(), 1 );
  
      // Set the axis feedback switch to display both.
      // They're displayed while dragging
      setSwitchValue( axisFeedbackSwitch.getValue(), SO_SWITCH_ALL );
  
      // There is no constrained direction...
      translateDir = -1;
  
      // Make a note of which modifier keys are down.
      shftDown = getEvent()->wasShiftDown();
  
      // This is the point we'll use if a metaKey callback makes us re-start.
  	worldRestartPt = getWorldStartingPoint();
  
      // Establish the projector plane in working space.
      // Working space is space at end of motion matrix.
      // Plane normal is defined relative to the translatorPart, so 
      // use z-axis to construct the projector plane.
  	SbVec3f startLocalHitPt = getLocalStartingPoint();
  	planeProj->setPlane( SbPlane(SbVec3f(0,0,1), startLocalHitPt ) );
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //    Stretch the dragger according to the motion along the plane
  //    projector
  //
  // Use: private
  //
  void
  SoTranslate2Dragger::drag()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // Set up the projector space and view.
      // Working space is space at end of motion matrix.
  	planeProj->setViewVolume( getViewVolume() );    
  	planeProj->setWorkingSpace( getLocalToWorldMatrix() );
  
      // Get newHitPt and startHitPt in workspace.
  	SbVec3f newHitPt = planeProj->project( getNormalizedLocaterPosition()); 
  	SbVec3f startHitPt = getLocalStartingPoint();
  
      // Convert newHitPt to world space and save this as our new worldRestartPt
  	getLocalToWorldMatrix().multVecMatrix( newHitPt, worldRestartPt );
  
      // Figure out the translation relative to start position.
          SbVec3f motion = newHitPt - startHitPt;
  
      // Maybe we need to constrain the motion...
      if ( !shftDown )
  	translateDir = -1;
      else {
  	// The shift 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( motion[0]) > fabs( motion[1]))
  		    translateDir = 0;
  		else 
  		    translateDir = 1;
  
  		// Set the axis feedback switch to the given direction.
  		setSwitchValue( axisFeedbackSwitch.getValue(), translateDir );
  	    }
  	    else {
  		// Not ready to pick a direction yet. Don't move.
  		return;
  	    }
  	}
  	// get the projection of 'motion' onto the preferred axis.
  	SbVec3f constrainedMotion(0,0,0);
  	constrainedMotion[translateDir] = motion[translateDir];
  	motion = constrainedMotion;
      }
  
      // Append this to the startMotionMatrix, which we saved at the beginning
      // of the drag, to find the current motion matrix.
  	setMotionMatrix( appendTranslation( getStartMotionMatrix(), motion ) );
  }
  
  ////////////////////////////////////////////////////////////////////////
  //
  // Description:
  //
  // Use: private
  //
  void
  SoTranslate2Dragger::dragFinish()
  //
  ////////////////////////////////////////////////////////////////////////
  {
      // Set the switches to 0...
      setSwitchValue( translatorSwitch.getValue(), 0 );
      setSwitchValue( feedbackSwitch.getValue(), 0 );
  
      // Set the axis feedback switch to SO_SWITCH_NONE
      // They're only displayed while dragging
      setSwitchValue( axisFeedbackSwitch.getValue(), SO_SWITCH_NONE );
  }
  
  //
  ////////////////////////////////////////////////////////////////////////
  // keypress/release callback functions
  //
  // These assure that the proper changes to the highlights, 
  // currentState, and projectors are made
  //
  ////////////////////////////////////////////////////////////////////////
  //
  
  void
  SoTranslate2Dragger::metaKeyChangeCB( void *, SoDragger *inDragger)
  {
      SoTranslate2Dragger *d = (SoTranslate2Dragger *) inDragger;
      SoHandleEventAction *ha = d->getHandleEventAction();
  
      // [1] Only do this if we are grabbing
      if (ha->getGrabber() != d )
  	return;
  
      // [2] We only want key press or release events.
      const SoEvent *event = d->getEvent();
      if ( !SO_KEY_PRESS_EVENT(event, ANY) && !SO_KEY_RELEASE_EVENT(event, ANY))
  	return;
  
      // [3] Is the key the shift key?
      const SoKeyboardEvent *ke = (const SoKeyboardEvent *) event;
      SoKeyboardEvent::Key key = ke->getKey();
  
      if ( key == SoKeyboardEvent::LEFT_SHIFT ||
  	 key == SoKeyboardEvent::RIGHT_SHIFT ) {
  
  	// 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)
  	    ha->releaseGrabber();
  
  	// [B] Set the starting point to be our saved worldRestartPoint
  	    d->setStartingPoint( d->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(d);
  
  	// [D] set handled
  	    ha->setHandled();
      }
  }
  
  ////////////////////////////////////////////////////////////////////
  //  Stubs for callbacks
  ////////////////////////////////////////////////////////////////////
  void
  SoTranslate2Dragger::startCB( void *, SoDragger *inDragger )
  {
      SoTranslate2Dragger *m = (SoTranslate2Dragger *) inDragger;
      m->dragStart();
  }
  
  void
  SoTranslate2Dragger::motionCB( void *, SoDragger *inDragger )
  {
      SoTranslate2Dragger *m = (SoTranslate2Dragger *) inDragger;
      m->drag();
  }
  
  void
  SoTranslate2Dragger::finishCB( void *, SoDragger *inDragger )
  {
      SoTranslate2Dragger *m = (SoTranslate2Dragger *) inDragger;
      m->dragFinish();
  }
  
  void
  SoTranslate2Dragger::valueChangedCB( void *, SoDragger *inDragger )
  {
      SoTranslate2Dragger *m = (SoTranslate2Dragger *) inDragger;
      SbMatrix motMat = m->getMotionMatrix();
  
      SbVec3f    trans, scale;
      SbRotation rot, scaleOrient;
      getTransformFast( motMat, trans, rot, scale, scaleOrient );
  
      // Disconnect the field sensor
      m->fieldSensor->detach();
  
      if ( m->translation.getValue() != trans )
  	m->translation = trans;
  
      // Reconnect the field sensor
      m->fieldSensor->attach( &(m->translation) );
  }
  
  void
  SoTranslate2Dragger::fieldSensorCB( void *inDragger, SoSensor *)
  {
      SoTranslate2Dragger *dragger = (SoTranslate2Dragger *) inDragger;
  
      // Incorporate the new field value into the matrix...
      SbMatrix motMat = dragger->getMotionMatrix();
      dragger->workFieldsIntoTransform( motMat );
  
      dragger->setMotionMatrix( motMat );
  }
  