/**
 * @file lltoolgrab.cpp
 * @brief LLToolGrab class implementation
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * 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;
 * version 2.1 of the License only.
 *
 * 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.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#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 "llagentcamera.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 "llviewerinput.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llvoavatarself.h"
#include "llworld.h"
#include "llmenugl.h"

const S32 SLOP_DIST_SQ = 4;

// Override modifier key behavior with these buttons
bool gGrabBtnVertical = false;
bool gGrabBtnSpin = false;
LLTool* gGrabTransientTool = NULL;
extern bool gDebugClicks;

//
// Methods
//
LLToolGrabBase::LLToolGrabBase( LLToolComposite* composite )
:   LLTool( std::string("Grab"), composite ),
    mMode( GRAB_INACTIVE ),
    mVerticalDragging( false ),
    mHitLand(false),
    mLastMouseX(0),
    mLastMouseY(0),
    mAccumDeltaX(0),
    mAccumDeltaY(0),
    mHasMoved( false ),
    mOutsideSlop(false),
    mDeselectedThisClick(false),
    mLastFace(0),
    mSpinGrabbing( false ),
    mSpinRotation(),
    mClickedInMouselook( false ),
    mHideBuildHighlight(false)
{ }

LLToolGrabBase::~LLToolGrabBase()
{ }


// virtual
void LLToolGrabBase::handleSelect()
{
    if(gFloaterTools)
    {
        // viewer can crash during startup if we don't check.
        gFloaterTools->setStatusText("grab");
        // in case we start from tools floater, we count any selection as valid
        mValidSelection = gFloaterTools->getVisible();
    }
    gGrabBtnVertical = false;
    gGrabBtnSpin = false;
}

void LLToolGrabBase::handleDeselect()
{
    if( hasMouseCapture() )
    {
        setMouseCapture( false );
    }

    // Make sure that temporary(invalid) selection won't pass anywhere except pie tool.
    MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0;
    if (!mValidSelection && (override_mask != MASK_NONE || (gFloaterTools && gFloaterTools->getVisible())))
    {
        LLMenuGL::sMenuContainer->hideMenus();
        LLSelectMgr::getInstance()->validateSelection();
    }

}

bool LLToolGrabBase::handleDoubleClick(S32 x, S32 y, MASK mask)
{
    if (gDebugClicks)
    {
        LL_INFOS() << "LLToolGrab handleDoubleClick (becoming mouseDown)" << LL_ENDL;
    }

    return false;
}

bool LLToolGrabBase::handleMouseDown(S32 x, S32 y, MASK mask)
{
    if (gDebugClicks)
    {
        LL_INFOS() << "LLToolGrab handleMouseDown" << LL_ENDL;
    }

    LLTool::handleMouseDown(x, y, mask);

    // leftButtonGrabbed() checks if controls are reserved by scripts, but does not take masks into account
    if (!gAgent.leftButtonGrabbed() || ((mask & DEFAULT_GRAB_MASK) != 0 && !gAgentCamera.cameraMouselook()))
    {
        // can grab transparent objects (how touch event propagates, scripters rely on this)
        gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ true);
    }
    mClickedInMouselook = gAgentCamera.cameraMouselook();

    if (mClickedInMouselook && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON))
    {
        // LLToolCompGun::handleMouseDown handles the event if ML controls are grabed,
        // but LLToolGrabBase is often the end point for mouselook clicks if ML controls
        // are not grabbed and LLToolGrabBase::handleMouseDown consumes the event,
        // so send clicks from here.
        // We are sending specifically CONTROL_LBUTTON_DOWN instead of _ML_ version.
        gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN);

        // Todo: LLToolGrabBase probably shouldn't consume the event if there is nothing
        // to grab in Mouselook, it intercepts handling in scanMouse
    }
    return true;
}

void LLToolGrabBase::pickCallback(const LLPickInfo& pick_info)
{
    LLToolGrab::getInstance()->mGrabPick = pick_info;
    LLViewerObject  *objectp = pick_info.getObject();

    bool extend_select = (pick_info.mKeyMask & MASK_SHIFT);

    if (!extend_select && !LLSelectMgr::getInstance()->getSelection()->isEmpty())
    {
        LLSelectMgr::getInstance()->deselectAll();
        LLToolGrab::getInstance()->mDeselectedThisClick = true;
    }
    else
    {
        LLToolGrab::getInstance()->mDeselectedThisClick = false;
    }

    // if not over object, do nothing
    if (!objectp)
    {
        LLToolGrab::getInstance()->setMouseCapture(true);
        LLToolGrab::getInstance()->mMode = GRAB_NOOBJECT;
        LLToolGrab::getInstance()->mGrabPick.mObjectID.setNull();
    }
    else
    {
        LLToolGrab::getInstance()->handleObjectHit(LLToolGrab::getInstance()->mGrabPick);
    }
}

bool LLToolGrabBase::handleObjectHit(const LLPickInfo& info)
{
    mGrabPick = info;
    LLViewerObject* objectp = mGrabPick.getObject();

    if (gDebugClicks)
    {
        LL_INFOS() << "LLToolGrab handleObjectHit " << info.mMousePt.mX << "," << info.mMousePt.mY << LL_ENDL;
    }

    if (NULL == objectp) // unexpected
    {
        LL_WARNS() << "objectp was NULL; returning false" << LL_ENDL;
        return false;
    }

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

    setMouseCapture( true );

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

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

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

    if (!objectp->flagUsePhysics())
    {
        if (script_touch)
        {
            mMode = GRAB_NONPHYSICAL;  // if it has a script, use the non-physical grab
        }
        else
        {
            // 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 (gAgentCamera.cameraMouselook())
            {
                mMode = GRAB_LOCKED;
                gViewerWindow->hideCursor();
                gViewerWindow->moveCursorToCenter();
            }
            else if (objectp->permMove() && !objectp->isPermanentEnforced())
            {
                mMode = GRAB_ACTIVE_CENTER;
                gViewerWindow->hideCursor();
                gViewerWindow->moveCursorToCenter();
            }
            else
            {
                mMode = GRAB_LOCKED;
            }


        }
    }
    else if( objectp->flagCharacter() || !objectp->permMove() || objectp->isPermanentEnforced())
    {
        // 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

    mLastMouseX = gViewerWindow->getCurrentMouseX();
    mLastMouseY = gViewerWindow->getCurrentMouseY();
    mAccumDeltaX = 0;
    mAccumDeltaY = 0;
    mHasMoved = false;
    mOutsideSlop = false;

    mVerticalDragging = (info.mKeyMask == MASK_VERTICAL) || gGrabBtnVertical;

    startGrab();

    if ((info.mKeyMask == MASK_SPIN) || gGrabBtnSpin)
    {
        startSpin();
    }

    LLSelectMgr::getInstance()->updateSelectionCenter();        // update selection beam

    // update point at
    LLViewerObject *edit_object = info.getObject();
    if (edit_object && info.mPickType != LLPickInfo::PICK_FLORA)
    {
        LLVector3 local_edit_point = gAgent.getPosAgentFromGlobal(info.mPosGlobal);
        local_edit_point -= edit_object->getPositionAgent();
        local_edit_point = local_edit_point * ~edit_object->getRenderRotation();
        gAgentCamera.setPointAt(POINTAT_TARGET_GRAB, edit_object, local_edit_point );
        gAgentCamera.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 LLToolGrabBase::startSpin()
{
    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp)
    {
        return;
    }
    mSpinGrabbing = true;

    // Was saveSelectedObjectTransform()
    LLViewerObject *root = (LLViewerObject *)objectp->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, mGrabPick.mObjectID );
    msg->sendMessage( objectp->getRegion()->getHost() );
}


void LLToolGrabBase::stopSpin()
{
    mSpinGrabbing = false;

    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp)
    {
        return;
    }

    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, objectp->getID() );
        msg->sendMessage( objectp->getRegion()->getHost() );
        break;

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


void LLToolGrabBase::startGrab()
{
    // Compute grab_offset in the OBJECT's root's coordinate frame
    // (sometimes root == object)
    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp)
    {
        return;
    }

    LLViewerObject *root = (LLViewerObject *)objectp->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() - objectp->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 - gAgentCamera.getCameraPositionGlobal();

    send_ObjectGrab_message(objectp, mGrabPick, grab_offset);

    mGrabOffsetFromCenterInitial = grab_offset;
    mGrabHiddenOffsetFromCamera = mDragStartFromCamera;

    mGrabTimer.reset();

    mLastUVCoords = mGrabPick.mUVCoords;
    mLastSTCoords = mGrabPick.mSTCoords;
    mLastFace = mGrabPick.mObjectFace;
    mLastIntersection = mGrabPick.mIntersection;
    mLastNormal = mGrabPick.mNormal;
    mLastBinormal = mGrabPick.mBinormal;
    mLastGrabPos = LLVector3(-1.f, -1.f, -1.f);
}


bool LLToolGrabBase::handleHover(S32 x, S32 y, MASK mask)
{
    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:
        handleHoverActive( x, y, mask );    // cursor hidden
        break;

    case GRAB_NONPHYSICAL:
        handleHoverNonPhysical(x, y, mask);
        break;

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

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

    }

    mLastMouseX = x;
    mLastMouseY = y;

    return true;
}

const F32 GRAB_SENSITIVITY_X = 0.0075f;
const F32 GRAB_SENSITIVITY_Y = 0.0075f;




// Dragging.
void LLToolGrabBase::handleHoverActive(S32 x, S32 y, MASK mask)
{
    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp || !hasMouseCapture() ) return;
    if (objectp->isDead())
    {
        // Bail out of drag because object has been killed
        setMouseCapture(false);
        return;
    }

    //--------------------------------------------------
    // Determine target mode
    //--------------------------------------------------
    bool vertical_dragging = false;
    bool spin_grabbing = false;
    if ((mask == MASK_VERTICAL)
        || (gGrabBtnVertical && (mask != MASK_SPIN)))
    {
        vertical_dragging = true;
    }
    else if ((mask == MASK_SPIN)
            || (gGrabBtnSpin && (mask != MASK_VERTICAL)))
    {
        spin_grabbing = true;
    }

    //--------------------------------------------------
    // Toggle spinning
    //--------------------------------------------------
    if (mSpinGrabbing && !spin_grabbing)
    {
        // user released or switched mask key(s), stop spinning
        stopSpin();
    }
    else if (!mSpinGrabbing && spin_grabbing)
    {
        // user pressed mask key(s), start spinning
        startSpin();
    }
    mSpinGrabbing = spin_grabbing;

    //--------------------------------------------------
    // Toggle vertical dragging
    //--------------------------------------------------
    if (mVerticalDragging && !vertical_dragging)
    {
        // ...switch to horizontal dragging
        mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, objectp);
        mDragStartFromCamera = mDragStartPointGlobal - gAgentCamera.getCameraPositionGlobal();
    }
    else if (!mVerticalDragging && vertical_dragging)
    {
        // ...switch to vertical dragging
        mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, objectp);
        mDragStartFromCamera = mDragStartPointGlobal - gAgentCamera.getCameraPositionGlobal();
    }
    mVerticalDragging = vertical_dragging;

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

    S32 dx = gViewerWindow->getCurrentMouseDX();
    S32 dy = gViewerWindow->getCurrentMouseDY();

    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 = LLViewerCamera::getInstance()->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, objectp->getID() );
            msg->addQuatFast(_PREHASH_Rotation, mSpinRotation );
            msg->sendMessage( objectp->getRegion()->getHost() );
        }
        else
        {
            //------------------------------------------------------
            // Handle grabbing
            //------------------------------------------------------

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

            LLVector3d y_part;
            if( mVerticalDragging )
            {
                y_part.setVec(LLViewerCamera::getInstance()->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 * GRAB_SENSITIVITY_X))
                + (y_part * ( dy * GRAB_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 = gAgentCamera.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 = LLWorld::getInstance()->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 = LLWorld::getInstance()->clipToVisibleRegions(mDragStartPointGlobal, grab_point_global);
            // propagate constrained grab point back to grab offset
            mGrabHiddenOffsetFromCamera = grab_point_global - gAgentCamera.getCameraPositionGlobal();

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

            LLCoordGL grab_center_gl( gViewerWindow->getWorldViewWidthScaled() / 2, gViewerWindow->getWorldViewHeightScaled() / 2);
            LLViewerCamera::getInstance()->projectPosAgentToScreen(grab_pos_agent, grab_center_gl);

            const S32 ROTATE_H_MARGIN = gViewerWindow->getWorldViewWidthScaled() / 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 (gAgentCamera.getFocusOnAvatar())
                {
                    gAgent.yaw(rotate_angle);
                }
                else
                {
                    gAgentCamera.cameraOrbitAround(rotate_angle);
                }
            }
            else if (grab_center_gl.mX > gViewerWindow->getWorldViewWidthScaled() - ROTATE_H_MARGIN)
            {
                if (gAgentCamera.getFocusOnAvatar())
                {
                    gAgent.yaw(-rotate_angle);
                }
                else
                {
                    gAgentCamera.cameraOrbitAround(-rotate_angle);
                }
            }

            // Don't move above top of screen or below bottom
            if ((grab_center_gl.mY < gViewerWindow->getWorldViewHeightScaled() - 6)
                && (grab_center_gl.mY > 24))
            {
                // Transmit update to simulator
                LLVector3 grab_pos_region = objectp->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, objectp->getID() );
                msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial );
                msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region );
                msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds );
                msg->nextBlock("SurfaceInfo");
                msg->addVector3("UVCoord", LLVector3(mGrabPick.mUVCoords));
                msg->addVector3("STCoord", LLVector3(mGrabPick.mSTCoords));
                msg->addS32Fast(_PREHASH_FaceIndex, mGrabPick.mObjectFace);
                msg->addVector3("Position", mGrabPick.mIntersection);
                msg->addVector3("Normal", mGrabPick.mNormal);
                msg->addVector3("Binormal", mGrabPick.mBinormal);

                msg->sendMessage( objectp->getRegion()->getHost() );
            }
        }

        gViewerWindow->moveCursorToCenter();

        LLSelectMgr::getInstance()->updateSelectionCenter();

    }

    // once we've initiated a drag, lock the camera down
    if (mHasMoved)
    {
        if (!gAgentCamera.cameraMouselook() &&
            !objectp->isHUDAttachment() &&
            objectp->getRoot() == gAgentAvatarp->getRoot())
        {
            // we are essentially editing object position
            if (!gSavedSettings.getBOOL("EditCameraMovement"))
            {
                // force focus to point in space where we were looking previously
                // Example of use: follow cam scripts shouldn't affect you when movng objects arouns
                gAgentCamera.setFocusGlobal(gAgentCamera.calcFocusPositionTargetGlobal(), LLUUID::null);
                gAgentCamera.setFocusOnAvatar(false, ANIMATE);
            }
        }
        else
        {
            gAgentCamera.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);

    LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (active) [cursor hidden]" << LL_ENDL;
}


void LLToolGrabBase::handleHoverNonPhysical(S32 x, S32 y, MASK mask)
{
    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp || !hasMouseCapture() ) return;
    if (objectp->isDead())
    {
        // Bail out of drag because object has been killed
        setMouseCapture(false);
        return;
    }

    LLPickInfo pick = mGrabPick;
    pick.mMousePt = LLCoordGL(x, y);
    pick.getSurfaceInfo();

    // compute elapsed time
    F32 dt = mGrabTimer.getElapsedTimeAndResetF32();
    U32 dt_milliseconds = (U32) (1000.f * dt);

    // i'm not a big fan of the following code - it's been culled from the physical grab case.
    // ideally these two would be nicely integrated - but the code in that method is a serious
    // mess of spaghetti.  so here we go:

    LLVector3 grab_pos_region(0,0,0);

    const bool SUPPORT_LLDETECTED_GRAB = true;
    if (SUPPORT_LLDETECTED_GRAB)
    {
        //--------------------------------------------------
        // Toggle vertical dragging
        //--------------------------------------------------
        if (!(mask == MASK_VERTICAL) && !gGrabBtnVertical)
        {
            mVerticalDragging = false;
        }

        else if ((gGrabBtnVertical && (mask != MASK_SPIN))
                || (mask == MASK_VERTICAL))
        {
            mVerticalDragging = true;
        }

        S32 dx = x - mLastMouseX;
        S32 dy = y - mLastMouseY;

        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
            mHasMoved = true;

            //------------------------------------------------------
            // Handle grabbing
            //------------------------------------------------------

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

            LLVector3d y_part;
            if( mVerticalDragging )
            {
                y_part.setVec(LLViewerCamera::getInstance()->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 * GRAB_SENSITIVITY_X))
                + (y_part * ( dy * GRAB_SENSITIVITY_Y));

        }

        // need to return offset from mGrabStartPoint
        LLVector3d grab_point_global = gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera;
        grab_pos_region = objectp->getRegion()->getPosRegionFromGlobal( grab_point_global );
    }


    // only send message if something has changed since last message

    bool changed_since_last_update = false;

    // test if touch data needs to be updated
    if ((pick.mObjectFace != mLastFace) ||
        (pick.mUVCoords != mLastUVCoords) ||
        (pick.mSTCoords != mLastSTCoords) ||
        (pick.mIntersection != mLastIntersection) ||
        (pick.mNormal != mLastNormal) ||
        (pick.mBinormal != mLastBinormal) ||
        (grab_pos_region != mLastGrabPos))
    {
        changed_since_last_update = true;
    }

    if (changed_since_last_update)
    {
        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, objectp->getID() );
        msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial );
        msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region );
        msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds );
        msg->nextBlock("SurfaceInfo");
        msg->addVector3("UVCoord", LLVector3(pick.mUVCoords));
        msg->addVector3("STCoord", LLVector3(pick.mSTCoords));
        msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace);
        msg->addVector3("Position", pick.mIntersection);
        msg->addVector3("Normal", pick.mNormal);
        msg->addVector3("Binormal", pick.mBinormal);

        msg->sendMessage( objectp->getRegion()->getHost() );

        mLastUVCoords = pick.mUVCoords;
        mLastSTCoords = pick.mSTCoords;
        mLastFace = pick.mObjectFace;
        mLastIntersection = pick.mIntersection;
        mLastNormal= pick.mNormal;
        mLastBinormal= pick.mBinormal;
        mLastGrabPos = grab_pos_region;
    }

    // update point-at / look-at
    if (pick.mObjectFace != -1) // if the intersection was on the surface of the obejct
    {
        LLVector3 local_edit_point = pick.mIntersection;
        local_edit_point -= objectp->getPositionAgent();
        local_edit_point = local_edit_point * ~objectp->getRenderRotation();
        gAgentCamera.setPointAt(POINTAT_TARGET_GRAB, objectp, local_edit_point );
        gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, objectp, local_edit_point );
    }



    gViewerWindow->setCursor(UI_CURSOR_HAND);
}


// Not dragging.  Just showing affordances
void LLToolGrabBase::handleHoverInactive(S32 x, S32 y, MASK mask)
{
    // JC - TODO - change cursor based on gGrabBtnVertical, gGrabBtnSpin
    LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (inactive-not over editable object)" << LL_ENDL;
    gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB);
}

// User is trying to do something that's not allowed.
void LLToolGrabBase::handleHoverFailed(S32 x, S32 y, MASK mask)
{
    if( GRAB_NOOBJECT == mMode )
    {
        gViewerWindow->setCursor(UI_CURSOR_NO);
        LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (not on object)" << LL_ENDL;
    }
    else
    {
        S32 dist_sq = (x-mGrabPick.mMousePt.mX) * (x-mGrabPick.mMousePt.mX) + (y-mGrabPick.mMousePt.mY) * (y-mGrabPick.mMousePt.mY);
        if( mOutsideSlop || dist_sq > SLOP_DIST_SQ )
        {
            mOutsideSlop = true;

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

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




bool LLToolGrabBase::handleMouseUp(S32 x, S32 y, MASK mask)
{
    LLTool::handleMouseUp(x, y, mask);

    if (gAgentCamera.cameraMouselook() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON))
    {
        // LLToolCompGun::handleMouseUp handles the event if ML controls are grabed,
        // but LLToolGrabBase is often the end point for mouselook clicks if ML controls
        // are not grabbed and LToolGrabBase::handleMouseUp consumes the event,
        // so send clicks from here.
        // We are sending specifically CONTROL_LBUTTON_UP instead of _ML_ version.
        gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP);
    }

    if( hasMouseCapture() )
    {
        setMouseCapture( false );
    }

    mMode = GRAB_INACTIVE;

    if(mClickedInMouselook && !gAgentCamera.cameraMouselook())
    {
        mClickedInMouselook = false;
    }
    else
    {
        // HACK: Make some grabs temporary
        if (gGrabTransientTool)
        {
            gBasicToolset->selectTool( gGrabTransientTool );
            gGrabTransientTool = NULL;
        }
    }

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

    return true;
}

void LLToolGrabBase::stopEditing()
{
    if( hasMouseCapture() )
    {
        setMouseCapture( false );
    }
}

void LLToolGrabBase::onMouseCaptureLost()
{
    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp)
    {
        gViewerWindow->showCursor();
        return;
    }
    // First, fix cursor placement
    if( !gAgentCamera.cameraMouselook()
        && (GRAB_ACTIVE_CENTER == mMode))
    {
        if (objectp->isHUDAttachment())
        {
            // ...move cursor "naturally", as if it had moved when hidden
            S32 x = mGrabPick.mMousePt.mX + mAccumDeltaX;
            S32 y = mGrabPick.mMousePt.mY + mAccumDeltaY;
            LLUI::getInstance()->setMousePositionScreen(x, y);
        }
        else if (mHasMoved)
        {
            // ...move cursor back to the center of the object
            LLVector3 grab_point_agent = objectp->getRenderPosition();

            LLCoordGL gl_point;
            if (LLViewerCamera::getInstance()->projectPosAgentToScreen(grab_point_agent, gl_point))
            {
                LLUI::getInstance()->setMousePositionScreen(gl_point.mX, gl_point.mY);
            }
        }
        else
        {
            // ...move cursor back to click position
            LLUI::getInstance()->setMousePositionScreen(mGrabPick.mMousePt.mX, mGrabPick.mMousePt.mY);
        }

        gViewerWindow->showCursor();
    }

    stopGrab();
    if (mSpinGrabbing)
    stopSpin();

    mMode = GRAB_INACTIVE;

    mHideBuildHighlight = false;

    mGrabPick.mObjectID.setNull();

    LLSelectMgr::getInstance()->updateSelectionCenter();
    gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR);
    gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR);

    dialog_refresh_all();
}


void LLToolGrabBase::stopGrab()
{
    LLViewerObject* objectp = mGrabPick.getObject();
    if (!objectp)
    {
        return;
    }

    LLPickInfo pick = mGrabPick;

    if (mMode == GRAB_NONPHYSICAL)
    {
        // for non-physical (touch) grabs,
        // gather surface info for this degrab (mouse-up)
        S32 x = gViewerWindow->getCurrentMouseX();
        S32 y = gViewerWindow->getCurrentMouseY();
        pick.mMousePt = LLCoordGL(x, y);
        pick.getSurfaceInfo();
    }

    // Next, send messages to simulator
    switch(mMode)
    {
    case GRAB_ACTIVE_CENTER:
    case GRAB_NONPHYSICAL:
    case GRAB_LOCKED:
        send_ObjectDeGrab_message(objectp, pick);
        mVerticalDragging = false;
        break;

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

    mHideBuildHighlight = false;
}


void LLToolGrabBase::draw()
{ }

void LLToolGrabBase::render()
{ }

bool LLToolGrabBase::isEditing()
{
    return (mGrabPick.getObject().notNull());
}

LLViewerObject* LLToolGrabBase::getEditingObject()
{
    return mGrabPick.getObject();
}


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

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

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


void send_ObjectGrab_message(LLViewerObject* object, const LLPickInfo & pick, const LLVector3 &grab_offset)
{
    if (!object) return;

    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, object->mLocalID);
    msg->addVector3Fast(_PREHASH_GrabOffset, grab_offset);
    msg->nextBlock("SurfaceInfo");
    msg->addVector3("UVCoord", LLVector3(pick.mUVCoords));
    msg->addVector3("STCoord", LLVector3(pick.mSTCoords));
    msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace);
    msg->addVector3("Position", pick.mIntersection);
    msg->addVector3("Normal", pick.mNormal);
    msg->addVector3("Binormal", pick.mBinormal);
    msg->sendMessage( object->getRegion()->getHost());

    /*  Diagnostic code
    LL_INFOS() << "mUVCoords: " << pick.mUVCoords
            << ", mSTCoords: " << pick.mSTCoords
            << ", mObjectFace: " << pick.mObjectFace
            << ", mIntersection: " << pick.mIntersection
            << ", mNormal: " << pick.mNormal
            << ", mBinormal: " << pick.mBinormal
            << LL_ENDL;

    LL_INFOS() << "Avatar pos: " << gAgent.getPositionAgent() << LL_ENDL;
    LL_INFOS() << "Object pos: " << object->getPosition() << LL_ENDL;
    */
}


void send_ObjectDeGrab_message(LLViewerObject* object, const LLPickInfo & pick)
{
    if (!object) return;

    LLMessageSystem *msg = gMessageSystem;

    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, object->mLocalID);
    msg->nextBlock("SurfaceInfo");
    msg->addVector3("UVCoord", LLVector3(pick.mUVCoords));
    msg->addVector3("STCoord", LLVector3(pick.mSTCoords));
    msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace);
    msg->addVector3("Position", pick.mIntersection);
    msg->addVector3("Normal", pick.mNormal);
    msg->addVector3("Binormal", pick.mBinormal);
    msg->sendMessage(object->getRegion()->getHost());
}