/**
 * @file llpanelobject.cpp
 * @brief Object editing (position, scale, etc.) in the tools floater
 *
 * $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"

// file include
#include "llpanelobject.h"

// linden library includes
#include "llerror.h"
#include "llfontgl.h"
#include "llpermissionsflags.h"
#include "llstring.h"
#include "llvolume.h"
#include "m3math.h"

// project includes
#include "llagent.h"
#include "llbutton.h"
#include "llcalc.h"
#include "llcheckboxctrl.h"
#include "llcolorswatch.h"
#include "llcombobox.h"
#include "llfocusmgr.h"
#include "llmanipscale.h"
#include "llmenubutton.h"
#include "llpreviewscript.h"
#include "llresmgr.h"
#include "llselectmgr.h"
#include "llspinctrl.h"
#include "lltexturectrl.h"
#include "lltextbox.h"
#include "lltool.h"
#include "lltoolcomp.h"
#include "lltoolmgr.h"
#include "llui.h"
#include "llviewerobject.h"
#include "llviewerregion.h"
#include "llviewerwindow.h"
#include "llvovolume.h"
#include "llworld.h"
#include "pipeline.h"
#include "llviewercontrol.h"
#include "lluictrlfactory.h"
//#include "llfirstuse.h"

#include "lldrawpool.h"

//
// Constants
//
enum {
    MI_BOX,
    MI_CYLINDER,
    MI_PRISM,
    MI_SPHERE,
    MI_TORUS,
    MI_TUBE,
    MI_RING,
    MI_SCULPT,
    MI_NONE,
    MI_VOLUME_COUNT
};

enum {
    MI_HOLE_SAME,
    MI_HOLE_CIRCLE,
    MI_HOLE_SQUARE,
    MI_HOLE_TRIANGLE,
    MI_HOLE_COUNT
};

const F32 MAX_ATTACHMENT_DIST = 3.5f; // meters

//static const std::string LEGACY_FULLBRIGHT_DESC =LLTrans::getString("Fullbright");

BOOL    LLPanelObject::postBuild()
{
    setMouseOpaque(FALSE);

    //--------------------------------------------------------
    // Top
    //--------------------------------------------------------

    // Lock checkbox
    mCheckLock = getChild<LLCheckBoxCtrl>("checkbox locked");
    childSetCommitCallback("checkbox locked",onCommitLock,this);

    // Physical checkbox
    mCheckPhysics = getChild<LLCheckBoxCtrl>("Physical Checkbox Ctrl");
    childSetCommitCallback("Physical Checkbox Ctrl",onCommitPhysics,this);

    // Temporary checkbox
    mCheckTemporary = getChild<LLCheckBoxCtrl>("Temporary Checkbox Ctrl");
    childSetCommitCallback("Temporary Checkbox Ctrl",onCommitTemporary,this);

    // Phantom checkbox
    mCheckPhantom = getChild<LLCheckBoxCtrl>("Phantom Checkbox Ctrl");
    childSetCommitCallback("Phantom Checkbox Ctrl",onCommitPhantom,this);

    // Position
    mMenuClipboardPos = getChild<LLMenuButton>("clipboard_pos_btn");
    mLabelPosition = getChild<LLTextBox>("label position");
    mCtrlPosX = getChild<LLSpinCtrl>("Pos X");
    childSetCommitCallback("Pos X",onCommitPosition,this);
    mCtrlPosY = getChild<LLSpinCtrl>("Pos Y");
    childSetCommitCallback("Pos Y",onCommitPosition,this);
    mCtrlPosZ = getChild<LLSpinCtrl>("Pos Z");
    childSetCommitCallback("Pos Z",onCommitPosition,this);

    // Scale
    mMenuClipboardSize = getChild<LLMenuButton>("clipboard_size_btn");
    mLabelSize = getChild<LLTextBox>("label size");
    mCtrlScaleX = getChild<LLSpinCtrl>("Scale X");
    childSetCommitCallback("Scale X",onCommitScale,this);

    // Scale Y
    mCtrlScaleY = getChild<LLSpinCtrl>("Scale Y");
    childSetCommitCallback("Scale Y",onCommitScale,this);

    // Scale Z
    mCtrlScaleZ = getChild<LLSpinCtrl>("Scale Z");
    childSetCommitCallback("Scale Z",onCommitScale,this);

    // Rotation
    mMenuClipboardRot = getChild<LLMenuButton>("clipboard_rot_btn");
    mLabelRotation = getChild<LLTextBox>("label rotation");
    mCtrlRotX = getChild<LLSpinCtrl>("Rot X");
    childSetCommitCallback("Rot X",onCommitRotation,this);
    mCtrlRotY = getChild<LLSpinCtrl>("Rot Y");
    childSetCommitCallback("Rot Y",onCommitRotation,this);
    mCtrlRotZ = getChild<LLSpinCtrl>("Rot Z");
    childSetCommitCallback("Rot Z",onCommitRotation,this);

    //--------------------------------------------------------

    // Base Type
    mComboBaseType = getChild<LLComboBox>("comboBaseType");
    childSetCommitCallback("comboBaseType",onCommitParametric,this);

    mMenuClipboardParams = getChild<LLMenuButton>("clipboard_obj_params_btn");

    // Cut
    mLabelCut = getChild<LLTextBox>("text cut");
    mSpinCutBegin = getChild<LLSpinCtrl>("cut begin");
    childSetCommitCallback("cut begin",onCommitParametric,this);
    mSpinCutBegin->setValidateBeforeCommit( precommitValidate );
    mSpinCutEnd = getChild<LLSpinCtrl>("cut end");
    childSetCommitCallback("cut end",onCommitParametric,this);
    mSpinCutEnd->setValidateBeforeCommit( &precommitValidate );

    // Hollow / Skew
    mLabelHollow = getChild<LLTextBox>("text hollow");
    mLabelSkew = getChild<LLTextBox>("text skew");
    mSpinHollow = getChild<LLSpinCtrl>("Scale 1");
    childSetCommitCallback("Scale 1",onCommitParametric,this);
    mSpinHollow->setValidateBeforeCommit( &precommitValidate );
    mSpinSkew = getChild<LLSpinCtrl>("Skew");
    childSetCommitCallback("Skew",onCommitParametric,this);
    mSpinSkew->setValidateBeforeCommit( &precommitValidate );
    mLabelHoleType = getChild<LLTextBox>("Hollow Shape");

    // Hole Type
    mComboHoleType = getChild<LLComboBox>("hole");
    childSetCommitCallback("hole",onCommitParametric,this);

    // Twist
    mLabelTwist = getChild<LLTextBox>("text twist");
    mSpinTwistBegin = getChild<LLSpinCtrl>("Twist Begin");
    childSetCommitCallback("Twist Begin",onCommitParametric,this);
    mSpinTwistBegin->setValidateBeforeCommit( precommitValidate );
    mSpinTwist = getChild<LLSpinCtrl>("Twist End");
    childSetCommitCallback("Twist End",onCommitParametric,this);
    mSpinTwist->setValidateBeforeCommit( &precommitValidate );

    // Scale
    mSpinScaleX = getChild<LLSpinCtrl>("Taper Scale X");
    childSetCommitCallback("Taper Scale X",onCommitParametric,this);
    mSpinScaleX->setValidateBeforeCommit( &precommitValidate );
    mSpinScaleY = getChild<LLSpinCtrl>("Taper Scale Y");
    childSetCommitCallback("Taper Scale Y",onCommitParametric,this);
    mSpinScaleY->setValidateBeforeCommit( &precommitValidate );

    // Shear
    mLabelShear = getChild<LLTextBox>("text topshear");
    mSpinShearX = getChild<LLSpinCtrl>("Shear X");
    childSetCommitCallback("Shear X",onCommitParametric,this);
    mSpinShearX->setValidateBeforeCommit( &precommitValidate );
    mSpinShearY = getChild<LLSpinCtrl>("Shear Y");
    childSetCommitCallback("Shear Y",onCommitParametric,this);
    mSpinShearY->setValidateBeforeCommit( &precommitValidate );

    // Path / Profile
    mCtrlPathBegin = getChild<LLSpinCtrl>("Path Limit Begin");
    childSetCommitCallback("Path Limit Begin",onCommitParametric,this);
    mCtrlPathBegin->setValidateBeforeCommit( &precommitValidate );
    mCtrlPathEnd = getChild<LLSpinCtrl>("Path Limit End");
    childSetCommitCallback("Path Limit End",onCommitParametric,this);
    mCtrlPathEnd->setValidateBeforeCommit( &precommitValidate );

    // Taper
    mLabelTaper = getChild<LLTextBox>("text taper2");
    mSpinTaperX = getChild<LLSpinCtrl>("Taper X");
    childSetCommitCallback("Taper X",onCommitParametric,this);
    mSpinTaperX->setValidateBeforeCommit( precommitValidate );
    mSpinTaperY = getChild<LLSpinCtrl>("Taper Y");
    childSetCommitCallback("Taper Y",onCommitParametric,this);
    mSpinTaperY->setValidateBeforeCommit( precommitValidate );

    // Radius Offset / Revolutions
    mLabelRadiusOffset = getChild<LLTextBox>("text radius delta");
    mLabelRevolutions = getChild<LLTextBox>("text revolutions");
    mSpinRadiusOffset = getChild<LLSpinCtrl>("Radius Offset");
    childSetCommitCallback("Radius Offset",onCommitParametric,this);
    mSpinRadiusOffset->setValidateBeforeCommit( &precommitValidate );
    mSpinRevolutions = getChild<LLSpinCtrl>("Revolutions");
    childSetCommitCallback("Revolutions",onCommitParametric,this);
    mSpinRevolutions->setValidateBeforeCommit( &precommitValidate );

    // Sculpt
    mCtrlSculptTexture = getChild<LLTextureCtrl>("sculpt texture control");
    if (mCtrlSculptTexture)
    {
        mCtrlSculptTexture->setDefaultImageAssetID(SCULPT_DEFAULT_TEXTURE);
        mCtrlSculptTexture->setCommitCallback( boost::bind(&LLPanelObject::onCommitSculpt, this, _2 ));
        mCtrlSculptTexture->setOnCancelCallback( boost::bind(&LLPanelObject::onCancelSculpt, this, _2 ));
        mCtrlSculptTexture->setOnSelectCallback( boost::bind(&LLPanelObject::onSelectSculpt, this, _2 ));
        mCtrlSculptTexture->setDropCallback( boost::bind(&LLPanelObject::onDropSculpt, this, _2 ));
        // Don't allow (no copy) or (no transfer) textures to be selected during immediate mode
        mCtrlSculptTexture->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER);
        mCtrlSculptTexture->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER);
        LLAggregatePermissions texture_perms;
        if (LLSelectMgr::getInstance()->selectGetAggregateTexturePermissions(texture_perms))
        {
            BOOL can_copy =
                texture_perms.getValue(PERM_COPY) == LLAggregatePermissions::AP_EMPTY ||
                texture_perms.getValue(PERM_COPY) == LLAggregatePermissions::AP_ALL;
            BOOL can_transfer =
                texture_perms.getValue(PERM_TRANSFER) == LLAggregatePermissions::AP_EMPTY ||
                texture_perms.getValue(PERM_TRANSFER) == LLAggregatePermissions::AP_ALL;
            mCtrlSculptTexture->setCanApplyImmediately(can_copy && can_transfer);
        }
        else
        {
            mCtrlSculptTexture->setCanApplyImmediately(FALSE);
        }
    }

    mLabelSculptType = getChild<LLTextBox>("label sculpt type");
    mCtrlSculptType = getChild<LLComboBox>("sculpt type control");
    childSetCommitCallback("sculpt type control", onCommitSculptType, this);
    mCtrlSculptMirror = getChild<LLCheckBoxCtrl>("sculpt mirror control");
    childSetCommitCallback("sculpt mirror control", onCommitSculptType, this);
    mCtrlSculptInvert = getChild<LLCheckBoxCtrl>("sculpt invert control");
    childSetCommitCallback("sculpt invert control", onCommitSculptType, this);

    // Start with everyone disabled
    clearCtrls();

    return TRUE;
}

LLPanelObject::LLPanelObject()
:   LLPanel(),
    mIsPhysical(FALSE),
    mIsTemporary(FALSE),
    mIsPhantom(FALSE),
    mSelectedType(MI_BOX),
    mSculptTextureRevert(LLUUID::null),
    mSculptTypeRevert(0),
    mHasClipboardPos(false),
    mHasClipboardSize(false),
    mHasClipboardRot(false),
    mSizeChanged(FALSE)
{
    mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", { boost::bind(&LLPanelObject::menuDoToSelected, this, _2) });
    mEnableCallbackRegistrar.add("PanelObject.menuEnable", boost::bind(&LLPanelObject::menuEnableItem, this, _2));
}


LLPanelObject::~LLPanelObject()
{
    // Children all cleaned up by default view destructor.
}

void LLPanelObject::getState( )
{
    LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject();
    LLViewerObject* root_objectp = objectp;
    if(!objectp)
    {
        objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
        // *FIX: shouldn't we just keep the child?
        if (objectp)
        {
            LLViewerObject* parentp = objectp->getRootEdit();

            if (parentp)
            {
                root_objectp = parentp;
            }
            else
            {
                root_objectp = objectp;
            }
        }
    }

    LLCalc* calcp = LLCalc::getInstance();

    LLVOVolume *volobjp = NULL;
    if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME))
    {
        volobjp = (LLVOVolume *)objectp;
    }

    if( !objectp )
    {
        //forfeit focus
        if (gFocusMgr.childHasKeyboardFocus(this))
        {
            gFocusMgr.setKeyboardFocus(NULL);
        }

        // Disable all text input fields
        clearCtrls();
        calcp->clearAllVariables();
        return;
    }

    S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount();
    BOOL single_volume = (LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ))
                         && (selected_count == 1);

    bool enable_move;
    bool enable_modify;

    LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify);

    BOOL enable_scale = enable_modify;
    BOOL enable_rotate = enable_move; // already accounts for a case of children, which needs permModify() as well

    LLVector3 vec;
    if (enable_move)
    {
        vec = objectp->getPositionEdit();
        mCtrlPosX->set( vec.mV[VX] );
        mCtrlPosY->set( vec.mV[VY] );
        mCtrlPosZ->set( vec.mV[VZ] );
        calcp->setVar(LLCalc::X_POS, vec.mV[VX]);
        calcp->setVar(LLCalc::Y_POS, vec.mV[VY]);
        calcp->setVar(LLCalc::Z_POS, vec.mV[VZ]);
    }
    else
    {
        mCtrlPosX->clear();
        mCtrlPosY->clear();
        mCtrlPosZ->clear();
        calcp->clearVar(LLCalc::X_POS);
        calcp->clearVar(LLCalc::Y_POS);
        calcp->clearVar(LLCalc::Z_POS);
    }

    mMenuClipboardPos->setEnabled(enable_move);
    mLabelPosition->setEnabled( enable_move );
    mCtrlPosX->setEnabled(enable_move);
    mCtrlPosY->setEnabled(enable_move);
    mCtrlPosZ->setEnabled(enable_move);

    if (enable_scale)
    {
        vec = objectp->getScale();
        mCtrlScaleX->set( vec.mV[VX] );
        mCtrlScaleY->set( vec.mV[VY] );
        mCtrlScaleZ->set( vec.mV[VZ] );
        calcp->setVar(LLCalc::X_SCALE, vec.mV[VX]);
        calcp->setVar(LLCalc::Y_SCALE, vec.mV[VY]);
        calcp->setVar(LLCalc::Z_SCALE, vec.mV[VZ]);
    }
    else
    {
        mCtrlScaleX->clear();
        mCtrlScaleY->clear();
        mCtrlScaleZ->clear();
        calcp->setVar(LLCalc::X_SCALE, 0.f);
        calcp->setVar(LLCalc::Y_SCALE, 0.f);
        calcp->setVar(LLCalc::Z_SCALE, 0.f);
    }

    mMenuClipboardSize->setEnabled(enable_scale);
    mLabelSize->setEnabled( enable_scale );
    mCtrlScaleX->setEnabled( enable_scale );
    mCtrlScaleY->setEnabled( enable_scale );
    mCtrlScaleZ->setEnabled( enable_scale );

    LLQuaternion object_rot = objectp->getRotationEdit();
    object_rot.getEulerAngles(&(mCurEulerDegrees.mV[VX]), &(mCurEulerDegrees.mV[VY]), &(mCurEulerDegrees.mV[VZ]));
    mCurEulerDegrees *= RAD_TO_DEG;
    mCurEulerDegrees.mV[VX] = fmod(ll_round(mCurEulerDegrees.mV[VX], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
    mCurEulerDegrees.mV[VY] = fmod(ll_round(mCurEulerDegrees.mV[VY], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
    mCurEulerDegrees.mV[VZ] = fmod(ll_round(mCurEulerDegrees.mV[VZ], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);

    if (enable_rotate)
    {
        mCtrlRotX->set( mCurEulerDegrees.mV[VX] );
        mCtrlRotY->set( mCurEulerDegrees.mV[VY] );
        mCtrlRotZ->set( mCurEulerDegrees.mV[VZ] );
        calcp->setVar(LLCalc::X_ROT, mCurEulerDegrees.mV[VX]);
        calcp->setVar(LLCalc::Y_ROT, mCurEulerDegrees.mV[VY]);
        calcp->setVar(LLCalc::Z_ROT, mCurEulerDegrees.mV[VZ]);
    }
    else
    {
        mCtrlRotX->clear();
        mCtrlRotY->clear();
        mCtrlRotZ->clear();
        calcp->clearVar(LLCalc::X_ROT);
        calcp->clearVar(LLCalc::Y_ROT);
        calcp->clearVar(LLCalc::Z_ROT);
    }

    mMenuClipboardRot->setEnabled(enable_rotate);
    mLabelRotation->setEnabled( enable_rotate );
    mCtrlRotX->setEnabled( enable_rotate );
    mCtrlRotY->setEnabled( enable_rotate );
    mCtrlRotZ->setEnabled( enable_rotate );

    LLUUID owner_id;
    std::string owner_name;
    LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name);

    // BUG? Check for all objects being editable?
    S32 roots_selected = LLSelectMgr::getInstance()->getSelection()->getRootObjectCount();
    BOOL editable = root_objectp->permModify();

    BOOL is_flexible = volobjp && volobjp->isFlexible();
    BOOL is_permanent = root_objectp->flagObjectPermanent();
    BOOL is_permanent_enforced = root_objectp->isPermanentEnforced();
    BOOL is_character = root_objectp->flagCharacter();
    llassert(!is_permanent || !is_character); // should never have a permanent object that is also a character

    // Lock checkbox - only modifiable if you own the object.
    BOOL self_owned = (gAgent.getID() == owner_id);
    mCheckLock->setEnabled( roots_selected > 0 && self_owned && !is_permanent_enforced);

    // More lock and debit checkbox - get the values
    BOOL valid;
    U32 owner_mask_on;
    U32 owner_mask_off;
    valid = LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, &owner_mask_on, &owner_mask_off);

    if(valid)
    {
        if(owner_mask_on & PERM_MOVE)
        {
            // owner can move, so not locked
            mCheckLock->set(FALSE);
            mCheckLock->setTentative(FALSE);
        }
        else if(owner_mask_off & PERM_MOVE)
        {
            // owner can't move, so locked
            mCheckLock->set(TRUE);
            mCheckLock->setTentative(FALSE);
        }
        else
        {
            // some locked, some not locked
            mCheckLock->set(FALSE);
            mCheckLock->setTentative(TRUE);
        }
    }

    // Physics checkbox
    mIsPhysical = root_objectp->flagUsePhysics();
    llassert(!is_permanent || !mIsPhysical); // should never have a permanent object that is also physical

    mCheckPhysics->set( mIsPhysical );
    mCheckPhysics->setEnabled( roots_selected>0
                                && (editable || gAgent.isGodlike())
                                && !is_flexible && !is_permanent);

    mIsTemporary = root_objectp->flagTemporaryOnRez();
    llassert(!is_permanent || !mIsTemporary); // should never has a permanent object that is also temporary

    mCheckTemporary->set( mIsTemporary );
    mCheckTemporary->setEnabled( roots_selected>0 && editable && !is_permanent);

    mIsPhantom = root_objectp->flagPhantom();
    BOOL is_volume_detect = root_objectp->flagVolumeDetect();
    llassert(!is_character || !mIsPhantom); // should never have a character that is also a phantom
    mCheckPhantom->set( mIsPhantom );
    mCheckPhantom->setEnabled( roots_selected>0 && editable && !is_flexible && !is_permanent_enforced && !is_character && !is_volume_detect);

    //----------------------------------------------------------------------------

    S32 selected_item   = MI_BOX;
    S32 selected_hole   = MI_HOLE_SAME;
    BOOL enabled = FALSE;
    BOOL hole_enabled = FALSE;
    F32 scale_x=1.f, scale_y=1.f;
    BOOL isMesh = FALSE;

    if( !objectp || !objectp->getVolume() || !editable || !single_volume)
    {
        // Clear out all geometry fields.
        mComboBaseType->clear();
        mSpinHollow->clear();
        mSpinCutBegin->clear();
        mSpinCutEnd->clear();
        mCtrlPathBegin->clear();
        mCtrlPathEnd->clear();
        mSpinScaleX->clear();
        mSpinScaleY->clear();
        mSpinTwist->clear();
        mSpinTwistBegin->clear();
        mComboHoleType->clear();
        mSpinShearX->clear();
        mSpinShearY->clear();
        mSpinTaperX->clear();
        mSpinTaperY->clear();
        mSpinRadiusOffset->clear();
        mSpinRevolutions->clear();
        mSpinSkew->clear();

        mSelectedType = MI_NONE;
    }
    else
    {
        // Only allowed to change these parameters for objects
        // that you have permissions on AND are not attachments.
        enabled = root_objectp->permModify() && !root_objectp->isPermanentEnforced();

        // Volume type
        const LLVolumeParams &volume_params = objectp->getVolume()->getParams();
        U8 path = volume_params.getPathParams().getCurveType();
        U8 profile_and_hole = volume_params.getProfileParams().getCurveType();
        U8 profile  = profile_and_hole & LL_PCODE_PROFILE_MASK;
        U8 hole     = profile_and_hole & LL_PCODE_HOLE_MASK;

        // Scale goes first so we can differentiate between a sphere and a torus,
        // which have the same profile and path types.

        // Scale
        scale_x = volume_params.getRatioX();
        scale_y = volume_params.getRatioY();

        BOOL linear_path = (path == LL_PCODE_PATH_LINE) || (path == LL_PCODE_PATH_FLEXIBLE);
        if ( linear_path && profile == LL_PCODE_PROFILE_CIRCLE )
        {
            selected_item = MI_CYLINDER;
        }
        else if ( linear_path && profile == LL_PCODE_PROFILE_SQUARE )
        {
            selected_item = MI_BOX;
        }
        else if ( linear_path && profile == LL_PCODE_PROFILE_ISOTRI )
        {
            selected_item = MI_PRISM;
        }
        else if ( linear_path && profile == LL_PCODE_PROFILE_EQUALTRI )
        {
            selected_item = MI_PRISM;
        }
        else if ( linear_path && profile == LL_PCODE_PROFILE_RIGHTTRI )
        {
            selected_item = MI_PRISM;
        }
        else if (path == LL_PCODE_PATH_FLEXIBLE) // shouldn't happen
        {
            selected_item = MI_CYLINDER; // reasonable default
        }
        else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE && scale_y > 0.75f)
        {
            selected_item = MI_SPHERE;
        }
        else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE && scale_y <= 0.75f)
        {
            selected_item = MI_TORUS;
        }
        else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE_HALF)
        {
            selected_item = MI_SPHERE;
        }
        else if ( path == LL_PCODE_PATH_CIRCLE2 && profile == LL_PCODE_PROFILE_CIRCLE )
        {
            // Spirals aren't supported.  Make it into a sphere.  JC
            selected_item = MI_SPHERE;
        }
        else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_EQUALTRI )
        {
            selected_item = MI_RING;
        }
        else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_SQUARE && scale_y <= 0.75f)
        {
            selected_item = MI_TUBE;
        }
        else
        {
            LL_INFOS("FloaterTools") << "Unknown path " << (S32) path << " profile " << (S32) profile << " in getState" << LL_ENDL;
            selected_item = MI_BOX;
        }


        if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT))
        {
            selected_item = MI_SCULPT;
            //LLFirstUse::useSculptedPrim();
        }


        mComboBaseType  ->setCurrentByIndex( selected_item );
        mSelectedType = selected_item;

        // Grab S path
        F32 begin_s = volume_params.getBeginS();
        F32 end_s   = volume_params.getEndS();

        // Compute cut and advanced cut from S and T
        F32 begin_t = volume_params.getBeginT();
        F32 end_t   = volume_params.getEndT();

        // Hollowness
        F32 hollow = 100.f * volume_params.getHollow();
        mSpinHollow->set( hollow );
        calcp->setVar(LLCalc::HOLLOW, hollow);
        // All hollow objects allow a shape to be selected.
        if (hollow > 0.f)
        {
            switch (hole)
            {
            case LL_PCODE_HOLE_CIRCLE:
                selected_hole = MI_HOLE_CIRCLE;
                break;
            case LL_PCODE_HOLE_SQUARE:
                selected_hole = MI_HOLE_SQUARE;
                break;
            case LL_PCODE_HOLE_TRIANGLE:
                selected_hole = MI_HOLE_TRIANGLE;
                break;
            case LL_PCODE_HOLE_SAME:
            default:
                selected_hole = MI_HOLE_SAME;
                break;
            }
            mComboHoleType->setCurrentByIndex( selected_hole );
            hole_enabled = enabled;
        }
        else
        {
            mComboHoleType->setCurrentByIndex( MI_HOLE_SAME );
            hole_enabled = FALSE;
        }

        // Cut interpretation varies based on base object type
        F32 cut_begin, cut_end, adv_cut_begin, adv_cut_end;

        if ( selected_item == MI_SPHERE || selected_item == MI_TORUS ||
             selected_item == MI_TUBE   || selected_item == MI_RING )
        {
            cut_begin       = begin_t;
            cut_end         = end_t;
            adv_cut_begin   = begin_s;
            adv_cut_end     = end_s;
        }
        else
        {
            cut_begin       = begin_s;
            cut_end         = end_s;
            adv_cut_begin   = begin_t;
            adv_cut_end     = end_t;
        }

        mSpinCutBegin   ->set( cut_begin );
        mSpinCutEnd     ->set( cut_end );
        mCtrlPathBegin  ->set( adv_cut_begin );
        mCtrlPathEnd    ->set( adv_cut_end );
        calcp->setVar(LLCalc::CUT_BEGIN, cut_begin);
        calcp->setVar(LLCalc::CUT_END, cut_end);
        calcp->setVar(LLCalc::PATH_BEGIN, adv_cut_begin);
        calcp->setVar(LLCalc::PATH_END, adv_cut_end);

        // Twist
        F32 twist       = volume_params.getTwist();
        F32 twist_begin = volume_params.getTwistBegin();
        // Check the path type for conversion.
        if (path == LL_PCODE_PATH_LINE || path == LL_PCODE_PATH_FLEXIBLE)
        {
            twist       *= OBJECT_TWIST_LINEAR_MAX;
            twist_begin *= OBJECT_TWIST_LINEAR_MAX;
        }
        else
        {
            twist       *= OBJECT_TWIST_MAX;
            twist_begin *= OBJECT_TWIST_MAX;
        }

        mSpinTwist      ->set( twist );
        mSpinTwistBegin ->set( twist_begin );
        calcp->setVar(LLCalc::TWIST_END, twist);
        calcp->setVar(LLCalc::TWIST_BEGIN, twist_begin);

        // Shear
        F32 shear_x = volume_params.getShearX();
        F32 shear_y = volume_params.getShearY();
        mSpinShearX->set( shear_x );
        mSpinShearY->set( shear_y );
        calcp->setVar(LLCalc::X_SHEAR, shear_x);
        calcp->setVar(LLCalc::Y_SHEAR, shear_y);

        // Taper
        F32 taper_x = volume_params.getTaperX();
        F32 taper_y = volume_params.getTaperY();
        mSpinTaperX->set( taper_x );
        mSpinTaperY->set( taper_y );
        calcp->setVar(LLCalc::X_TAPER, taper_x);
        calcp->setVar(LLCalc::Y_TAPER, taper_y);

        // Radius offset.
        F32 radius_offset = volume_params.getRadiusOffset();
        // Limit radius offset, based on taper and hole size y.
        F32 radius_mag = fabs(radius_offset);
        F32 hole_y_mag = fabs(scale_y);
        F32 taper_y_mag  = fabs(taper_y);
        // Check to see if the taper effects us.
        if ( (radius_offset > 0.f && taper_y < 0.f) ||
             (radius_offset < 0.f && taper_y > 0.f) )
        {
            // The taper does not help increase the radius offset range.
            taper_y_mag = 0.f;
        }
        F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag);
        // Enforce the maximum magnitude.
        if (radius_mag > max_radius_mag)
        {
            // Check radius offset sign.
            if (radius_offset < 0.f)
            {
                radius_offset = -max_radius_mag;
            }
            else
            {
                radius_offset = max_radius_mag;
            }
        }
        mSpinRadiusOffset->set( radius_offset);
        calcp->setVar(LLCalc::RADIUS_OFFSET, radius_offset);

        // Revolutions
        F32 revolutions = volume_params.getRevolutions();
        mSpinRevolutions->set( revolutions );
        calcp->setVar(LLCalc::REVOLUTIONS, revolutions);

        // Skew
        F32 skew    = volume_params.getSkew();
        // Limit skew, based on revolutions hole size x.
        F32 skew_mag= fabs(skew);
        F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f);
        // Discontinuity; A revolution of 1 allows skews below 0.5.
        if ( fabs(revolutions - 1.0f) < 0.001)
            min_skew_mag = 0.0f;

        // Clip skew.
        if (skew_mag < min_skew_mag)
        {
            // Check skew sign.
            if (skew < 0.0f)
            {
                skew = -min_skew_mag;
            }
            else
            {
                skew = min_skew_mag;
            }
        }
        mSpinSkew->set( skew );
        calcp->setVar(LLCalc::SKEW, skew);
    }

    // Compute control visibility, label names, and twist range.
    // Start with defaults.
    BOOL cut_visible                = TRUE;
    BOOL hollow_visible             = TRUE;
    BOOL top_size_x_visible         = TRUE;
    BOOL top_size_y_visible         = TRUE;
    BOOL top_shear_x_visible        = TRUE;
    BOOL top_shear_y_visible        = TRUE;
    BOOL twist_visible              = TRUE;
    BOOL advanced_cut_visible       = FALSE;
    BOOL taper_visible              = FALSE;
    BOOL skew_visible               = FALSE;
    BOOL radius_offset_visible      = FALSE;
    BOOL revolutions_visible        = FALSE;
    BOOL sculpt_texture_visible     = FALSE;
    F32  twist_min                  = OBJECT_TWIST_LINEAR_MIN;
    F32  twist_max                  = OBJECT_TWIST_LINEAR_MAX;
    F32  twist_inc                  = OBJECT_TWIST_LINEAR_INC;

    BOOL advanced_is_dimple = FALSE;
    BOOL advanced_is_slice = FALSE;
    BOOL size_is_hole = FALSE;

    // Tune based on overall volume type
    switch (selected_item)
    {
    case MI_SPHERE:
        top_size_x_visible      = FALSE;
        top_size_y_visible      = FALSE;
        top_shear_x_visible     = FALSE;
        top_shear_y_visible     = FALSE;
        //twist_visible         = FALSE;
        advanced_cut_visible    = TRUE;
        advanced_is_dimple      = TRUE;
        twist_min               = OBJECT_TWIST_MIN;
        twist_max               = OBJECT_TWIST_MAX;
        twist_inc               = OBJECT_TWIST_INC;
        break;

    case MI_TORUS:
    case MI_TUBE:
    case MI_RING:
        //top_size_x_visible        = FALSE;
        //top_size_y_visible        = FALSE;
        size_is_hole            = TRUE;
        skew_visible            = TRUE;
        advanced_cut_visible    = TRUE;
        taper_visible           = TRUE;
        radius_offset_visible   = TRUE;
        revolutions_visible     = TRUE;
        twist_min               = OBJECT_TWIST_MIN;
        twist_max               = OBJECT_TWIST_MAX;
        twist_inc               = OBJECT_TWIST_INC;

        break;

    case MI_SCULPT:
        cut_visible             = FALSE;
        hollow_visible          = FALSE;
        twist_visible           = FALSE;
        top_size_x_visible      = FALSE;
        top_size_y_visible      = FALSE;
        top_shear_x_visible     = FALSE;
        top_shear_y_visible     = FALSE;
        skew_visible            = FALSE;
        advanced_cut_visible    = FALSE;
        taper_visible           = FALSE;
        radius_offset_visible   = FALSE;
        revolutions_visible     = FALSE;
        sculpt_texture_visible  = TRUE;

        break;

    case MI_BOX:
        advanced_cut_visible    = TRUE;
        advanced_is_slice       = TRUE;
        break;

    case MI_CYLINDER:
        advanced_cut_visible    = TRUE;
        advanced_is_slice       = TRUE;
        break;

    case MI_PRISM:
        advanced_cut_visible    = TRUE;
        advanced_is_slice       = TRUE;
        break;

    default:
        break;
    }

    // Check if we need to change top size/hole size params.
    switch (selected_item)
    {
    case MI_SPHERE:
    case MI_TORUS:
    case MI_TUBE:
    case MI_RING:
        mSpinScaleX->set( scale_x );
        mSpinScaleY->set( scale_y );
        calcp->setVar(LLCalc::X_HOLE, scale_x);
        calcp->setVar(LLCalc::Y_HOLE, scale_y);
        mSpinScaleX->setMinValue(OBJECT_MIN_HOLE_SIZE);
        mSpinScaleX->setMaxValue(OBJECT_MAX_HOLE_SIZE_X);
        mSpinScaleY->setMinValue(OBJECT_MIN_HOLE_SIZE);
        mSpinScaleY->setMaxValue(OBJECT_MAX_HOLE_SIZE_Y);
        break;
    default:
        if (editable && single_volume)
        {
            mSpinScaleX->set( 1.f - scale_x );
            mSpinScaleY->set( 1.f - scale_y );
            mSpinScaleX->setMinValue(-1.f);
            mSpinScaleX->setMaxValue(1.f);
            mSpinScaleY->setMinValue(-1.f);
            mSpinScaleY->setMaxValue(1.f);

            // Torus' Hole Size is Box/Cyl/Prism's Taper
            calcp->setVar(LLCalc::X_TAPER, 1.f - scale_x);
            calcp->setVar(LLCalc::Y_TAPER, 1.f - scale_y);

            // Box/Cyl/Prism have no hole size
            calcp->setVar(LLCalc::X_HOLE, 0.f);
            calcp->setVar(LLCalc::Y_HOLE, 0.f);
        }
        break;
    }

    // Check if we need to limit the hollow based on the hole type.
    if (  selected_hole == MI_HOLE_SQUARE &&
          ( selected_item == MI_CYLINDER || selected_item == MI_TORUS ||
            selected_item == MI_PRISM    || selected_item == MI_RING  ||
            selected_item == MI_SPHERE ) )
    {
        mSpinHollow->setMinValue(0.f);
        mSpinHollow->setMaxValue(70.f);
    }
    else
    {
        mSpinHollow->setMinValue(0.f);
        mSpinHollow->setMaxValue(95.f);
    }

    // Update field enablement
    mComboBaseType  ->setEnabled( enabled );
    mMenuClipboardParams->setEnabled(enabled);

    mLabelCut       ->setEnabled( enabled );
    mSpinCutBegin   ->setEnabled( enabled );
    mSpinCutEnd     ->setEnabled( enabled );

    mLabelHollow    ->setEnabled( enabled );
    mSpinHollow     ->setEnabled( enabled );
    mLabelHoleType  ->setEnabled( hole_enabled );
    mComboHoleType  ->setEnabled( hole_enabled );

    mLabelTwist     ->setEnabled( enabled );
    mSpinTwist      ->setEnabled( enabled );
    mSpinTwistBegin ->setEnabled( enabled );

    mLabelSkew      ->setEnabled( enabled );
    mSpinSkew       ->setEnabled( enabled );

    getChildView("scale_hole")->setVisible( FALSE);
    getChildView("scale_taper")->setVisible( FALSE);
    if (top_size_x_visible || top_size_y_visible)
    {
        if (size_is_hole)
        {
            getChildView("scale_hole")->setVisible( TRUE);
            getChildView("scale_hole")->setEnabled(enabled);
        }
        else
        {
            getChildView("scale_taper")->setVisible( TRUE);
            getChildView("scale_taper")->setEnabled(enabled);
        }
    }

    mSpinScaleX     ->setEnabled( enabled );
    mSpinScaleY     ->setEnabled( enabled );

    mLabelShear     ->setEnabled( enabled );
    mSpinShearX     ->setEnabled( enabled );
    mSpinShearY     ->setEnabled( enabled );

    getChildView("advanced_cut")->setVisible( FALSE);
    getChildView("advanced_dimple")->setVisible( FALSE);
    getChildView("advanced_slice")->setVisible( FALSE);

    if (advanced_cut_visible)
    {
        if (advanced_is_dimple)
        {
            getChildView("advanced_dimple")->setVisible( TRUE);
            getChildView("advanced_dimple")->setEnabled(enabled);
        }

        else if (advanced_is_slice)
        {
            getChildView("advanced_slice")->setVisible( TRUE);
            getChildView("advanced_slice")->setEnabled(enabled);
        }
        else
        {
            getChildView("advanced_cut")->setVisible( TRUE);
            getChildView("advanced_cut")->setEnabled(enabled);
        }
    }

    mCtrlPathBegin  ->setEnabled( enabled );
    mCtrlPathEnd    ->setEnabled( enabled );

    mLabelTaper     ->setEnabled( enabled );
    mSpinTaperX     ->setEnabled( enabled );
    mSpinTaperY     ->setEnabled( enabled );

    mLabelRadiusOffset->setEnabled( enabled );
    mSpinRadiusOffset ->setEnabled( enabled );

    mLabelRevolutions->setEnabled( enabled );
    mSpinRevolutions ->setEnabled( enabled );

    // Update field visibility
    mLabelCut       ->setVisible( cut_visible );
    mSpinCutBegin   ->setVisible( cut_visible );
    mSpinCutEnd     ->setVisible( cut_visible );

    mLabelHollow    ->setVisible( hollow_visible );
    mSpinHollow     ->setVisible( hollow_visible );
    mLabelHoleType  ->setVisible( hollow_visible );
    mComboHoleType  ->setVisible( hollow_visible );

    mLabelTwist     ->setVisible( twist_visible );
    mSpinTwist      ->setVisible( twist_visible );
    mSpinTwistBegin ->setVisible( twist_visible );
    mSpinTwist      ->setMinValue(  twist_min );
    mSpinTwist      ->setMaxValue(  twist_max );
    mSpinTwist      ->setIncrement( twist_inc );
    mSpinTwistBegin ->setMinValue(  twist_min );
    mSpinTwistBegin ->setMaxValue(  twist_max );
    mSpinTwistBegin ->setIncrement( twist_inc );

    mSpinScaleX     ->setVisible( top_size_x_visible );
    mSpinScaleY     ->setVisible( top_size_y_visible );

    mLabelSkew      ->setVisible( skew_visible );
    mSpinSkew       ->setVisible( skew_visible );

    mLabelShear     ->setVisible( top_shear_x_visible || top_shear_y_visible );
    mSpinShearX     ->setVisible( top_shear_x_visible );
    mSpinShearY     ->setVisible( top_shear_y_visible );

    mCtrlPathBegin  ->setVisible( advanced_cut_visible );
    mCtrlPathEnd    ->setVisible( advanced_cut_visible );

    mLabelTaper     ->setVisible( taper_visible );
    mSpinTaperX     ->setVisible( taper_visible );
    mSpinTaperY     ->setVisible( taper_visible );

    mLabelRadiusOffset->setVisible( radius_offset_visible );
    mSpinRadiusOffset ->setVisible( radius_offset_visible );

    mLabelRevolutions->setVisible( revolutions_visible );
    mSpinRevolutions ->setVisible( revolutions_visible );

    mCtrlSculptTexture->setVisible(sculpt_texture_visible);
    mLabelSculptType->setVisible(sculpt_texture_visible);
    mCtrlSculptType->setVisible(sculpt_texture_visible);


    // sculpt texture
    if (selected_item == MI_SCULPT)
    {


        LLUUID id;
        LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT);


        if (sculpt_params) // if we have a legal sculpt param block for this object:
        {
            if (mObject != objectp)  // we've just selected a new object, so save for undo
            {
                mSculptTextureRevert = sculpt_params->getSculptTexture();
                mSculptTypeRevert    = sculpt_params->getSculptType();
            }

            U8 sculpt_type = sculpt_params->getSculptType();
            U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK;
            BOOL sculpt_invert = sculpt_type & LL_SCULPT_FLAG_INVERT;
            BOOL sculpt_mirror = sculpt_type & LL_SCULPT_FLAG_MIRROR;
            isMesh = (sculpt_stitching == LL_SCULPT_TYPE_MESH);

            LLTextureCtrl*  mTextureCtrl = getChild<LLTextureCtrl>("sculpt texture control");
            if(mTextureCtrl)
            {
                mTextureCtrl->setTentative(FALSE);
                mTextureCtrl->setEnabled(editable && !isMesh);
                if (editable)
                    mTextureCtrl->setImageAssetID(sculpt_params->getSculptTexture());
                else
                    mTextureCtrl->setImageAssetID(LLUUID::null);
            }

            mComboBaseType->setEnabled(!isMesh);
            mMenuClipboardParams->setEnabled(!isMesh);

            if (mCtrlSculptType)
            {
                if (sculpt_stitching == LL_SCULPT_TYPE_NONE)
                {
                    // since 'None' is no longer an option in the combo box
                    // use 'Plane' as an equivalent sculpt type
                    mCtrlSculptType->setSelectedByValue(LLSD(LL_SCULPT_TYPE_PLANE), true);
                }
                else
                {
                    mCtrlSculptType->setSelectedByValue(LLSD(sculpt_stitching), true);
                }
                mCtrlSculptType->setEnabled(editable && !isMesh);
            }

            if (mCtrlSculptMirror)
            {
                mCtrlSculptMirror->set(sculpt_mirror);
                mCtrlSculptMirror->setEnabled(editable && !isMesh);
            }

            if (mCtrlSculptInvert)
            {
                mCtrlSculptInvert->set(sculpt_invert);
                mCtrlSculptInvert->setEnabled(editable);
            }

            if (mLabelSculptType)
            {
                mLabelSculptType->setEnabled(TRUE);
            }

        }
    }
    else
    {
        mSculptTextureRevert = LLUUID::null;
    }

    mCtrlSculptMirror->setVisible(sculpt_texture_visible && !isMesh);
    mCtrlSculptInvert->setVisible(sculpt_texture_visible && !isMesh);

    //----------------------------------------------------------------------------

    mObject = objectp;
    mRootObject = root_objectp;
}

// static
bool LLPanelObject::precommitValidate( const LLSD& data )
{
    // TODO: Richard will fill this in later.
    return TRUE; // FALSE means that validation failed and new value should not be commited.
}

void LLPanelObject::sendIsPhysical()
{
    BOOL value = mCheckPhysics->get();
    if( mIsPhysical != value )
    {
        LLSelectMgr::getInstance()->selectionUpdatePhysics(value);
        mIsPhysical = value;

        LL_INFOS("FloaterTools") << "update physics sent" << LL_ENDL;
    }
    else
    {
        LL_INFOS("FloaterTools") << "update physics not changed" << LL_ENDL;
    }
}

void LLPanelObject::sendIsTemporary()
{
    BOOL value = mCheckTemporary->get();
    if( mIsTemporary != value )
    {
        LLSelectMgr::getInstance()->selectionUpdateTemporary(value);
        mIsTemporary = value;

        LL_INFOS("FloaterTools") << "update temporary sent" << LL_ENDL;
    }
    else
    {
        LL_INFOS("FloaterTools") << "update temporary not changed" << LL_ENDL;
    }
}


void LLPanelObject::sendIsPhantom()
{
    BOOL value = mCheckPhantom->get();
    if( mIsPhantom != value )
    {
        LLSelectMgr::getInstance()->selectionUpdatePhantom(value);
        mIsPhantom = value;

        LL_INFOS("FloaterTools") << "update phantom sent" << LL_ENDL;
    }
    else
    {
        LL_INFOS("FloaterTools") << "update phantom not changed" << LL_ENDL;
    }
}

// static
void LLPanelObject::onCommitParametric( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;

    if (self->mObject.isNull())
    {
        return;
    }

    if (self->mObject->getPCode() != LL_PCODE_VOLUME)
    {
        // Don't allow modification of non-volume objects.
        return;
    }

    LLVolume *volume = self->mObject->getVolume();
    if (!volume)
    {
        return;
    }

    LLVolumeParams volume_params;
    self->getVolumeParams(volume_params);



    // set sculpting
    S32 selected_type = self->mComboBaseType->getCurrentIndex();

    if (selected_type == MI_SCULPT)
    {
        self->mObject->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, TRUE, TRUE);
        LLSculptParams *sculpt_params = (LLSculptParams *)self->mObject->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
        if (sculpt_params)
            volume_params.setSculptID(sculpt_params->getSculptTexture(), sculpt_params->getSculptType());
    }
    else
    {
        LLSculptParams *sculpt_params = (LLSculptParams *)self->mObject->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
        if (sculpt_params)
            self->mObject->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, FALSE, TRUE);
    }

    // Update the volume, if necessary.
    self->mObject->updateVolume(volume_params);


    // This was added to make sure thate when changes are made, the UI
    // adjusts to present valid options.
    // *FIX: only some changes, ie, hollow or primitive type changes,
    // require a refresh.
    self->refresh();

}

void LLPanelObject::getVolumeParams(LLVolumeParams& volume_params)
{
    // Figure out what type of volume to make
    S32 was_selected_type = mSelectedType;
    S32 selected_type = mComboBaseType->getCurrentIndex();
    U8 profile;
    U8 path;
    switch ( selected_type )
    {
    case MI_CYLINDER:
        profile = LL_PCODE_PROFILE_CIRCLE;
        path = LL_PCODE_PATH_LINE;
        break;

    case MI_BOX:
        profile = LL_PCODE_PROFILE_SQUARE;
        path = LL_PCODE_PATH_LINE;
        break;

    case MI_PRISM:
        profile = LL_PCODE_PROFILE_EQUALTRI;
        path = LL_PCODE_PATH_LINE;
        break;

    case MI_SPHERE:
        profile = LL_PCODE_PROFILE_CIRCLE_HALF;
        path = LL_PCODE_PATH_CIRCLE;
        break;

    case MI_TORUS:
        profile = LL_PCODE_PROFILE_CIRCLE;
        path = LL_PCODE_PATH_CIRCLE;
        break;

    case MI_TUBE:
        profile = LL_PCODE_PROFILE_SQUARE;
        path = LL_PCODE_PATH_CIRCLE;
        break;

    case MI_RING:
        profile = LL_PCODE_PROFILE_EQUALTRI;
        path = LL_PCODE_PATH_CIRCLE;
        break;

    case MI_SCULPT:
        profile = LL_PCODE_PROFILE_CIRCLE;
        path = LL_PCODE_PATH_CIRCLE;
        break;

    default:
        LL_WARNS("FloaterTools") << "Unknown base type " << selected_type
            << " in getVolumeParams()" << LL_ENDL;
        // assume a box
        selected_type = MI_BOX;
        profile = LL_PCODE_PROFILE_SQUARE;
        path = LL_PCODE_PATH_LINE;
        break;
    }


    if (path == LL_PCODE_PATH_LINE)
    {
        LLVOVolume *volobjp = (LLVOVolume *)(LLViewerObject*)(mObject);
        if (volobjp->isFlexible())
        {
            path = LL_PCODE_PATH_FLEXIBLE;
        }
    }

    S32 selected_hole = mComboHoleType->getCurrentIndex();
    U8 hole;
    switch (selected_hole)
    {
    case MI_HOLE_CIRCLE:
        hole = LL_PCODE_HOLE_CIRCLE;
        break;
    case MI_HOLE_SQUARE:
        hole = LL_PCODE_HOLE_SQUARE;
        break;
    case MI_HOLE_TRIANGLE:
        hole = LL_PCODE_HOLE_TRIANGLE;
        break;
    case MI_HOLE_SAME:
    default:
        hole = LL_PCODE_HOLE_SAME;
        break;
    }

    volume_params.setType(profile | hole, path);
    mSelectedType = selected_type;

    // Compute cut start/end
    F32 cut_begin   = mSpinCutBegin->get();
    F32 cut_end     = mSpinCutEnd->get();

    // Make sure at least OBJECT_CUT_INC of the object survives
    if (cut_begin > cut_end - OBJECT_MIN_CUT_INC)
    {
        cut_begin = cut_end - OBJECT_MIN_CUT_INC;
        mSpinCutBegin->set(cut_begin);
    }

    F32 adv_cut_begin   = mCtrlPathBegin->get();
    F32 adv_cut_end     = mCtrlPathEnd->get();

    // Make sure at least OBJECT_CUT_INC of the object survives
    if (adv_cut_begin > adv_cut_end - OBJECT_MIN_CUT_INC)
    {
        adv_cut_begin = adv_cut_end - OBJECT_MIN_CUT_INC;
        mCtrlPathBegin->set(adv_cut_begin);
    }

    F32 begin_s, end_s;
    F32 begin_t, end_t;

    if (selected_type == MI_SPHERE || selected_type == MI_TORUS ||
        selected_type == MI_TUBE   || selected_type == MI_RING)
    {
        begin_s = adv_cut_begin;
        end_s   = adv_cut_end;

        begin_t = cut_begin;
        end_t   = cut_end;
    }
    else
    {
        begin_s = cut_begin;
        end_s   = cut_end;

        begin_t = adv_cut_begin;
        end_t   = adv_cut_end;
    }

    volume_params.setBeginAndEndS(begin_s, end_s);
    volume_params.setBeginAndEndT(begin_t, end_t);

    // Hollowness
    F32 hollow = mSpinHollow->get() / 100.f;

    if (  selected_hole == MI_HOLE_SQUARE &&
        ( selected_type == MI_CYLINDER || selected_type == MI_TORUS ||
          selected_type == MI_PRISM    || selected_type == MI_RING  ||
          selected_type == MI_SPHERE ) )
    {
        if (hollow > 0.7f) hollow = 0.7f;
    }

    volume_params.setHollow( hollow );

    // Twist Begin,End
    F32 twist_begin = mSpinTwistBegin->get();
    F32 twist       = mSpinTwist->get();
    // Check the path type for twist conversion.
    if (path == LL_PCODE_PATH_LINE || path == LL_PCODE_PATH_FLEXIBLE)
    {
        twist_begin /= OBJECT_TWIST_LINEAR_MAX;
        twist       /= OBJECT_TWIST_LINEAR_MAX;
    }
    else
    {
        twist_begin /= OBJECT_TWIST_MAX;
        twist       /= OBJECT_TWIST_MAX;
    }

    volume_params.setTwistBegin(twist_begin);
    volume_params.setTwist(twist);

    // Scale X,Y
    F32 scale_x = mSpinScaleX->get();
    F32 scale_y = mSpinScaleY->get();
    if ( was_selected_type == MI_BOX || was_selected_type == MI_CYLINDER || was_selected_type == MI_PRISM)
    {
        scale_x = 1.f - scale_x;
        scale_y = 1.f - scale_y;
    }

    // Skew
    F32 skew = mSpinSkew->get();

    // Taper X,Y
    F32 taper_x = mSpinTaperX->get();
    F32 taper_y = mSpinTaperY->get();

    // Radius offset
    F32 radius_offset = mSpinRadiusOffset->get();

    // Revolutions
    F32 revolutions   = mSpinRevolutions->get();

    if ( selected_type == MI_SPHERE )
    {
        // Snap values to valid sphere parameters.
        scale_x         = 1.0f;
        scale_y         = 1.0f;
        skew            = 0.0f;
        taper_x         = 0.0f;
        taper_y         = 0.0f;
        radius_offset   = 0.0f;
        revolutions     = 1.0f;
    }
    else if ( selected_type == MI_TORUS || selected_type == MI_TUBE ||
              selected_type == MI_RING )
    {
        scale_x = llclamp(
            scale_x,
            OBJECT_MIN_HOLE_SIZE,
            OBJECT_MAX_HOLE_SIZE_X);
        scale_y = llclamp(
            scale_y,
            OBJECT_MIN_HOLE_SIZE,
            OBJECT_MAX_HOLE_SIZE_Y);

        // Limit radius offset, based on taper and hole size y.
        F32 radius_mag = fabs(radius_offset);
        F32 hole_y_mag = fabs(scale_y);
        F32 taper_y_mag  = fabs(taper_y);
        // Check to see if the taper effects us.
        if ( (radius_offset > 0.f && taper_y < 0.f) ||
             (radius_offset < 0.f && taper_y > 0.f) )
        {
            // The taper does not help increase the radius offset range.
            taper_y_mag = 0.f;
        }
        F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag);
        // Enforce the maximum magnitude.
        if (radius_mag > max_radius_mag)
        {
            // Check radius offset sign.
            if (radius_offset < 0.f)
            {
                radius_offset = -max_radius_mag;
            }
            else
            {
                radius_offset = max_radius_mag;
            }
        }

        // Check the skew value against the revolutions.
        F32 skew_mag= fabs(skew);
        F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f);
        // Discontinuity; A revolution of 1 allows skews below 0.5.
        if ( fabs(revolutions - 1.0f) < 0.001)
            min_skew_mag = 0.0f;

        // Clip skew.
        if (skew_mag < min_skew_mag)
        {
            // Check skew sign.
            if (skew < 0.0f)
            {
                skew = -min_skew_mag;
            }
            else
            {
                skew = min_skew_mag;
            }
        }
    }

    volume_params.setRatio( scale_x, scale_y );
    volume_params.setSkew(skew);
    volume_params.setTaper( taper_x, taper_y );
    volume_params.setRadiusOffset(radius_offset);
    volume_params.setRevolutions(revolutions);

    // Shear X,Y
    F32 shear_x = mSpinShearX->get();
    F32 shear_y = mSpinShearY->get();
    volume_params.setShear( shear_x, shear_y );

    if (selected_type == MI_SCULPT)
    {
        volume_params.setSculptID(LLUUID::null, 0);
        volume_params.setBeginAndEndT   (0, 1);
        volume_params.setBeginAndEndS   (0, 1);
        volume_params.setHollow         (0);
        volume_params.setTwistBegin     (0);
        volume_params.setTwistEnd       (0);
        volume_params.setRatio          (1, 0.5);
        volume_params.setShear          (0, 0);
        volume_params.setTaper          (0, 0);
        volume_params.setRevolutions    (1);
        volume_params.setRadiusOffset   (0);
        volume_params.setSkew           (0);
    }

}

// BUG: Make work with multiple objects
void LLPanelObject::sendRotation(BOOL btn_down)
{
    if (mObject.isNull()) return;

    LLVector3 new_rot(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get());
    new_rot.mV[VX] = ll_round(new_rot.mV[VX], OBJECT_ROTATION_PRECISION);
    new_rot.mV[VY] = ll_round(new_rot.mV[VY], OBJECT_ROTATION_PRECISION);
    new_rot.mV[VZ] = ll_round(new_rot.mV[VZ], OBJECT_ROTATION_PRECISION);

    // Note: must compare before conversion to radians
    LLVector3 delta = new_rot - mCurEulerDegrees;

    if (delta.magVec() >= 0.0005f)
    {
        mCurEulerDegrees = new_rot;
        new_rot *= DEG_TO_RAD;

        LLQuaternion rotation;
        rotation.setQuat(new_rot.mV[VX], new_rot.mV[VY], new_rot.mV[VZ]);

        if (mRootObject != mObject)
        {
            rotation = rotation * ~mRootObject->getRotationRegion();
        }

        // To include avatars into movements and rotation
        // If false, all children are selected anyway - move avatar
        // If true, not all children are selected - save positions
        bool individual_selection = gSavedSettings.getBOOL("EditLinkedParts");
        std::vector<LLVector3>& child_positions = mObject->mUnselectedChildrenPositions ;
        std::vector<LLQuaternion> child_rotations;
        if (mObject->isRootEdit() && individual_selection)
        {
            mObject->saveUnselectedChildrenRotation(child_rotations) ;
            mObject->saveUnselectedChildrenPosition(child_positions) ;
        }

        mObject->setRotation(rotation);
        LLManip::rebuild(mObject) ;

        // for individually selected roots, we need to counterrotate all the children
        if (mObject->isRootEdit() && individual_selection)
        {
            mObject->resetChildrenRotationAndPosition(child_rotations, child_positions) ;
        }

        if(!btn_down)
        {
            child_positions.clear() ;
            LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_ROTATION | UPD_POSITION);
        }
    }
}


// BUG: Make work with multiple objects
void LLPanelObject::sendScale(BOOL btn_down)
{
    if (mObject.isNull()) return;

    LLVector3 newscale(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get());

    LLVector3 delta = newscale - mObject->getScale();
    if (delta.magVec() >= 0.0005f || (mSizeChanged && !btn_down))
    {
        // scale changed by more than 1/2 millimeter
        mSizeChanged = btn_down;

        // check to see if we aren't scaling the textures
        // (in which case the tex coord's need to be recomputed)
        BOOL dont_stretch_textures = !LLManipScale::getStretchTextures();
        if (dont_stretch_textures)
        {
            LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_SCALE);
        }

        mObject->setScale(newscale, TRUE);

        if(!btn_down)
        {
            LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_SCALE | UPD_POSITION);
        }

        LLSelectMgr::getInstance()->adjustTexturesByScale(TRUE, !dont_stretch_textures);
//      LL_INFOS() << "scale sent" << LL_ENDL;
    }
    else
    {
//      LL_INFOS() << "scale not changed" << LL_ENDL;
    }
}


void LLPanelObject::sendPosition(BOOL btn_down)
{
    if (mObject.isNull()) return;

    LLVector3 newpos(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get());
    LLViewerRegion* regionp = mObject->getRegion();

    if (!regionp) return;

    if (!mObject->isAttachment())
    {
        // Clamp the Z height
        const F32 height = newpos.mV[VZ];
        const F32 min_height = LLWorld::getInstance()->getMinAllowedZ(mObject, mObject->getPositionGlobal());
        const F32 max_height = LLWorld::getInstance()->getRegionMaxHeight();

        if ( height < min_height)
        {
            newpos.mV[VZ] = min_height;
            mCtrlPosZ->set( min_height );
        }
        else if ( height > max_height )
        {
            newpos.mV[VZ] = max_height;
            mCtrlPosZ->set( max_height );
        }

        // Grass is always drawn on the ground, so clamp its position to the ground
        if (mObject->getPCode() == LL_PCODE_LEGACY_GRASS)
        {
            mCtrlPosZ->set(LLWorld::getInstance()->resolveLandHeightAgent(newpos) + 1.f);
        }
    }
    else
    {
        if (newpos.length() > MAX_ATTACHMENT_DIST)
        {
            newpos.clampLength(MAX_ATTACHMENT_DIST);
            mCtrlPosX->set(newpos.mV[VX]);
            mCtrlPosY->set(newpos.mV[VY]);
            mCtrlPosZ->set(newpos.mV[VZ]);
        }
    }

    // Make sure new position is in a valid region, so the object
    // won't get dumped by the simulator.
    LLVector3d new_pos_global = regionp->getPosGlobalFromRegion(newpos);
    bool is_valid_pos = true;
    if (mObject->isAttachment())
    {
        LLVector3 delta_pos = mObject->getPositionEdit() - newpos;
        LLVector3d attachment_pos = regionp->getPosGlobalFromRegion(mObject->getPositionRegion() + delta_pos);
        is_valid_pos = LLWorld::getInstance()->positionRegionValidGlobal(attachment_pos);
    }
    else
    {
        is_valid_pos = LLWorld::getInstance()->positionRegionValidGlobal(new_pos_global);
    }

    if (is_valid_pos)
    {
        // send only if the position is changed, that is, the delta vector is not zero
        LLVector3d old_pos_global = mObject->getPositionGlobal();
        LLVector3d delta = new_pos_global - old_pos_global;
        // moved more than 1/2 millimeter
        if (delta.magVec() >= 0.0005f)
        {
            if (mRootObject != mObject)
            {
                newpos = newpos - mRootObject->getPositionRegion();
                newpos = newpos * ~mRootObject->getRotationRegion();
                mObject->setPositionParent(newpos);
            }
            else
            {
                mObject->setPositionEdit(newpos);
            }

            LLManip::rebuild(mObject) ;

            // for individually selected roots, we need to counter-translate all unselected children
            if (mObject->isRootEdit())
            {
                // only offset by parent's translation
                mObject->resetChildrenPosition(LLVector3(-delta), TRUE, TRUE) ;
            }

            if(!btn_down)
            {
                LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_POSITION);
            }

            LLSelectMgr::getInstance()->updateSelectionCenter();
        }
    }
    else
    {
        // move failed, so we update the UI with the correct values
        LLVector3 vec = mRootObject->getPositionRegion();
        mCtrlPosX->set(vec.mV[VX]);
        mCtrlPosY->set(vec.mV[VY]);
        mCtrlPosZ->set(vec.mV[VZ]);
    }
}

void LLPanelObject::sendSculpt()
{
    if (mObject.isNull())
        return;

    LLSculptParams sculpt_params;
    LLUUID sculpt_id = LLUUID::null;

    if (mCtrlSculptTexture)
        sculpt_id = mCtrlSculptTexture->getImageAssetID();

    U8 sculpt_type = 0;

    if (mCtrlSculptType)
        sculpt_type |= mCtrlSculptType->getValue().asInteger();

    bool enabled = sculpt_type != LL_SCULPT_TYPE_MESH;

    if (mCtrlSculptMirror)
    {
        mCtrlSculptMirror->setEnabled(enabled ? TRUE : FALSE);
    }
    if (mCtrlSculptInvert)
    {
        mCtrlSculptInvert->setEnabled(enabled ? TRUE : FALSE);
    }

    if ((mCtrlSculptMirror) && (mCtrlSculptMirror->get()))
        sculpt_type |= LL_SCULPT_FLAG_MIRROR;

    if ((mCtrlSculptInvert) && (mCtrlSculptInvert->get()))
        sculpt_type |= LL_SCULPT_FLAG_INVERT;

    sculpt_params.setSculptTexture(sculpt_id, sculpt_type);
    mObject->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, TRUE);
}

void LLPanelObject::refresh()
{
    getState();
    if (mObject.notNull() && mObject->isDead())
    {
        mObject = NULL;
    }

    if (mRootObject.notNull() && mRootObject->isDead())
    {
        mRootObject = NULL;
    }

    F32 max_scale = get_default_max_prim_scale(LLPickInfo::isFlora(mObject));

    getChild<LLSpinCtrl>("Scale X")->setMaxValue(max_scale);
    getChild<LLSpinCtrl>("Scale Y")->setMaxValue(max_scale);
    getChild<LLSpinCtrl>("Scale Z")->setMaxValue(max_scale);
}


void LLPanelObject::draw()
{
    const LLColor4  white(  1.0f,   1.0f,   1.0f,   1);
    const LLColor4  red(    1.0f,   0.25f,  0.f,    1);
    const LLColor4  green(  0.f,    1.0f,   0.f,    1);
    const LLColor4  blue(   0.f,    0.5f,   1.0f,   1);

    // Tune the colors of the labels
    LLTool* tool = LLToolMgr::getInstance()->getCurrentTool();

    if (tool == LLToolCompTranslate::getInstance())
    {
        mCtrlPosX   ->setLabelColor(red);
        mCtrlPosY   ->setLabelColor(green);
        mCtrlPosZ   ->setLabelColor(blue);

        mCtrlScaleX ->setLabelColor(white);
        mCtrlScaleY ->setLabelColor(white);
        mCtrlScaleZ ->setLabelColor(white);

        mCtrlRotX   ->setLabelColor(white);
        mCtrlRotY   ->setLabelColor(white);
        mCtrlRotZ   ->setLabelColor(white);
    }
    else if ( tool == LLToolCompScale::getInstance() )
    {
        mCtrlPosX   ->setLabelColor(white);
        mCtrlPosY   ->setLabelColor(white);
        mCtrlPosZ   ->setLabelColor(white);

        mCtrlScaleX ->setLabelColor(red);
        mCtrlScaleY ->setLabelColor(green);
        mCtrlScaleZ ->setLabelColor(blue);

        mCtrlRotX   ->setLabelColor(white);
        mCtrlRotY   ->setLabelColor(white);
        mCtrlRotZ   ->setLabelColor(white);
    }
    else if ( tool == LLToolCompRotate::getInstance() )
    {
        mCtrlPosX   ->setLabelColor(white);
        mCtrlPosY   ->setLabelColor(white);
        mCtrlPosZ   ->setLabelColor(white);

        mCtrlScaleX ->setLabelColor(white);
        mCtrlScaleY ->setLabelColor(white);
        mCtrlScaleZ ->setLabelColor(white);

        mCtrlRotX   ->setLabelColor(red);
        mCtrlRotY   ->setLabelColor(green);
        mCtrlRotZ   ->setLabelColor(blue);
    }
    else
    {
        mCtrlPosX   ->setLabelColor(white);
        mCtrlPosY   ->setLabelColor(white);
        mCtrlPosZ   ->setLabelColor(white);

        mCtrlScaleX ->setLabelColor(white);
        mCtrlScaleY ->setLabelColor(white);
        mCtrlScaleZ ->setLabelColor(white);

        mCtrlRotX   ->setLabelColor(white);
        mCtrlRotY   ->setLabelColor(white);
        mCtrlRotZ   ->setLabelColor(white);
    }

    LLPanel::draw();
}

// virtual
void LLPanelObject::clearCtrls()
{
    LLPanel::clearCtrls();

    mCheckLock      ->set(FALSE);
    mCheckLock      ->setEnabled( FALSE );
    mCheckPhysics   ->set(FALSE);
    mCheckPhysics   ->setEnabled( FALSE );
    mCheckTemporary ->set(FALSE);
    mCheckTemporary ->setEnabled( FALSE );
    mCheckPhantom   ->set(FALSE);
    mCheckPhantom   ->setEnabled( FALSE );

    // Disable text labels
    mLabelPosition  ->setEnabled( FALSE );
    mLabelSize      ->setEnabled( FALSE );
    mLabelRotation  ->setEnabled( FALSE );
    mLabelCut       ->setEnabled( FALSE );
    mLabelHollow    ->setEnabled( FALSE );
    mLabelHoleType  ->setEnabled( FALSE );
    mLabelTwist     ->setEnabled( FALSE );
    mLabelSkew      ->setEnabled( FALSE );
    mLabelShear     ->setEnabled( FALSE );
    mLabelTaper     ->setEnabled( FALSE );
    mLabelRadiusOffset->setEnabled( FALSE );
    mLabelRevolutions->setEnabled( FALSE );

    getChildView("scale_hole")->setEnabled(FALSE);
    getChildView("scale_taper")->setEnabled(FALSE);
    getChildView("advanced_cut")->setEnabled(FALSE);
    getChildView("advanced_dimple")->setEnabled(FALSE);
    getChildView("advanced_slice")->setVisible( FALSE);
}

//
// Static functions
//

// static
void LLPanelObject::onCommitLock(LLUICtrl *ctrl, void *data)
{
    // Checkbox will have toggled itself
    LLPanelObject *self = (LLPanelObject *)data;

    if(self->mRootObject.isNull()) return;

    BOOL new_state = self->mCheckLock->get();

    LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, !new_state, PERM_MOVE | PERM_MODIFY);
}

// static
void LLPanelObject::onCommitPosition( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;
    BOOL btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ;
    self->sendPosition(btn_down);
}

// static
void LLPanelObject::onCommitScale( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;
    BOOL btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ;
    self->sendScale(btn_down);
}

// static
void LLPanelObject::onCommitRotation( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;
    BOOL btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ;
    self->sendRotation(btn_down);
}

// static
void LLPanelObject::onCommitPhysics( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;
    self->sendIsPhysical();
}

// static
void LLPanelObject::onCommitTemporary( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;
    self->sendIsTemporary();
}

// static
void LLPanelObject::onCommitPhantom( LLUICtrl* ctrl, void* userdata )
{
    LLPanelObject* self = (LLPanelObject*) userdata;
    self->sendIsPhantom();
}

void LLPanelObject::onSelectSculpt(const LLSD& data)
{
    LLTextureCtrl* mTextureCtrl = getChild<LLTextureCtrl>("sculpt texture control");

    if (mTextureCtrl)
    {
        mSculptTextureRevert = mTextureCtrl->getImageAssetID();
    }

    sendSculpt();
}


void LLPanelObject::onCommitSculpt( const LLSD& data )
{
    sendSculpt();
}

BOOL LLPanelObject::onDropSculpt(LLInventoryItem* item)
{
    LLTextureCtrl* mTextureCtrl = getChild<LLTextureCtrl>("sculpt texture control");

    if (mTextureCtrl)
    {
        LLUUID asset = item->getAssetUUID();

        mTextureCtrl->setImageAssetID(asset);
        mSculptTextureRevert = asset;
    }

    return TRUE;
}


void LLPanelObject::onCancelSculpt(const LLSD& data)
{
    LLTextureCtrl* mTextureCtrl = getChild<LLTextureCtrl>("sculpt texture control");
    if(!mTextureCtrl)
        return;

    if(mSculptTextureRevert == LLUUID::null)
    {
        mSculptTextureRevert = SCULPT_DEFAULT_TEXTURE;
    }
    mTextureCtrl->setImageAssetID(mSculptTextureRevert);

    sendSculpt();
}

// static
void LLPanelObject::onCommitSculptType(LLUICtrl *ctrl, void* userdata)
{
    LLPanelObject* self = (LLPanelObject*) userdata;

    self->sendSculpt();
}

void LLPanelObject::menuDoToSelected(const LLSD& userdata)
{
    std::string command = userdata.asString();

    // paste
    if (command == "psr_paste")
    {
        onPastePos();
        onPasteSize();
        onPasteRot();
    }
    else if (command == "pos_paste")
    {
        onPastePos();
    }
    else if (command == "size_paste")
    {
        onPasteSize();
    }
    else if (command == "rot_paste")
    {
        onPasteRot();
    }
    else if (command == "params_paste")
    {
        onPasteParams();
    }
    // copy
    else if (command == "psr_copy")
    {
        onCopyPos();
        onCopySize();
        onCopyRot();
    }
    else if (command == "pos_copy")
    {
        onCopyPos();
    }
    else if (command == "size_copy")
    {
        onCopySize();
    }
    else if (command == "rot_copy")
    {
        onCopyRot();
    }
    else if (command == "params_copy")
    {
        onCopyParams();
    }
}

bool LLPanelObject::menuEnableItem(const LLSD& userdata)
{
    std::string command = userdata.asString();

    // paste options
    if (command == "psr_paste")
    {
        S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount();
        BOOL single_volume = (LLSelectMgr::getInstance()->selectionAllPCode(LL_PCODE_VOLUME))
            && (selected_count == 1);

        if (!single_volume)
        {
            return false;
        }

        bool enable_move;
        bool enable_modify;

        LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify);

        return enable_move && enable_modify && mHasClipboardPos && mHasClipboardSize && mHasClipboardRot;
    }
    else if (command == "pos_paste")
    {
        // assumes that menu won't be active if there is no move permission
        return mHasClipboardPos;
    }
    else if (command == "size_paste")
    {
        return mHasClipboardSize;
    }
    else if (command == "rot_paste")
    {
        return mHasClipboardRot;
    }
    else if (command == "params_paste")
    {
        return mClipboardParams.isMap() && (mClipboardParams.size() != 0);
    }
    // copy options
    else if (command == "psr_copy")
    {
        S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount();
        BOOL single_volume = (LLSelectMgr::getInstance()->selectionAllPCode(LL_PCODE_VOLUME))
            && (selected_count == 1);

        if (!single_volume)
        {
            return false;
        }

        bool enable_move;
        bool enable_modify;

        LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify);

        // since we forbid seeing values we also should forbid copying them
        return enable_move && enable_modify;
    }
    return false;
}

void LLPanelObject::onCopyPos()
{
    mClipboardPos = LLVector3(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get());

    std::string stringVec = llformat("<%g, %g, %g>", mClipboardPos.mV[VX], mClipboardPos.mV[VY], mClipboardPos.mV[VZ]);
    LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec));

    mHasClipboardPos = true;
}

void LLPanelObject::onCopySize()
{
    mClipboardSize = LLVector3(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get());

    std::string stringVec = llformat("<%g, %g, %g>", mClipboardSize.mV[VX], mClipboardSize.mV[VY], mClipboardSize.mV[VZ]);
    LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec));

    mHasClipboardSize = true;
}

void LLPanelObject::onCopyRot()
{
    mClipboardRot = LLVector3(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get());

    std::string stringVec = llformat("<%g, %g, %g>", mClipboardRot.mV[VX], mClipboardRot.mV[VY], mClipboardRot.mV[VZ]);
    LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec));

    mHasClipboardRot = true;
}

void LLPanelObject::onPastePos()
{
    if (!mHasClipboardPos) return;
    if (mObject.isNull()) return;

    LLViewerRegion* regionp = mObject->getRegion();
    if (!regionp) return;


    // Clamp pos on non-attachments, just keep the prims within the region
    if (!mObject->isAttachment())
    {
        F32 max_width = regionp->getWidth(); // meters
        mClipboardPos.mV[VX] = llclamp(mClipboardPos.mV[VX], 0.f, max_width);
        mClipboardPos.mV[VY] = llclamp(mClipboardPos.mV[VY], 0.f, max_width);
        //height will get properly clamped by sendPosition
    }
    else
    {
        mClipboardPos.clampLength(MAX_ATTACHMENT_DIST);
    }

    mCtrlPosX->set( mClipboardPos.mV[VX] );
    mCtrlPosY->set( mClipboardPos.mV[VY] );
    mCtrlPosZ->set( mClipboardPos.mV[VZ] );

    sendPosition(FALSE);
}

void LLPanelObject::onPasteSize()
{
    if (!mHasClipboardSize) return;

    mClipboardSize.mV[VX] = llclamp(mClipboardSize.mV[VX], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE);
    mClipboardSize.mV[VY] = llclamp(mClipboardSize.mV[VY], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE);
    mClipboardSize.mV[VZ] = llclamp(mClipboardSize.mV[VZ], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE);

    mCtrlScaleX->set(mClipboardSize.mV[VX]);
    mCtrlScaleY->set(mClipboardSize.mV[VY]);
    mCtrlScaleZ->set(mClipboardSize.mV[VZ]);

    sendScale(FALSE);
}

void LLPanelObject::onPasteRot()
{
    if (!mHasClipboardRot) return;

    mCtrlRotX->set(mClipboardRot.mV[VX]);
    mCtrlRotY->set(mClipboardRot.mV[VY]);
    mCtrlRotZ->set(mClipboardRot.mV[VZ]);

    sendRotation(FALSE);
}

void LLPanelObject::onCopyParams()
{
    LLViewerObject* objectp = mObject;
    if (!objectp || objectp->isMesh())
    {
        return;
    }

    mClipboardParams.clear();

    // Parametrics
    LLVolumeParams params;
    getVolumeParams(params);
    mClipboardParams["volume_params"] = params.asLLSD();

    // Sculpted Prim
    if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT))
    {
        LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT);

        LLUUID texture_id = sculpt_params->getSculptTexture();
        if (get_can_copy_texture(texture_id))
        {
            LL_DEBUGS("FloaterTools") << "Recording texture" << LL_ENDL;
            mClipboardParams["sculpt"]["id"] = texture_id;
        }
        else
        {
            mClipboardParams["sculpt"]["id"] = SCULPT_DEFAULT_TEXTURE;
        }

        mClipboardParams["sculpt"]["type"] = sculpt_params->getSculptType();
    }
}

void LLPanelObject::onPasteParams()
{
    LLViewerObject* objectp = mObject;
    if (!objectp)
    {
        return;
    }

    // Sculpted Prim
    if (mClipboardParams.has("sculpt"))
    {
        LLSculptParams sculpt_params;
        LLUUID sculpt_id = mClipboardParams["sculpt"]["id"].asUUID();
        U8 sculpt_type = (U8)mClipboardParams["sculpt"]["type"].asInteger();
        sculpt_params.setSculptTexture(sculpt_id, sculpt_type);
        objectp->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, TRUE);
    }
    else
    {
        LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
        if (sculpt_params)
        {
            objectp->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, FALSE, TRUE);
        }
    }

    // volume params
    // make sure updateVolume() won't affect flexible
    if (mClipboardParams.has("volume_params"))
    {
        LLVolumeParams params;
        params.fromLLSD(mClipboardParams["volume_params"]);
        LLVOVolume *volobjp = (LLVOVolume *)objectp;
        if (volobjp->isFlexible())
        {
            if (params.getPathParams().getCurveType() == LL_PCODE_PATH_LINE)
            {
                params.getPathParams().setCurveType(LL_PCODE_PATH_FLEXIBLE);
            }
        }
        else if (params.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE)
        {
            params.getPathParams().setCurveType(LL_PCODE_PATH_LINE);
        }

        objectp->updateVolume(params);
    }
}