/** 
 * @file llfloatercolorpicker.cpp
 * @brief Generic system color picker
 *
 * $LicenseInfo:firstyear=2004&license=viewergpl$
 * 
 * Copyright (c) 2004-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include <sstream>
#include <iomanip>

#include "llfloatercolorpicker.h"

#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 "llviewercontrol.h"
#include "lluictrlfactory.h"
#include "llviewerwindow.h"
#include "llwindow.h"
#include "llgl.h"
#include "llmemory.h"
#include "llimage.h"
#include "llmousehandler.h"
#include "llimagegl.h"
#include "llglheaders.h"
#include "llcheckboxctrl.h"
#include "llworld.h"
#include "lltextbox.h"
#include "lluiconstants.h"
#include "llfocusmgr.h"
#include "lltoolmgr.h"
#include "lltoolpipette.h"
#include "lldraghandle.h"

const F32 CONTEXT_CONE_IN_ALPHA = 0.0f;
const F32 CONTEXT_CONE_OUT_ALPHA = 1.f;
const F32 CONTEXT_FADE_TIME = 0.08f;

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

//////////////////////////////////////////////////////////////////////////////
// default ctor
LLFloaterColorPicker::
LLFloaterColorPicker (LLColorSwatchCtrl* swatch, BOOL show_apply_immediate )
	: LLFloater (std::string("Color Picker Floater")),
	  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		( 170 ),
	  mSwatchRegionWidth	( 116 ),
	  mSwatchRegionHeight	( 60 ),
	  mSwatchView			( NULL ),
	  // *TODO: Specify this in XML
	  numPaletteColumns		( 16 ),
	  numPaletteRows		( 2 ),
	  highlightEntry		( -1 ),
	  mPaletteRegionLeft	( 11 ),
	  mPaletteRegionTop		( 92 ),
	  mPaletteRegionWidth	( mLumRegionLeft + mLumRegionWidth - 10 ),
	  mPaletteRegionHeight	( 40 ),
	  mSwatch				( swatch ),
	  mActive				( TRUE ),
	  mCanApplyImmediately	( show_apply_immediate ),
	  mContextConeOpacity	( 0.f )
{
	// create user interface for this picker
	createUI ();

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

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

//////////////////////////////////////////////////////////////////////////////
//
void
LLFloaterColorPicker::
createUI ()
{
	// build the majority of the gui using the factory builder
	LLUICtrlFactory::getInstance()->buildFloater ( this, "floater_color_picker.xml" );
	setVisible ( FALSE );

	// create RGB type area (not really RGB but it's got R,G & B in it.,..

	LLPointer<LLImageRaw> raw = new LLImageRaw ( mRGBViewerImageWidth, mRGBViewerImageHeight, mComponents );
	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 )
	{
		std::ostringstream codec;
		codec << "ColorPaletteEntry" << std::setfill ( '0' ) << std::setw ( 2 ) << each + 1;

		// argh!
		const std::string s ( codec.str () );
		mPalette.push_back ( new LLColor4 ( gSavedSettings.getColor4 ( s )  ) );
	}
}

//////////////////////////////////////////////////////////////////////////////
//
void
LLFloaterColorPicker::
showUI ()
{
	setVisible ( TRUE );
	setFocus ( TRUE );
	open();		/*Flawfinder: ignore*/

	// 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 )
		{
			LLColor4 curCol = swatch->get ();
			send_agent_pause();
			gViewerWindow->getWindow()->dialogColorPicker( &curCol [ 0 ], &curCol [ 1 ], &curCol [ 2 ] );
			send_agent_resume();

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

			LLColorSwatchCtrl::onColorChanged ( swatch, LLColorSwatchCtrl::COLOR_CHANGE );
		}

		close();
	}
}

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

	mSelectBtn = getChild<LLButton>( "select_btn");
    mSelectBtn->setClickedCallback ( boost::bind(&LLFloaterColorPicker::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->setClickedCallback( boost::bind(&LLFloaterColorPicker::onClickPipette,this) );

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

	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 );
	childSetCommitCallback("hexval", onHexCommit, (void*)this );

    return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
void
LLFloaterColorPicker::
initUI ( F32 rValIn, F32 gValIn, F32 bValIn )
{
	// start catching lose-focus events from entry widgets
	enableTextCallbacks ( TRUE );

	// 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 );
		delete mSwatchView;
		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 (fixes SL-16968)
    // HACK: turn off the call back wilst we update the text or we recurse ourselves into oblivion
    // CP: this was required when I first wrote the code but this may not be necessary anymore - leaving it there just in case
    enableTextCallbacks( FALSE );
    updateTextEntry();
    enableTextCallbacks( TRUE );
}

//////////////////////////////////////////////////////////////////////////////
// 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->close();
		}
	}
}

//////////////////////////////////////////////////////////////////////////////
// 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->close();
		}
	}
}

void LLFloaterColorPicker::onClickPipette( void* data )
{
	LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data;

	if ( self)
	{
		BOOL pipette_active = self->mPipetteBtn->getToggleState();
		pipette_active = !pipette_active;
		if (pipette_active)
		{
			LLToolPipette::getInstance()->setSelectCallback(onColorSelect, self);
			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())
		{
			LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
		}
	}
}

void LLFloaterColorPicker::onColorSelect( const LLTextureEntry& te, void *data )
{
	LLFloaterColorPicker* self = (LLFloaterColorPicker*)data;
	if (self)
	{
		self->setCurRgb(te.getColor().mV[VRED], te.getColor().mV[VGREEN], te.getColor().mV[VBLUE]);
		if (self->mApplyImmediateCheck->get())
		{
			LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
		}
	}
}

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

//////////////////////////////////////////////////////////////////////////////
//
void LLFloaterColorPicker::draw()
{
	LLRect swatch_rect;
	mSwatch->localRectToOtherView(mSwatch->getLocalRect(), &swatch_rect, this);
	// draw context cone connecting color picker with color swatch in parent floater
	LLRect local_rect = getLocalRect();
	if (gFocusMgr.childHasKeyboardFocus(this) && mSwatch->isInVisibleChain() && mContextConeOpacity > 0.001f)
	{
		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
		LLGLEnable(GL_CULL_FACE);
		gGL.begin(LLRender::QUADS);
		{
			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity);
			gGL.vertex2i(swatch_rect.mLeft, swatch_rect.mTop);
			gGL.vertex2i(swatch_rect.mRight, swatch_rect.mTop);
			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity);
			gGL.vertex2i(local_rect.mRight, local_rect.mTop);
			gGL.vertex2i(local_rect.mLeft, local_rect.mTop);

			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity);
			gGL.vertex2i(local_rect.mLeft, local_rect.mTop);
			gGL.vertex2i(local_rect.mLeft, local_rect.mBottom);
			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity);
			gGL.vertex2i(swatch_rect.mLeft, swatch_rect.mBottom);
			gGL.vertex2i(swatch_rect.mLeft, swatch_rect.mTop);

			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity);
			gGL.vertex2i(local_rect.mRight, local_rect.mBottom);
			gGL.vertex2i(local_rect.mRight, local_rect.mTop);
			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity);
			gGL.vertex2i(swatch_rect.mRight, swatch_rect.mTop);
			gGL.vertex2i(swatch_rect.mRight, swatch_rect.mBottom);

			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity);
			gGL.vertex2i(local_rect.mLeft, local_rect.mBottom);
			gGL.vertex2i(local_rect.mRight, local_rect.mBottom);
			gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity);
			gGL.vertex2i(swatch_rect.mRight, swatch_rect.mBottom);
			gGL.vertex2i(swatch_rect.mLeft, swatch_rect.mBottom);
		}
		gGL.end();
	}

	if (gFocusMgr.childHasMouseCapture(getDragHandle()))
	{
		mContextConeOpacity = lerp(mContextConeOpacity, gSavedSettings.getF32("PickerContextOpacity"), LLCriticalDamp::getInterpolant(CONTEXT_FADE_TIME));
	}
	else
	{
		mContextConeOpacity = lerp(mContextConeOpacity, 0.f, LLCriticalDamp::getInterpolant(CONTEXT_FADE_TIME));
	}

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

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

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

	// 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 + 1,
				 mRGBViewerImageLeft + mRGBViewerImageWidth + 1,
				 mRGBViewerImageTop,
				 LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ),
				 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, 1.0f ) );
	}


	// draw luminance 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.0f, 0.0f, 0.0f, 1.0f ), TRUE );

	// draw a white marker inside the black marker to be visible with dark or bright ui.
	gl_triangle_2d ( startX+2, startY,
		startX + mLumMarkerSize - 1, startY - mLumMarkerSize + 2,
			startX + mLumMarkerSize - 1, startY + mLumMarkerSize - 2,
				LLColor4 ( 1.0f, 1.0f, 1.0f, 1.0f ), TRUE );

	// draw luminance slider outline
	gl_rect_2d ( mLumRegionLeft,
				 mLumRegionTop - mLumRegionHeight + 1,
				 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 (), 1.0f ),
				 TRUE );

	// draw selected color swatch outline
	gl_rect_2d ( mSwatchRegionLeft,
				 mSwatchRegionTop - mSwatchRegionHeight + 1,
				 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;
}

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

	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 + 1, y1 - 2, x2 - 2, y2 + 1, *mPalette [ curEntry++ ], 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 );
	}
}

//Convert to Hex.
std::string RGBToHex(int rNum, int gNum, int bNum)
{
	std::string result;
	
	result.append(llformat("%.2X", rNum));
	result.append(llformat("%.2X", gNum));
	result.append(llformat("%.2X", bNum));
	
	return result;
}

//Called when a hex value is entered into the Hex field  - Convert and set values.
void
LLFloaterColorPicker::
onHexCommit ( LLUICtrl* ctrl, void* data )
{
	if ( data )
	{
		LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data;
		if ( self )
		{
			char* pStop;
			int num = strtol(ctrl->getValue().asString().c_str(), &pStop, 16);
			int r = (num & 0xFF0000) >> 16;
			int g = (num & 0xFF00) >> 8;
			int b = num & 0xFF;
			self->setCurRgb (r / 255.0f, g / 255.0f, b / 255.0f);

			// HACK: turn off the call back wilst we update the text or we recurse ourselves into oblivion
			self->enableTextCallbacks ( FALSE );
			self->updateTextEntry ();
			self->enableTextCallbacks ( TRUE );
			if (self->mApplyImmediateCheck->get())
			{
				LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE );
			}
		}
	}
}

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

//////////////////////////////////////////////////////////////////////////////
// turns on or off text entry commit call backs
void
LLFloaterColorPicker::
enableTextCallbacks ( BOOL stateIn )
{
	if ( stateIn )
	{
		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 );
		childSetCommitCallback("hexval", onHexCommit, (void*)this );
	}
	else
	{
		childSetCommitCallback("rspin", 0, (void*)this );
		childSetCommitCallback("gspin", 0, (void*)this );
		childSetCommitCallback("bspin", 0, (void*)this );
		childSetCommitCallback("hspin", 0, (void*)this );
		childSetCommitCallback("sspin", 0, (void*)this );
		childSetCommitCallback("lspin", 0, (void*)this );
		childSetCommitCallback("hexval", 0, (void*)this );
	}
}


//////////////////////////////////////////////////////////////////////////////
//
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)
		setCurRgb ( rVal, gVal, bVal );

		// HACK: turn off the call back wilst we update the text or we recurse ourselves into oblivion
		enableTextCallbacks ( FALSE );
		updateTextEntry ();
		enableTextCallbacks ( TRUE );
	}
	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)
		setCurHsl ( hVal, sVal, lVal );

		// HACK: turn off the call back wilst we update the text or we recurse ourselves into oblivion
		enableTextCallbacks ( FALSE );
		updateTextEntry ();
		enableTextCallbacks ( TRUE );
	}

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

//////////////////////////////////////////////////////////////////////////////
//
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
		setCurHsl ( ( ( 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
		 setCurHsl ( 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 ];

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

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

			// HACK: turn off the call back wilst we update the text or we recurse ourselves into oblivion
			enableTextCallbacks ( FALSE );
			updateTextEntry ();
			enableTextCallbacks ( TRUE );
		}

		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 );
}

void LLFloaterColorPicker::onClose(bool app_quitting)
{
	//RN: this is consistent with texture picker in that closing the window leaves the current selection
	// to change this to "close to cancel", uncomment the following line
	//cancelSelection();
	LLFloater::onClose(app_quitting);
}

//////////////////////////////////////////////////////////////////////////////
// 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 () );
							gSavedSettings.setColor4( 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 () );

	// 	we're going away and when we do and the entry widgets lose focus, they do bad things so turn them off
	enableTextCallbacks ( FALSE );

	// 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();
	}
}
