/** 
 * @file lltoolgrab.cpp
 * @brief LLToolGrab class implementation
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "llviewerprecompiledheaders.h"

#include "lltoolgrab.h"
 
// library headers
#include "indra_constants.h"		// for agent control flags
#include "llviewercontrol.h"
#include "llquaternion.h"
#include "llbox.h"
#include "message.h"
#include "llview.h"
#include "llfontgl.h"
#include "llui.h"

// newview headers
#include "llagent.h"
//#include "llfloateravatarinfo.h"
#include "lldrawable.h"
#include "llfloatertools.h"
#include "llhudeffect.h"
#include "llhudmanager.h"
#include "llregionposition.h"
#include "llselectmgr.h"
#include "llstatusbar.h"
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "llviewercamera.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h" 
#include "llviewerregion.h"
#include "llviewerwindow.h"
#include "llvoavatar.h"
#include "llworld.h"
#include "viewer.h"

const S32 SLOP_DIST_SQ = 4;

// Override modifier key behavior with these buttons
BOOL gGrabBtnVertical = FALSE;
BOOL gGrabBtnSpin = FALSE;
LLTool* gGrabTransientTool = NULL;
LLToolGrab *gToolGrab = NULL;
extern BOOL gDebugClicks;

//
// Methods
//
LLToolGrab::LLToolGrab( LLToolComposite* composite )
:	LLTool( "Grab", composite ),
	mMode( GRAB_INACTIVE ),
	mVerticalDragging( FALSE ),
	mHitLand(FALSE),
	mHitObjectID(),
	mGrabObject( NULL ),
	mMouseDownX( -1 ),
	mMouseDownY( -1 ),
	mHasMoved( FALSE ),
	mSpinGrabbing( FALSE ),
	mSpinRotation(),
	mHideBuildHighlight(FALSE)
{ }

LLToolGrab::~LLToolGrab()
{ }


// virtual
void LLToolGrab::handleSelect()
{
	gFloaterTools->setStatusText("Drag to move objects, Ctrl to lift, Ctrl-Shift to spin");
	gGrabBtnVertical = FALSE;
	gGrabBtnSpin = FALSE;
}

void LLToolGrab::handleDeselect()
{
	if( hasMouseCapture() )
	{
		setMouseCapture( FALSE );
	}

	gFloaterTools->setStatusText("");
}

BOOL LLToolGrab::handleDoubleClick(S32 x, S32 y, MASK mask)
{
	if (gDebugClicks)
	{
		llinfos << "LLToolGrab handleDoubleClick (becoming mouseDown)" << llendl;
	}

	return FALSE;
}

BOOL LLToolGrab::handleMouseDown(S32 x, S32 y, MASK mask)
{
	if (gDebugClicks)
	{
		llinfos << "LLToolGrab handleMouseDown" << llendl;
	}

	mHitLand = FALSE;

	// call the base class to propogate info to sim
	LLTool::handleMouseDown(x, y, mask);
	
	if (!gAgent.leftButtonGrabbed())
	{
		// can grab transparent objects (how touch event propagates, scripters rely on this)
		gViewerWindow->hitObjectOrLandGlobalAsync(x, y, mask, pickCallback, TRUE);
	}
	return TRUE;
}

void LLToolGrab::pickCallback(S32 x, S32 y, MASK mask)
{
	LLViewerObject	*objectp = gObjectList.findObject( gLastHitObjectID );

	BOOL extend_select = (mask & MASK_SHIFT);

	if (!extend_select && !gSelectMgr->isEmpty())
	{
		gSelectMgr->deselectAll();
		gToolGrab->mDeselectedThisClick = TRUE;
	}
	else
	{
		gToolGrab->mDeselectedThisClick = FALSE;
	}

	// if not over object, do nothing
	if (!objectp)
	{
		gToolGrab->setMouseCapture(TRUE);
		gToolGrab->mMode = GRAB_NOOBJECT;
		gToolGrab->mHitObjectID.setNull();
	}
	else
	{
		gToolGrab->handleObjectHit(objectp, x, y, mask);
	}
}

BOOL LLToolGrab::handleObjectHit(LLViewerObject *objectp, S32 x, S32 y, MASK mask)
{
	mMouseDownX = x;
	mMouseDownY = y;
	mMouseMask = mask;

	if (gDebugClicks)
	{
		llinfos << "LLToolGrab handleObjectHit " << mMouseDownX << "," << mMouseDownY << llendl;
	}

	if (objectp->isAvatar())
	{
		if (gGrabTransientTool)
		{
			gBasicToolset->selectTool( gGrabTransientTool );
			gGrabTransientTool = NULL;
		}
		return TRUE;
	}

	setMouseCapture( TRUE );

	mHitObjectID = objectp->getID();

	// Grabs always start from the root
	// objectp = (LLViewerObject *)objectp->getRoot();

	LLViewerObject* parent = objectp->getRootEdit();
	BOOL script_touch = (objectp && objectp->flagHandleTouch()) || (parent && parent->flagHandleTouch());

	// Clicks on scripted or physical objects are temporary grabs, so
	// not "Build mode"
	mHideBuildHighlight = script_touch || objectp->usePhysics();

	if (!objectp->usePhysics())
	{
		// In mouselook, we shouldn't be able to grab non-physical, 
		// non-touchable objects.  If it has a touch handler, we
		// do grab it (so llDetectedGrab works), but movement is
		// blocked on the server side. JC
		if (gAgent.cameraMouselook() && !script_touch)
		{
			mMode = GRAB_LOCKED;
		}
		else
		{
			mMode = GRAB_NONPHYSICAL;
		}
		gViewerWindow->hideCursor();
		gViewerWindow->moveCursorToCenter();
		// Don't bail out here, go on and grab so buttons can get
		// their "touched" event.
	}
	else if( !objectp->permMove() )
	{
		// if mouse is over a physical object without move permission, show feedback if user tries to move it.
		mMode = GRAB_LOCKED;

		// Don't bail out here, go on and grab so buttons can get
		// their "touched" event.
	}
	else
	{
		// if mouse is over a physical object with move permission, 
		// select it and enter "grab" mode (hiding cursor, etc.)

		mMode = GRAB_ACTIVE_CENTER;

		gViewerWindow->hideCursor();
		gViewerWindow->moveCursorToCenter();
	}

	// Always send "touched" message

	mAccumDeltaX = 0;
	mAccumDeltaY = 0;
	mHasMoved = FALSE;
	mOutsideSlop = FALSE;

	mGrabObject = objectp;

	mGrabOffset.clearVec();

	mVerticalDragging = (mask == MASK_VERTICAL) || gGrabBtnVertical;

	startGrab(x, y);

	if ((mask == MASK_SPIN) || gGrabBtnSpin)
	{
		startSpin();
	}

	gSelectMgr->updateSelectionCenter();		// update selection beam

	// update point at
	LLViewerObject *edit_object = gObjectList.findObject(mHitObjectID);
	if (edit_object)
	{
		LLVector3 local_edit_point = gAgent.getPosAgentFromGlobal(gLastHitNonFloraPosGlobal);
		local_edit_point -= edit_object->getPositionAgent();
		local_edit_point = local_edit_point * ~edit_object->getRenderRotation();
		gAgent.setPointAt(POINTAT_TARGET_GRAB, edit_object, local_edit_point );
		gAgent.setLookAt(LOOKAT_TARGET_SELECT, edit_object, local_edit_point );
	}

	// on transient grabs (clicks on world objects), kill the grab immediately
	if (!gViewerWindow->getLeftMouseDown() 
		&& gGrabTransientTool 
		&& (mMode == GRAB_NONPHYSICAL || mMode == GRAB_LOCKED))
	{
		gBasicToolset->selectTool( gGrabTransientTool );
		gGrabTransientTool = NULL;
	}

	return TRUE;
}


void LLToolGrab::startSpin()
{
	mSpinGrabbing = TRUE;

	// Was saveSelectedObjectTransform()
	LLViewerObject *root = (LLViewerObject *)mGrabObject->getRoot();
	mSpinRotation = root->getRotation();

	LLMessageSystem *msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_ObjectSpinStart);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_ObjectData);
	msg->addUUIDFast(_PREHASH_ObjectID, mGrabObject->getID() );
	msg->sendMessage( mGrabObject->getRegion()->getHost() );
}


void LLToolGrab::stopSpin()
{
	mSpinGrabbing = FALSE;

	LLMessageSystem *msg = gMessageSystem;
	switch(mMode)
	{
	case GRAB_ACTIVE_CENTER:
	case GRAB_NONPHYSICAL:
	case GRAB_LOCKED:
		msg->newMessageFast(_PREHASH_ObjectSpinStop);
		msg->nextBlockFast(_PREHASH_AgentData);
		msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
		msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
		msg->nextBlockFast(_PREHASH_ObjectData);
		msg->addUUIDFast(_PREHASH_ObjectID, mGrabObject->getID() );
		msg->sendMessage( mGrabObject->getRegion()->getHost() );
		break;

	case GRAB_NOOBJECT:
	case GRAB_INACTIVE:
	default:
		// do nothing
		break;
	}
}


void LLToolGrab::startGrab(S32 x, S32 y)
{
	// Compute grab_offset in the OBJECT's root's coordinate frame
	// (sometimes root == object)
	LLViewerObject *root = (LLViewerObject *)mGrabObject->getRoot();

	// drag from center
	LLVector3d grab_start_global = root->getPositionGlobal();

	// Where the grab starts, relative to the center of the root object of the set.
	// JC - This code looks wonky, but I believe it does the right thing.
	// Otherwise, when you grab a linked object set, it "pops" on the start
	// of the drag.
	LLVector3d grab_offsetd = root->getPositionGlobal() - mGrabObject->getPositionGlobal();

	LLVector3 grab_offset;
	grab_offset.setVec(grab_offsetd);

	LLQuaternion rotation = root->getRotation();
	rotation.conjQuat();
	grab_offset = grab_offset * rotation;

	// This planar drag starts at the grab point
	mDragStartPointGlobal = grab_start_global;
	mDragStartFromCamera = grab_start_global - gAgent.getCameraPositionGlobal();

	LLMessageSystem	*msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_ObjectGrab);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_ObjectData);
	msg->addU32Fast(_PREHASH_LocalID, mGrabObject->mLocalID);
	msg->addVector3Fast(_PREHASH_GrabOffset, grab_offset );
	msg->sendMessage( mGrabObject->getRegion()->getHost());

	mGrabOffsetFromCenterInitial = grab_offset;
	mGrabHiddenOffsetFromCamera = mDragStartFromCamera;

	mGrabTimer.reset();
}


BOOL LLToolGrab::handleHover(S32 x, S32 y, MASK mask)
{
	mLastMouseX = x;
	mLastMouseY = y;

	if (!gViewerWindow->getLeftMouseDown())
	{
		gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB);
		setMouseCapture(FALSE);
		return TRUE;
	}

	// Do the right hover based on mode
	switch( mMode )
	{
	case GRAB_ACTIVE_CENTER:
	case GRAB_NONPHYSICAL:
		handleHoverActive( x, y, mask );	// cursor hidden
		break;

	case GRAB_INACTIVE:
		handleHoverInactive( x, y, mask );  // cursor set here
		break;

	case GRAB_NOOBJECT:
	case GRAB_LOCKED:
		handleHoverFailed( x, y, mask );
		break;

	}

	return TRUE;
}



		
// Dragging.
void LLToolGrab::handleHoverActive(S32 x, S32 y, MASK mask)
{
	llassert( hasMouseCapture() );
	llassert( mGrabObject );
	if (mGrabObject->isDead())
	{
		// Bail out of drag because object has been killed
		setMouseCapture(FALSE);
		return;
	}

	//--------------------------------------------------
	// Toggle spinning
	//--------------------------------------------------
	if (mSpinGrabbing && !(mask == MASK_SPIN) && !gGrabBtnSpin)
	{
		// user released ALT key, stop spinning
		stopSpin();
	}
	else if (!mSpinGrabbing && (mask == MASK_SPIN) )
	{
		// user pressed ALT key, start spinning
		startSpin();
	}

	//--------------------------------------------------
	// Toggle vertical dragging
	//--------------------------------------------------
	if (mVerticalDragging && !(mask == MASK_VERTICAL) && !gGrabBtnVertical)
	{
		// ...switch to horizontal dragging
		mVerticalDragging = FALSE;

		mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, mGrabObject);
		mDragStartFromCamera = mDragStartPointGlobal - gAgent.getCameraPositionGlobal();
	}
	else if (!mVerticalDragging && (mask == MASK_VERTICAL) )
	{
		// ...switch to vertical dragging
		mVerticalDragging = TRUE;

		mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, mGrabObject);
		mDragStartFromCamera = mDragStartPointGlobal - gAgent.getCameraPositionGlobal();
	}

	const F32 RADIANS_PER_PIXEL_X = 0.01f;
	const F32 RADIANS_PER_PIXEL_Y = 0.01f;

	const F32 SENSITIVITY_X = 0.0075f;
	const F32 SENSITIVITY_Y = 0.0075f;

	S32 dx = x - (gViewerWindow->getWindowWidth() / 2);
	S32 dy = y - (gViewerWindow->getWindowHeight() / 2);

	if (dx != 0 || dy != 0)
	{
		mAccumDeltaX += dx;
		mAccumDeltaY += dy;
		S32 dist_sq = mAccumDeltaX * mAccumDeltaX + mAccumDeltaY * mAccumDeltaY;
		if (dist_sq > SLOP_DIST_SQ)
		{
			mOutsideSlop = TRUE;
		}

		// mouse has moved outside center
		mHasMoved = TRUE;
		
		if (mSpinGrabbing)
		{
			//------------------------------------------------------
			// Handle spinning
			//------------------------------------------------------

			// x motion maps to rotation around vertical axis
			LLVector3 up(0.f, 0.f, 1.f);
			LLQuaternion rotation_around_vertical( dx*RADIANS_PER_PIXEL_X, up );

			// y motion maps to rotation around left axis
			const LLVector3 &agent_left = gCamera->getLeftAxis();
			LLQuaternion rotation_around_left( dy*RADIANS_PER_PIXEL_Y, agent_left );

			// compose with current rotation
			mSpinRotation = mSpinRotation * rotation_around_vertical;
			mSpinRotation = mSpinRotation * rotation_around_left;

			// TODO: Throttle these
			LLMessageSystem *msg = gMessageSystem;
			msg->newMessageFast(_PREHASH_ObjectSpinUpdate);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
			msg->nextBlockFast(_PREHASH_ObjectData);
			msg->addUUIDFast(_PREHASH_ObjectID, mGrabObject->getID() );
			msg->addQuatFast(_PREHASH_Rotation, mSpinRotation );
			msg->sendMessage( mGrabObject->getRegion()->getHost() );
		}
		else
		{
			//------------------------------------------------------
			// Handle grabbing
			//------------------------------------------------------

			LLVector3d x_part;
			x_part.setVec(gCamera->getLeftAxis());
			x_part.mdV[VZ] = 0.0;
			x_part.normVec();

			LLVector3d y_part;
			if( mVerticalDragging )
			{
				y_part.setVec(gCamera->getUpAxis());
				// y_part.setVec(0.f, 0.f, 1.f);
			}
			else
			{
				// drag toward camera
				y_part = x_part % LLVector3d::z_axis;
				y_part.mdV[VZ] = 0.0;
				y_part.normVec();
			}

			mGrabHiddenOffsetFromCamera = mGrabHiddenOffsetFromCamera 
				+ (x_part * (-dx * SENSITIVITY_X)) 
				+ (y_part * ( dy * SENSITIVITY_Y));


			// Send the message to the viewer.
			F32 dt = mGrabTimer.getElapsedTimeAndResetF32();
			U32 dt_milliseconds = (U32) (1000.f * dt);

			// need to return offset from mGrabStartPoint
			LLVector3d grab_point_global;

			grab_point_global = gAgent.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera;

			/* Snap to grid disabled for grab tool - very confusing
			// Handle snapping to grid, but only when the tool is formally selected.
			BOOL snap_on = gSavedSettings.getBOOL("SnapEnabled");
			if (snap_on && !gGrabTransientTool)
			{
				F64	snap_size = gSavedSettings.getF32("GridResolution");
				U8 snap_dimensions = (mVerticalDragging ? 3 : 2);

				for (U8 i = 0; i < snap_dimensions; i++)
				{
					grab_point_global.mdV[i] += snap_size / 2;
					grab_point_global.mdV[i] -= fmod(grab_point_global.mdV[i], snap_size);
				}
			}
			*/

			// Don't let object centers go underground.
			F32 land_height = gWorldPointer->resolveLandHeightGlobal(grab_point_global);

			if (grab_point_global.mdV[VZ] < land_height)
			{
				grab_point_global.mdV[VZ] = land_height;
			}

			// For safety, cap heights where objects can be dragged
			if (grab_point_global.mdV[VZ] > MAX_OBJECT_Z)
			{
				grab_point_global.mdV[VZ] = MAX_OBJECT_Z;
			}

			grab_point_global = gWorldp->clipToVisibleRegions(mDragStartPointGlobal, grab_point_global);
			// propagate constrained grab point back to grab offset
			mGrabHiddenOffsetFromCamera = grab_point_global - gAgent.getCameraPositionGlobal();

			// Handle auto-rotation at screen edge.
			LLVector3 grab_pos_agent = gAgent.getPosAgentFromGlobal( grab_point_global );

			LLCoordGL grab_center_gl( gViewerWindow->getWindowWidth() / 2, gViewerWindow->getWindowHeight() / 2);
			gCamera->projectPosAgentToScreen(grab_pos_agent, grab_center_gl);

			const S32 ROTATE_H_MARGIN = gViewerWindow->getWindowWidth() / 20;
			const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD;
			const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped;
			// ...build mode moves camera about focus point
			if (grab_center_gl.mX < ROTATE_H_MARGIN)
			{
				if (gAgent.getFocusOnAvatar())
				{
					gAgent.yaw(rotate_angle);
				}
				else
				{
					gAgent.cameraOrbitAround(rotate_angle);
				}
			}
			else if (grab_center_gl.mX > gViewerWindow->getWindowWidth() - ROTATE_H_MARGIN)
			{
				if (gAgent.getFocusOnAvatar())
				{
					gAgent.yaw(-rotate_angle);
				}
				else
				{
					gAgent.cameraOrbitAround(-rotate_angle);
				}
			}

			// Don't move above top of screen or below bottom
			if ((grab_center_gl.mY < gViewerWindow->getWindowHeight() - 6)
				&& (grab_center_gl.mY > 24))
			{
				// Transmit update to simulator
				LLVector3 grab_pos_region = mGrabObject->getRegion()->getPosRegionFromGlobal( grab_point_global );

				LLMessageSystem *msg = gMessageSystem;
				msg->newMessageFast(_PREHASH_ObjectGrabUpdate);
				msg->nextBlockFast(_PREHASH_AgentData);
				msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
				msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
				msg->nextBlockFast(_PREHASH_ObjectData);
				msg->addUUIDFast(_PREHASH_ObjectID, mGrabObject->getID() );
				msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial );
				msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region );
				msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds );
				msg->sendMessage( mGrabObject->getRegion()->getHost() );
			}
		}

		gViewerWindow->moveCursorToCenter();

		gSelectMgr->updateSelectionCenter();

	}

	// once we've initiated a drag, lock the camera down
	if (mHasMoved)
	{
		if (!gAgent.cameraMouselook() && 
			!mGrabObject->isHUDAttachment() && 
			mGrabObject->getRoot() == gAgent.getAvatarObject()->getRoot())
		{
			// force focus to point in space where we were looking previously
			gAgent.setFocusGlobal(gAgent.calcFocusPositionTargetGlobal(), LLUUID::null);
			gAgent.setFocusOnAvatar(FALSE, ANIMATE);
		}
		else
		{
			gAgent.clearFocusObject();
		}
	}

	// HACK to avoid assert: error checking system makes sure that the cursor is set during every handleHover.  This is actually a no-op since the cursor is hidden.
	gViewerWindow->setCursor(UI_CURSOR_ARROW);  

	lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolGrab (active) [cursor hidden]" << llendl;		
}
 

// Not dragging.  Just showing affordances
void LLToolGrab::handleHoverInactive(S32 x, S32 y, MASK mask)
{
	const F32 ROTATE_ANGLE_PER_SECOND = 40.f * DEG_TO_RAD;
	const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped;

	// Look for cursor against the edge of the screen
	// Only works in fullscreen
	if (gSavedSettings.getBOOL("FullScreen"))
	{
		if (gAgent.cameraThirdPerson() )
		{
			if (x == 0)
			{
				gAgent.yaw(rotate_angle);
				//gAgent.setControlFlags(AGENT_CONTROL_YAW_POS);
			}
			else if (x == (gViewerWindow->getWindowWidth() - 1) )
			{
				gAgent.yaw(-rotate_angle);
				//gAgent.setControlFlags(AGENT_CONTROL_YAW_NEG);
			}
		}
	}

	// JC - TODO - change cursor based on gGrabBtnVertical, gGrabBtnSpin
	lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolGrab (inactive-not over editable object)" << llendl;		
	gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB);
}

// User is trying to do something that's not allowed.
void LLToolGrab::handleHoverFailed(S32 x, S32 y, MASK mask)
{
	if( GRAB_NOOBJECT == mMode )
	{
		gViewerWindow->setCursor(UI_CURSOR_NO);
		lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolGrab (not on object)" << llendl;		
	}
	else
	{
		S32 dist_sq = (x-mMouseDownX) * (x-mMouseDownX) + (y-mMouseDownY) * (y-mMouseDownY);
		if( mOutsideSlop || dist_sq > SLOP_DIST_SQ )
		{
			mOutsideSlop = TRUE;

			switch( mMode )
			{
			case GRAB_LOCKED:
				gViewerWindow->setCursor(UI_CURSOR_GRABLOCKED);
				lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolGrab (grab failed, no move permission)" << llendl;		
				break;

//  Non physical now handled by handleHoverActive - CRO				
//			case GRAB_NONPHYSICAL:
//				gViewerWindow->setCursor(UI_CURSOR_ARROW);
//				lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolGrab (grab failed, nonphysical)" << llendl;		
//				break;
			default:
				llassert(0);
			}
		}
		else
		{
			gViewerWindow->setCursor(UI_CURSOR_ARROW);
			lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolGrab (grab failed but within slop)" << llendl;		
		}
	}
}




BOOL LLToolGrab::handleMouseUp(S32 x, S32 y, MASK mask)
{
	// call the base class to propogate info to sim
	LLTool::handleMouseUp(x, y, mask);

	if( hasMouseCapture() )
	{
		setMouseCapture( FALSE );
	}
	mMode = GRAB_INACTIVE;

	// HACK: Make some grabs temporary
	if (gGrabTransientTool)
	{
		gBasicToolset->selectTool( gGrabTransientTool );
		gGrabTransientTool = NULL;
	}

	//gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject"));

	return TRUE;
} 

void LLToolGrab::stopEditing()
{
	if( hasMouseCapture() )
	{
		setMouseCapture( FALSE );
	}
}

void LLToolGrab::onMouseCaptureLost()
{
	// First, fix cursor placement
	if( !gAgent.cameraMouselook() 
		&& (GRAB_ACTIVE_CENTER == mMode || GRAB_NONPHYSICAL == mMode))
	{
		llassert( mGrabObject ); 

		if (mGrabObject->isHUDAttachment())
		{
			// ...move cursor "naturally", as if it had moved when hidden
			S32 x = mMouseDownX + mAccumDeltaX;
			S32 y = mMouseDownY + mAccumDeltaY;
			LLUI::setCursorPositionScreen(x, y);
		}
		else if (mHasMoved)
		{
			// ...move cursor back to the center of the object
			LLVector3 grab_point_agent = mGrabObject->getRenderPosition();

			LLCoordGL gl_point;
			gCamera->projectPosAgentToScreen(grab_point_agent, gl_point);

			LLUI::setCursorPositionScreen(gl_point.mX, gl_point.mY);
		}
		else
		{
			// ...move cursor back to click position
			LLUI::setCursorPositionScreen(mMouseDownX, mMouseDownY);
		}

		gViewerWindow->showCursor();
	}

	stopGrab();
	stopSpin();
	mMode = GRAB_INACTIVE;

	mHideBuildHighlight = FALSE;

	mGrabObject = NULL;

	gSelectMgr->updateSelectionCenter();
	gAgent.setPointAt(POINTAT_TARGET_CLEAR);
	gAgent.setLookAt(LOOKAT_TARGET_CLEAR);

	dialog_refresh_all();
}


void LLToolGrab::stopGrab()
{
	// Next, send messages to simulator
	LLMessageSystem *msg = gMessageSystem;
	switch(mMode)
	{
	case GRAB_ACTIVE_CENTER:
	case GRAB_NONPHYSICAL:
	case GRAB_LOCKED:
		msg->newMessageFast(_PREHASH_ObjectDeGrab);
		msg->nextBlockFast(_PREHASH_AgentData);
		msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
		msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
		msg->nextBlockFast(_PREHASH_ObjectData);
		msg->addU32Fast(_PREHASH_LocalID, mGrabObject->mLocalID);
		msg->sendMessage(mGrabObject->getRegion()->getHost());

		mVerticalDragging = FALSE;
		mGrabOffset.clearVec();
		break;

	case GRAB_NOOBJECT:
	case GRAB_INACTIVE:
	default:
		// do nothing
		break;
	}

	mHideBuildHighlight = FALSE;
}


void LLToolGrab::draw()
{ }

void LLToolGrab::render()
{ }

BOOL LLToolGrab::isEditing()
{
	// Can't just compare to null directly due to "smart" pointer.
	LLViewerObject *obj = mGrabObject;
	return (obj != NULL);
}

LLViewerObject* LLToolGrab::getEditingObject()
{
	return mGrabObject;
}


LLVector3d LLToolGrab::getEditingPointGlobal()
{
	return getGrabPointGlobal();
}

LLVector3d LLToolGrab::getGrabPointGlobal()
{
	switch(mMode)
	{
	case GRAB_ACTIVE_CENTER:
	case GRAB_NONPHYSICAL:
	case GRAB_LOCKED:
		return gAgent.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera;

	case GRAB_NOOBJECT:
	case GRAB_INACTIVE:
	default:
		return gAgent.getPositionGlobal();
	}
}
