/** 
 * @file lllineeditor.cpp
 * @brief LLLineEditor base class
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

// Text editor widget to let users enter a single line.

#include "linden_common.h"
 
#include "lllineeditor.h"

#include "audioengine.h"
#include "llmath.h"
#include "llfontgl.h"
#include "llgl.h"
#include "sound_ids.h"
#include "lltimer.h"

//#include "llclipboard.h"
#include "llcontrol.h"
#include "llbutton.h"
#include "llfocusmgr.h"
#include "llkeyboard.h"
#include "llrect.h"
#include "llresmgr.h"
#include "llstring.h"
#include "llwindow.h"
#include "llui.h"
#include "lluictrlfactory.h"
#include "llclipboard.h"

//
// Imported globals
//

//
// Constants
//

const S32	UI_LINEEDITOR_CURSOR_THICKNESS = 2;
const S32	UI_LINEEDITOR_H_PAD = 2;
const S32	UI_LINEEDITOR_V_PAD = 1;
const F32	CURSOR_FLASH_DELAY = 1.0f;  // in seconds
const S32	SCROLL_INCREMENT_ADD = 0;	// make space for typing
const S32   SCROLL_INCREMENT_DEL = 4;	// make space for baskspacing
const F32   AUTO_SCROLL_TIME = 0.05f;
const F32	LABEL_HPAD = 5.f;

// This is a friend class of and is only used by LLLineEditor
class LLLineEditorRollback
{
public:
	LLLineEditorRollback( LLLineEditor* ed )
		:
		mCursorPos( ed->mCursorPos ),
		mScrollHPos( ed->mScrollHPos ),
		mIsSelecting( ed->mIsSelecting ),
		mSelectionStart( ed->mSelectionStart ),
		mSelectionEnd( ed->mSelectionEnd )
	{
		mText = ed->getText();
	}

	void doRollback( LLLineEditor* ed )
	{
		ed->mCursorPos = mCursorPos;
		ed->mScrollHPos = mScrollHPos;
		ed->mIsSelecting = mIsSelecting;
		ed->mSelectionStart = mSelectionStart;
		ed->mSelectionEnd = mSelectionEnd;
		ed->mText = mText;
	}

	LLString getText()   { return mText; }

private:
	LLString mText;
	S32		mCursorPos;
	S32		mScrollHPos;
	BOOL	mIsSelecting;
	S32		mSelectionStart;
	S32		mSelectionEnd;
};


//
// Member functions
//
 
LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect,
						   const LLString& default_text, const LLFontGL* font,
						   S32 max_length_bytes,
						   void (*commit_callback)(LLUICtrl* caller, void* user_data ),
						   void (*keystroke_callback)(LLLineEditor* caller, void* user_data ),
						   void (*focus_lost_callback)(LLLineEditor* caller, void* user_data ),
						   void* userdata,
						   LLLinePrevalidateFunc prevalidate_func,
						   LLViewBorder::EBevel border_bevel,
						   LLViewBorder::EStyle border_style,
						   S32 border_thickness)
	:
		LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ),
		mMaxLengthChars(max_length_bytes),
		mMaxLengthBytes(max_length_bytes),
		mCursorPos( 0 ),
		mScrollHPos( 0 ),
		mBorderLeft(0),
		mBorderRight(0),
		mCommitOnFocusLost( TRUE ),
		mKeystrokeCallback( keystroke_callback ),
		mFocusLostCallback( focus_lost_callback ),
		mIsSelecting( FALSE ),
		mSelectionStart( 0 ),
		mSelectionEnd( 0 ),
		mLastSelectionX(-1),
		mLastSelectionY(-1),
		mPrevalidateFunc( prevalidate_func ),
		mCursorColor(		LLUI::sColorsGroup->getColor( "TextCursorColor" ) ),
		mFgColor(			LLUI::sColorsGroup->getColor( "TextFgColor" ) ),
		mReadOnlyFgColor(	LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ),
		mTentativeFgColor(	LLUI::sColorsGroup->getColor( "TextFgTentativeColor" ) ),
		mWriteableBgColor(	LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ),
		mReadOnlyBgColor(	LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ),
		mFocusBgColor(		LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ),
		mBorderThickness( border_thickness ),
		mIgnoreArrowKeys( FALSE ),
		mIgnoreTab( TRUE ),
		mDrawAsterixes( FALSE ),
		mHandleEditKeysDirectly( FALSE ),
		mSelectAllonFocusReceived( FALSE ),
		mPassDelete(FALSE),
		mReadOnly(FALSE)
{
	llassert( max_length_bytes > 0 );

	if (font)
	{
		mGLFont = font;
	}
	else
	{
		mGLFont = LLFontGL::sSansSerifSmall;
	}

	mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft;
	mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight;

	mScrollTimer.reset();

	setText(default_text);

	setCursor(mText.length());

	// Scalable UI somehow made these rectangles off-by-one.
	// I don't know why. JC
	LLRect border_rect(0, mRect.getHeight()-1, mRect.getWidth()-1, 0);
	mBorder = new LLViewBorder( "line ed border", border_rect, border_bevel, border_style, mBorderThickness );
	addChild( mBorder );
	mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM);
}


LLLineEditor::~LLLineEditor()
{
	mFocusLostCallback = NULL;
	mCommitOnFocusLost = FALSE;

	gFocusMgr.releaseFocusIfNeeded( this );

	if( gEditMenuHandler == this )
	{
		gEditMenuHandler = NULL;
	}
}

//virtual
EWidgetType LLLineEditor::getWidgetType() const
{
	return WIDGET_TYPE_LINE_EDITOR;
}

//virtual
LLString LLLineEditor::getWidgetTag() const
{
	return LL_LINE_EDITOR_TAG;
}

void LLLineEditor::onFocusLost()
{
	if( mFocusLostCallback )
	{
		mFocusLostCallback( this, mCallbackUserData );
	}
	
	if( mCommitOnFocusLost ) 
	{
		onCommit();
	}

	if( gEditMenuHandler == this )
	{
		gEditMenuHandler = NULL;
	}

	getWindow()->showCursorFromMouseMove();
}

void LLLineEditor::onCommit()
{
	LLUICtrl::onCommit();
	selectAll();
}

void LLLineEditor::reshape(S32 width, S32 height, BOOL called_from_parent)
{
	LLUICtrl::reshape(width, height, called_from_parent );

	mMaxHPixels = mRect.getWidth() - 2 * (mBorderThickness + UI_LINEEDITOR_H_PAD) + 1 - mBorderRight;
}


void LLLineEditor::setEnabled(BOOL enabled)
{
	mReadOnly = !enabled;
	setTabStop(!mReadOnly);
}


void LLLineEditor::setMaxTextLength(S32 max_text_length)
{
	S32 max_len = llmax(0, max_text_length);
	mMaxLengthBytes = max_len;
	mMaxLengthChars = max_len;
} 

void LLLineEditor::setBorderWidth(S32 left, S32 right)
{
	mBorderLeft = llclamp(left, 0, mRect.getWidth());
	mBorderRight = llclamp(right, 0, mRect.getWidth());
	mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft;
	mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight;
}

void LLLineEditor::setLabel(const LLString &new_label)
{
	mLabel = new_label;
}

void LLLineEditor::setText(const LLString &new_text)
{
	// If new text is identical, don't copy and don't move insertion point
	if (mText.getString() == new_text)
	{
		return;
	}

	// Check to see if entire field is selected.
	S32 len = mText.length();
	BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len ) 
		|| ( mSelectionStart == len && mSelectionEnd == 0 ));

	LLString truncated_utf8 = new_text;
	if (truncated_utf8.size() > (U32)mMaxLengthBytes)
	{
		utf8str_truncate(truncated_utf8, mMaxLengthBytes);
	}
	mText.assign(truncated_utf8);
	mText.truncate(mMaxLengthChars);

	if (allSelected)
	{
		// ...keep whole thing selected
		selectAll();
	}
	else
	{
		// try to preserve insertion point, but deselect text
		deselect();
	}
	setCursor(llmin((S32)mText.length(), getCursor()));
}


// Picks a new cursor position based on the actual screen size of text being drawn.
void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x )
{
	const llwchar* wtext = mText.getWString().c_str();
	LLWString asterix_text;
	if (mDrawAsterixes)
	{
		for (S32 i = 0; i < mText.length(); i++)
		{
			asterix_text += '*';
		}
		wtext = asterix_text.c_str();
	}

	S32 cursor_pos =
		mScrollHPos + 
		mGLFont->charFromPixelOffset(
			wtext, mScrollHPos,
			(F32)(local_mouse_x - mMinHPixels),
			(F32)(mMaxHPixels - mMinHPixels + 1)); // min-max range is inclusive
	setCursor(cursor_pos);
}

void LLLineEditor::setCursor( S32 pos )
{
	S32 old_cursor_pos = getCursor();
	mCursorPos = llclamp( pos, 0, mText.length());

	S32 pixels_after_scroll = findPixelNearestPos();
	if( pixels_after_scroll > mMaxHPixels )
	{
		S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos);
		S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mMaxHPixels - mMinHPixels + width_chars_to_left))); 
		S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mMaxHPixels - mMinHPixels), mText.length(), getCursor());
		if (old_cursor_pos == last_visible_char)
		{
			mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD));
		}
		else
		{
			mScrollHPos = min_scroll;
		}
	}
	else if (getCursor() < mScrollHPos)
	{
		if (old_cursor_pos == mScrollHPos)
		{
			mScrollHPos = llmax(0, llmin(getCursor(), mScrollHPos - SCROLL_INCREMENT_DEL));
		}
		else
		{
			mScrollHPos = getCursor();
		}
	}
}


void LLLineEditor::setCursorToEnd()
{
	setCursor(mText.length());
	deselect();
}

BOOL LLLineEditor::canDeselect()
{
	return hasSelection();
}


void LLLineEditor::deselect()
{
	mSelectionStart = 0;
	mSelectionEnd = 0;
	mIsSelecting = FALSE;
}


void LLLineEditor::startSelection()
{
	mIsSelecting = TRUE;
	mSelectionStart = getCursor();
	mSelectionEnd = getCursor();
}

void LLLineEditor::endSelection()
{
	if( mIsSelecting )
	{
		mIsSelecting = FALSE;
		mSelectionEnd = getCursor();
	}
}

BOOL LLLineEditor::canSelectAll()
{
	return TRUE;
}

void LLLineEditor::selectAll()
{
	mSelectionStart = mText.length();
	mSelectionEnd = 0;
	setCursor(mSelectionEnd);
	//mScrollHPos = 0;
	mIsSelecting = TRUE;
}


BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
{
	setFocus( TRUE );

	if (mSelectionEnd == 0 && mSelectionStart == mText.length())
	{
		// if everything is selected, handle this as a normal click to change insertion point
		handleMouseDown(x, y, mask);
	}
	else
	{
		// otherwise select everything
		selectAll();
	}

	// We don't want handleMouseUp() to "finish" the selection (and thereby
	// set mSelectionEnd to where the mouse is), so we finish the selection 
	// here.
	mIsSelecting = FALSE;  

	// delay cursor flashing
	mKeystrokeTimer.reset();

	return TRUE;
}

BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
	if (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight))
	{
		return LLUICtrl::handleMouseDown(x, y, mask);
	}
	if (mSelectAllonFocusReceived
		&& gFocusMgr.getKeyboardFocus() != this)
	{
		setFocus( TRUE );
	}
	else
	{
		setFocus( TRUE );

		if (mask & MASK_SHIFT)
		{
			// Handle selection extension
			S32 old_cursor_pos = getCursor();
			setCursorAtLocalPos(x);

			if (hasSelection())
			{
				/* Mac-like behavior - extend selection towards the cursor
				if (getCursor() < mSelectionStart
					&& getCursor() < mSelectionEnd)
				{
					// ...left of selection
					mSelectionStart = llmax(mSelectionStart, mSelectionEnd);
					mSelectionEnd = getCursor();
				}
				else if (getCursor() > mSelectionStart
					&& getCursor() > mSelectionEnd)
				{
					// ...right of selection
					mSelectionStart = llmin(mSelectionStart, mSelectionEnd);
					mSelectionEnd = getCursor();
				}
				else
				{
					mSelectionEnd = getCursor();
				}
				*/
				// Windows behavior
				mSelectionEnd = getCursor();
			}
			else
			{
				mSelectionStart = old_cursor_pos;
				mSelectionEnd = getCursor();
			}
			// assume we're starting a drag select
			mIsSelecting = TRUE;
		}
		else
		{
			// Move cursor and deselect for regular click
			setCursorAtLocalPos( x );
			deselect();
			startSelection();
		}

		gFocusMgr.setMouseCapture( this, &LLLineEditor::onMouseCaptureLost );
	}

	// delay cursor flashing
	mKeystrokeTimer.reset();

	return TRUE;
}


BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask)
{
	BOOL handled = FALSE;
	if (gFocusMgr.getMouseCapture() != this && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight)))
	{
		return LLUICtrl::handleHover(x, y, mask);
	}

	if( getVisible() )
	{
		if( (gFocusMgr.getMouseCapture() == this) && mIsSelecting )
		{
			if (x != mLastSelectionX || y != mLastSelectionY)
			{
				mLastSelectionX = x;
				mLastSelectionY = y;
			}
			// Scroll if mouse cursor outside of bounds
			if (mScrollTimer.hasExpired())
			{
				S32 increment = llround(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME);
				mScrollTimer.reset();
				mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME);
				if( (x < mMinHPixels) && (mScrollHPos > 0 ) )
				{
					// Scroll to the left
					mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length());
				}
				else
				if( (x > mMaxHPixels) && (mCursorPos < (S32)mText.length()) )
				{
					// If scrolling one pixel would make a difference...
					S32 pixels_after_scrolling_one_char = findPixelNearestPos(1);
					if( pixels_after_scrolling_one_char >= mMaxHPixels )
					{
						// ...scroll to the right
						mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length());
					}
				}
			}

			setCursorAtLocalPos( x );
			mSelectionEnd = getCursor();

			// delay cursor flashing
			mKeystrokeTimer.reset();

			getWindow()->setCursor(UI_CURSOR_IBEAM);
			lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;		
			handled = TRUE;
		}

		if( !handled  )
		{
			getWindow()->setCursor(UI_CURSOR_IBEAM);
			lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;		
			handled = TRUE;
		}
	}

	return handled;
}


BOOL LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask)
{
	BOOL	handled = FALSE;

	if( gFocusMgr.getMouseCapture() == this )
	{
		gFocusMgr.setMouseCapture( NULL, NULL );
		handled = TRUE;
	}

	if (!handled && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight)))
	{
		return LLUICtrl::handleMouseUp(x, y, mask);
	}

	if( mIsSelecting )
	{
		setCursorAtLocalPos( x );
		mSelectionEnd = getCursor();

		handled = TRUE;
	}

	if( handled )
	{
		// delay cursor flashing
		mKeystrokeTimer.reset();
	}

	return handled;
}


// Remove a single character from the text
void LLLineEditor::removeChar()
{
	if( getCursor() > 0 )
	{
		mText.erase(getCursor() - 1, 1);

		setCursor(getCursor() - 1);
	}
	else
	{
		reportBadKeystroke();
	}
}


void LLLineEditor::addChar(const llwchar uni_char)
{
	llwchar new_c = uni_char;
	if (hasSelection())
	{
		deleteSelection();
	}
	else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
	{
		mText.erase(getCursor(), 1);
	}

	S32 length_chars = mText.length();
	S32 cur_bytes = mText.getString().size();;
	S32 new_bytes = wchar_utf8_length(new_c);

	BOOL allow_char = TRUE;

	// Inserting character
	if (length_chars == mMaxLengthChars)
	{
		allow_char = FALSE;
	}
	if ((new_bytes + cur_bytes) > mMaxLengthBytes)
	{
		allow_char = FALSE;
	}

	if (allow_char)
	{
		// Will we need to scroll?
		LLWString w_buf;
		w_buf.assign(1, new_c);

		mText.insert(getCursor(), w_buf);
		setCursor(getCursor() + 1);
	}
	else
	{
		reportBadKeystroke();
	}

	getWindow()->hideCursorUntilMouseMove();
}

// Extends the selection box to the new cursor position
void LLLineEditor::extendSelection( S32 new_cursor_pos )
{
	if( !mIsSelecting )
	{
		startSelection();
	}
	
	setCursor(new_cursor_pos);
	mSelectionEnd = getCursor();
}


void LLLineEditor::setSelection(S32 start, S32 end)
{
	S32 len = mText.length();

	mIsSelecting = TRUE;

	// JC, yes, this seems odd, but I think you have to presume a 
	// selection dragged from the end towards the start.
	mSelectionStart = llclamp(end, 0, len);
	mSelectionEnd = llclamp(start, 0, len);
	setCursor(start);
}

S32 LLLineEditor::prevWordPos(S32 cursorPos) const
{
	const LLWString& wtext = mText.getWString();
	while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') )
	{
		cursorPos--;
	}
	while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) )
	{
		cursorPos--;
	}
	return cursorPos;
}

S32 LLLineEditor::nextWordPos(S32 cursorPos) const
{
	const LLWString& wtext = mText.getWString();
	while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) )
	{
		cursorPos++;
	} 
	while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') )
	{
		cursorPos++;
	}
	return cursorPos;
}


BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask)
{
	BOOL handled = FALSE;

	if( mask & MASK_SHIFT )
	{
		handled = TRUE;

		switch( key )
		{
		case KEY_LEFT:
			if (mIgnoreArrowKeys) 
			{
				handled = FALSE;
				break;
			}
			if( 0 < getCursor() )
			{
				S32 cursorPos = getCursor() - 1;
				if( mask & MASK_CONTROL )
				{
					cursorPos = prevWordPos(cursorPos);
				}
				extendSelection( cursorPos );
			}
			else
			{
				reportBadKeystroke();
			}
			break;

		case KEY_RIGHT:
			if (mIgnoreArrowKeys) 
			{
				handled = FALSE;
				break;
			}
			if( getCursor() < mText.length())
			{
				S32 cursorPos = getCursor() + 1;
				if( mask & MASK_CONTROL )
				{
					cursorPos = nextWordPos(cursorPos);
				}
				extendSelection( cursorPos );
			}
			else
			{
				reportBadKeystroke();
			}
			break;

		case KEY_PAGE_UP:
		case KEY_HOME:
			if (mIgnoreArrowKeys) 
			{
				handled = FALSE;
				break;
			}
			extendSelection( 0 );
			break;
		
		case KEY_PAGE_DOWN:
		case KEY_END:
			{
				if (mIgnoreArrowKeys) 
				{
					handled = FALSE;
					break;
				}
				S32 len = mText.length();
				if( len )
				{
					extendSelection( len );
				}
				break;
			}

		default:
			handled = FALSE;
			break;
		}
	}

	if (!handled && mHandleEditKeysDirectly)
	{
		if( (MASK_CONTROL & mask) && ('A' == key) )
		{
			if( canSelectAll() )
			{
				selectAll();
			}
			else
			{
				reportBadKeystroke();
			}
			handled = TRUE;
		}
	}


	return handled;
}

void LLLineEditor::deleteSelection()
{
	if( !mReadOnly && hasSelection() )
	{
		S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
		S32 selection_length = abs( mSelectionStart - mSelectionEnd );

		mText.erase(left_pos, selection_length);
		deselect();
		setCursor(left_pos);
	}
}

BOOL LLLineEditor::canCut()
{
	return !mReadOnly && !mDrawAsterixes && hasSelection();
}

// cut selection to clipboard
void LLLineEditor::cut()
{
	if( canCut() )
	{
		// Prepare for possible rollback
		LLLineEditorRollback rollback( this );


		S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
		S32 length = abs( mSelectionStart - mSelectionEnd );
		gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
		deleteSelection();

		// Validate new string and rollback the if needed.
		BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
		if( need_to_rollback )
		{
			rollback.doRollback( this );
			reportBadKeystroke();
		}
		else
		if( mKeystrokeCallback )
		{
			mKeystrokeCallback( this, mCallbackUserData );
		}
	}
}

BOOL LLLineEditor::canCopy()
{
	return !mDrawAsterixes && hasSelection();
}


// copy selection to clipboard
void LLLineEditor::copy()
{
	if( canCopy() )
	{
		S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
		S32 length = abs( mSelectionStart - mSelectionEnd );
		gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
	}
}

BOOL LLLineEditor::canPaste()
{
	return !mReadOnly && gClipboard.canPasteString(); 
}


// paste from clipboard
void LLLineEditor::paste()
{
	if (canPaste())
	{
		LLWString paste = gClipboard.getPasteWString();
		if (!paste.empty())
		{
			// Prepare for possible rollback
			LLLineEditorRollback rollback(this);
			
			// Delete any selected characters
			if (hasSelection())
			{
				deleteSelection();
			}

			// Clean up string (replace tabs and returns and remove characters that our fonts don't support.)
			LLWString clean_string(paste);
			LLWString::replaceTabsWithSpaces(clean_string, 1);
			//clean_string = wstring_detabify(paste, 1);
			LLWString::replaceChar(clean_string, '\n', ' ');

			// Insert the string

			//check to see that the size isn't going to be larger than the
			//max number of characters or bytes
			U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText);
			size_t available_chars = mMaxLengthChars - mText.length();

			if ( available_bytes < (U32) wstring_utf8_length(clean_string) )
			{
				llwchar current_symbol = clean_string[0];
				U32 wchars_that_fit = 0;
				U32 total_bytes = wchar_utf8_length(current_symbol);

				//loop over the "wide" characters (symbols)
				//and check to see how large (in bytes) each symbol is.
				while ( total_bytes <= available_bytes )
				{
					//while we still have available bytes
					//"accept" the current symbol and check the size
					//of the next one
					current_symbol = clean_string[++wchars_that_fit];
					total_bytes += wchar_utf8_length(current_symbol);
				}

				clean_string = clean_string.substr(0, wchars_that_fit);
				reportBadKeystroke();
			}
			else if (available_chars < clean_string.length())
			{
				// We can't insert all the characters.  Insert as many as possible
				// but make a noise to alert the user. JC
				clean_string = clean_string.substr(0, available_chars);
				reportBadKeystroke();
			}

			mText.insert(getCursor(), clean_string);
			setCursor(llmin(mMaxLengthChars, getCursor() + (S32)clean_string.length()));
			deselect();

			// Validate new string and rollback the if needed.
			BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
			if( need_to_rollback )
			{
				rollback.doRollback( this );
				reportBadKeystroke();
			}
			else
			if( mKeystrokeCallback )
			{
				mKeystrokeCallback( this, mCallbackUserData );
			}
		}
	}
}

	
BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)	
{
	BOOL handled = FALSE;

	switch( key )
	{
	case KEY_INSERT:
		if (mask == MASK_NONE)
		{
			gKeyboard->toggleInsertMode();
		}

		handled = TRUE;
		break;

	case KEY_BACKSPACE:
		if (!mReadOnly)
		{
			//llinfos << "Handling backspace" << llendl;
			if( hasSelection() )
			{
				deleteSelection();
			}
			else
			if( 0 < getCursor() )
			{
				removeChar();
			}
			else
			{
				reportBadKeystroke();
			}
		}
		handled = TRUE;
		break;

	case KEY_PAGE_UP:
	case KEY_HOME:
		if (!mIgnoreArrowKeys)
		{
			setCursor(0);
			handled = TRUE;
		}
		break;

	case KEY_PAGE_DOWN:
	case KEY_END:
		if (!mIgnoreArrowKeys)
		{
			S32 len = mText.length();
			if( len )
			{
				setCursor(len);
			}
			handled = TRUE;
		}
		break;

	case KEY_LEFT:
		if (!mIgnoreArrowKeys)
		{
			if( hasSelection() )
			{
				setCursor(llmin( getCursor() - 1, mSelectionStart, mSelectionEnd ));
			}
			else
			if( 0 < getCursor() )
			{
				S32 cursorPos = getCursor() - 1;
				if( mask & MASK_CONTROL )
				{
					cursorPos = prevWordPos(cursorPos);
				}
				setCursor(cursorPos);
			}
			else
			{
				reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	case KEY_RIGHT:
		if (!mIgnoreArrowKeys)
		{
			if (hasSelection())
			{
				setCursor(llmax(getCursor() + 1, mSelectionStart, mSelectionEnd));
			}
			else
			if (getCursor() < mText.length())
			{
				S32 cursorPos = getCursor() + 1;
				if( mask & MASK_CONTROL )
				{
					cursorPos = nextWordPos(cursorPos);
				}
				setCursor(cursorPos);
			}
			else
			{
				reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	default:
		break;
	}

	if( !handled && mHandleEditKeysDirectly )
	{
		// Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system.
		if( KEY_DELETE == key )
		{
			if( canDoDelete() )
			{
				doDelete();
			}
			else
			{
				reportBadKeystroke();
			}
			handled = TRUE;
		}
		else
		if( MASK_CONTROL & mask )
		{
			if( 'C' == key )
			{
				if( canCopy() )
				{
					copy();
				}
				else
				{
					reportBadKeystroke();
				}
				handled = TRUE;
			}
			else
			if( 'V' == key )
			{
				if( canPaste() )
				{
					paste();
				}
				else
				{
					reportBadKeystroke();
				}
				handled = TRUE;
			}
			else
			if( 'X' == key )
			{
				if( canCut() )
				{
					cut();
				}
				else
				{
					reportBadKeystroke();
				}
				handled = TRUE;
			}
		}
	}
	return handled;
}


BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent )
{
	BOOL	handled = FALSE;
	BOOL	selection_modified = FALSE;

	if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible())
	{
		LLLineEditorRollback rollback( this );

		if( !handled )
		{
			handled = handleSelectionKey( key, mask );
			selection_modified = handled;
		}
		
		// Handle most keys only if the text editor is writeable.
		if ( !mReadOnly )
		{
			if( !handled )
			{
				handled = handleSpecialKey( key, mask );
			}
		}

		if( handled )
		{
			mKeystrokeTimer.reset();

			// Most keystrokes will make the selection box go away, but not all will.
			if( !selection_modified &&
				KEY_SHIFT != key &&
				KEY_CONTROL != key &&
				KEY_ALT != key &&
				KEY_CAPSLOCK )
			{
				deselect();
			}

			BOOL need_to_rollback = FALSE;

			// If read-only, don't allow changes
			need_to_rollback |= (mReadOnly && (mText.getString() == rollback.getText()));

			// Validate new string and rollback the keystroke if needed.
			need_to_rollback |= (mPrevalidateFunc && !mPrevalidateFunc(mText.getWString()));

			if (need_to_rollback)
			{
				rollback.doRollback(this);

				reportBadKeystroke();
			}

			// Notify owner if requested
			if (!need_to_rollback && handled)
			{
				if (mKeystrokeCallback)
				{
					mKeystrokeCallback(this, mCallbackUserData);
				}
			}
		}
	}

	return handled;
}


BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
{
	if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
	{
		return FALSE;
	}

	BOOL	handled = FALSE;

	if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly)
	{
		handled = TRUE;

		LLLineEditorRollback rollback( this );

		addChar(uni_char);

		mKeystrokeTimer.reset();

		deselect();

		BOOL need_to_rollback = FALSE;

		// Validate new string and rollback the keystroke if needed.
		need_to_rollback |= ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );

		if( need_to_rollback )
		{
			rollback.doRollback( this );

			reportBadKeystroke();
		}

		// Notify owner if requested
		if( !need_to_rollback && handled )
		{
			if( mKeystrokeCallback )
			{
				// HACK! The only usage of this callback doesn't do anything with the character.
				// We'll have to do something about this if something ever changes! - Doug
				mKeystrokeCallback( this, mCallbackUserData );
			}
		}
	}
	return handled;
}


BOOL LLLineEditor::canDoDelete()
{
	return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) );
}

void LLLineEditor::doDelete()
{
	if (canDoDelete())
	{
		// Prepare for possible rollback
		LLLineEditorRollback rollback( this );

		if (hasSelection())
		{
			deleteSelection();
		}
		else if ( getCursor() < mText.length())
		{	
			setCursor(getCursor() + 1);
			removeChar();
		}

		// Validate new string and rollback the if needed.
		BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
		if( need_to_rollback )
		{
			rollback.doRollback( this );
			reportBadKeystroke();
		}
		else
		{
			if( mKeystrokeCallback )
			{
				mKeystrokeCallback( this, mCallbackUserData );
			}
		}
	}
}


void LLLineEditor::draw()
{
	if( !getVisible() )
	{
		return;
	}

	S32 text_len = mText.length();

	LLString saved_text;
	if (mDrawAsterixes)
	{
		saved_text = mText.getString();
		LLString text;
		for (S32 i = 0; i < mText.length(); i++)
		{
			text += '*';
		}
		mText = text;
	}

	// draw rectangle for the background
	LLRect background( 0, mRect.getHeight(), mRect.getWidth(), 0 );
	background.stretch( -mBorderThickness );

	LLColor4 bg_color = mReadOnlyBgColor;

	// drawing solids requires texturing be disabled
	{
		LLGLSNoTexture no_texture;
		// draw background for text
		if( !mReadOnly )
		{
			if( gFocusMgr.getKeyboardFocus() == this )
			{
				bg_color = mFocusBgColor;
			}
			else
			{
				bg_color = mWriteableBgColor;
			}
		}
		gl_rect_2d(background, bg_color);
	}

	// draw text

	S32 cursor_bottom = background.mBottom + 1;
	S32 cursor_top = background.mTop - 1;

	LLColor4 text_color;
	if (!mReadOnly)
	{
		if (!mTentative)
		{
			text_color = mFgColor;
		}
		else
		{
			text_color = mTentativeFgColor;
		}
	}
	else
	{
		text_color = mReadOnlyFgColor;
	}
	LLColor4 label_color = mTentativeFgColor;

	S32 rendered_text = 0;
	F32 rendered_pixels_right = (F32)mMinHPixels;
	F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD;

	if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() )
	{
		S32 select_left;
		S32 select_right;
		if( mSelectionStart < getCursor() )
		{
			select_left = mSelectionStart;
			select_right = getCursor();
		}
		else
		{
			select_left = getCursor();
			select_right = mSelectionStart;
		}
		
		if( select_left > mScrollHPos )
		{
			// unselected, left side
			rendered_text = mGLFont->render( 
				mText, mScrollHPos,
				rendered_pixels_right, text_bottom,
				text_color,
				LLFontGL::LEFT, LLFontGL::BOTTOM,
				LLFontGL::NORMAL,
				select_left - mScrollHPos,
				mMaxHPixels - llround(rendered_pixels_right),
				&rendered_pixels_right);
		}
		
		if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) )
		{
			LLColor4 color(1.f - bg_color.mV[0], 1.f - bg_color.mV[1], 1.f - bg_color.mV[2], 1.f);
			// selected middle
			S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text);
			width = llmin(width, mMaxHPixels - llround(rendered_pixels_right));
			gl_rect_2d(llround(rendered_pixels_right), cursor_top, llround(rendered_pixels_right)+width, cursor_bottom, color);

			rendered_text += mGLFont->render( 
				mText, mScrollHPos + rendered_text,
				rendered_pixels_right, text_bottom,
				LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ),
				LLFontGL::LEFT, LLFontGL::BOTTOM,
				LLFontGL::NORMAL,
				select_right - mScrollHPos - rendered_text,
				mMaxHPixels - llround(rendered_pixels_right),
				&rendered_pixels_right);
		}

		if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) )
		{
			// unselected, right side
			mGLFont->render( 
				mText, mScrollHPos + rendered_text,
				rendered_pixels_right, text_bottom,
				text_color,
				LLFontGL::LEFT, LLFontGL::BOTTOM,
				LLFontGL::NORMAL,
				S32_MAX,
				mMaxHPixels - llround(rendered_pixels_right),
				&rendered_pixels_right);
		}
	}
	else
	{
		mGLFont->render( 
			mText, mScrollHPos, 
			rendered_pixels_right, text_bottom,
			text_color,
			LLFontGL::LEFT, LLFontGL::BOTTOM,
			LLFontGL::NORMAL,
			S32_MAX,
			mMaxHPixels - llround(rendered_pixels_right),
			&rendered_pixels_right);
	}

	// If we're editing...
	if( gFocusMgr.getKeyboardFocus() == this)
	{
		// (Flash the cursor every half second)
		if (gShowTextEditCursor && !mReadOnly)
		{
			F32 elapsed = mKeystrokeTimer.getElapsedTimeF32();
			if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
			{
				S32 cursor_left = findPixelNearestPos();
				cursor_left -= UI_LINEEDITOR_CURSOR_THICKNESS / 2;
				S32 cursor_right = cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS;
				if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
				{
					const LLWString space(utf8str_to_wstring(LLString(" ")));
					S32 wswidth = mGLFont->getWidth(space.c_str());
					S32 width = mGLFont->getWidth(mText.getWString().c_str(), getCursor(), 1) + 1;
					cursor_right = cursor_left + llmax(wswidth, width);
				}
				// Use same color as text for the Cursor
				gl_rect_2d(cursor_left, cursor_top,
					cursor_right, cursor_bottom, text_color);
				if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
				{
					mGLFont->render(mText, getCursor(), (F32)(cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS / 2), text_bottom, 
						LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ),
						LLFontGL::LEFT, LLFontGL::BOTTOM,
						LLFontGL::NORMAL,
						1);
				}
			}
		}

		// Draw children (border)
		//mBorder->setVisible(TRUE);
		mBorder->setKeyboardFocusHighlight( TRUE );
		LLView::draw();
		mBorder->setKeyboardFocusHighlight( FALSE );
		//mBorder->setVisible(FALSE);
	}
	else // does not have keyboard input
	{
		// draw label if no text provided
		if (0 == mText.length())
		{
			mGLFont->render(mLabel.getWString(), 0, 
							LABEL_HPAD, (F32)text_bottom,
							label_color,
							LLFontGL::LEFT, LLFontGL::BOTTOM,
							LLFontGL::NORMAL,
							S32_MAX,
							mMaxHPixels - llround(rendered_pixels_right),
							&rendered_pixels_right, FALSE);
		}
		// Draw children (border)
		LLView::draw();
	}

	if (mDrawAsterixes)
	{
		mText = saved_text;
	}
}


// Returns the local screen space X coordinate associated with the text cursor position.
S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset)
{
	S32 dpos = getCursor() - mScrollHPos + cursor_offset;
	S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels;
	return result;
}

void LLLineEditor::reportBadKeystroke()
{
	make_ui_sound("UISndBadKeystroke");
}

//virtual
void LLLineEditor::clear()
{
	mText.clear();
	setCursor(0);
}

//virtual
void LLLineEditor::onTabInto()
{
	selectAll();
}

//virtual
BOOL LLLineEditor::acceptsTextInput() const
{
	return TRUE;
}

// Start or stop the editor from accepting text-editing keystrokes
void LLLineEditor::setFocus( BOOL new_state )
{
	BOOL old_state = hasFocus();

	// getting focus when we didn't have it before, and we want to select all
	if (!old_state && new_state && mSelectAllonFocusReceived)
	{
		selectAll();
		// We don't want handleMouseUp() to "finish" the selection (and thereby
		// set mSelectionEnd to where the mouse is), so we finish the selection 
		// here.
		mIsSelecting = FALSE;
	}

	if( new_state )
	{
		gEditMenuHandler = this;

		// Don't start the cursor flashing right away
		mKeystrokeTimer.reset();
	}
	else
	{
		// Not really needed, since loss of keyboard focus should take care of this,
		// but limited paranoia is ok.
		if( gEditMenuHandler == this )
		{
			gEditMenuHandler = NULL;
		}

		endSelection();
	}

	LLUICtrl::setFocus( new_state );
}

//virtual 
void LLLineEditor::setRect(const LLRect& rect)
{
	LLUICtrl::setRect(rect);
	if (mBorder)
	{
		LLRect border_rect = mBorder->getRect();
		// Scalable UI somehow made these rectangles off-by-one.
		// I don't know why. JC
		border_rect.setOriginAndSize(border_rect.mLeft, border_rect.mBottom, 
				rect.getWidth()-1, rect.getHeight()-1);
		mBorder->setRect(border_rect);
	}
}

// Limits what characters can be used to [1234567890.-] with [-] only valid in the first position.
// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for
// the simple reasons that intermediate states may be invalid even if the final result is valid.
// 
// static
BOOL LLLineEditor::prevalidateFloat(const LLWString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	BOOL success = TRUE;
	LLWString trimmed = str;
	LLWString::trim(trimmed);
	S32 len = trimmed.length();
	if( 0 < len )
	{
		// May be a comma or period, depending on the locale
		char decimal_point = gResMgr->getDecimalPoint();

		S32 i = 0;

		// First character can be a negative sign
		if( '-' == trimmed[0] )
		{
			i++;
		}

		for( ; i < len; i++ )
		{
			if( (decimal_point != trimmed[i] ) && !isdigit( trimmed[i] ) )
			{
				success = FALSE;
				break;
			}
		}
	}		

	return success;
}

//static
BOOL LLLineEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); }

// static
BOOL LLLineEditor::postvalidateFloat(const LLString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	BOOL success = TRUE;
	BOOL has_decimal = FALSE;
	BOOL has_digit = FALSE;

	LLWString trimmed = utf8str_to_wstring(str);
	LLWString::trim(trimmed);
	S32 len = trimmed.length();
	if( 0 < len )
	{
		S32 i = 0;

		// First character can be a negative sign
		if( '-' == trimmed[0] )
		{
			i++;
		}

		// May be a comma or period, depending on the locale
		char decimal_point = gResMgr->getDecimalPoint();

		for( ; i < len; i++ )
		{
			if( decimal_point == trimmed[i] )
			{
				if( has_decimal )
				{
					// can't have two
					success = FALSE;
					break;
				}
				else
				{
					has_decimal = TRUE;
				}
			}
			else
			if( isdigit( trimmed[i] ) )
			{
				has_digit = TRUE;
			}
			else
			{
				success = FALSE;
				break;
			}
		}
	}		

	// Gotta have at least one
	success = has_digit;

	return success;
}

// Limits what characters can be used to [1234567890-] with [-] only valid in the first position.
// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for
// the simple reasons that intermediate states may be invalid even if the final result is valid.
//
// static
BOOL LLLineEditor::prevalidateInt(const LLWString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	BOOL success = TRUE;
	LLWString trimmed = str;
	LLWString::trim(trimmed);
	S32 len = trimmed.length();
	if( 0 < len )
	{
		S32 i = 0;

		// First character can be a negative sign
		if( '-' == trimmed[0] )
		{
			i++;
		}

		for( ; i < len; i++ )
		{
			if( !isdigit( trimmed[i] ) )
			{
				success = FALSE;
				break;
			}
		}
	}		

	return success;
}

// static
BOOL LLLineEditor::prevalidatePositiveS32(const LLWString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	LLWString trimmed = str;
	LLWString::trim(trimmed);
	S32 len = trimmed.length();
	BOOL success = TRUE;
	if(0 < len)
	{
		if(('-' == trimmed[0]) || ('0' == trimmed[0]))
		{
			success = FALSE;
		}
		S32 i = 0;
		while(success && (i < len))
		{
			if(!isdigit(trimmed[i++]))
			{
				success = FALSE;
			}
		}
	}
	if (success)
	{
		S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10);
		if (val <= 0)
		{
			success = FALSE;
		}
	}
	return success;
}

BOOL LLLineEditor::prevalidateNonNegativeS32(const LLWString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	LLWString trimmed = str;
	LLWString::trim(trimmed);
	S32 len = trimmed.length();
	BOOL success = TRUE;
	if(0 < len)
	{
		if('-' == trimmed[0])
		{
			success = FALSE;
		}
		S32 i = 0;
		while(success && (i < len))
		{
			if(!isdigit(trimmed[i++]))
			{
				success = FALSE;
			}
		}
	}
	if (success)
	{
		S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10);
		if (val < 0)
		{
			success = FALSE;
		}
	}
	return success;
}

BOOL LLLineEditor::prevalidateAlphaNum(const LLWString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	BOOL rv = TRUE;
	S32 len = str.length();
	if(len == 0) return rv;
	while(len--)
	{
		if( !isalnum(str[len]) )
		{
			rv = FALSE;
			break;
		}
	}
	return rv;
}

// static
BOOL LLLineEditor::prevalidateAlphaNumSpace(const LLWString &str)
{
	LLLocale locale(LLLocale::USER_LOCALE);

	BOOL rv = TRUE;
	S32 len = str.length();
	if(len == 0) return rv;
	while(len--)
	{
		if(!(isalnum(str[len]) || (' ' == str[len])))
		{
			rv = FALSE;
			break;
		}
	}
	return rv;
}

// static
BOOL LLLineEditor::prevalidatePrintableNotPipe(const LLWString &str)
{
	BOOL rv = TRUE;
	S32 len = str.length();
	if(len == 0) return rv;
	while(len--)
	{
		if('|' == str[len])
		{
			rv = FALSE;
			break;
		}
		if(!((' ' == str[len]) || isalnum(str[len]) || ispunct(str[len])))
		{
			rv = FALSE;
			break;
		}
	}
	return rv;
}


// static
BOOL LLLineEditor::prevalidatePrintableNoSpace(const LLWString &str)
{
	BOOL rv = TRUE;
	S32 len = str.length();
	if(len == 0) return rv;
	while(len--)
	{
		if(iswspace(str[len]))
		{
			rv = FALSE;
			break;
		}
		if( !(isalnum(str[len]) || ispunct(str[len]) ) )
		{
			rv = FALSE;
			break;
		}
	}
	return rv;
}

// static
BOOL LLLineEditor::prevalidateASCII(const LLWString &str)
{
	BOOL rv = TRUE;
	S32 len = str.length();
	while(len--)
	{
		if (str[len] < 0x20 || str[len] > 0x7f)
		{
			rv = FALSE;
			break;
		}
	}
	return rv;
}

//static
void LLLineEditor::onMouseCaptureLost( LLMouseHandler* old_captor )
{
	LLLineEditor* self = (LLLineEditor*) old_captor;
	self->endSelection();
}


void LLLineEditor::setSelectAllonFocusReceived(BOOL b)
{
	mSelectAllonFocusReceived = b;
}


void LLLineEditor::setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data))
{
	mKeystrokeCallback = keystroke_callback;
}

void LLLineEditor::setFocusLostCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data))
{
	mFocusLostCallback = keystroke_callback;
}

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

	node->createChild("max_length", TRUE)->setIntValue(mMaxLengthBytes);

	node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont));

	if (mBorder)
	{
		LLString bevel;
		switch(mBorder->getBevel())
		{
		default:
		case LLViewBorder::BEVEL_NONE:	bevel = "none"; break;
		case LLViewBorder::BEVEL_IN:	bevel = "in"; break;
		case LLViewBorder::BEVEL_OUT:	bevel = "out"; break;
		case LLViewBorder::BEVEL_BRIGHT:bevel = "bright"; break;
		}
		node->createChild("bevel_style", TRUE)->setStringValue(bevel);

		LLString style;
		switch(mBorder->getStyle())
		{
		default:
		case LLViewBorder::STYLE_LINE:		style = "line"; break;
		case LLViewBorder::STYLE_TEXTURE:	style = "texture"; break;
		}
		node->createChild("border_style", TRUE)->setStringValue(style);

		node->createChild("border_thickness", TRUE)->setIntValue(mBorder->getBorderWidth());
	}

	if (!mLabel.empty())
	{
		node->createChild("label", TRUE)->setStringValue(mLabel.getString());
	}

	node->createChild("select_all_on_focus_received", TRUE)->setBoolValue(mSelectAllonFocusReceived);

	node->createChild("handle_edit_keys_directly", TRUE)->setBoolValue(mHandleEditKeysDirectly );

	addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor");
	addColorXML(node, mFgColor, "text_color", "TextFgColor");
	addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor");
	addColorXML(node, mTentativeFgColor, "text_tentative_color", "TextFgTentativeColor");
	addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor");
	addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor");
	addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor");

	node->createChild("select_on_focus", TRUE)->setBoolValue(mSelectAllonFocusReceived );

	return node;
}

// static
LLView* LLLineEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
	LLString name("line_editor");
	node->getAttributeString("name", name);

	LLRect rect;
	createRect(node, rect, parent, LLRect());

	S32 max_text_length = 128;
	node->getAttributeS32("max_length", max_text_length);

	LLFontGL* font = LLView::selectFont(node);

	LLString text = node->getTextContents().substr(0, max_text_length - 1);

	LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN;
	LLViewBorder::getBevelFromAttribute(node, bevel_style);
	
	LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE;
	LLString border_string;
	node->getAttributeString("border_style", border_string);
	LLString::toLower(border_string);

	if (border_string == "texture")
	{
		border_style = LLViewBorder::STYLE_TEXTURE;
	}

	S32 border_thickness = 1;
	node->getAttributeS32("border_thickness", border_thickness);

	LLUICtrlCallback commit_callback = NULL;

	LLLineEditor* line_editor = new LLLineEditor(name,
								rect, 
								text, 
								font,
								max_text_length,
								commit_callback,
								NULL,
								NULL,
								NULL,
								NULL,
								bevel_style,
								border_style,
								border_thickness);

	LLString label;
	if(node->getAttributeString("label", label))
	{
		line_editor->setLabel(label);
	}
	BOOL select_all_on_focus_received = FALSE;
	if (node->getAttributeBOOL("select_all_on_focus_received", select_all_on_focus_received))
	{
		line_editor->setSelectAllonFocusReceived(select_all_on_focus_received);
	}
	BOOL handle_edit_keys_directly = FALSE;
	if (node->getAttributeBOOL("handle_edit_keys_directly", handle_edit_keys_directly))
	{
		line_editor->setHandleEditKeysDirectly(handle_edit_keys_directly);
	}
	
	line_editor->setColorParameters(node);
	
	if(node->hasAttribute("select_on_focus"))
	{
		BOOL selectall = FALSE;
		node->getAttributeBOOL("select_on_focus", selectall);
		line_editor->setSelectAllonFocusReceived(selectall);
	}

	LLString prevalidate;
	if(node->getAttributeString("prevalidate", prevalidate))
	{
		LLString::toLower(prevalidate);

		if ("ascii" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidateASCII );
		}
		else if ("float" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidateFloat );
		}
		else if ("int" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidateInt );
		}
		else if ("positive_s32" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidatePositiveS32 );
		}
		else if ("non_negative_s32" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidateNonNegativeS32 );
		}
		else if ("alpha_num" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNum );
		}
		else if ("alpha_num_space" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNumSpace );
		}
		else if ("printable_not_pipe" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNotPipe );
		}
		else if ("printable_no_space" == prevalidate)
		{
			line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNoSpace );
		}
	}
	
	line_editor->initFromXML(node, parent);
	
	return line_editor;
}

void LLLineEditor::setColorParameters(LLXMLNodePtr node)
{
	LLColor4 color;
	if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) 
	{
		setCursorColor(color);
	}
	if(node->hasAttribute("text_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"text_color", color);
		setFgColor(color);
	}
	if(node->hasAttribute("text_readonly_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color);
		setReadOnlyFgColor(color);
	}
	if (LLUICtrlFactory::getAttributeColor(node,"text_tentative_color", color))
	{
		setTentativeFgColor(color);
	}
	if(node->hasAttribute("bg_readonly_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color);
		setReadOnlyBgColor(color);
	}
	if(node->hasAttribute("bg_writeable_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color);
		setWriteableBgColor(color);
	}
}

void LLLineEditor::setValue(const LLSD& value )
{
	setText(value.asString());
}

LLSD LLLineEditor::getValue() const
{
	LLString str = getText();
	LLSD ret(str);
	return ret;
}

BOOL LLLineEditor::setTextArg( const LLString& key, const LLString& text )
{
	mText.setArg(key, text);
	return TRUE;
}

BOOL LLLineEditor::setLabelArg( const LLString& key, const LLString& text )
{
	mLabel.setArg(key, text);
	return TRUE;
}

LLSearchEditor::LLSearchEditor(const LLString& name, 
		const LLRect& rect,
		S32 max_length_bytes,
		void (*search_callback)(const LLString& search_string, void* user_data),
		void* userdata)
	: 
		LLUICtrl(name, rect, TRUE, NULL, userdata),
		mSearchCallback(search_callback)
{
	LLRect search_edit_rect(0, mRect.getHeight(), mRect.getWidth(), 0);
	mSearchEdit = new LLLineEditor("search edit",
			search_edit_rect,
			LLString::null,
			NULL,
			max_length_bytes,
			NULL,
			onSearchEdit,
			NULL,
			this);
	// TODO: this should be translatable
	mSearchEdit->setLabel("Type here to search");
	mSearchEdit->setFollowsAll();
	mSearchEdit->setSelectAllonFocusReceived(TRUE);

	addChild(mSearchEdit);

	S32 btn_width = rect.getHeight(); // button is square, and as tall as search editor
	LLRect clear_btn_rect(rect.getWidth() - btn_width, rect.getHeight(), rect.getWidth(), 0);
	mClearSearchButton = new LLButton("clear search", 
								clear_btn_rect, 
								"closebox.tga",
								"UIImgBtnCloseInactiveUUID",
								LLString::null,
								onClearSearch,
								this,
								NULL,
								LLString::null);
	mClearSearchButton->setFollowsRight();
	mClearSearchButton->setFollowsTop();
	mClearSearchButton->setImageColor(LLUI::sColorsGroup->getColor("TextFgTentativeColor"));
	mClearSearchButton->setTabStop(FALSE);
	mSearchEdit->addChild(mClearSearchButton);

	mSearchEdit->setBorderWidth(0, btn_width);
}

LLSearchEditor::~LLSearchEditor() 
{
}

//virtual
EWidgetType LLSearchEditor::getWidgetType() const
{
	return WIDGET_TYPE_SEARCH_EDITOR;
}

//virtual
LLString LLSearchEditor::getWidgetTag() const
{
	return LL_SEARCH_EDITOR_TAG;
}

//virtual
void LLSearchEditor::setValue(const LLSD& value )
{
	mSearchEdit->setValue(value);
}

//virtual
LLSD LLSearchEditor::getValue() const
{
	return mSearchEdit->getValue();
}

//virtual
BOOL LLSearchEditor::setTextArg( const LLString& key, const LLString& text )
{
	return mSearchEdit->setTextArg(key, text);
}

//virtual
BOOL LLSearchEditor::setLabelArg( const LLString& key, const LLString& text )
{
	return mSearchEdit->setLabelArg(key, text);
}

//virtual
void LLSearchEditor::clear()
{
	if (mSearchEdit)
	{
		mSearchEdit->clear();
	}
}


void LLSearchEditor::draw()
{
	mClearSearchButton->setVisible(!mSearchEdit->getWText().empty());

	LLUICtrl::draw();
}

void LLSearchEditor::setText(const LLString &new_text)
{
	mSearchEdit->setText(new_text);
}

//static
void LLSearchEditor::onSearchEdit(LLLineEditor* caller, void* user_data )
{
	LLSearchEditor* search_editor = (LLSearchEditor*)user_data;
	if (search_editor->mSearchCallback)
	{
		search_editor->mSearchCallback(caller->getText(), search_editor->mCallbackUserData);
	}
}

//static
void LLSearchEditor::onClearSearch(void* user_data)
{
	LLSearchEditor* search_editor = (LLSearchEditor*)user_data;

	search_editor->setText(LLString::null);
	if (search_editor->mSearchCallback)
	{
		search_editor->mSearchCallback(LLString::null, search_editor->mCallbackUserData);
	}
}

// static
LLView* LLSearchEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
	LLString name("search_editor");
	node->getAttributeString("name", name);

	LLRect rect;
	createRect(node, rect, parent, LLRect());

	S32 max_text_length = 128;
	node->getAttributeS32("max_length", max_text_length);

	LLString text = node->getValue().substr(0, max_text_length - 1);

	LLSearchEditor* search_editor = new LLSearchEditor(name,
								rect, 
								max_text_length,
								NULL, NULL);

	search_editor->setText(text);

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