/**
 * @file llfloatercolorpicker.cpp
 * @brief Generic system color picker
 *
 * $LicenseInfo:firstyear=2004&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 "llfloatercolorpicker.h"

// Viewer project includes
#include "lltoolmgr.h"
#include "lltoolpipette.h"
#include "llviewercontrol.h"
#include "llworld.h"

// Linden library includes
#include "llfontgl.h"
#include "llsys.h"
#include "llgl.h"
#include "llrender.h"
#include "v3dmath.h"
#include "lldir.h"
#include "llui.h"
#include "lllineeditor.h"
#include "v4coloru.h"
#include "llbutton.h"
#include "lluictrlfactory.h"
#include "llgl.h"
#include "llpointer.h"
#include "llimage.h"
#include "llmousehandler.h"
#include "llglheaders.h"
#include "llcheckboxctrl.h"
#include "lltextbox.h"
#include "lluiconstants.h"
#include "llfocusmgr.h"
#include "lldraghandle.h"
#include "llwindow.h"

// System includes
#include <sstream>
#include <iomanip>

//////////////////////////////////////////////////////////////////////////////
//
// Class LLFloaterColorPicker
//
//////////////////////////////////////////////////////////////////////////////

LLFloaterColorPicker::LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show_apply_immediate )
    : LLFloater(LLSD()),
      mComponents           ( 3 ),
      mMouseDownInLumRegion ( false ),
      mMouseDownInHueRegion ( false ),
      mMouseDownInSwatch    ( false ),
      // *TODO: Specify this in XML
      mRGBViewerImageLeft   ( 140 ),
      mRGBViewerImageTop    ( 356 ),
      mRGBViewerImageWidth  ( 256 ),
      mRGBViewerImageHeight ( 256 ),
      mLumRegionLeft        ( mRGBViewerImageLeft + mRGBViewerImageWidth + 16 ),
      mLumRegionTop         ( mRGBViewerImageTop ),
      mLumRegionWidth       ( 16 ),
      mLumRegionHeight      ( mRGBViewerImageHeight ),
      mLumMarkerSize        ( 6 ),
      // *TODO: Specify this in XML
      mSwatchRegionLeft     ( 12 ),
      mSwatchRegionTop      ( 190 ),
      mSwatchRegionWidth    ( 116 ),
      mSwatchRegionHeight   ( 60 ),
      mSwatchView           ( NULL ),
      // *TODO: Specify this in XML
      numPaletteColumns     ( 16 ),
      numPaletteRows        ( 2 ),
      highlightEntry        ( -1 ),
      mPaletteRegionLeft    ( 11 ),
      mPaletteRegionTop     ( 100 - 8 ),
      mPaletteRegionWidth   ( mLumRegionLeft + mLumRegionWidth - 10 ),
      mPaletteRegionHeight  ( 40 ),
      mSwatch               ( swatch ),
      mActive               ( true ),
      mCanApplyImmediately  ( show_apply_immediate ),
      mContextConeOpacity   ( 0.f ),
      mContextConeInAlpha   (CONTEXT_CONE_IN_ALPHA),
      mContextConeOutAlpha   (CONTEXT_CONE_OUT_ALPHA),
      mContextConeFadeTime   (CONTEXT_CONE_FADE_TIME)
{
    buildFromFile ( "floater_color_picker.xml");

    // create user interface for this picker
    createUI ();
}

LLFloaterColorPicker::~LLFloaterColorPicker()
{
    // destroy the UI we created
    destroyUI ();
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::createUI ()
{
    // create RGB type area (not really RGB but it's got R,G & B in it.,..

    LLPointer<LLImageRaw> raw = new LLImageRaw ( mRGBViewerImageWidth, mRGBViewerImageHeight, mComponents );
    LLImageDataLock lock(raw);

    U8* bits = raw->getData();
    S32 linesize = mRGBViewerImageWidth * mComponents;
    for ( S32 y = 0; y < mRGBViewerImageHeight; ++y )
    {
        for ( S32 x = 0; x < linesize; x += mComponents )
        {
            F32 rVal, gVal, bVal;

            hslToRgb ( (F32)x / (F32) ( linesize - 1 ),
                       (F32)y / (F32) ( mRGBViewerImageHeight - 1 ),
                       0.5f,
                       rVal,
                       gVal,
                       bVal );

            * ( bits + x + y * linesize + 0 ) = ( U8 )( rVal * 255.0f );
            * ( bits + x + y * linesize + 1 ) = ( U8 )( gVal * 255.0f );
            * ( bits + x + y * linesize + 2 ) = ( U8 )( bVal * 255.0f );
        }
    }
    mRGBImage = LLViewerTextureManager::getLocalTexture( (LLImageRaw*)raw, false );
    gGL.getTexUnit(0)->bind(mRGBImage);
    mRGBImage->setAddressMode(LLTexUnit::TAM_CLAMP);

    // create palette
    for ( S32 each = 0; each < numPaletteColumns * numPaletteRows; ++each )
    {
        mPalette.push_back(new LLColor4(LLUIColorTable::instance().getColor(llformat("ColorPaletteEntry%02d", each + 1))));
    }
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::showUI ()
{
    openFloater(getKey());
    setVisible ( true );
    setFocus ( true );

    // HACK: if system color picker is required - close the SL one we made and use default system dialog
    if ( gSavedSettings.getBOOL ( "UseDefaultColorPicker" ) )
    {
        LLColorSwatchCtrl* swatch = getSwatch ();

        setVisible ( false );

        // code that will get switched in for default system color picker
        if ( swatch )
        {
            // Todo: this needs to be threaded for viewer not to timeout
            LLColor4 curCol = swatch->get ();
            send_agent_pause();
            bool commit = getWindow()->dialogColorPicker( &curCol [ 0 ], &curCol [ 1 ], &curCol [ 2 ] );
            send_agent_resume();

            if (commit)
            {
                setOrigRgb(curCol[0], curCol[1], curCol[2]);
                setCurRgb(curCol[0], curCol[1], curCol[2]);

                LLColorSwatchCtrl::onColorChanged(swatch, LLColorSwatchCtrl::COLOR_SELECT);
            }
            else
            {
                LLColorSwatchCtrl::onColorChanged(swatch, LLColorSwatchCtrl::COLOR_CANCEL);
            }
        }

        closeFloater();
    }
}

//////////////////////////////////////////////////////////////////////////////
// called after the dialog is rendered
bool LLFloaterColorPicker::postBuild()
{
    mCancelBtn = getChild<LLButton>( "cancel_btn" );
    mCancelBtn->setClickedCallback ( onClickCancel, this );

    mSelectBtn = getChild<LLButton>( "select_btn");
    mSelectBtn->setClickedCallback ( onClickSelect, this );
    mSelectBtn->setFocus ( true );

    mPipetteBtn = getChild<LLButton>("color_pipette" );

    mPipetteBtn->setImages(std::string("eye_button_inactive.tga"), std::string("eye_button_active.tga"));

    mPipetteBtn->setCommitCallback( boost::bind(&LLFloaterColorPicker::onClickPipette, this ));

    mApplyImmediateCheck = getChild<LLCheckBoxCtrl>("apply_immediate");
    mApplyImmediateCheck->set(gSavedSettings.getBOOL("ApplyColorImmediately"));
    mApplyImmediateCheck->setCommitCallback(onImmediateCheck, this);

    if (!mCanApplyImmediately)
    {
        mApplyImmediateCheck->setEnabled(false);
        mApplyImmediateCheck->set(false);
    }

    childSetCommitCallback("rspin", onTextCommit, (void*)this );
    childSetCommitCallback("gspin", onTextCommit, (void*)this );
    childSetCommitCallback("bspin", onTextCommit, (void*)this );
    childSetCommitCallback("hspin", onTextCommit, (void*)this );
    childSetCommitCallback("sspin", onTextCommit, (void*)this );
    childSetCommitCallback("lspin", onTextCommit, (void*)this );

    LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterColorPicker::onColorSelect, this, _1));

    return true;
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::initUI ( F32 rValIn, F32 gValIn, F32 bValIn )
{
    // under some circumstances, we get rogue values that can be calmed by clamping...
    rValIn = llclamp ( rValIn, 0.0f, 1.0f );
    gValIn = llclamp ( gValIn, 0.0f, 1.0f );
    bValIn = llclamp ( bValIn, 0.0f, 1.0f );

    // store initial value in case cancel or revert is selected
    setOrigRgb ( rValIn, gValIn, bValIn );

    // starting point for current value to
    setCurRgb ( rValIn, gValIn, bValIn );

    // unpdate text entry fields
    updateTextEntry ();
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::destroyUI ()
{
    // shut down pipette tool if active
    stopUsingPipette();

    // delete palette we created
    std::vector < LLColor4* >::iterator iter = mPalette.begin ();
    while ( iter != mPalette.end () )
    {
        delete ( *iter );
        ++iter;
    }

    if ( mSwatchView )
    {
        this->removeChild ( mSwatchView );
        mSwatchView->die();;
        mSwatchView = NULL;
    }
}


//////////////////////////////////////////////////////////////////////////////
//
F32 LLFloaterColorPicker::hueToRgb ( F32 val1In, F32 val2In, F32 valHUeIn )
{
    if ( valHUeIn < 0.0f ) valHUeIn += 1.0f;
    if ( valHUeIn > 1.0f ) valHUeIn -= 1.0f;
    if ( ( 6.0f * valHUeIn ) < 1.0f ) return ( val1In + ( val2In - val1In ) * 6.0f * valHUeIn );
    if ( ( 2.0f * valHUeIn ) < 1.0f ) return ( val2In );
    if ( ( 3.0f * valHUeIn ) < 2.0f ) return ( val1In + ( val2In - val1In ) * ( ( 2.0f / 3.0f ) - valHUeIn ) * 6.0f );
    return ( val1In );
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::hslToRgb ( F32 hValIn, F32 sValIn, F32 lValIn, F32& rValOut, F32& gValOut, F32& bValOut )
{
    if ( sValIn < 0.00001f )
    {
        rValOut = lValIn;
        gValOut = lValIn;
        bValOut = lValIn;
    }
    else
    {
        F32 interVal1;
        F32 interVal2;

        if ( lValIn < 0.5f )
            interVal2 = lValIn * ( 1.0f + sValIn );
        else
            interVal2 = ( lValIn + sValIn ) - ( sValIn * lValIn );

        interVal1 = 2.0f * lValIn - interVal2;

        rValOut = hueToRgb ( interVal1, interVal2, hValIn + ( 1.f / 3.f ) );
        gValOut = hueToRgb ( interVal1, interVal2, hValIn );
        bValOut = hueToRgb ( interVal1, interVal2, hValIn - ( 1.f / 3.f ) );
    }
}

//////////////////////////////////////////////////////////////////////////////
// mutator for original RGB value
void LLFloaterColorPicker::setOrigRgb ( F32 origRIn, F32 origGIn, F32 origBIn )
{
    origR = origRIn;
    origG = origGIn;
    origB = origBIn;
}

//////////////////////////////////////////////////////////////////////////////
// accessor for original RGB value
void LLFloaterColorPicker::getOrigRgb ( F32& origROut, F32& origGOut, F32& origBOut )
{
    origROut = origR;
    origGOut = origG;
    origBOut = origB;
}

//////////////////////////////////////////////////////////////////////////////
// mutator for current RGB value
void LLFloaterColorPicker::setCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn )
{
    // save current RGB
    curR = curRIn;
    curG = curGIn;
    curB = curBIn;

    // update corresponding HSL values and
    LLColor3(curRIn, curGIn, curBIn).calcHSL(&curH, &curS, &curL);

    // color changed so update text fields
    updateTextEntry();
}

//////////////////////////////////////////////////////////////////////////////
// accessor for current RGB value
void LLFloaterColorPicker::getCurRgb ( F32& curROut, F32& curGOut, F32& curBOut )
{
    curROut = curR;
    curGOut = curG;
    curBOut = curB;
}

//////////////////////////////////////////////////////////////////////////////
// mutator for current HSL value
void LLFloaterColorPicker::setCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn )
{
    // save current HSL
    curH = curHIn;
    curS = curSIn;
    curL = curLIn;

    // update corresponding RGB values and
    hslToRgb ( curH, curS, curL, curR, curG, curB );
}

//////////////////////////////////////////////////////////////////////////////
// accessor for current HSL value
void LLFloaterColorPicker::getCurHsl ( F32& curHOut, F32& curSOut, F32& curLOut )
{
    curHOut = curH;
    curSOut = curS;
    curLOut = curL;
}

//////////////////////////////////////////////////////////////////////////////
// called when 'cancel' clicked
void LLFloaterColorPicker::onClickCancel ( void* data )
{
    if (data)
    {
        LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data;

        if ( self )
        {
            self->cancelSelection();
            self->closeFloater();
        }
    }
}

//////////////////////////////////////////////////////////////////////////////
// called when 'select' clicked
void LLFloaterColorPicker::onClickSelect ( void* data )
{
    if (data)
    {
        LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data;

        if ( self )
        {
            // apply to selection
            LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_SELECT );
            self->closeFloater();
        }
    }
}

void LLFloaterColorPicker::onClickPipette( )
{
    bool pipette_active = mPipetteBtn->getToggleState();
    pipette_active = !pipette_active;
    if (pipette_active)
    {
        LLToolMgr::getInstance()->setTransientTool(LLToolPipette::getInstance());
    }
    else
    {
        LLToolMgr::getInstance()->clearTransientTool();
    }
}

//////////////////////////////////////////////////////////////////////////////
// called when 'text is committed' - i,e. focus moves from a text field
void LLFloaterColorPicker::onTextCommit ( LLUICtrl* ctrl, void* data )
{
    if ( data )
    {
        LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data;
        if ( self )
        {
            self->onTextEntryChanged ( ctrl );
        }
    }
}

void LLFloaterColorPicker::onImmediateCheck( LLUICtrl* ctrl, void* data)
{
    LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data;
    if (self)
    {
        gSavedSettings.setBOOL("ApplyColorImmediately", self->mApplyImmediateCheck->get());
        if (self->mApplyImmediateCheck->get() && self->isColorChanged())
        {
            LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
        }
    }
}

void LLFloaterColorPicker::onColorSelect( const LLTextureEntry& te )
{
    // Pipete
    selectCurRgb(te.getColor().mV[VRED], te.getColor().mV[VGREEN], te.getColor().mV[VBLUE]);
}

void LLFloaterColorPicker::onMouseCaptureLost()
{
    setMouseDownInHueRegion(false);
    setMouseDownInLumRegion(false);
}

F32 LLFloaterColorPicker::getSwatchTransparency()
{
    // If the floater is focused, don't apply its alpha to the color swatch (STORM-676).
    return getTransparencyType() == TT_ACTIVE ? 1.f : LLFloater::getCurrentTransparency();
}

bool LLFloaterColorPicker::isColorChanged()
{
    return ((getOrigR() != getCurR()) || (getOrigG() != getCurG()) || (getOrigB() != getCurB()));
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::draw()
{
    static LLCachedControl<F32> max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f);
    drawConeToOwner(mContextConeOpacity, max_opacity, mSwatch, mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha);

    mPipetteBtn->setToggleState(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance());
    mApplyImmediateCheck->setEnabled(mActive && mCanApplyImmediately);
    mSelectBtn->setEnabled(mActive);

    // base floater stuff
    LLFloater::draw ();

    const F32 alpha = getSwatchTransparency();

    // draw image for RGB area (not really RGB but you'll see what I mean...
    gl_draw_image ( mRGBViewerImageLeft, mRGBViewerImageTop - mRGBViewerImageHeight, mRGBImage, LLColor4::white % alpha);

    // update 'cursor' into RGB Section
    S32 xPos = ( S32 ) ( ( F32 )mRGBViewerImageWidth * getCurH () ) - 8;
    S32 yPos = ( S32 ) ( ( F32 )mRGBViewerImageHeight * getCurS () ) - 8;
    gl_line_2d ( mRGBViewerImageLeft + xPos,
                 mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 8,
                 mRGBViewerImageLeft + xPos + 16,
                 mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 8,
                 LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ) );

    gl_line_2d ( mRGBViewerImageLeft + xPos + 8,
                 mRGBViewerImageTop - mRGBViewerImageHeight + yPos,
                 mRGBViewerImageLeft + xPos + 8,
                 mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 16,
                 LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ) );

    // create rgb area outline
    gl_rect_2d ( mRGBViewerImageLeft,
                 mRGBViewerImageTop - mRGBViewerImageHeight,
                 mRGBViewerImageLeft + mRGBViewerImageWidth + 1,
                 mRGBViewerImageTop,
                 LLColor4 ( 0.0f, 0.0f, 0.0f, alpha ),
                 false );

    // draw luminance slider
    for ( S32 y = 0; y < mLumRegionHeight; ++y )
    {
        F32 rValSlider, gValSlider, bValSlider;
        hslToRgb ( getCurH (), getCurS (), ( F32 )y / ( F32 )mLumRegionHeight, rValSlider, gValSlider, bValSlider );

        gl_rect_2d( mLumRegionLeft,
            mLumRegionTop - mLumRegionHeight + y,
                mLumRegionLeft + mLumRegionWidth,
                    mLumRegionTop - mLumRegionHeight + y - 1,
                        LLColor4 ( rValSlider, gValSlider, bValSlider, alpha ) );
    }


    // draw luninance marker
    S32 startX = mLumRegionLeft + mLumRegionWidth;
    S32 startY = mLumRegionTop - mLumRegionHeight + ( S32 ) ( mLumRegionHeight * getCurL () );
    gl_triangle_2d ( startX, startY,
            startX + mLumMarkerSize, startY - mLumMarkerSize,
                startX + mLumMarkerSize, startY + mLumMarkerSize,
                    LLColor4 ( 0.75f, 0.75f, 0.75f, 1.0f ), true );

    // draw luminance slider outline
    gl_rect_2d ( mLumRegionLeft,
                 mLumRegionTop - mLumRegionHeight,
                 mLumRegionLeft + mLumRegionWidth + 1,
                 mLumRegionTop,
                 LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ),
                 false );

    // draw selected color swatch
    gl_rect_2d ( mSwatchRegionLeft,
                 mSwatchRegionTop - mSwatchRegionHeight,
                 mSwatchRegionLeft + mSwatchRegionWidth,
                 mSwatchRegionTop,
                 LLColor4 ( getCurR (), getCurG (), getCurB (), alpha ),
                 true );

    // draw selected color swatch outline
    gl_rect_2d ( mSwatchRegionLeft,
                 mSwatchRegionTop - mSwatchRegionHeight,
                 mSwatchRegionLeft + mSwatchRegionWidth + 1,
                 mSwatchRegionTop,
                 LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ),
                 false );

    // color palette code is a little more involved so break it out into its' own method
    drawPalette ();
}

//////////////////////////////////////////////////////////////////////////////
// find a complimentary color to the one passed in that can be used to highlight
const LLColor4& LLFloaterColorPicker::getComplimentaryColor ( const LLColor4& backgroundColor )
{
    // going to base calculation on luminance
    F32 hVal, sVal, lVal;
    backgroundColor.calcHSL(&hVal, &sVal, &lVal);
    hVal *= 360.f;
    sVal *= 100.f;
    lVal *= 100.f;

    // fairly simple heuristic for now...!
    if ( lVal < 0.5f )
    {
        return LLColor4::white;
    }

    return LLColor4::black;
}

//////////////////////////////////////////////////////////////////////////////
// set current RGB and rise change event if needed.
void LLFloaterColorPicker::selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn )
{
    setCurRgb(curRIn, curGIn, curBIn);
    if (mApplyImmediateCheck->get())
    {
        LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
    }
}

//////////////////////////////////////////////////////////////////////////////
// set current HSL and rise change event if needed.
void LLFloaterColorPicker::selectCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn )
{
    setCurHsl(curHIn, curSIn, curLIn);
    if (mApplyImmediateCheck->get())
    {
        LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
    }
}

//////////////////////////////////////////////////////////////////////////////
// draw color palette
void LLFloaterColorPicker::drawPalette ()
{
    S32 curEntry = 0;
    const F32 alpha = getSwatchTransparency();

    for ( S32 y = 0; y < numPaletteRows; ++y )
    {
        for ( S32 x = 0; x < numPaletteColumns; ++x )
        {
            // calculate position
            S32 x1 = mPaletteRegionLeft + ( mPaletteRegionWidth * x ) / numPaletteColumns;
            S32 y1 = mPaletteRegionTop - ( mPaletteRegionHeight * y ) / numPaletteRows;
            S32 x2 = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( x + 1 ) ) / numPaletteColumns );
            S32 y2 = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( y + 1 ) ) / numPaletteRows );

            // draw palette entry color
            if ( mPalette [ curEntry ] )
            {
                gl_rect_2d ( x1 + 2, y1 - 2, x2 - 2, y2 + 2, *mPalette [ curEntry++ ] % alpha, true );
                gl_rect_2d ( x1 + 1, y1 - 1, x2 - 1, y2 + 1, LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), false );
            }
        }
    }

    // if there is something to highlight (mouse down in swatch & hovering over palette)
    if ( highlightEntry >= 0 )
    {
        // extract row/column from palette index
        S32 entryColumn = highlightEntry % numPaletteColumns;
        S32 entryRow = highlightEntry / numPaletteColumns;

        // calculate position of this entry
        S32 x1 = mPaletteRegionLeft + ( mPaletteRegionWidth * entryColumn ) / numPaletteColumns;
        S32 y1 = mPaletteRegionTop - ( mPaletteRegionHeight * entryRow ) / numPaletteRows;
        S32 x2 = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( entryColumn + 1 ) ) / numPaletteColumns );
        S32 y2 = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( entryRow + 1 ) ) / numPaletteRows );

        // center position of entry
        S32 xCenter = x1 + ( x2 - x1 ) / 2;
        S32 yCenter = y1 - ( y1 - y2 ) / 2;

        // find a color that works well as a highlight color
        LLColor4 hlColor ( getComplimentaryColor ( *mPalette [ highlightEntry ] ) );

        // mark a cross for entry that is being hovered
        gl_line_2d ( xCenter - 4, yCenter - 4, xCenter + 4, yCenter + 4, hlColor );
        gl_line_2d ( xCenter + 4, yCenter - 4, xCenter - 4, yCenter + 4, hlColor );
    }
}

//////////////////////////////////////////////////////////////////////////////
// update text entry values for RGB/HSL (can't be done in ::draw () since this overwrites input
void LLFloaterColorPicker::updateTextEntry ()
{
    // set values in spinners
    getChild<LLUICtrl>("rspin")->setValue(( getCurR () * 255.0f ) );
    getChild<LLUICtrl>("gspin")->setValue(( getCurG () * 255.0f ) );
    getChild<LLUICtrl>("bspin")->setValue(( getCurB () * 255.0f ) );
    getChild<LLUICtrl>("hspin")->setValue(( getCurH () * 360.0f ) );
    getChild<LLUICtrl>("sspin")->setValue(( getCurS () * 100.0f ) );
    getChild<LLUICtrl>("lspin")->setValue(( getCurL () * 100.0f ) );
}

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::onTextEntryChanged ( LLUICtrl* ctrl )
{
    // value in RGB boxes changed
    std::string name = ctrl->getName();
    if ( ( name == "rspin" ) || ( name == "gspin" ) || ( name == "bspin" ) )
    {
        // get current RGB
        F32 rVal, gVal, bVal;
        getCurRgb ( rVal, gVal, bVal );

        // update component value with new value from text
        if ( name == "rspin" )
        {
            rVal = (F32)ctrl->getValue().asReal() / 255.0f;
        }
        else
        if ( name == "gspin" )
        {
            gVal = (F32)ctrl->getValue().asReal() / 255.0f;
        }
        else
        if ( name == "bspin" )
        {
            bVal = (F32)ctrl->getValue().asReal() / 255.0f;
        }

        // update current RGB (and implicitly HSL)
        selectCurRgb ( rVal, gVal, bVal );

        updateTextEntry ();
    }
    else
    // value in HSL boxes changed
    if ( ( name == "hspin" ) || ( name == "sspin" ) || ( name == "lspin" ) )
    {
        // get current HSL
        F32 hVal, sVal, lVal;
        getCurHsl ( hVal, sVal, lVal );

        // update component value with new value from text
        if ( name == "hspin" )
            hVal = (F32)ctrl->getValue().asReal() / 360.0f;
        else
        if ( name == "sspin" )
            sVal = (F32)ctrl->getValue().asReal() / 100.0f;
        else
        if ( name == "lspin" )
            lVal = (F32)ctrl->getValue().asReal() / 100.0f;

        // update current HSL (and implicitly RGB)
        selectCurHsl ( hVal, sVal, lVal );

        updateTextEntry ();
    }
}

//////////////////////////////////////////////////////////////////////////////
//
bool LLFloaterColorPicker::updateRgbHslFromPoint ( S32 xPosIn, S32 yPosIn )
{
    if ( xPosIn >= mRGBViewerImageLeft &&
         xPosIn <= mRGBViewerImageLeft + mRGBViewerImageWidth &&
         yPosIn <= mRGBViewerImageTop &&
         yPosIn >= mRGBViewerImageTop - mRGBViewerImageHeight )
    {
        // update HSL (and therefore RGB) based on new H & S and current L
        selectCurHsl ( ( ( F32 )xPosIn - ( F32 )mRGBViewerImageLeft ) / ( F32 )mRGBViewerImageWidth,
                    ( ( F32 )yPosIn - ( ( F32 )mRGBViewerImageTop - ( F32 )mRGBViewerImageHeight ) ) / ( F32 )mRGBViewerImageHeight,
                    getCurL () );

        // indicate a value changed
        return true;
    }
    else
    if ( xPosIn >= mLumRegionLeft &&
         xPosIn <= mLumRegionLeft + mLumRegionWidth &&
         yPosIn <= mLumRegionTop &&
         yPosIn >= mLumRegionTop - mLumRegionHeight )
    {

        // update HSL (and therefore RGB) based on current HS and new L
         selectCurHsl ( getCurH (),
                     getCurS (),
                    ( ( F32 )yPosIn - ( ( F32 )mRGBViewerImageTop - ( F32 )mRGBViewerImageHeight ) ) / ( F32 )mRGBViewerImageHeight );

        // indicate a value changed
        return true;
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////////
//
bool LLFloaterColorPicker::handleMouseDown ( S32 x, S32 y, MASK mask )
{
    // make it the frontmost
    gFloaterView->bringToFront(this);

    // rect containing RGB area
    LLRect rgbAreaRect ( mRGBViewerImageLeft,
                         mRGBViewerImageTop,
                         mRGBViewerImageLeft + mRGBViewerImageWidth,
                         mRGBViewerImageTop - mRGBViewerImageHeight );

    if ( rgbAreaRect.pointInRect ( x, y ) )
    {
        gFocusMgr.setMouseCapture(this);
        // mouse button down
        setMouseDownInHueRegion ( true );

        // update all values based on initial click
        updateRgbHslFromPoint ( x, y );

        // required by base class
        return true;
    }

    // rect containing RGB area
    LLRect lumAreaRect ( mLumRegionLeft,
                         mLumRegionTop,
                         mLumRegionLeft + mLumRegionWidth + mLumMarkerSize,
                         mLumRegionTop - mLumRegionHeight );

    if ( lumAreaRect.pointInRect ( x, y ) )
    {
        gFocusMgr.setMouseCapture(this);
        // mouse button down
        setMouseDownInLumRegion ( true );

        // required by base class
        return true;
    }

    // rect containing swatch area
    LLRect swatchRect ( mSwatchRegionLeft,
                        mSwatchRegionTop,
                        mSwatchRegionLeft + mSwatchRegionWidth,
                        mSwatchRegionTop - mSwatchRegionHeight );

    setMouseDownInSwatch( false );
    if ( swatchRect.pointInRect ( x, y ) )
    {
        setMouseDownInSwatch( true );

        // required - dont drag windows here.
        return true;
    }

    // rect containing palette area
    LLRect paletteRect ( mPaletteRegionLeft,
                         mPaletteRegionTop,
                         mPaletteRegionLeft + mPaletteRegionWidth,
                         mPaletteRegionTop - mPaletteRegionHeight );

    if ( paletteRect.pointInRect ( x, y ) )
    {
        // release keyboard focus so we can change text values
        if (gFocusMgr.childHasKeyboardFocus(this))
        {
            mSelectBtn->setFocus(true);
        }

        // calculate which palette index we selected
        S32 c = ( ( x - mPaletteRegionLeft ) * numPaletteColumns ) / mPaletteRegionWidth;
        S32 r = ( ( y - ( mPaletteRegionTop - mPaletteRegionHeight ) ) * numPaletteRows ) / mPaletteRegionHeight;

        U32 index = ( numPaletteRows - r - 1 ) * numPaletteColumns + c;

        if ( index <= mPalette.size () )
        {
            LLColor4 selected = *mPalette [ index ];

            selectCurRgb ( selected [ 0 ], selected [ 1 ], selected [ 2 ] );

            if (mApplyImmediateCheck->get())
            {
                LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
            }

            updateTextEntry ();
        }

        return true;
    }

    // dispatch to base class for the rest of things

    return LLFloater::handleMouseDown ( x, y, mask );
}

//////////////////////////////////////////////////////////////////////////////
//
bool LLFloaterColorPicker::handleHover ( S32 x, S32 y, MASK mask )
{
    // if we're the front most window
    if ( isFrontmost () )
    {
        // mouse was pressed within region
        if ( getMouseDownInHueRegion() || getMouseDownInLumRegion())
        {
            S32 clamped_x, clamped_y;
            if (getMouseDownInHueRegion())
            {
                clamped_x = llclamp(x, mRGBViewerImageLeft, mRGBViewerImageLeft + mRGBViewerImageWidth);
                clamped_y = llclamp(y, mRGBViewerImageTop - mRGBViewerImageHeight, mRGBViewerImageTop);
            }
            else
            {
                clamped_x = llclamp(x, mLumRegionLeft, mLumRegionLeft + mLumRegionWidth);
                clamped_y = llclamp(y, mLumRegionTop - mLumRegionHeight, mLumRegionTop);
            }

            // update the stored RGB/HSL values using the mouse position - returns true if RGB was updated
            if ( updateRgbHslFromPoint ( clamped_x, clamped_y ) )
            {
                // update text entry fields
                updateTextEntry ();

                // RN: apparently changing color when dragging generates too much traffic and results in sporadic updates
                //// commit changed color to swatch subject
                //// REVIEW: this gets sent each time a color changes - is this okay ?
                //if (mApplyImmediateCheck->get())
                //{
                //  LLColorSwatchCtrl::onColorChanged ( getSwatch () );
                //}
            }
        }

        highlightEntry = -1;

        if ( mMouseDownInSwatch )
        {
            getWindow()->setCursor ( UI_CURSOR_ARROWDRAG );

            // if cursor if over a palette entry
            LLRect paletteRect ( mPaletteRegionLeft,
                                mPaletteRegionTop,
                                mPaletteRegionLeft + mPaletteRegionWidth,
                                mPaletteRegionTop - mPaletteRegionHeight );

            if ( paletteRect.pointInRect ( x, y ) )
            {
                // find row/column in palette
                S32 xOffset = ( ( x - mPaletteRegionLeft ) * numPaletteColumns ) / mPaletteRegionWidth;
                S32 yOffset = ( ( mPaletteRegionTop - y - 1 ) * numPaletteRows ) / mPaletteRegionHeight;

                // calculate the entry 0..n-1 to highlight and set variable to next draw() picks it up
                highlightEntry = xOffset + yOffset * numPaletteColumns;
            }

            return true;
        }
    }

    // dispatch to base class for the rest of things
    return LLFloater::handleHover ( x, y, mask );
}

//////////////////////////////////////////////////////////////////////////////
// reverts state once mouse button is released
bool LLFloaterColorPicker::handleMouseUp ( S32 x, S32 y, MASK mask )
{
    getWindow()->setCursor ( UI_CURSOR_ARROW );

    if (getMouseDownInHueRegion() || getMouseDownInLumRegion())
    {
        if (mApplyImmediateCheck->get())
        {
            LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
        }
    }

    // rect containing palette area
    LLRect paletteRect ( mPaletteRegionLeft,
                            mPaletteRegionTop,
                            mPaletteRegionLeft + mPaletteRegionWidth,
                            mPaletteRegionTop - mPaletteRegionHeight );

    if ( paletteRect.pointInRect ( x, y ) )
    {
        if ( mMouseDownInSwatch )
        {
            S32 curEntry = 0;
            for ( S32 row = 0; row < numPaletteRows; ++row )
            {
                for ( S32 column = 0; column < numPaletteColumns; ++column )
                {
                    S32 left = mPaletteRegionLeft + ( mPaletteRegionWidth * column ) / numPaletteColumns;
                    S32 top = mPaletteRegionTop - ( mPaletteRegionHeight * row ) / numPaletteRows;
                    S32 right = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( column + 1 ) ) / numPaletteColumns );
                    S32 bottom = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( row + 1 ) ) / numPaletteRows );

                    // rect is flipped vertically when testing here
                    LLRect dropRect ( left, top, right, bottom );

                    if ( dropRect.pointInRect ( x, y ) )
                    {
                        if ( mPalette [ curEntry ] )
                        {
                            delete mPalette [ curEntry ];

                            mPalette [ curEntry ] = new LLColor4 ( getCurR (), getCurG (), getCurB (), 1.0f );

                            // save off color
                            std::ostringstream codec;
                            codec << "ColorPaletteEntry" << std::setfill ( '0' ) << std::setw ( 2 ) << curEntry + 1;
                            const std::string s ( codec.str () );
                            LLUIColorTable::instance().setColor(s, *mPalette [ curEntry ] );
                        }
                    }

                    ++curEntry;
                }
            }
        }
    }

    // mouse button not down anymore
    setMouseDownInHueRegion ( false );
    setMouseDownInLumRegion ( false );

    // mouse button not down in color swatch anymore
    mMouseDownInSwatch = false;

    if (hasMouseCapture())
    {
        gFocusMgr.setMouseCapture(NULL);
    }

    // dispatch to base class for the rest of things
    return LLFloater::handleMouseUp ( x, y, mask );
}

//////////////////////////////////////////////////////////////////////////////
// cancel current color selection, revert to original and close picker
void LLFloaterColorPicker::cancelSelection ()
{
    // restore the previous color selection
    setCurRgb ( getOrigR (), getOrigG (), getOrigB () );

    // update in world item with original color via current swatch
    LLColorSwatchCtrl::onColorChanged( getSwatch(), LLColorSwatchCtrl::COLOR_CANCEL );

    // hide picker dialog
    this->setVisible ( false );
}

void LLFloaterColorPicker::setMouseDownInHueRegion ( bool mouse_down_in_region )
{
    mMouseDownInHueRegion = mouse_down_in_region;
    if (mouse_down_in_region)
    {
        if (gFocusMgr.childHasKeyboardFocus(this))
        {
            // get focus out of spinners so that they can update freely
            mSelectBtn->setFocus(true);
        }
    }
}

void LLFloaterColorPicker::setMouseDownInLumRegion ( bool mouse_down_in_region )
{
    mMouseDownInLumRegion = mouse_down_in_region;
    if (mouse_down_in_region)
    {
        if (gFocusMgr.childHasKeyboardFocus(this))
        {
            // get focus out of spinners so that they can update freely
            mSelectBtn->setFocus(true);
        }
    }
}

void LLFloaterColorPicker::setMouseDownInSwatch (bool mouse_down_in_swatch)
{
    mMouseDownInSwatch = mouse_down_in_swatch;
    if (mouse_down_in_swatch)
    {
        if (gFocusMgr.childHasKeyboardFocus(this))
        {
            // get focus out of spinners so that they can update freely
            mSelectBtn->setFocus(true);
        }
    }
}

void LLFloaterColorPicker::setActive(bool active)
{
    // shut down pipette tool if active
    if (!active && mPipetteBtn->getToggleState())
    {
        stopUsingPipette();
    }
    mActive = active;
}

void LLFloaterColorPicker::stopUsingPipette()
{
    if (LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance())
    {
        LLToolMgr::getInstance()->clearTransientTool();
    }
}