Skip to content
Snippets Groups Projects
  • Josh Bell's avatar
    810a0b3d
    svn merge -r 88872:89137 svn+ssh://svn.lindenlab.com/svn/linden/qa/qar-652 --> release · 810a0b3d
    Josh Bell authored
    Effectively:
    svn merge -r 87048:88773 svn+ssh://svn.lindenlab.com/svn/linden/branches/Branch_1-22-Server
    * Plus some gcc4.x fixes
    
    In detail:
    
    * Bump version numbers to 1.22.0 Server
    * Updated files for enabled conductor.
    * Added tarball pulling during deploy for central servers.
    * DEV-15160 Logins not remembering start location on beta grid
    * DEV-15167 Random failure with set-classified-stats
    * DEV-15193 Viewer Passing Through Unsanitized Strings to DB when creating classifieds.
    * Add --location to the curl invocation in fetch_agni_ws_snapshot
    * QAR-609 Some Havok4 bug fixes
    * QAR-460 Automatic XUI selection & Kick-ass Linux Fonts MergeMe
    * DEV-14704 1.21.1.86182 revealing hidden groups in profile
    * DEV-14641 Change automated Subject Line messages for IM-to-Email to avoid SPAM association
    * DEV-15387 Write unit test for LLStringFn::strip_invalid_xml().
    * Changed getpass.getuser() to os.getuid() as getuser() was returning the saved-UID of the process (root user, which kic
    ks off parabuild as parabuild u
    ser). We want os.getuid() as it correctly returns the parabuild user's uid. / Stupid mistake using os.getuid().. .fixed
    by using the pwd module to return the unix username given the userid from os.getuid()
    * QAR-623 More single ref attachment awesomeness testing
    * Committed temporary fix for running dwell and query-cache on deneb.lindenlab.com, since it's in the DFW colo with the
    current asset cluster.
    * DEV-15783 Groups not shown in search wont appear in your profile while using webservice
    * QAR-642 Even more single ref attachment awesomeness testing
    
    From Branch_1-21-Server (which didn't go directly into release)
    * addinng the phputf8 library found from http://phputf8.sourceforge.net/ and liscensed LGPL. For DEV-15202
    * Checking in a fix for settle balance portion proc - the event query was moved.
    * fix for DEV-15202 - replace non-utf8 with REPLACEMENT CHAR. See jira for more info.
    * Checking return value from /proc/vault/charge, must get back a string.
    * removed code used for the browser hud AB test.
    * Changed TTL from stupid default of 1234 to 5
    * DEV-15788  Fixing crashloganalyzer.py cleanup of unparseable crash reports.
    * DEV-15737  offline scripted IMs not working inprocess_im_using_ws
    
    Merge conflict resolutions/fixes include:
    * Addition of indra/test/llstring_tut.cpp to indra/test/test.vcproj migrated to CMakeLists.txt
    * Changes to BuildParams, scripts/automated_build_scripts/* reviewed w/ cg
    * Changes to deploy scripts (scripts/farm, install_indra.py, farm_deploy) based on the 1.22 changes except for mkpersist
    dir addition (retained from release)
    * Changes to scripts/crash_report/crashloganalyzer.py retained from 1.22, per Mani
    * scripts/generate_indra_xml.py, etc/services-station.dtd, indra/newsim/llagentinfo.cpp analyzed carefully by josh
    * indra/llcommon/llstring.h required fix for client build ("" --> std::basic_string<T>()) in LLStringBase<T> initializer
    * indra/llphysics/abstract/llphysicsshape.h #include syntax changed from <llmath/llvolume.h> to "llvolume.h"
    
    Psst, don't tell anyone, but the dataserver-is-deprecated
    810a0b3d
    History
    svn merge -r 88872:89137 svn+ssh://svn.lindenlab.com/svn/linden/qa/qar-652 --> release
    Josh Bell authored
    Effectively:
    svn merge -r 87048:88773 svn+ssh://svn.lindenlab.com/svn/linden/branches/Branch_1-22-Server
    * Plus some gcc4.x fixes
    
    In detail:
    
    * Bump version numbers to 1.22.0 Server
    * Updated files for enabled conductor.
    * Added tarball pulling during deploy for central servers.
    * DEV-15160 Logins not remembering start location on beta grid
    * DEV-15167 Random failure with set-classified-stats
    * DEV-15193 Viewer Passing Through Unsanitized Strings to DB when creating classifieds.
    * Add --location to the curl invocation in fetch_agni_ws_snapshot
    * QAR-609 Some Havok4 bug fixes
    * QAR-460 Automatic XUI selection & Kick-ass Linux Fonts MergeMe
    * DEV-14704 1.21.1.86182 revealing hidden groups in profile
    * DEV-14641 Change automated Subject Line messages for IM-to-Email to avoid SPAM association
    * DEV-15387 Write unit test for LLStringFn::strip_invalid_xml().
    * Changed getpass.getuser() to os.getuid() as getuser() was returning the saved-UID of the process (root user, which kic
    ks off parabuild as parabuild u
    ser). We want os.getuid() as it correctly returns the parabuild user's uid. / Stupid mistake using os.getuid().. .fixed
    by using the pwd module to return the unix username given the userid from os.getuid()
    * QAR-623 More single ref attachment awesomeness testing
    * Committed temporary fix for running dwell and query-cache on deneb.lindenlab.com, since it's in the DFW colo with the
    current asset cluster.
    * DEV-15783 Groups not shown in search wont appear in your profile while using webservice
    * QAR-642 Even more single ref attachment awesomeness testing
    
    From Branch_1-21-Server (which didn't go directly into release)
    * addinng the phputf8 library found from http://phputf8.sourceforge.net/ and liscensed LGPL. For DEV-15202
    * Checking in a fix for settle balance portion proc - the event query was moved.
    * fix for DEV-15202 - replace non-utf8 with REPLACEMENT CHAR. See jira for more info.
    * Checking return value from /proc/vault/charge, must get back a string.
    * removed code used for the browser hud AB test.
    * Changed TTL from stupid default of 1234 to 5
    * DEV-15788  Fixing crashloganalyzer.py cleanup of unparseable crash reports.
    * DEV-15737  offline scripted IMs not working inprocess_im_using_ws
    
    Merge conflict resolutions/fixes include:
    * Addition of indra/test/llstring_tut.cpp to indra/test/test.vcproj migrated to CMakeLists.txt
    * Changes to BuildParams, scripts/automated_build_scripts/* reviewed w/ cg
    * Changes to deploy scripts (scripts/farm, install_indra.py, farm_deploy) based on the 1.22 changes except for mkpersist
    dir addition (retained from release)
    * Changes to scripts/crash_report/crashloganalyzer.py retained from 1.22, per Mani
    * scripts/generate_indra_xml.py, etc/services-station.dtd, indra/newsim/llagentinfo.cpp analyzed carefully by josh
    * indra/llcommon/llstring.h required fix for client build ("" --> std::basic_string<T>()) in LLStringBase<T> initializer
    * indra/llphysics/abstract/llphysicsshape.h #include syntax changed from <llmath/llvolume.h> to "llvolume.h"
    
    Psst, don't tell anyone, but the dataserver-is-deprecated
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
llbutton.cpp 26.85 KiB
/** 
 * @file llbutton.cpp
 * @brief LLButton base class
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2007, 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://secondlife.com/developers/opensource/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://secondlife.com/developers/opensource/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 "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 LLString& name, const LLRect& rect, const LLString& control_name, void (*click_callback)(void*), void *callback_data)
:	LLUICtrl(name, rect, TRUE, NULL, NULL),
	mClickedCallback( click_callback ),
	mMouseDownCallback( NULL ),
	mMouseUpCallback( NULL ),
	mHeldDownCallback( NULL ),
	mGLFont( NULL ),
	mMouseDownFrame( 0 ),
	mHeldDownDelay( 0.5f ),			// seconds until held-down callback is called
	mHeldDownFrameDelay( 0 ),
	mImageUnselected( NULL ),
	mImageSelected( NULL ),
	mImageHoverSelected( NULL ),
	mImageHoverUnselected( NULL ),
	mImageDisabled( NULL ),
	mImageDisabledSelected( NULL ),
	mToggleState( FALSE ),
	mIsToggle( FALSE ),
	mScaleImage( TRUE ),
	mDropShadowedText( TRUE ),
	mBorderEnabled( FALSE ),
	mFlashing( FALSE ),
	mHAlign( LLFontGL::HCENTER ),
	mLeftHPad( LLBUTTON_H_PAD ),
	mRightHPad( LLBUTTON_H_PAD ),
	mHoverGlowStrength(0.15f),
	mCurGlowStrength(0.f),
	mNeedsHighlight(FALSE),
	mCommitOnReturn(TRUE),
	mImagep( NULL )
{
	mUnselectedLabel = name;
	mSelectedLabel = name;

	setImageUnselected("button_enabled_32x128.tga");
	setImageSelected("button_enabled_selected_32x128.tga");
	setImageDisabled("button_disabled_32x128.tga");
	setImageDisabledSelected("button_disabled_32x128.tga");

	mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" );
	mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" );

	init(click_callback, callback_data, NULL, control_name);
}


LLButton::LLButton(const LLString& name, const LLRect& rect, 
				   const LLString &unselected_image_name, 
				   const LLString &selected_image_name, 
				   const LLString& control_name,
				   void (*click_callback)(void*),
				   void *callback_data,
				   const LLFontGL *font,
				   const LLString& unselected_label, 
				   const LLString& selected_label )
:	LLUICtrl(name, rect, TRUE, NULL, NULL),
	mClickedCallback( click_callback ),
	mMouseDownCallback( NULL ),
	mMouseUpCallback( NULL ),
	mHeldDownCallback( NULL ),
	mGLFont( NULL ),
	mMouseDownFrame( 0 ),
	mHeldDownDelay( 0.5f ),			// seconds until held-down callback is called
	mHeldDownFrameDelay( 0 ),
	mImageUnselected( NULL ),
	mImageSelected( NULL ),
	mImageHoverSelected( NULL ),
	mImageHoverUnselected( NULL ),
	mImageDisabled( NULL ),
	mImageDisabledSelected( NULL ),
	mToggleState( FALSE ),
	mIsToggle( FALSE ),
	mScaleImage( TRUE ),
	mDropShadowedText( TRUE ),
	mBorderEnabled( FALSE ),
	mFlashing( FALSE ),
	mHAlign( LLFontGL::HCENTER ),
	mLeftHPad( LLBUTTON_H_PAD ), 
	mRightHPad( LLBUTTON_H_PAD ),
	mHoverGlowStrength(0.25f),
	mCurGlowStrength(0.f),
	mNeedsHighlight(FALSE),
	mCommitOnReturn(TRUE),
	mImagep( NULL )
{
	mUnselectedLabel = unselected_label;
	mSelectedLabel = selected_label;

	// by default, disabled color is same as enabled
	mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" );
	mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" );

	if( unselected_image_name != "" )
	{
		// user-specified image - don't use fixed borders unless requested
		setImageUnselected(unselected_image_name);
		setImageDisabled(unselected_image_name);
		
		mDisabledImageColor.mV[VALPHA] = 0.5f;
		mScaleImage = FALSE;
	}
	else
	{
		setImageUnselected("button_enabled_32x128.tga");
		setImageDisabled("button_disabled_32x128.tga");
	}

	if( selected_image_name != "" )
	{
		// user-specified image - don't use fixed borders unless requested
		setImageSelected(selected_image_name);
		setImageDisabledSelected(selected_image_name);

		mDisabledImageColor.mV[VALPHA] = 0.5f;
		mScaleImage = FALSE;
	}
	else
	{
		setImageSelected("button_enabled_selected_32x128.tga");
		setImageDisabledSelected("button_disabled_32x128.tga");
	}

	init(click_callback, callback_data, font, control_name);
}

void LLButton::init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const LLString& control_name)
{
	mGLFont = ( font ? font : LLFontGL::sSansSerif);

	// Hack to make sure there is space for at least one character
	if (getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(" "))
	{
		// Use old defaults
		mLeftHPad = LLBUTTON_ORIG_H_PAD;
		mRightHPad = LLBUTTON_ORIG_H_PAD;
	}
	
	mCallbackUserData = callback_data;
	mMouseDownTimer.stop();

	setControlName(control_name, NULL);

	mUnselectedLabelColor = (			LLUI::sColorsGroup->getColor( "ButtonLabelColor" ) );
	mSelectedLabelColor = (			LLUI::sColorsGroup->getColor( "ButtonLabelSelectedColor" ) );
	mDisabledLabelColor = (			LLUI::sColorsGroup->getColor( "ButtonLabelDisabledColor" ) );
	mDisabledSelectedLabelColor = (	LLUI::sColorsGroup->getColor( "ButtonLabelSelectedDisabledColor" ) );
	mHighlightColor = (				LLUI::sColorsGroup->getColor( "ButtonUnselectedFgColor" ) );
	mUnselectedBgColor = (				LLUI::sColorsGroup->getColor( "ButtonUnselectedBgColor" ) );
	mSelectedBgColor = (				LLUI::sColorsGroup->getColor( "ButtonSelectedBgColor" ) );

	mImageOverlayAlignment = LLFontGL::HCENTER;
	mImageOverlayColor = LLColor4::white;
}

LLButton::~LLButton()
{
 	if( hasMouseCapture() )
	{
		gFocusMgr.setMouseCapture( NULL );
	}
}

// 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 mClickedCallback
	// LAST, otherwise this becomes deleted memory.
	LLUICtrl::onCommit();

	if (mMouseDownCallback)
	{
		(*mMouseDownCallback)(mCallbackUserData);
	}
	
	if (mMouseUpCallback)
	{
		(*mMouseUpCallback)(mCallbackUserData);
	}

	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
	if (mClickedCallback)
	{
		(*mClickedCallback)( mCallbackUserData );
	}
}



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

		if (mClickedCallback)
		{
			(*mClickedCallback)( mCallbackUserData );
		}
		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;

		if (mClickedCallback)
		{
			(*mClickedCallback)( mCallbackUserData );
		}
	}
	return handled;
}


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

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

	if (mMouseDownCallback)
	{
		(*mMouseDownCallback)(mCallbackUserData);
	}

	mMouseDownTimer.start();
	mMouseDownFrame = (S32) LLFrameTimer::getFrameCount();
	
	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 );

		// Regardless of where mouseup occurs, handle callback
		if (mMouseUpCallback)
		{
			(*mMouseUpCallback)(mCallbackUserData);
		}

		mMouseDownTimer.stop();
		mMouseDownTimer.reset();

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

			if (mClickedCallback)
			{
				(*mClickedCallback)( mCallbackUserData );
			}			
		}
	}

	return TRUE;
}


BOOL LLButton::handleHover(S32 x, S32 y, MASK mask)
{
	LLMouseHandler* other_captor = gFocusMgr.getMouseCapture();
	mNeedsHighlight = other_captor == NULL || 
				other_captor == this ||
				// this following bit is to support modal dialogs
				(other_captor->isView() && hasAncestor((LLView*)other_captor));

	if (mMouseDownTimer.getStarted() && NULL != mHeldDownCallback)
	{
		F32 elapsed = getHeldDownTime();
		if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame)
		{
			mHeldDownCallback( mCallbackUserData );		
		}
	}

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


// virtual
void LLButton::draw()
{
	BOOL flash = FALSE;
	if( mFlashing )
	{
		F32 elapsed = mFlashingTimer.getElapsedTimeF32();
		S32 flash_count = S32(elapsed * LLUI::sConfigGroup->getF32("ButtonFlashRate") * 2.f);
		// flash on or off?
		flash = (flash_count % 2 == 0) || flash_count > S32((F32)LLUI::sConfigGroup->getS32("ButtonFlashCount") * 2.f);
	}

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

	// Unselected image assignments
	S32 local_mouse_x;
	S32 local_mouse_y;
	LLCoordWindow cursor_pos_window;
	getWindow()->getCursorPosition(&cursor_pos_window);
	LLCoordGL cursor_pos_gl;
	getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl);
	cursor_pos_gl.mX = llround((F32)cursor_pos_gl.mX / LLUI::sGLScaleFactor.mV[VX]);
	cursor_pos_gl.mY = llround((F32)cursor_pos_gl.mY / LLUI::sGLScaleFactor.mV[VY]);
	screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &local_mouse_x, &local_mouse_y);

	BOOL pressed = pressed_by_keyboard 
					|| (hasMouseCapture() && pointInView(local_mouse_x, local_mouse_y)) 
					|| mToggleState;
	
	BOOL use_glow_effect = FALSE;
	if ( mNeedsHighlight || flash )
	{
		if (pressed)
		{
			if (mImageHoverSelected)
			{
				mImagep = mImageHoverSelected;
			}
			else
			{
				mImagep = mImageSelected;
				use_glow_effect = TRUE;
			}
		}
		else
		{
			if (mImageHoverUnselected)
			{
				mImagep = mImageHoverUnselected;
			}
			else
			{
				mImagep = mImageUnselected;
				use_glow_effect = TRUE;
			}
		}
	}
	else if ( pressed )
	{
		mImagep = mImageSelected;
	}
	else
	{
		mImagep = 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() 
		&& 
			( (getEnabled() && getTentative()) 
			|| (!getEnabled() && pressed ) ) )
	{
		mImagep = mImageDisabledSelected;
	}
	else if (!mImageDisabled.isNull() 
		&& !getEnabled() 
		&& !pressed)
	{
		mImagep = mImageDisabled;
	}
	if (mNeedsHighlight && !mImagep)
	{
		use_glow_effect = TRUE;
	}

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

	// label changes when button state changes, not when pressed
	if ( getEnabled() )
	{
		if ( mToggleState )
		{
			label_color = mSelectedLabelColor;
		}
		else
		{
			label_color = mUnselectedLabelColor;
		}
	}
	else
	{
		if ( mToggleState )
		{
			label_color = mDisabledSelectedLabelColor;
		}
		else
		{
			label_color = mDisabledLabelColor;
		}
	}

	// Unselected label assignments
	LLWString label;

	if( mToggleState )
	{
		if( getEnabled() || mDisabledSelectedLabel.empty() )
		{
			label = mSelectedLabel;
		}
		else
		{
			label = mDisabledSelectedLabel;
		}
	}
	else
	{
		if( getEnabled() || mDisabledLabel.empty() )
		{
			label = mUnselectedLabel;
		}
		else
		{
			label = mDisabledLabel;
		}
	}

	// overlay with keyboard focus border
	if (hasFocus())
	{
		F32 lerp_amt = gFocusMgr.getFocusFlashAmt();
		drawBorder(gFocusMgr.getFocusColor(), llround(lerp(1.f, 3.f, lerp_amt)));
	}
	
	if (use_glow_effect)
	{
		mCurGlowStrength = lerp(mCurGlowStrength, 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 (mImagep.notNull())
	{
		if ( mScaleImage)
		{
			mImagep->draw(getLocalRect(), getEnabled() ? mImageColor : mDisabledImageColor  );
			if (mCurGlowStrength > 0.01f)
			{
				gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA);
				mImagep->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), LLColor4(1.f, 1.f, 1.f, mCurGlowStrength));
				gGL.setSceneBlendType(LLRender::BT_ALPHA);
			}
		}
		else
		{
			mImagep->draw(0, 0, getEnabled() ? mImageColor : mDisabledImageColor );
			if (mCurGlowStrength > 0.01f)
			{
				gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA);
				mImagep->drawSolid(0, 0, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength));
				gGL.setSceneBlendType(LLRender::BT_ALPHA);
			}
		}
	}
	else
	{
		// no image
		llwarns << "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, 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())
	{
		// get max width and height (discard level 0)
		S32 overlay_width = mImageOverlay->getWidth();
		S32 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);

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

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

		// fade out overlay images on disabled buttons
		LLColor4 overlay_color = mImageOverlayColor;
		if (!getEnabled())
		{
			overlay_color.mV[VALPHA] = 0.5f;
		}
		switch(mImageOverlayAlignment)
		{
		case LLFontGL::LEFT:
			text_left += overlay_width + 1;
			text_width -= overlay_width + 1;
			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 + 1;				
			text_width -= overlay_width + 1;
			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() )
	{
		LLWString::trim(label);

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

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

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

	if (sDebugRects	
		|| (LLView::sEditingUI && this == LLView::sEditingUIView))
	{
		drawDebugRect();
	}

	// reset hover status for next frame
	mNeedsHighlight = FALSE;
}

void LLButton::drawBorder(const LLColor4& color, S32 size)
{
	if (mScaleImage)
	{
		mImagep->drawBorder(getLocalRect(), color, size);
	}
	else
	{
		mImagep->drawBorder(0, 0, color, size);
	}
}

void LLButton::setClickedCallback(void (*cb)(void*), void* userdata)
{
	mClickedCallback = cb;
	if (userdata)
	{
		mCallbackUserData = userdata;
	}
}


void LLButton::setToggleState(BOOL b)
{
	if( b != mToggleState )
	{
		setControlValue(b); // will fire LLControlVariable callbacks (if any)
		mToggleState = b; // may or may not be redundant
	}
}

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


BOOL LLButton::toggleState()			
{ 
	setToggleState( !mToggleState ); 
	return mToggleState; 
}

void LLButton::setValue(const LLSD& value )
{
	mToggleState = value.asBoolean();
}

LLSD LLButton::getValue() const
{
	return mToggleState == TRUE;
}

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

//virtual
BOOL LLButton::setLabelArg( const LLString& 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;
}

void LLButton::setDisabledLabel( const LLStringExplicit& label )
{
	mDisabledLabel = label;
}

void LLButton::setDisabledSelectedLabel( const LLStringExplicit& label )
{
	mDisabledSelectedLabel = label;
}

void LLButton::setImageUnselected(LLPointer<LLUIImage> image)
{
	mImageUnselected = image;
}

void LLButton::setImages( const LLString &image_name, const LLString &selected_name )
{
	setImageUnselected(image_name);
	setImageSelected(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;
	mDisabledImageColor.mV[VALPHA] *= 0.5f;
}

void LLButton::setImageDisabledSelected(LLPointer<LLUIImage> image)
{
	mImageDisabledSelected = image;
	mDisabledImageColor = mImageColor;
	mDisabledImageColor.mV[VALPHA] *= 0.5f;
}

void LLButton::setDisabledImages( const LLString &image_name, const LLString &selected_name, const LLColor4& c )
{
	setImageDisabled(image_name);
	setImageDisabledSelected(selected_name);
	mDisabledImageColor = c;
}

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

void LLButton::setDisabledImages( const LLString &image_name, const LLString &selected_name)
{
	LLColor4 clr = mImageColor;
	clr.mV[VALPHA] *= .5f;
	setDisabledImages( image_name, selected_name, clr );
}

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

void LLButton::setHoverImages( const LLString& image_name, const LLString& selected_name )
{
	setImageHoverUnselected(image_name);
	setImageHoverSelected(selected_name);
}

void LLButton::setImageOverlay(const LLString &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::onMouseCaptureLost()
{
	mMouseDownTimer.stop();
	mMouseDownTimer.reset();
}

//-------------------------------------------------------------------------
// 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::setImageUnselected(const LLString &image_name)
{	
	setImageUnselected(LLUI::getUIImage(image_name));
	mImageUnselectedName = image_name;
}

void			LLButton::setImageSelected(const LLString &image_name)
{	
	setImageSelected(LLUI::getUIImage(image_name));
	mImageSelectedName = image_name;
}

void			LLButton::setImageHoverSelected(const LLString &image_name)
{
	setImageHoverSelected(LLUI::getUIImage(image_name));
	mImageHoverSelectedName = image_name;
}

void			LLButton::setImageHoverUnselected(const LLString &image_name)
{
	setImageHoverUnselected(LLUI::getUIImage(image_name));
	mImageHoverUnselectedName = image_name;
}

void			LLButton::setImageDisabled(const LLString &image_name)
{
	setImageDisabled(LLUI::getUIImage(image_name));
	mImageDisabledName = image_name;
}

void			LLButton::setImageDisabledSelected(const LLString &image_name)
{
	setImageDisabledSelected(LLUI::getUIImage(image_name));
	mImageDisabledSelectedName = image_name;
}

void LLButton::addImageAttributeToXML(LLXMLNodePtr node, 
									  const LLString& image_name,
									  const LLUUID&	image_id,
									  const LLString& xml_tag_name) const
{
	if( !image_name.empty() )
	{
		node->createChild(xml_tag_name, TRUE)->setStringValue(image_name);
	}
	else if( image_id != LLUUID::null )
	{
		node->createChild(xml_tag_name + "_id", TRUE)->setUUIDValue(image_id);
	}
}

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

	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,mImageUnselectedName,mImageUnselectedID,"image_unselected");
	addImageAttributeToXML(node,mImageSelectedName,mImageSelectedID,"image_selected");
	addImageAttributeToXML(node,mImageHoverSelectedName,mImageHoverSelectedID,"image_hover_selected");
	addImageAttributeToXML(node,mImageHoverUnselectedName,mImageHoverUnselectedID,"image_hover_unselected");
	addImageAttributeToXML(node,mImageDisabledName,mImageDisabledID,"image_disabled");
	addImageAttributeToXML(node,mImageDisabledSelectedName,mImageDisabledSelectedID,"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)
{
	LLString name("button");
	node->getAttributeString("name", name);

	LLString label = name;
	node->getAttributeString("label", label);

	LLString label_selected = label;
	node->getAttributeString("label_selected", label_selected);

	LLFontGL* font = selectFont(node);

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

	LLString	image_overlay;
	node->getAttributeString("image_overlay", image_overlay);

	LLFontGL::HAlign image_overlay_alignment = LLFontGL::HCENTER;
	LLString 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,
			"", 
			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 != LLString::null) button->setImageHoverSelected(image_hover_selected);
	
	if(image_hover_unselected != LLString::null) button->setImageHoverUnselected(image_hover_unselected);
	
	if(image_disabled_selected != LLString::null) button->setImageDisabledSelected(image_disabled_selected );
	
	if(image_disabled != LLString::null) button->setImageDisabled(image_disabled);
	
	if(image_overlay != LLString::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")) 
	{
		LLString	help_url;
		node->getAttributeString("help_url",help_url);
		button->setHelpURLCallback(help_url);
	}

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

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