/** 
 * @file llbutton.cpp
 * @brief LLButton base class
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-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 "linden_common.h"

#include "llbutton.h"

// Linden library includes
#include "v4color.h"
#include "llstring.h"

// Project includes
#include "llhtmlhelp.h"
#include "llkeyboard.h"
#include "llui.h"
#include "lluiconstants.h"
#include "llresmgr.h"
#include "llcriticaldamp.h"
#include "llfocusmgr.h"
#include "llwindow.h"
#include "llrender.h"

static LLRegisterWidget<LLButton> r("button");

// globals loaded from settings.xml
S32	LLBUTTON_ORIG_H_PAD	= 6; // Pre-zoomable UI
S32	LLBUTTON_H_PAD	= 0;
S32	LLBUTTON_V_PAD	= 0;
S32 BTN_HEIGHT_SMALL= 0;
S32 BTN_HEIGHT		= 0;

S32 BTN_GRID		= 12;
S32 BORDER_SIZE = 1;

LLButton::LLButton(	const std::string& name, const LLRect& rect, const std::string& control_name, void (*click_callback)(void*), void *callback_data)
:	LLUICtrl(name, rect, TRUE, NULL, NULL),
	
	mMouseDownFrame( 0 ),
	mMouseHeldDownCount(0),
	mBorderEnabled( FALSE ),
	mFlashing( FALSE ),
	mCurGlowStrength(0.f),
	mNeedsHighlight(FALSE),
	mUnselectedLabel(name),
	mSelectedLabel(name),
	mGLFont( LLFontGL::getFontSansSerif() ),
	mHeldDownDelay( 0.5f ),			// seconds until held-down callback is called
	mHeldDownFrameDelay( 0 ),
	mImageUnselected(LLUI::getUIImage("button_enabled_32x128.tga")),
	mImageSelected(LLUI::getUIImage("button_enabled_selected_32x128.tga")),
	mImageDisabled(LLUI::getUIImage("button_disabled_32x128.tga")),
	mImageDisabledSelected(LLUI::getUIImage("button_disabled_32x128.tga")),
	mImagePressed(LLUI::getUIImage("button_enabled_selected_32x128.tga")),
	mImagePressedSelected(LLUI::getUIImage("button_enabled_32x128.tga")),
	mImageFlash(NULL),
	mImageHoverSelected( NULL ),
	mImageHoverUnselected( NULL ),
	mUnselectedLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelColor" )),
	mSelectedLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelSelectedColor")),
	mDisabledLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelDisabledColor")),
	mDisabledSelectedLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelSelectedDisabledColor")),
	mImageColor(LLUI::sColorsGroup->getColor("ButtonImageColor")),
	mFlashBgColor(LLUI::sColorsGroup->getColor("ButtonFlashBgColor")),
	mDisabledImageColor(LLUI::sColorsGroup->getColor("ButtonImageColor")),
	mImageOverlay(NULL),
	mImageOverlayColor(LLColor4::white),
	mImageOverlayDisabledColor(LLColor4(1.f,1.f,1.f,.5f)),
	mImageOverlaySelectedColor(LLColor4::white),
	mImageOverlayAlignment(LLFontGL::HCENTER),
	mImageOverlayTopPad(0),
	mImageOverlayBottomPad(0),
	mImgOverlayLabelSpace(1),
	mIsToggle( FALSE ),
	mScaleImage( TRUE ),
	mDropShadowedText( TRUE ),	
	mAutoResize( FALSE ),
	mHAlign( LLFontGL::HCENTER ),
	mLeftHPad( LLBUTTON_H_PAD ),
	mRightHPad( LLBUTTON_H_PAD ),
	mBottomVPad( LLBUTTON_V_PAD ),
	mHoverGlowStrength(0.25f),
	mCommitOnReturn(TRUE),
	mFadeWhenDisabled(FALSE),
	mForcePressedState(false),
	mDisplayPressedState(TRUE),
	mMouseDownSignal(NULL),
	mMouseUpSignal(NULL),
	mHeldDownSignal(NULL),
	mHandleRightMouse(false),
	mButtonFlashCount(LLUI::sConfigGroup->getS32("ButtonFlashCount")),
	mButtonFlashRate(LLUI::sConfigGroup->getF32("ButtonFlashRate")),
	mAlpha(1.f)
{
	init(click_callback, callback_data, control_name);
}


LLButton::LLButton(const std::string& name, const LLRect& rect, 
				   const std::string &unselected_image_name, 
				   const std::string &selected_image_name, 
				   const std::string& control_name,
				   void (*click_callback)(void*),
				   void *callback_data,
				   const LLFontGL *font,
				   const std::string& unselected_label, 
				   const std::string& selected_label )
:	LLUICtrl(name, rect, TRUE, NULL, NULL),
	mMouseDownFrame( 0 ),
	mMouseHeldDownCount(0),
	mBorderEnabled( FALSE ),
	mFlashing( FALSE ),
	mCurGlowStrength(0.f),
	mNeedsHighlight(FALSE),
	mUnselectedLabel(unselected_label),
	mSelectedLabel(selected_label),
	mGLFont( font ? font : LLFontGL::getFontSansSerif() ),
	mHeldDownDelay( 0.5f ),			// seconds until held-down callback is called
	mHeldDownFrameDelay( 0 ),
	mImageUnselected(LLUI::getUIImage("button_enabled_32x128.tga")),
	mImageSelected(LLUI::getUIImage("button_enabled_selected_32x128.tga")),
	mImageDisabled(LLUI::getUIImage("button_disabled_32x128.tga")),
	mImageDisabledSelected(LLUI::getUIImage("button_disabled_32x128.tga")),
	mImagePressed(LLUI::getUIImage("button_enabled_selected_32x128.tga")),
	mImagePressedSelected(LLUI::getUIImage("button_enabled_32x128.tga")),
	mImageFlash(NULL),
	mImageHoverSelected( NULL ),
	mImageHoverUnselected( NULL ),
	mUnselectedLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelColor" )),
	mSelectedLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelSelectedColor")),
	mDisabledLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelDisabledColor")),
	mDisabledSelectedLabelColor(LLUI::sColorsGroup->getColor("ButtonLabelSelectedDisabledColor")),
	mImageColor(LLUI::sColorsGroup->getColor("ButtonImageColor")),
	mFlashBgColor(LLUI::sColorsGroup->getColor("ButtonFlashBgColor")),
	mDisabledImageColor(LLUI::sColorsGroup->getColor("ButtonImageColor")),
	mImageOverlay(NULL),
	mImageOverlayColor(LLColor4::white),
	mImageOverlayDisabledColor(LLColor4(1.f,1.f,1.f,.5f)),
	mImageOverlaySelectedColor(LLColor4::white),
	mImageOverlayAlignment(LLFontGL::HCENTER),
	mImageOverlayTopPad(0),
	mImageOverlayBottomPad(0),
	mImgOverlayLabelSpace(1),
	mIsToggle( FALSE ),
	mScaleImage( TRUE ),
	mDropShadowedText( TRUE ),
	mAutoResize( FALSE ),
	mHAlign( LLFontGL::HCENTER ),
	mLeftHPad( LLBUTTON_H_PAD ), 
	mRightHPad( LLBUTTON_H_PAD ),
	mBottomVPad( LLBUTTON_V_PAD ),
	mHoverGlowStrength(0.25f),
	mCommitOnReturn(TRUE),
	mFadeWhenDisabled(FALSE),
	mForcePressedState(false),
	mDisplayPressedState(TRUE),
	mMouseDownSignal(NULL),
	mMouseUpSignal(NULL),
	mHeldDownSignal(NULL),
	mHandleRightMouse(false),
	mButtonFlashCount(LLUI::sConfigGroup->getS32("ButtonFlashCount")),
	mButtonFlashRate(LLUI::sConfigGroup->getF32("ButtonFlashRate")),
	mAlpha(1.f)
{

	if( unselected_image_name != "" )
	{
		// user-specified image - don't use fixed borders unless requested
		mImageUnselected = LLUI::getUIImage(unselected_image_name);
		mImageDisabled = mImageUnselected;
		
		mFadeWhenDisabled = TRUE;
		//mScaleImage = FALSE;
		
		mImagePressedSelected = mImageUnselected;
	}

	if( selected_image_name != "" )
	{
		// user-specified image - don't use fixed borders unless requested
		mImageSelected = LLUI::getUIImage(selected_image_name);
		mImageDisabledSelected = mImageSelected;

		mFadeWhenDisabled = TRUE;
		//mScaleImage = FALSE;
		
		mImagePressed = mImageSelected;
	}

	init(click_callback, callback_data, control_name);
}

void LLButton::init(void (*click_callback)(void*), void *callback_data, const std::string& control_name)
{
	// Hack to make sure there is space for at least one character
	if (getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" ")))
	{
		// Use old defaults
		mLeftHPad = LLBUTTON_ORIG_H_PAD;
		mRightHPad = LLBUTTON_ORIG_H_PAD;
	}
	
	mMouseDownTimer.stop();
	
	if(click_callback)
		setClickedCallback(click_callback, callback_data);
	
	setControlName(control_name, NULL);

}


// virtual
LLButton::~LLButton()
{
	delete mMouseDownSignal;
	delete mMouseUpSignal;
	delete mHeldDownSignal;
}

// HACK: Committing a button is the same as instantly clicking it.
// virtual
void LLButton::onCommit()
{
	// WARNING: Sometimes clicking a button destroys the floater or
	// panel containing it.  Therefore we need to call 	LLUICtrl::onCommit()
	// LAST, otherwise this becomes deleted memory.

	if (mMouseDownSignal) (*mMouseDownSignal)(this, LLSD());
	
	if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD());

	if (getSoundFlags() & MOUSE_DOWN)
	{
		make_ui_sound("UISndClick");
	}

	if (getSoundFlags() & MOUSE_UP)
	{
		make_ui_sound("UISndClickRelease");
	}

	if (mIsToggle)
	{
		toggleState();
	}

	// do this last, as it can result in destroying this button
	LLUICtrl::onCommit();
}

/*boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb)
{
	return setClickedCallback(initCommitCallback(cb));
}
boost::signals2::connection LLButton::setMouseDownCallback(const CommitCallbackParam& cb)
{
	return setMouseDownCallback(initCommitCallback(cb));
}
boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackParam& cb)
{
	return setMouseUpCallback(initCommitCallback(cb));
}
boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb)
{
	return setHeldDownCallback(initCommitCallback(cb));
}*/


boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb )
{
	return LLUICtrl::setCommitCallback(cb);
}
boost::signals2::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb )
{
	if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t();
	return mMouseDownSignal->connect(cb);
}
boost::signals2::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb )
{
	if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t();
	return mMouseUpSignal->connect(cb);
}
boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb )
{
	if (!mHeldDownSignal) mHeldDownSignal = new commit_signal_t();
	return mHeldDownSignal->connect(cb);
}


// *TODO: Deprecate (for backwards compatibility only)
boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data )
{
	return setClickedCallback(boost::bind(cb, data));
}
boost::signals2::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data )
{
	return setMouseDownCallback(boost::bind(cb, data));
}
boost::signals2::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data )
{
	return setMouseUpCallback(boost::bind(cb, data));
}
boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data )
{
	return setHeldDownCallback(boost::bind(cb, data));
}

BOOL LLButton::handleUnicodeCharHere(llwchar uni_char)
{
	BOOL handled = FALSE;
	if(' ' == uni_char 
		&& !gKeyboard->getKeyRepeated(' '))
	{
		if (mIsToggle)
		{
			toggleState();
		}

		LLUICtrl::onCommit();
		
		handled = TRUE;		
	}
	return handled;	
}

BOOL LLButton::handleKeyHere(KEY key, MASK mask )
{
	BOOL handled = FALSE;
	if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key))
	{
		if (mIsToggle)
		{
			toggleState();
		}

		handled = TRUE;

		LLUICtrl::onCommit();
	}
	return handled;
}


BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask)
{
	if (!childrenHandleMouseDown(x, y, mask))
	{
		// Route future Mouse messages here preemptively.  (Release on mouse up.)
		gFocusMgr.setMouseCapture( this );

		if (hasTabStop() && !getIsChrome())
		{
			setFocus(TRUE);
		}

		/*
		 * ATTENTION! This call fires another mouse down callback.
		 * If you wish to remove this call emit that signal directly
		 * by calling LLUICtrl::mMouseDownSignal(x, y, mask);
		 */
		LLUICtrl::handleMouseDown(x, y, mask);

		if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD());

	mMouseDownTimer.start();
		mMouseDownFrame = (S32) LLFrameTimer::getFrameCount();
		mMouseHeldDownCount = 0;

		
		if (getSoundFlags() & MOUSE_DOWN)
		{
			make_ui_sound("UISndClick");
		}
	}
	return TRUE;
}


BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask)
{
	// We only handle the click if the click both started and ended within us
	if( hasMouseCapture() )
	{
		// Always release the mouse
		gFocusMgr.setMouseCapture( NULL );

		/*
		 * ATTENTION! This call fires another mouse up callback.
		 * If you wish to remove this call emit that signal directly
		 * by calling LLUICtrl::mMouseUpSignal(x, y, mask);
		 */
		LLUICtrl::handleMouseUp(x, y, mask);

		// Regardless of where mouseup occurs, handle callback
		if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD());

		resetMouseDownTimer();

		// DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked.
		// If mouseup in the widget, it's been clicked
		if (pointInView(x, y))
		{
			if (getSoundFlags() & MOUSE_UP)
			{
				make_ui_sound("UISndClickRelease");
			}

			if (mIsToggle)
			{
				toggleState();
			}

			LLUICtrl::onCommit();
		}
	}
	else
	{
		childrenHandleMouseUp(x, y, mask);
	}

	return TRUE;
}

BOOL	LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
	if (mHandleRightMouse && !childrenHandleRightMouseDown(x, y, mask))
	{
		// Route future Mouse messages here preemptively.  (Release on mouse up.)
		gFocusMgr.setMouseCapture( this );

		if (hasTabStop() && !getIsChrome())
		{
			setFocus(TRUE);
		}

//		if (pointInView(x, y))
//		{
//		}
		// send the mouse down signal
		LLUICtrl::handleRightMouseDown(x,y,mask);
		// *TODO: Return result of LLUICtrl call above?  Should defer to base class
		// but this might change the mouse handling of existing buttons in a bad way
		// if they are not mouse opaque.
	}

	return TRUE;
}

BOOL	LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask)
{
	if (mHandleRightMouse)
	{
		// We only handle the click if the click both started and ended within us
		if( hasMouseCapture() )
		{
			// Always release the mouse
			gFocusMgr.setMouseCapture( NULL );

	//		if (pointInView(x, y))
	//		{
	//			mRightMouseUpSignal(this, x,y,mask);
	//		}
		}
		else 
		{
			childrenHandleRightMouseUp(x, y, mask);
		}
	
		// send the mouse up signal
		LLUICtrl::handleRightMouseUp(x,y,mask);
		// *TODO: Return result of LLUICtrl call above?  Should defer to base class
		// but this might change the mouse handling of existing buttons in a bad way.
		// if they are not mouse opaque.
	}
	return TRUE;
}

void LLButton::setHighlight(bool b)
{
	mNeedsHighlight = b;
}

BOOL LLButton::handleHover(S32 x, S32 y, MASK mask)
{
	if (isInEnabledChain() 
		&& (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this))
		mNeedsHighlight = TRUE;

	if (!childrenHandleHover(x, y, mask))
	{
		if (mMouseDownTimer.getStarted())
		{
			F32 elapsed = getHeldDownTime();
			if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame)
			{
				LLSD param;
				param["count"] = mMouseHeldDownCount++;
				if (mHeldDownSignal) (*mHeldDownSignal)(this, param);
			}
		}

		// We only handle the click if the click both started and ended within us
		getWindow()->setCursor(UI_CURSOR_ARROW);
		lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl;
	}
	return TRUE;
}

void LLButton::getOverlayImageSize(S32& overlay_width, S32& overlay_height)
{
	overlay_width = mImageOverlay->getWidth();
	overlay_height = mImageOverlay->getHeight();

	F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f);
	overlay_width = llround((F32)overlay_width * scale_factor);
	overlay_height = llround((F32)overlay_height * scale_factor);
}


// virtual
void LLButton::draw()
{
	F32 alpha = mAlpha;
	bool flash = FALSE;
	if( mFlashing )
	{
		F32 elapsed = mFlashingTimer.getElapsedTimeF32();
		S32 flash_count = S32(elapsed * mButtonFlashRate * 2.f);
		// flash on or off?
		flash = (flash_count % 2 == 0) || flash_count > S32((F32)mButtonFlashCount * 2.f);
	}

	bool pressed_by_keyboard = FALSE;
	if (hasFocus())
	{
		pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN));
	}

	bool mouse_pressed_and_over = false;
	if (hasMouseCapture())
	{
		S32 local_mouse_x ;
		S32 local_mouse_y;
		LLUI::getMousePositionLocal(this, &local_mouse_x, &local_mouse_y);
		mouse_pressed_and_over = pointInView(local_mouse_x, local_mouse_y);
	}

	bool enabled = isInEnabledChain();
	
	bool pressed = pressed_by_keyboard 
					|| mouse_pressed_and_over
					|| mForcePressedState;
	bool selected = getToggleState();
	
	bool use_glow_effect = FALSE;
	LLColor4 glow_color = LLColor4::white;
	LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA;
	LLUIImage* imagep = NULL;
	if (pressed && mDisplayPressedState)
	{
		imagep = selected ? mImagePressedSelected : mImagePressed;
	}
	else if ( mNeedsHighlight )
	{
		if (selected)
		{
			if (mImageHoverSelected)
			{
				imagep = mImageHoverSelected;
			}
			else
			{
				imagep = mImageSelected;
				use_glow_effect = TRUE;
			}
		}
		else
		{
			if (mImageHoverUnselected)
			{
				imagep = mImageHoverUnselected;
			}
			else
			{
				imagep = mImageUnselected;
				use_glow_effect = TRUE;
			}
		}
	}
	else 
	{
		imagep = selected ? mImageSelected : mImageUnselected;
	}

	// Override if more data is available
	// HACK: Use gray checked state to mean either:
	//   enabled and tentative
	// or
	//   disabled but checked
	if (!mImageDisabledSelected.isNull() 
		&& 
			( (enabled && getTentative()) 
			|| (!enabled && selected ) ) )
	{
		imagep = mImageDisabledSelected;
	}
	else if (!mImageDisabled.isNull() 
		&& !enabled 
		&& !selected)
	{
		imagep = mImageDisabled;
	}

	if (mFlashing)
	{
		// if button should flash and we have icon for flashing, use it as image for button
		if(flash && mImageFlash)
		{
			// setting flash to false to avoid its further influence on glow
			flash = false;
			imagep = mImageFlash;
		}
		// else use usual flashing via flash_color
		else
		{
			LLColor4 flash_color = mFlashBgColor.get();
			use_glow_effect = TRUE;
			glow_type = LLRender::BT_ALPHA; // blend the glow
			if (mNeedsHighlight) // highlighted AND flashing
				glow_color = (glow_color*0.5f + flash_color*0.5f) % 2.0f; // average between flash and highlight colour, with sum of the opacity
			else
				glow_color = flash_color;
		}
	}

	if (mNeedsHighlight && !imagep)
	{
		use_glow_effect = TRUE;
	}

	// Figure out appropriate color for the text
	LLColor4 label_color;

	// label changes when button state changes, not when pressed
	if ( enabled )
	{
		if ( getToggleState() )
		{
			label_color = mSelectedLabelColor.get();
		}
		else
		{
			label_color = mUnselectedLabelColor.get();
		}
	}
	else
	{
		if ( getToggleState() )
		{
			label_color = mDisabledSelectedLabelColor.get();
		}
		else
		{
			label_color = mDisabledLabelColor.get();
		}
	}

	// Unselected label assignments
	LLWString label = getCurrentLabel();

	// overlay with keyboard focus border
	if (hasFocus())
	{
		F32 lerp_amt = gFocusMgr.getFocusFlashAmt();
		drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, llround(lerp(1.f, 3.f, lerp_amt)));
	}
	
	if (use_glow_effect)
	{
		mCurGlowStrength = lerp(mCurGlowStrength,
					mFlashing ? (flash? 1.0 : 0.0)
					: mHoverGlowStrength,
					LLCriticalDamp::getInterpolant(0.05f));
	}
	else
	{
		mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLCriticalDamp::getInterpolant(0.05f));
	}

	// Draw button image, if available.
	// Otherwise draw basic rectangular button.
	if (imagep != NULL)
	{
		// apply automatic 50% alpha fade to disabled image
		LLColor4 disabled_color = mFadeWhenDisabled ? mDisabledImageColor.get() % 0.5f : mDisabledImageColor.get();
		if ( mScaleImage)
		{
			imagep->draw(getLocalRect(), (enabled ? mImageColor.get() : disabled_color) % alpha  );
			if (mCurGlowStrength > 0.01f)
			{
				gGL.setSceneBlendType(glow_type);
				imagep->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha));
				gGL.setSceneBlendType(LLRender::BT_ALPHA);
			}
		}
		else
		{
			imagep->draw(0, 0, (enabled ? mImageColor.get() : disabled_color) % alpha );
			if (mCurGlowStrength > 0.01f)
			{
				gGL.setSceneBlendType(glow_type);
				imagep->drawSolid(0, 0, glow_color % (mCurGlowStrength * alpha));
				gGL.setSceneBlendType(LLRender::BT_ALPHA);
			}
		}
	}
	else
	{
		// no image
		lldebugs << "No image for button " << getName() << llendl;
		// draw it in pink so we can find it
		gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1 % alpha, FALSE);
	}

	// let overlay image and text play well together
	S32 text_left = mLeftHPad;
	S32 text_right = getRect().getWidth() - mRightHPad;
	S32 text_width = getRect().getWidth() - mLeftHPad - mRightHPad;

	// draw overlay image
	if (mImageOverlay.notNull() && mImageOverlay->getWidth() > 1)
	{
		// get max width and height (discard level 0)
		S32 overlay_width;
		S32 overlay_height;

		getOverlayImageSize(overlay_width, overlay_height);

		S32 center_x = getLocalRect().getCenterX();
		S32 center_y = getLocalRect().getCenterY();

		//FUGLY HACK FOR "DEPRESSED" BUTTONS
		if (pressed && mDisplayPressedState)
		{
			center_y--;
			center_x++;
		}

		center_y += (mImageOverlayBottomPad - mImageOverlayTopPad);
		// fade out overlay images on disabled buttons
		LLColor4 overlay_color = mImageOverlayColor.get();
		if (!enabled)
		{
			overlay_color = mImageOverlayDisabledColor.get();
		}
		else if (getToggleState())
		{
			overlay_color = mImageOverlaySelectedColor.get();
		}
		overlay_color.mV[VALPHA] *= alpha;

		switch(mImageOverlayAlignment)
		{
		case LLFontGL::LEFT:
			text_left += overlay_width + mImgOverlayLabelSpace;
			text_width -= overlay_width + mImgOverlayLabelSpace;
			mImageOverlay->draw(
				mLeftHPad,
				center_y - (overlay_height / 2), 
				overlay_width, 
				overlay_height, 
				overlay_color);
			break;
		case LLFontGL::HCENTER:
			mImageOverlay->draw(
				center_x - (overlay_width / 2), 
				center_y - (overlay_height / 2), 
				overlay_width, 
				overlay_height, 
				overlay_color);
			break;
		case LLFontGL::RIGHT:
			text_right -= overlay_width + mImgOverlayLabelSpace;
			text_width -= overlay_width + mImgOverlayLabelSpace;
			mImageOverlay->draw(
				getRect().getWidth() - mRightHPad - overlay_width,
				center_y - (overlay_height / 2), 
				overlay_width, 
				overlay_height, 
				overlay_color);
			break;
		default:
			// draw nothing
			break;
		}
	}

	// Draw label
	if( !label.empty() )
	{
		LLWStringUtil::trim(label);

		S32 x;
		switch( mHAlign )
		{
		case LLFontGL::RIGHT:
			x = text_right;
			break;
		case LLFontGL::HCENTER:
			x = text_left + (text_width / 2);
			break;
		case LLFontGL::LEFT:
		default:
			x = text_left;
			break;
		}

		S32 y_offset = 2 + (getRect().getHeight() - 20)/2;
	
		if (pressed && mDisplayPressedState)
		{
			y_offset--;
			x++;
		}

		mGLFont->render(label, 0, 
			(F32)x, 
			(F32)(mBottomVPad + y_offset), 
			label_color % alpha,
			mHAlign, LLFontGL::BOTTOM,
			LLFontGL::NORMAL,
			mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW,
			U32_MAX, text_width,
			NULL, FALSE, FALSE);
	}

	if (sDebugRects	
		|| (LLView::sEditingUI && this == LLView::sEditingUIView))
	{
		drawDebugRect();
	}
	
	// reset hover status for next frame
	mNeedsHighlight = FALSE;
	
	LLUICtrl::draw();
}

void LLButton::drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size)
{
	if (imagep == NULL) return;
	if (mScaleImage)
	{
		imagep->drawBorder(getLocalRect(), color, size);
	}
	else
	{
		imagep->drawBorder(0, 0, color, size);
	}
}

BOOL LLButton::getToggleState() const
{
    return getValue().asBoolean();
}

void LLButton::setToggleState(BOOL b)
{
	if( b != getToggleState() )
	{
		setControlValue(b); // will fire LLControlVariable callbacks (if any)
		setValue(b);        // may or may not be redundant
		// Unselected label assignments
		autoResize();
	}
}

void LLButton::setFlashing( BOOL b )	
{ 
	if ((bool)b != mFlashing)
	{
		mFlashing = b; 
		mFlashingTimer.reset();
	}
}


BOOL LLButton::toggleState()			
{ 
    bool flipped = ! getToggleState();
	setToggleState(flipped); 

	return flipped; 
}

void LLButton::setLabel( const LLStringExplicit& label )
{
	setLabelUnselected(label);
	setLabelSelected(label);
}

//virtual
BOOL LLButton::setLabelArg( const std::string& key, const LLStringExplicit& text )
{
	mUnselectedLabel.setArg(key, text);
	mSelectedLabel.setArg(key, text);
	return TRUE;
}

void LLButton::setLabelUnselected( const LLStringExplicit& label )
{
	mUnselectedLabel = label;
}

void LLButton::setLabelSelected( const LLStringExplicit& label )
{
	mSelectedLabel = label;
}

const LLUIString& LLButton::getCurrentLabel() const
{
	if( getToggleState() )
	{
		return mSelectedLabel;
	}
	else
	{
		return mUnselectedLabel;
	}
}

void LLButton::setImageUnselected(LLPointer<LLUIImage> image)
{
	mImageUnselected = image;
	if (mImageUnselected.isNull())
	{
		llwarns << "Setting default button image for: " << getName() << " to NULL" << llendl;
	}
}

void LLButton::autoResize()
{
	resize(getCurrentLabel());
}

void LLButton::resize(LLUIString label)
{
	// get label length 
	S32 label_width = mGLFont->getWidth(label.getString());
	// get current btn length 
	S32 btn_width =getRect().getWidth();
    // check if it need resize 
	if (mAutoResize)
	{ 
		S32 min_width = label_width + mLeftHPad + mRightHPad;
		if (mImageOverlay)
		{
			S32 overlay_width = mImageOverlay->getWidth();
			F32 scale_factor = (getRect().getHeight() - (mImageOverlayBottomPad + mImageOverlayTopPad)) / (F32)mImageOverlay->getHeight();
			overlay_width = llround((F32)overlay_width * scale_factor);

			switch(mImageOverlayAlignment)
			{
			case LLFontGL::LEFT:
			case LLFontGL::RIGHT:
				min_width += overlay_width + mImgOverlayLabelSpace;
				break;
			case LLFontGL::HCENTER:
				min_width = llmax(min_width, overlay_width + mLeftHPad + mRightHPad);
				break;
			default:
				// draw nothing
				break;
			}
		}
		if (btn_width < min_width)
		{
			reshape(min_width, getRect().getHeight());
		}
	} 
}
void LLButton::setImages( const std::string &image_name, const std::string &selected_name )
{
	setImageUnselected(LLUI::getUIImage(image_name));
	setImageSelected(LLUI::getUIImage(selected_name));
}

void LLButton::setImageSelected(LLPointer<LLUIImage> image)
{
	mImageSelected = image;
}

void LLButton::setImageColor(const LLColor4& c)		
{ 
	mImageColor = c; 
}

void LLButton::setColor(const LLColor4& color)
{
	setImageColor(color);
}

void LLButton::setImageDisabled(LLPointer<LLUIImage> image)
{
	mImageDisabled = image;
	mDisabledImageColor = mImageColor;
	mFadeWhenDisabled = TRUE;
}

void LLButton::setImageDisabledSelected(LLPointer<LLUIImage> image)
{
	mImageDisabledSelected = image;
	mDisabledImageColor = mImageColor;
	mFadeWhenDisabled = TRUE;
}

void LLButton::setImageHoverSelected(LLPointer<LLUIImage> image)
{
	mImageHoverSelected = image;
}

void LLButton::setImageHoverUnselected(LLPointer<LLUIImage> image)
{
	mImageHoverUnselected = image;
}

void LLButton::setImageFlash(LLPointer<LLUIImage> image)
{
	mImageFlash = image;
}

void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment, const LLColor4& color)
{
	if (image_name.empty())
	{
		mImageOverlay = NULL;
	}
	else
	{
		mImageOverlay = LLUI::getUIImage(image_name);
		mImageOverlayAlignment = alignment;
		mImageOverlayColor = color;
	}
}

void LLButton::setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment, const LLColor4& color)
{
	if (image_id.isNull())
	{
		mImageOverlay = NULL;
	}
	else
	{
		mImageOverlay = LLUI::getUIImageByID(image_id);
		mImageOverlayAlignment = alignment;
		mImageOverlayColor = color;
	}
}

void LLButton::onMouseCaptureLost()
{
	resetMouseDownTimer();
}

//-------------------------------------------------------------------------
// Utilities
//-------------------------------------------------------------------------
S32 round_up(S32 grid, S32 value)
{
	S32 mod = value % grid;

	if (mod > 0)
	{
		// not even multiple
		return value + (grid - mod);
	}
	else
	{
		return value;
	}
}

void LLButton::addImageAttributeToXML(LLXMLNodePtr node, 
									  const LLPointer<LLUIImage> image,
									  const std::string& xml_tag_name) const
{
	if(!image)
		return;

	const std::string image_name = image->getName();

	if(image_name.empty())
		return;

	LLUUID id;
	if(!id.set(image_name, false))
		node->createChild(xml_tag_name.c_str(), TRUE)->setStringValue(image_name);
	else
		node->createChild((xml_tag_name + "_id").c_str(), TRUE)->setUUIDValue(id);
}

// virtual
LLXMLNodePtr LLButton::getXML(bool save_children) const
{
	LLXMLNodePtr node = LLUICtrl::getXML();

	node->setName(LL_BUTTON_TAG);

	node->createChild("label", TRUE)->setStringValue(getLabelUnselected());
	node->createChild("label_selected", TRUE)->setStringValue(getLabelSelected());
	node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont));
	node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign));

	addImageAttributeToXML(node,mImageUnselected,std::string("image_unselected"));
	addImageAttributeToXML(node,mImageSelected,std::string("image_selected"));
	addImageAttributeToXML(node,mImageHoverSelected,std::string("image_hover_selected"));
	addImageAttributeToXML(node,mImageHoverUnselected,std::string("image_hover_unselected"));
	addImageAttributeToXML(node,mImageDisabled,std::string("image_disabled"));
	addImageAttributeToXML(node,mImageDisabledSelected,std::string("image_disabled_selected"));

	node->createChild("scale_image", TRUE)->setBoolValue(mScaleImage);

	return node;
}

void clicked_help(void* data)
{
	LLButton* self = (LLButton*)data;
	if (!self) return;
	
	if (!LLUI::sHtmlHelp)
	{
		return;
	}
	
	LLUI::sHtmlHelp->show(self->getHelpURL());
}

// static
LLView* LLButton::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
	std::string name("button");
	node->getAttributeString("name", name);

	std::string label = name;
	node->getAttributeString("label", label);

	std::string label_selected = label;
	node->getAttributeString("label_selected", label_selected);

	LLFontGL* font = selectFont(node);

	std::string	image_unselected;
	if (node->hasAttribute("image_unselected")) node->getAttributeString("image_unselected",image_unselected);
	
	std::string	image_selected;
	if (node->hasAttribute("image_selected")) node->getAttributeString("image_selected",image_selected);
	
	std::string	image_hover_selected;
	if (node->hasAttribute("image_hover_selected")) node->getAttributeString("image_hover_selected",image_hover_selected);
	
	std::string	image_hover_unselected;
	if (node->hasAttribute("image_hover_unselected")) node->getAttributeString("image_hover_unselected",image_hover_unselected);
	
	std::string	image_disabled_selected;
	if (node->hasAttribute("image_disabled_selected")) node->getAttributeString("image_disabled_selected",image_disabled_selected);
	
	std::string	image_disabled;
	if (node->hasAttribute("image_disabled")) node->getAttributeString("image_disabled",image_disabled);

	std::string	image_overlay;
	node->getAttributeString("image_overlay", image_overlay);

	LLFontGL::HAlign image_overlay_alignment = LLFontGL::HCENTER;
	std::string image_overlay_alignment_string;
	if (node->hasAttribute("image_overlay_alignment"))
	{
		node->getAttributeString("image_overlay_alignment", image_overlay_alignment_string);
		image_overlay_alignment = LLFontGL::hAlignFromName(image_overlay_alignment_string);
	}


	LLButton *button = new LLButton(name, 
			LLRect(),
			image_unselected,
			image_selected,
			LLStringUtil::null, 
			NULL, 
			parent,
			font,
			label,
			label_selected);

	node->getAttributeS32("pad_right", button->mRightHPad);
	node->getAttributeS32("pad_left", button->mLeftHPad);

	BOOL is_toggle = button->getIsToggle();
	node->getAttributeBOOL("toggle", is_toggle);
	button->setIsToggle(is_toggle);

	if(image_hover_selected != LLStringUtil::null) button->setImageHoverSelected(LLUI::getUIImage(image_hover_selected));
	
	if(image_hover_unselected != LLStringUtil::null) button->setImageHoverUnselected(LLUI::getUIImage(image_hover_unselected));
	
	if(image_disabled_selected != LLStringUtil::null) button->setImageDisabledSelected(LLUI::getUIImage(image_disabled_selected));
	
	if(image_disabled != LLStringUtil::null) button->setImageDisabled(LLUI::getUIImage(image_disabled));
	
	if(image_overlay != LLStringUtil::null) button->setImageOverlay(image_overlay, image_overlay_alignment);

	if (node->hasAttribute("halign"))
	{
		LLFontGL::HAlign halign = selectFontHAlign(node);
		button->setHAlign(halign);
	}

	if (node->hasAttribute("scale_image"))
	{
		BOOL	needsScale = FALSE;
		node->getAttributeBOOL("scale_image",needsScale);
		button->setScaleImage( needsScale );
	}

	if(label.empty())
	{
		button->setLabelUnselected(node->getTextContents());
	}
	if (label_selected.empty())
	{
		button->setLabelSelected(node->getTextContents());
	}
		
	if (node->hasAttribute("help_url")) 
	{
		std::string	help_url;
		node->getAttributeString("help_url",help_url);
		button->setHelpURLCallback(help_url);
	}

	button->initFromXML(node, parent);
	
	return button;
}

void LLButton::resetMouseDownTimer()
{
	mMouseDownTimer.stop();
	mMouseDownTimer.reset();
}

BOOL LLButton::handleDoubleClick(S32 x, S32 y, MASK mask)
{
	// just treat a double click as a second click
	return handleMouseDown(x, y, mask);
}

void LLButton::setHelpURLCallback(const std::string &help_url)
{
	mHelpURL = help_url;
	setClickedCallback(clicked_help,this);
}
