/** 
 * @file lllineeditor.cpp
 * @brief LLLineEditor base class
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

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

#include "linden_common.h"
 
#define LLLINEEDITOR_CPP
#include "lllineeditor.h"

#include "lltexteditor.h"
#include "llmath.h"
#include "llfontgl.h"
#include "llgl.h"
#include "lltimer.h"

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

//
// Imported globals
//

//
// Constants
//

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	TRIPLE_CLICK_INTERVAL = 0.3f;	// delay between double and triple click. *TODO: make this equal to the double click interval?
const F32	SPELLCHECK_DELAY = 0.5f;	// delay between the last keypress and spell checking the word the cursor is on

const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET

static LLDefaultChildRegistry::Register<LLLineEditor> r1("line_editor");

// Compiler optimization, generate extern template
template class LLLineEditor* LLView::getChild<class LLLineEditor>(
	const std::string& name, BOOL recurse) const;

//
// Member functions
//

LLLineEditor::Params::Params()
:	max_length(""),
    keystroke_callback("keystroke_callback"),
	prevalidate_callback("prevalidate_callback"),
	prevalidate_input_callback("prevalidate_input_callback"),
	background_image("background_image"),
	background_image_disabled("background_image_disabled"),
	background_image_focused("background_image_focused"),
	bg_image_always_focused("bg_image_always_focused", false),
	select_on_focus("select_on_focus", false),
	revert_on_esc("revert_on_esc", true),
	spellcheck("spellcheck", false),
	commit_on_focus_lost("commit_on_focus_lost", true),
	ignore_tab("ignore_tab", true),
	is_password("is_password", false),
	cursor_color("cursor_color"),
	use_bg_color("use_bg_color", false),
	bg_color("bg_color"),
	text_color("text_color"),
	text_readonly_color("text_readonly_color"),
	text_tentative_color("text_tentative_color"),
	highlight_color("highlight_color"),
	preedit_bg_color("preedit_bg_color"),
	border(""),
	bg_visible("bg_visible"),
	text_pad_left("text_pad_left"),
	text_pad_right("text_pad_right"),
	default_text("default_text")
{
	changeDefault(mouse_opaque, true);
	addSynonym(select_on_focus, "select_all_on_focus_received");
	addSynonym(border, "border");
	addSynonym(label, "watermark_text");
	addSynonym(max_length.chars, "max_length");
}

LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
:	LLUICtrl(p),
	mMaxLengthBytes(p.max_length.bytes),
	mMaxLengthChars(p.max_length.chars),
	mCursorPos( 0 ),
	mScrollHPos( 0 ),
	mTextPadLeft(p.text_pad_left),
	mTextPadRight(p.text_pad_right),
	mTextLeftEdge(0),		// computed in updateTextPadding() below
	mTextRightEdge(0),		// computed in updateTextPadding() below
	mCommitOnFocusLost( p.commit_on_focus_lost ),
	mKeystrokeOnEsc(FALSE),
	mRevertOnEsc( p.revert_on_esc ),
	mKeystrokeCallback( p.keystroke_callback() ),
	mIsSelecting( FALSE ),
	mSelectionStart( 0 ),
	mSelectionEnd( 0 ),
	mLastSelectionX(-1),
	mLastSelectionY(-1),
	mLastSelectionStart(-1),
	mLastSelectionEnd(-1),
	mBorderThickness( 0 ),
	mIgnoreArrowKeys( FALSE ),
	mIgnoreTab( p.ignore_tab ),
	mDrawAsterixes( p.is_password ),
	mSpellCheck( p.spellcheck ),
	mSpellCheckStart(-1),
	mSpellCheckEnd(-1),
	mSelectAllonFocusReceived( p.select_on_focus ),
	mSelectAllonCommit( TRUE ),
	mPassDelete(FALSE),
	mReadOnly(FALSE),
	mBgImage( p.background_image ),
	mBgImageDisabled( p.background_image_disabled ),
	mBgImageFocused( p.background_image_focused ),
	mShowImageFocused( p.bg_image_always_focused ),
	mUseBgColor(p.use_bg_color),
	mHaveHistory(FALSE),
	mReplaceNewlinesWithSpaces( TRUE ),
	mLabel(p.label),
	mCursorColor(p.cursor_color()),
	mBgColor(p.bg_color()),
	mFgColor(p.text_color()),
	mReadOnlyFgColor(p.text_readonly_color()),
	mTentativeFgColor(p.text_tentative_color()),
	mHighlightColor(p.highlight_color()),
	mPreeditBgColor(p.preedit_bg_color()),
	mGLFont(p.font),
	// <FS:Ansariel> Delay context menu initialization if LLMenuGL::sMenuContainer is still NULL
	mDelayedInit(false),
	mContextMenuHandle(),
	mAutoreplaceCallback()
{
	llassert( mMaxLengthBytes > 0 );

	LLUICtrl::setEnabled(TRUE);
	setEnabled(p.enabled);

	mScrollTimer.reset();
	mTripleClickTimer.reset();
	setText(p.default_text());

    if (p.initial_value.isProvided()
        && !p.control_name.isProvided())
    {
        // Initial value often is descriptive, like "Type some ID here"
        // and can be longer than size limitation, ignore size
        setText(p.initial_value.getValue().asString(), false);
    }

	// Initialize current history line iterator
	mCurrentHistoryLine = mLineHistory.begin();

	LLRect border_rect(getLocalRect());
	// adjust for gl line drawing glitch
	border_rect.mTop -= 1;
	border_rect.mRight -=1;
	LLViewBorder::Params border_p(p.border);
	border_p.rect = border_rect;
	border_p.follows.flags = FOLLOWS_ALL;
	border_p.bevel_style = LLViewBorder::BEVEL_IN;
	mBorder = LLUICtrlFactory::create<LLViewBorder>(border_p);
	addChild( mBorder );

	// clamp text padding to current editor size
	updateTextPadding();
	setCursor(mText.length());

	if (mSpellCheck)
	{
		LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this));
	}
	mSpellCheckTimer.reset();

	setPrevalidateInput(p.prevalidate_input_callback());
	setPrevalidate(p.prevalidate_callback());

	// <FS:Ansariel> Delay context menu initialization if LLMenuGL::sMenuContainer is still NULL
	if (LLMenuGL::sMenuContainer)
	{
	// </FS:Ansariel>
	llassert(LLMenuGL::sMenuContainer != NULL);
	LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>
		("menu_text_editor.xml",
		 LLMenuGL::sMenuContainer,
		 LLMenuHolderGL::child_registry_t::instance());
	setContextMenu(menu);
	// <FS:Ansariel> Delay context menu initialization if LLMenuGL::sMenuContainer is still NULL
	}
	else
	{
		mDelayedInit = true;
	}
	// </FS:Ansariel>
}
 
LLLineEditor::~LLLineEditor()
{
	mCommitOnFocusLost = FALSE;
    
    // Make sure no context menu linger around once the widget is deleted
	LLContextMenu* menu = static_cast<LLContextMenu*>(mContextMenuHandle.get());
	if (menu)
	{
        menu->hide();
    }
	setContextMenu(NULL);

	// <FS:Ansariel> Delay context menu initialization if LLMenuGL::sMenuContainer is still NULL
	mDelayedInit = false;

	// calls onCommit() while LLLineEditor still valid
	gFocusMgr.releaseFocusIfNeeded( this );
}

void LLLineEditor::initFromParams(const LLLineEditor::Params& params)
{
	LLUICtrl::initFromParams(params);
	LLUICtrl::setEnabled(TRUE);
	setEnabled(params.enabled);
}

void LLLineEditor::onFocusReceived()
{
	gEditMenuHandler = this;
	LLUICtrl::onFocusReceived();
	updateAllowingLanguageInput();
}

void LLLineEditor::onFocusLost()
{
	// The call to updateAllowLanguageInput()
	// when loosing the keyboard focus *may*
	// indirectly invoke handleUnicodeCharHere(), 
	// so it must be called before onCommit.
	updateAllowingLanguageInput();

	if( mCommitOnFocusLost && mText.getString() != mPrevText) 
	{
		onCommit();
	}

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

	getWindow()->showCursorFromMouseMove();

	LLUICtrl::onFocusLost();
}

// virtual
void LLLineEditor::onCommit()
{
	// put current line into the line history
	updateHistory();

	setControlValue(getValue());
	LLUICtrl::onCommit();
	resetDirty();

	// Selection on commit needs to be turned off when evaluating maths
	// expressions, to allow indication of the error position
	if (mSelectAllonCommit) selectAll();
}

// Returns TRUE if user changed value at all
// virtual
BOOL LLLineEditor::isDirty() const
{
	return mText.getString() != mPrevText;
}

// Clear dirty state
// virtual
void LLLineEditor::resetDirty()
{
	mPrevText = mText.getString();
}		

// assumes UTF8 text
// virtual
void LLLineEditor::setValue(const LLSD& value )
{
	setText(value.asString());
}

//virtual
LLSD LLLineEditor::getValue() const
{
	return LLSD(getText());
}


// line history support
void LLLineEditor::updateHistory()
{
	// On history enabled line editors, remember committed line and
	// reset current history line number.
	// Be sure only to remember lines that are not empty and that are
	// different from the last on the list.
	if( mHaveHistory && getLength() )
	{
		if( !mLineHistory.empty() )
		{
			// When not empty, last line of history should always be blank.
			if( mLineHistory.back().empty() )
			{
				// discard the empty line
				mLineHistory.pop_back();
			}
			else
			{
				LL_WARNS("") << "Last line of history was not blank." << LL_ENDL;
			}
		}

		// Add text to history, ignoring duplicates
		if( mLineHistory.empty() || getText() != mLineHistory.back() )
		{
			mLineHistory.push_back( getText() );
		}

		// Restore the blank line and set mCurrentHistoryLine to point at it
		mLineHistory.push_back( "" );
		mCurrentHistoryLine = mLineHistory.end() - 1;
	}
}

void LLLineEditor::reshape(S32 width, S32 height, BOOL called_from_parent)
{
	LLUICtrl::reshape(width, height, called_from_parent);
	updateTextPadding(); // For clamping side-effect.
	setCursor(mCursorPos); // For clamping side-effect.
}

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


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

void LLLineEditor::setMaxTextChars(S32 max_text_chars)
{
	S32 max_chars = llmax(0, max_text_chars);
	mMaxLengthChars = max_chars;
} 

void LLLineEditor::getTextPadding(S32 *left, S32 *right)
{
	*left = mTextPadLeft;
	*right = mTextPadRight;
}

void LLLineEditor::setTextPadding(S32 left, S32 right)
{
	mTextPadLeft = left;
	mTextPadRight = right;
	updateTextPadding();
}

void LLLineEditor::updateTextPadding()
{
	mTextLeftEdge = llclamp(mTextPadLeft, 0, getRect().getWidth());
	mTextRightEdge = getRect().getWidth() - llclamp(mTextPadRight, 0, getRect().getWidth());
}


void LLLineEditor::setText(const LLStringExplicit &new_text)
{
    setText(new_text, true);
}

void LLLineEditor::setText(const LLStringExplicit &new_text, bool use_size_limit)
{
	// 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 all_selected = (len > 0)
		&& (( mSelectionStart == 0 && mSelectionEnd == len ) 
			|| ( mSelectionStart == len && mSelectionEnd == 0 ));

	// Do safe truncation so we don't split multi-byte characters
	// also consider entire string selected when mSelectAllonFocusReceived is set on an empty, focused line editor
	all_selected = all_selected || (len == 0 && hasFocus() && mSelectAllonFocusReceived);

	std::string truncated_utf8 = new_text;
	if (use_size_limit && truncated_utf8.size() > (U32)mMaxLengthBytes)
	{	
		truncated_utf8 = utf8str_truncate(new_text, mMaxLengthBytes);
	}
	mText.assign(truncated_utf8);

	if (use_size_limit && mMaxLengthChars)
	{
		mText.assign(utf8str_symbol_truncate(truncated_utf8, mMaxLengthChars));
	}

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

	// Set current history line to end of history.
	if (mLineHistory.empty())
	{
		mCurrentHistoryLine = mLineHistory.end();
	}
	else
	{
		mCurrentHistoryLine = mLineHistory.end() - 1;
	}

	mPrevText = mText;
}


// Picks a new cursor position based on the actual screen size of text being drawn.
void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x )
{
	S32 cursor_pos = calcCursorPos(local_mouse_x);

	S32 left_pos = llmin( mSelectionStart, cursor_pos );
	S32 length = llabs( mSelectionStart - cursor_pos );
	const LLWString& substr = mText.getWString().substr(left_pos, length);

	if (mIsSelecting && !prevalidateInput(substr))
		return;

	setCursor(cursor_pos);
}

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

	// position of end of next character after cursor
	S32 pixels_after_scroll = findPixelNearestPos();
	if( pixels_after_scroll > mTextRightEdge )
	{
		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)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); 
		// character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters)
		// or first character if cursor is at beginning
		S32 new_last_visible_char = llmax(0, getCursor() - 1);
		S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char);
		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();
}

void LLLineEditor::resetScrollPosition()
{
	mScrollHPos = 0;
	// make sure cursor says in visible range
	setCursor(getCursor());
}

BOOL LLLineEditor::canDeselect() const
{
	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() const
{
	return TRUE;
}

void LLLineEditor::selectAll()
{
	if (!prevalidateInput(mText.getWString()))
	{
		return;
	}

	mSelectionStart = mText.length();
	mSelectionEnd = 0;
	setCursor(mSelectionEnd);
	//mScrollHPos = 0;
	mIsSelecting = TRUE;
	// <FS:AW> Linux primary "clipboard" tainted by auto-selection
	//updatePrimary();
}

bool LLLineEditor::getSpellCheck() const
{
	return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
}

const std::string& LLLineEditor::getSuggestion(U32 index) const
{
	return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null;
}

U32 LLLineEditor::getSuggestionCount() const
{
	return mSuggestionList.size();
}

void LLLineEditor::replaceWithSuggestion(U32 index)
{
	for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
	{
		if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
		{
			deselect();

			// Delete the misspelled word
			mText.erase(it->first, it->second - it->first);

			// Insert the suggestion in its place
			LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
			mText.insert(it->first, suggestion);
			setCursor(it->first + (S32)suggestion.length());

			break;
		}
	}
	mSpellCheckStart = mSpellCheckEnd = -1;
}

void LLLineEditor::addToDictionary()
{
	if (canAddToDictionary())
	{
		LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos));
	}
}

bool LLLineEditor::canAddToDictionary() const
{
	return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
}

void LLLineEditor::addToIgnore()
{
	if (canAddToIgnore())
	{
		LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos));
	}
}

bool LLLineEditor::canAddToIgnore() const
{
	return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
}

std::string LLLineEditor::getMisspelledWord(U32 pos) const
{
	for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
	{
		if ( (it->first <= pos) && (it->second >= pos) )
		{
			return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first));
		}
	}
	return LLStringUtil::null;
}

bool LLLineEditor::isMisspelledWord(U32 pos) const
{
	for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
	{
		if ( (it->first <= pos) && (it->second >= pos) )
		{
			return true;
		}
	}
	return false;
}

void LLLineEditor::onSpellCheckSettingsChange()
{
	// Recheck the spelling on every change
	mMisspellRanges.clear();
	mSpellCheckStart = mSpellCheckEnd = -1;
}

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

	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
	{
		const LLWString& wtext = mText.getWString();

		BOOL doSelectAll = TRUE;

		// Select the word we're on
		if( LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) )
		{
			S32 old_selection_start = mLastSelectionStart;
			S32 old_selection_end = mLastSelectionEnd;

			// Select word the cursor is over
			while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[mCursorPos-1] ))
			{	// Find the start of the word
				mCursorPos--;
			}
			startSelection();	

			while ((mCursorPos < (S32)wtext.length()) && LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) )
			{	// Find the end of the word
				mCursorPos++;
			}
			mSelectionEnd = mCursorPos;

			// If nothing changed, then the word was already selected.  Select the whole line.
			doSelectAll = (old_selection_start == mSelectionStart) &&  
						  (old_selection_end   == mSelectionEnd);
		}
		
		if ( doSelectAll )
		{	// 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();

	// take selection to 'primary' clipboard
	// <FS:AW> Linux primary "clipboard" tainted by auto-selection
	//updatePrimary();

	return TRUE;
}

BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
	// Check first whether the "clear search" button wants to deal with this.
	if(childrenHandleMouseDown(x, y, mask) != NULL) 
	{
		return TRUE;
	}
	
	if (!mSelectAllonFocusReceived
		|| gFocusMgr.getKeyboardFocus() == this)
	{
		mLastSelectionStart = -1;
		mLastSelectionStart = -1;

		if (mask & MASK_SHIFT)
		{
			// assume we're starting a drag select
			mIsSelecting = TRUE;

			// 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();
			}
		}
		else
		{
			if (mTripleClickTimer.hasExpired())
			{
				// Save selection for word/line selecting on double-click
				mLastSelectionStart = mSelectionStart;
				mLastSelectionEnd = mSelectionEnd;

				// Move cursor and deselect for regular click
				setCursorAtLocalPos( x );
				deselect();
				startSelection();
			}
			else // handle triple click
			{
				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;
			}
		}

		gFocusMgr.setMouseCapture( this );
	}

	setFocus(TRUE);

	// delay cursor flashing
	mKeystrokeTimer.reset();
	
	if (mMouseDownSignal)
		(*mMouseDownSignal)(this,x,y,mask);

	return TRUE;
}

BOOL LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
{
        // LL_INFOS() << "MiddleMouseDown" << LL_ENDL;
	setFocus( TRUE );
	if( canPastePrimary() )
	{
		setCursorAtLocalPos(x);
		pastePrimary();
	}
	return TRUE;
}

BOOL LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
	setFocus(TRUE);
	if (!LLUICtrl::handleRightMouseDown(x, y, mask))
	{
		showContextMenu(x, y);
	}
	return TRUE;
}

BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask)
{
	BOOL handled = FALSE;
	// Check first whether the "clear search" button wants to deal with this.
	if(!hasMouseCapture())
	{
		if(childrenHandleHover(x, y, mask) != NULL) 
		{
			return TRUE;
		}
	}

	if( (hasMouseCapture()) && mIsSelecting )
	{
		if (x != mLastSelectionX || y != mLastSelectionY)
		{
			mLastSelectionX = x;
			mLastSelectionY = y;
		}
		// Scroll if mouse cursor outside of bounds
		if (mScrollTimer.hasExpired())
		{
			S32 increment = ll_round(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME);
			mScrollTimer.reset();
			mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME);
			if( (x < mTextLeftEdge) && (mScrollHPos > 0 ) )
			{
				// Scroll to the left
				mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length());
			}
			else
			if( (x > mTextRightEdge) && (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 >= mTextRightEdge )
				{
					// ...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);
		LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL;		
		handled = TRUE;
	}

	if( !handled  )
	{
		getWindow()->setCursor(UI_CURSOR_IBEAM);
		LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL;		
		handled = TRUE;
	}

	return handled;
}


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

	if( hasMouseCapture() )
	{
		gFocusMgr.setMouseCapture( NULL );
		handled = TRUE;
	}

	// Check first whether the "clear search" button wants to deal with this.
	if(!handled && childrenHandleMouseUp(x, y, mask) != NULL) 
	{
		return TRUE;
	}

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

		handled = TRUE;
	}

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

		// take selection to 'primary' clipboard
		updatePrimary();
	}
	
	// We won't call LLUICtrl::handleMouseUp to avoid double calls of  childrenHandleMouseUp().Just invoke the signal manually.
	if (mMouseUpSignal)
		(*mMouseUpSignal)(this,x,y, mask);
	return handled;
}


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

		mText.erase(getCursor() - 1, 1);

		setCursor(getCursor() - 1);
	}
	else
	{
		LLUI::getInstance()->reportBadKeystroke();
	}
}

// <FS> Ctrl-Backspace remove word
// Remove a word (set of characters up to next space/punctuation) from the text
void LLLineEditor::removeWord(bool prev)
{
	const U32 pos(getCursor());
	if (prev ? pos > 0 : static_cast<S32>(pos) < getLength())
	{
		U32 new_pos(prev ? prevWordPos(pos) : nextWordPos(pos));
		if (new_pos == pos) // Other character we don't jump over
			new_pos = prev ? prevWordPos(new_pos-1) : nextWordPos(new_pos+1);

		const U32 diff(labs(static_cast<S32>(pos) - static_cast<S32>(new_pos)));
		if (prev)
		{
			mText.erase(new_pos, diff);
			setCursor(new_pos);
		}
		else
		{
			mText.erase(pos, diff);
		}
	}
	else
	{
		LLUI::getInstance()->reportBadKeystroke();
	}
}
// </FS>

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

		mText.erase(getCursor(), 1);
	}

	S32 cur_bytes = mText.getString().size();

	S32 new_bytes = wchar_utf8_length(new_c);

	BOOL allow_char = TRUE;

	// Check byte length limit
	if ((new_bytes + cur_bytes) > mMaxLengthBytes)
	{
		allow_char = FALSE;
	}
	else if (mMaxLengthChars)
	{
		S32 wide_chars = mText.getWString().size();
		if ((wide_chars + 1) > mMaxLengthChars)
		{
			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
	{
		LLUI::getInstance()->reportBadKeystroke();
	}

	//<FS:TS> FIRE-11373: Autoreplace doesn't work in nearby chat bar
	if (!mReadOnly && mAutoreplaceCallback != NULL)
	{
		// autoreplace the text, if necessary
		S32 replacement_start;
		S32 replacement_length;
		LLWString replacement_string;
		S32 new_cursor_pos = getCursor();
		mAutoreplaceCallback(replacement_start, replacement_length, replacement_string, new_cursor_pos, getWText());

		if (replacement_length > 0 || !replacement_string.empty())
		{
			mText.erase(replacement_start, replacement_length);
			mText.insert(replacement_start, replacement_string);
			setCursor(new_cursor_pos);
		}
	}
	//</FS:TS> FIRE-11373

	getWindow()->hideCursorUntilMouseMove();
}

// Extends the selection box to the new cursor position
void LLLineEditor::extendSelection( S32 new_cursor_pos )
{
	if( !mIsSelecting )
	{
		startSelection();
	}
	
	S32 left_pos = llmin( mSelectionStart, new_cursor_pos );
	S32 selection_length = llabs( mSelectionStart - new_cursor_pos );
	const LLWString& selection = mText.getWString().substr(left_pos, selection_length);

	if (!prevalidateInput(selection))
		return;

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

void LLLineEditor::setDrawAsterixes(BOOL b)
{
	mDrawAsterixes = b;
	updateAllowingLanguageInput();
}

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

S32 LLLineEditor::nextWordPos(S32 cursorPos) const
{
	const LLWString& wtext = mText.getWString();
	while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) )
	{
		cursorPos++;
	} 
	while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') )
	{
		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( 0 < getCursor() )
			{
				S32 cursorPos = getCursor() - 1;
				if( mask & MASK_CONTROL )
				{
					cursorPos = prevWordPos(cursorPos);
				}
				extendSelection( cursorPos );
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			break;

		case KEY_RIGHT:
			if( getCursor() < mText.length())
			{
				S32 cursorPos = getCursor() + 1;
				if( mask & MASK_CONTROL )
				{
					cursorPos = nextWordPos(cursorPos);
				}
				extendSelection( cursorPos );
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			break;

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

		default:
			handled = FALSE;
			break;
		}
	}

	// <FS:AW> Linux primary "clipboard" tainted by auto-selection
	//if(handled)
	//{
	//	// take selection to 'primary' clipboard
	//	updatePrimary();
	//}
	// </FS:AW> Linux primary "clipboard" tainted by auto-selection
 
	return handled;
}

void LLLineEditor::deleteSelection()
{
	if( !mReadOnly && hasSelection() )
	{
		S32 left_pos, selection_length;
		getSelectionRange(&left_pos, &selection_length);
		const LLWString& selection = mText.getWString().substr(left_pos, selection_length);

		if (!prevalidateInput(selection))
			return;

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

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

// cut selection to clipboard
void LLLineEditor::cut()
{
	if( canCut() )
	{
		S32 left_pos, length;
		getSelectionRange(&left_pos, &length);
		const LLWString& selection = mText.getWString().substr(left_pos, length);

		if (!prevalidateInput(selection))
			return;

		// Prepare for possible rollback
		LLLineEditorRollback rollback( this );

		LLClipboard::instance().copyToClipboard( 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 );
			LLUI::getInstance()->reportBadKeystroke();
		}
		else
		{
			onKeystroke();
		}
	}
}

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


// copy selection to clipboard
void LLLineEditor::copy()
{
	if( canCopy() )
	{
		S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
		S32 length = llabs( mSelectionStart - mSelectionEnd );
		LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length );
	}
}

BOOL LLLineEditor::canPaste() const
{
	return !mReadOnly && LLClipboard::instance().isTextAvailable(); 
}

void LLLineEditor::paste()
{
	bool is_primary = false;
	pasteHelper(is_primary);
}

void LLLineEditor::pastePrimary()
{
	bool is_primary = true;
	pasteHelper(is_primary);
}

// paste from primary (is_primary==true) or clipboard (is_primary==false)
void LLLineEditor::pasteHelper(bool is_primary)
{
	bool can_paste_it;
	if (is_primary)
	{
		can_paste_it = canPastePrimary();
	}
	else
	{
		can_paste_it = canPaste();
	}

	if (can_paste_it)
	{
		LLWString paste;
		LLClipboard::instance().pasteFromClipboard(paste, is_primary);

		if (!paste.empty())
		{
			if (!prevalidateInput(paste))
				return;

			// Prepare for possible rollback
			LLLineEditorRollback rollback(this);
			
			// Delete any selected characters
			if ((!is_primary) && hasSelection())
			{
				deleteSelection();
			}

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

			// Insert the string

			// Check to see that the size isn't going to be larger than the max number of bytes
			U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText);

			if ( available_bytes < (U32) wstring_utf8_length(clean_string) )
			{	// Doesn't all fit
				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);
				}
				// Truncate the clean string at the limit of what will fit
				clean_string = clean_string.substr(0, wchars_that_fit);
				LLUI::getInstance()->reportBadKeystroke();
			}

			if (mMaxLengthChars)
			{
				U32 available_chars = mMaxLengthChars - mText.getWString().size();
		
				if (available_chars < clean_string.size())
				{
					clean_string = clean_string.substr(0, available_chars);
				}

				LLUI::getInstance()->reportBadKeystroke();
			}

			mText.insert(getCursor(), clean_string);
			setCursor( 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 );
				LLUI::getInstance()->reportBadKeystroke();
			}
			else
			{
				onKeystroke();
			}
		}
	}
}

// copy selection to primary
void LLLineEditor::copyPrimary()
{
	if( canCopy() )
	{
		S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
		S32 length = llabs( mSelectionStart - mSelectionEnd );
		LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length, true);
	}
}

BOOL LLLineEditor::canPastePrimary() const
{
	return !mReadOnly && LLClipboard::instance().isTextAvailable(true); 
}

void LLLineEditor::updatePrimary()
{
	if(canCopy() )
	{
		copyPrimary();
	}
}

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)
		{
			//LL_INFOS() << "Handling backspace" << LL_ENDL;
			if( hasSelection() )
			{
				deleteSelection();
			}
			else
			if( 0 < getCursor() )
			{
				// <FS> Ctrl-Backspace remove word
				//removeChar();
				if (mask == MASK_CONTROL)
					removeWord(true);
				else
					removeChar();
				// </FS>
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
		}
		handled = TRUE;
		break;

	// <FS> Ctrl-Backspace remove word
	case KEY_DELETE:
		if (!mReadOnly && mask == MASK_CONTROL)
		{
			removeWord(false);
			handled = true;
		}
		break;
	// </FS>

	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 && mask == MASK_NONE)
			break;
		if ((mask & MASK_ALT) == 0)
		{
			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
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	case KEY_RIGHT:
		if (mIgnoreArrowKeys && mask == MASK_NONE)
			break;
		if ((mask & MASK_ALT) == 0)
		{
			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
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	// handle ctrl-uparrow if we have a history enabled line editor.
	case KEY_UP:
		if( mHaveHistory && ((mIgnoreArrowKeys == false) || ( MASK_CONTROL == mask )) )
		{
			if( mCurrentHistoryLine > mLineHistory.begin() )
			{
				// <FS> FIRE-324. Nearby and IM chat bars forget what was written after browsing history
				(*mCurrentHistoryLine).assign(getText());
				mText.assign( *(--mCurrentHistoryLine) );
				setCursorToEnd();
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	// handle [ctrl]-downarrow if we have a history enabled line editor
	case KEY_DOWN:
		if( mHaveHistory  && ((mIgnoreArrowKeys == false) || ( MASK_CONTROL == mask )) )
		{
			if( !mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.end() - 1 )
			{
				mText.assign( *(++mCurrentHistoryLine) );
				setCursorToEnd();
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	case KEY_RETURN:
		// store sent line in history
		updateHistory();
		break;

	case KEY_ESCAPE:
	    if (mRevertOnEsc && mText.getString() != mPrevText)
		{
			setText(mPrevText);
			// Note, don't set handled, still want to loose focus (won't commit becase text is now unchanged)
			if (mKeystrokeOnEsc)
			{
				onKeystroke();
			}
		}
		break;
		
	default:
		break;
	}

	return handled;
}


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

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

		// <FS:Ansariel> FIRE-19933: Open context menu on context menu key press
		if (key == KEY_CONTEXT_MENU)
		{
			showContextMenu(getLocalRect().getCenterX(), getLocalRect().getCenterY(), false);
		}
		// </FS:Ansariel>

		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 &&
				// <FS> Capslock deselecting text
				//KEY_CAPSLOCK )
				KEY_CAPSLOCK != key)
				// </FS>
			{
				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);

				LLUI::getInstance()->reportBadKeystroke();
			}

			// Notify owner if requested
			if (!need_to_rollback && handled)
			{
				onKeystroke();
				if ( (!selection_modified) && (KEY_BACKSPACE == key) )
				{
					mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
				}
			}
		}
	}

	return handled;
}


BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
{
	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 );

		{
			LLWString u_char;
			u_char.assign(1, uni_char);
			if (!prevalidateInput(u_char))
				return handled;
		}

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

			LLUI::getInstance()->reportBadKeystroke();
		}

		// Notify owner if requested
		if( !need_to_rollback && handled )
		{
			// 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
			onKeystroke();

			mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
		}
	}
	return handled;
}


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

void LLLineEditor::doDelete()
{
	if (canDoDelete() && mText.length() > 0)
	{
		// Prepare for possible rollback
		LLLineEditorRollback rollback( this );

		if (hasSelection())
		{
			deleteSelection();
		}
		else if ( getCursor() < mText.length())
		{	
			const LLWString& text_to_delete = mText.getWString().substr(getCursor(), 1);

			if (!prevalidateInput(text_to_delete))
			{
				onKeystroke();
				return;
			}
			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 );
			LLUI::getInstance()->reportBadKeystroke();
		}
		else
		{
			onKeystroke();

			mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
		}
	}
}


void LLLineEditor::drawBackground()
{
	F32 alpha = getCurrentTransparency();
	if (mUseBgColor)
	{
		gl_rect_2d(getLocalRect(), mBgColor % alpha, TRUE);
	}
	else
	{
		bool has_focus = hasFocus();
		LLUIImage* image;
		if (mReadOnly)
		{
			image = mBgImageDisabled;
		}
		else if (has_focus || mShowImageFocused)
		{
			image = mBgImageFocused;
		}
		else
		{
			image = mBgImage;
		}

		if (!image) return;
		// optionally draw programmatic border
		if (has_focus)
		{
			LLColor4 tmp_color = gFocusMgr.getFocusColor();
			tmp_color.setAlpha(alpha);
			image->drawBorder(0, 0, getRect().getWidth(), getRect().getHeight(),
				tmp_color,
				gFocusMgr.getFocusFlashWidth());
		}
		LLColor4 tmp_color = UI_VERTEX_COLOR;
		tmp_color.setAlpha(alpha);
		image->draw(getLocalRect(), tmp_color);
	}
}

void LLLineEditor::draw()
{
	F32 alpha = getDrawContext().mAlpha;
	S32 text_len = mText.length();
	static LLUICachedControl<S32> lineeditor_cursor_thickness ("UILineEditorCursorThickness", 0);
	static LLUICachedControl<F32> preedit_marker_brightness ("UIPreeditMarkerBrightness", 0);
	static LLUICachedControl<S32> preedit_marker_gap ("UIPreeditMarkerGap", 0);
	static LLUICachedControl<S32> preedit_marker_position ("UIPreeditMarkerPosition", 0);
	static LLUICachedControl<S32> preedit_marker_thickness ("UIPreeditMarkerThickness", 0);
	static LLUICachedControl<F32> preedit_standout_brightness ("UIPreeditStandoutBrightness", 0);
	static LLUICachedControl<S32> preedit_standout_gap ("UIPreeditStandoutGap", 0);
	static LLUICachedControl<S32> preedit_standout_position ("UIPreeditStandoutPosition", 0);
	static LLUICachedControl<S32> preedit_standout_thickness ("UIPreeditStandoutThickness", 0);

	std::string saved_text;
	if (mDrawAsterixes)
	{
		saved_text = mText.getString();
		std::string text;
		for (S32 i = 0; i < mText.length(); i++)
		{
			text += PASSWORD_ASTERISK;
		}
		mText = text;
	}

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

	S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2;
	if (mSpellCheck)
	{
		lineeditor_v_pad += 1;
	}

	drawBackground();
	
	// draw text

	// With viewer-2 art files, input region is 2 pixels up
	S32 cursor_bottom = background.mBottom + 2;
	S32 cursor_top = background.mTop - 1;

	LLColor4 text_color;
	if (!mReadOnly)
	{
		if (!getTentative())
		{
			text_color = mFgColor.get();
		}
		else
		{
			text_color = mTentativeFgColor.get();
		}
	}
	else
	{
		text_color = mReadOnlyFgColor.get();
	}
	text_color.setAlpha(alpha);
	LLColor4 label_color = mTentativeFgColor.get();
	label_color.setAlpha(alpha);
	
	if (hasPreeditString())
	{
		// Draw preedit markers.  This needs to be before drawing letters.
		for (U32 i = 0; i < mPreeditStandouts.size(); i++)
		{
			const S32 preedit_left = mPreeditPositions[i];
			const S32 preedit_right = mPreeditPositions[i + 1];
			if (preedit_right > mScrollHPos)
			{
				S32 preedit_pixels_left = findPixelNearestPos(llmax(preedit_left, mScrollHPos) - getCursor());
				S32 preedit_pixels_right = llmin(findPixelNearestPos(preedit_right - getCursor()), background.mRight);
				if (preedit_pixels_left >= background.mRight)
				{
					break;
				}
				if (mPreeditStandouts[i])
				{
					gl_rect_2d(preedit_pixels_left + preedit_standout_gap,
						background.mBottom + preedit_standout_position,
						preedit_pixels_right - preedit_standout_gap - 1,
						background.mBottom + preedit_standout_position - preedit_standout_thickness,
						(text_color * preedit_standout_brightness 
						 + mPreeditBgColor * (1 - preedit_standout_brightness)).setAlpha(alpha/*1.0f*/));
				}
				else
				{
					gl_rect_2d(preedit_pixels_left + preedit_marker_gap,
						background.mBottom + preedit_marker_position,
						preedit_pixels_right - preedit_marker_gap - 1,
						background.mBottom + preedit_marker_position - preedit_marker_thickness,
						(text_color * preedit_marker_brightness
						 + mPreeditBgColor * (1 - preedit_marker_brightness)).setAlpha(alpha/*1.0f*/));
				}
			}
		}
	}

	S32 rendered_text = 0;
	F32 rendered_pixels_right = (F32)mTextLeftEdge;
	F32 text_bottom = (F32)background.mBottom + (F32)lineeditor_v_pad;

	if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() )
	{
		S32 select_left;
		S32 select_right;
		if (mSelectionStart < mSelectionEnd)
		{
			select_left = mSelectionStart;
			select_right = mSelectionEnd;
		}
		else
		{
			select_left = mSelectionEnd;
			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,
				0,
				LLFontGL::NO_SHADOW,
				select_left - mScrollHPos,
				mTextRightEdge - ll_round(rendered_pixels_right),
				&rendered_pixels_right);
		}
		
		if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) )
		{
			LLColor4 color = mHighlightColor;
			color.setAlpha(alpha);
			// selected middle
			S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text);
			width = llmin(width, mTextRightEdge - ll_round(rendered_pixels_right));
			gl_rect_2d(ll_round(rendered_pixels_right), cursor_top, ll_round(rendered_pixels_right)+width, cursor_bottom, color);

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

		if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) )
		{
			// unselected, right side
			rendered_text += mGLFont->render( 
				mText, mScrollHPos + rendered_text,
				rendered_pixels_right, text_bottom,
				text_color,
				LLFontGL::LEFT, LLFontGL::BOTTOM,
				0,
				LLFontGL::NO_SHADOW,
				S32_MAX,
				mTextRightEdge - ll_round(rendered_pixels_right),
				&rendered_pixels_right);
		}
	}
	else
	{
		rendered_text = mGLFont->render( 
			mText, mScrollHPos, 
			rendered_pixels_right, text_bottom,
			text_color,
			LLFontGL::LEFT, LLFontGL::BOTTOM,
			0,
			LLFontGL::NO_SHADOW,
			S32_MAX,
			mTextRightEdge - ll_round(rendered_pixels_right),
			&rendered_pixels_right);
	}
#if 1 // for when we're ready for image art.
	mBorder->setVisible(FALSE); // no more programmatic art.
#endif

	if ( (getSpellCheck()) && (mText.length() > 2) )
	{
		// Calculate start and end indices for the first and last visible word
		U32 start = prevWordPos(mScrollHPos), end = nextWordPos(mScrollHPos + rendered_text);

		if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) )
		{
			const LLWString& text = mText.getWString().substr(start, end);

			// Find the start of the first word
			U32 word_start = 0, word_end = 0;
			while ( (word_start < text.length()) && (!LLStringOps::isAlpha(text[word_start])) )
			{
				word_start++;
			}

			// Iterate over all words in the text block and check them one by one
			mMisspellRanges.clear();
			while (word_start < text.length())
			{
				// Find the end of the current word (special case handling for "'" when it's used as a contraction)
				word_end = word_start + 1;
				while ( (word_end < text.length()) && 
					    ((LLWStringUtil::isPartOfWord(text[word_end])) ||
						 ((L'\'' == text[word_end]) && (word_end + 1 < text.length()) &&
						  (LLStringOps::isAlnum(text[word_end - 1])) && (LLStringOps::isAlnum(text[word_end + 1])))) )
				{
					word_end++;
				}
				if (word_end > text.length())
				{
					break;
				}

				// Don't process words shorter than 3 characters
				std::string word = wstring_to_utf8str(text.substr(word_start, word_end - word_start));
				if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) )
				{
					mMisspellRanges.push_back(std::pair<U32, U32>(start + word_start, start + word_end));
				}

				// Find the start of the next word
				word_start = word_end + 1;
				while ( (word_start < text.length()) && (!LLWStringUtil::isPartOfWord(text[word_start])) )
				{
					word_start++;
				}
			}

			mSpellCheckStart = start;
			mSpellCheckEnd = end;
		}

		// Draw squiggly lines under any (visible) misspelled words
		for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
		{
			// Skip over words that aren't (partially) visible
			if ( ((it->first < start) && (it->second < start)) || (it->first > end) )
			{
				continue;
			}

			// Skip the current word if the user is still busy editing it
			if ( (!mSpellCheckTimer.hasExpired()) && (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
			{
 				continue;
			}

			S32 pxWidth = getRect().getWidth();
			S32 pxStart = findPixelNearestPos(it->first - getCursor());
			if (pxStart > pxWidth)
			{
				continue;
			}
			S32 pxEnd = findPixelNearestPos(it->second - getCursor());
			if (pxEnd > pxWidth)
			{
				pxEnd = pxWidth;
			}

			S32 pxBottom = (S32)(text_bottom + mGLFont->getDescenderHeight());

			gGL.color4ub(255, 0, 0, 200);
			while (pxStart + 1 < pxEnd)
			{
				gl_line_2d(pxStart, pxBottom, pxStart + 2, pxBottom - 2);
				if (pxStart + 3 < pxEnd)
				{
					gl_line_2d(pxStart + 2, pxBottom - 3, pxStart + 4, pxBottom - 1);
				}
				pxStart += 4;
			}
		}
	}

	// If we're editing...
	if( hasFocus())
	{
		//mBorder->setVisible(TRUE); // ok, programmer art just this once.
		// (Flash the cursor every half second)
		if (!mReadOnly && gFocusMgr.getAppHasFocus())
		{
			F32 elapsed = mKeystrokeTimer.getElapsedTimeF32();
			if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
			{
				S32 cursor_left = findPixelNearestPos();
				cursor_left -= lineeditor_cursor_thickness / 2;
				S32 cursor_right = cursor_left + lineeditor_cursor_thickness;
				if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
				{
					const LLWString space(utf8str_to_wstring(std::string(" ")));
					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())
				{
					LLColor4 tmp_color( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], alpha );
					mGLFont->render(mText, getCursor(), (F32)(cursor_left + lineeditor_cursor_thickness / 2), text_bottom, 
						tmp_color,
						LLFontGL::LEFT, LLFontGL::BOTTOM,
						0,
						LLFontGL::NO_SHADOW,
						1);
				}

				// Make sure the IME is in the right place
				S32 pixels_after_scroll = findPixelNearestPos();	// RCalculcate for IME position
				LLRect screen_pos = calcScreenRect();
				LLCoordGL ime_pos( screen_pos.mLeft + pixels_after_scroll, screen_pos.mTop - lineeditor_v_pad );

				ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]);
				ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]);
				getWindow()->setLanguageTextInput( ime_pos );
			}
		}

		//draw label if no text is provided
		//but we should draw it in a different color
		//to give indication that it is not text you typed in
		if (0 == mText.length() && mReadOnly)
		{
			mGLFont->render(mLabel.getWString(), 0,
							mTextLeftEdge, (F32)text_bottom,
							label_color,
							LLFontGL::LEFT,
							LLFontGL::BOTTOM,
							0,
							LLFontGL::NO_SHADOW,
							S32_MAX,
							mTextRightEdge - ll_round(rendered_pixels_right),
							&rendered_pixels_right, FALSE);
		}


		// 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,
							mTextLeftEdge, (F32)text_bottom,
							label_color,
							LLFontGL::LEFT,
							LLFontGL::BOTTOM,
							0,
							LLFontGL::NO_SHADOW,
							S32_MAX,
							mTextRightEdge - ll_round(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) const
{
	S32 dpos = getCursor() - mScrollHPos + cursor_offset;
	S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mTextLeftEdge;
	return result;
}

S32 LLLineEditor::calcCursorPos(S32 mouse_x)
{
	const llwchar* wtext = mText.getWString().c_str();
	LLWString asterix_text;
	if (mDrawAsterixes)
	{
		for (S32 i = 0; i < mText.length(); i++)
		{
			asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK);
		}
		wtext = asterix_text.c_str();
	}

	S32 cur_pos = mScrollHPos +
			mGLFont->charFromPixelOffset(
				wtext, mScrollHPos,
				(F32)(mouse_x - mTextLeftEdge),
				(F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive

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

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

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

	if (!new_state)
	{
		getWindow()->allowLanguageTextInput(this, FALSE);
	}


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

	if (new_state)
	{
		// Allow Language Text Input only when this LineEditor has
		// no prevalidate function attached.  This criterion works
		// fine on 1.15.0.2, since all prevalidate func reject any
		// non-ASCII characters.  I'm not sure on future versions,
		// however.
		getWindow()->allowLanguageTextInput(this, mPrevalidateFunc == NULL);
	}
}

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

void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func)
{
	mPrevalidateFunc = func;
	updateAllowingLanguageInput();
}

void LLLineEditor::setPrevalidateInput(LLTextValidate::validate_func_t func)
{
	mPrevalidateInputFunc = func;
	updateAllowingLanguageInput();
}

bool LLLineEditor::prevalidateInput(const LLWString& wstr)
{
	if (mPrevalidateInputFunc && !mPrevalidateInputFunc(wstr))
	{
		return false;
	}

	return true;
}

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

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

	LLWString trimmed = utf8str_to_wstring(str);
	LLWStringUtil::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
		llwchar decimal_point = (llwchar)LLResMgr::getInstance()->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( LLStringOps::isDigit( trimmed[i] ) )
			{
				has_digit = TRUE;
			}
			else
			{
				success = FALSE;
				break;
			}
		}
	}		

	// Gotta have at least one
	success = has_digit;

	return success;
}

BOOL LLLineEditor::evaluateFloat()
{
	bool success;
	F32 result = 0.f;
	std::string expr = getText();
	LLStringUtil::toUpper(expr);

	success = LLCalc::getInstance()->evalString(expr, result);

	if (!success)
	{
		// Move the cursor to near the error on failure
		setCursor(LLCalc::getInstance()->getLastErrorPos());
		// *TODO: Translated error message indicating the type of error? Select error text?
	}
	else
	{
		// Replace the expression with the result
		std::string result_str = llformat("%f",result);
		setText(result_str);
		selectAll();
	}

	return success;
}

void LLLineEditor::onMouseCaptureLost()
{
	endSelection();
}


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

void LLLineEditor::onKeystroke()
{
	if (mKeystrokeCallback)
	{
		mKeystrokeCallback(this);
	}

	mSpellCheckStart = mSpellCheckEnd = -1;
}

void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data)
{
	mKeystrokeCallback = boost::bind(callback, _1, user_data);
}


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

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


void LLLineEditor::updateAllowingLanguageInput()
{
	// Allow Language Text Input only when this LineEditor has
	// no prevalidate function attached (as long as other criteria
	// common to LLTextEditor).  This criterion works
	// fine on 1.15.0.2, since all prevalidate func reject any
	// non-ASCII characters.  I'm not sure on future versions,
	// however...
	LLWindow* window = getWindow();
	if (!window)
	{
		// test app, no window available
		return;	
	}
	if (hasFocus() && !mReadOnly && !mDrawAsterixes && mPrevalidateFunc == NULL)
	{
		window->allowLanguageTextInput(this, TRUE);
	}
	else
	{
		window->allowLanguageTextInput(this, FALSE);
	}
}

BOOL LLLineEditor::hasPreeditString() const
{
	return (mPreeditPositions.size() > 1);
}

void LLLineEditor::resetPreedit()
{
	if (hasSelection())
	{
		if (hasPreeditString())
		{
			LL_WARNS() << "Preedit and selection!" << LL_ENDL;
			deselect();
		}
		else
		{
			deleteSelection();
		}
	}
	if (hasPreeditString())
	{
		const S32 preedit_pos = mPreeditPositions.front();
		mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos);
		mText.insert(preedit_pos, mPreeditOverwrittenWString);
		setCursor(preedit_pos);
		
		mPreeditWString.clear();
		mPreeditOverwrittenWString.clear();
		mPreeditPositions.clear();

		// Don't reset key stroke timer nor invoke keystroke callback,
		// because a call to updatePreedit should be follow soon in 
		// normal course of operation, and timer and callback will be 
		// maintained there.  Doing so here made an odd sound.  (VWR-3410) 
	}
}

void LLLineEditor::updatePreedit(const LLWString &preedit_string,
		const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position)
{
	// Just in case.
	if (mReadOnly)
	{
		return;
	}

	// Note that call to updatePreedit is always preceeded by resetPreedit,
	// so we have no existing selection/preedit.

	S32 insert_preedit_at = getCursor();

	mPreeditWString = preedit_string;
	mPreeditPositions.resize(preedit_segment_lengths.size() + 1);
	S32 position = insert_preedit_at;
	for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++)
	{
		mPreeditPositions[i] = position;
		position += preedit_segment_lengths[i];
	}
	mPreeditPositions.back() = position;
	if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
	{
		mPreeditOverwrittenWString.assign( LLWString( mText, insert_preedit_at, mPreeditWString.length() ) );
		mText.erase(insert_preedit_at, mPreeditWString.length());
	}
	else
	{
		mPreeditOverwrittenWString.clear();
	}
	mText.insert(insert_preedit_at, mPreeditWString);

	mPreeditStandouts = preedit_standouts;

	setCursor(position);
	setCursor(mPreeditPositions.front() + caret_position);

	// Update of the preedit should be caused by some key strokes.
	mKeystrokeTimer.reset();
	onKeystroke();

	mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}

BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const
{
	if (control)
	{
		LLRect control_rect_screen;
		localRectToScreen(getRect(), &control_rect_screen);
		LLUI::getInstance()->screenRectToGL(control_rect_screen, control);
	}

	S32 preedit_left_column, preedit_right_column;
	if (hasPreeditString())
	{
		preedit_left_column = mPreeditPositions.front();
		preedit_right_column = mPreeditPositions.back();
	}
	else
	{
		preedit_left_column = preedit_right_column = getCursor();
	}
	if (preedit_right_column < mScrollHPos)
	{
		// This should not occure...
		return FALSE;
	}

	const S32 query = (query_offset >= 0 ? preedit_left_column + query_offset : getCursor());
	if (query < mScrollHPos || query < preedit_left_column || query > preedit_right_column)
	{
		return FALSE;
	}

	if (coord)
	{
		S32 query_local = findPixelNearestPos(query - getCursor());
		S32 query_screen_x, query_screen_y;
		localPointToScreen(query_local, getRect().getHeight() / 2, &query_screen_x, &query_screen_y);
		LLUI::getInstance()->screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY);
	}

	if (bounds)
	{
		S32 preedit_left_local = findPixelNearestPos(llmax(preedit_left_column, mScrollHPos) - getCursor());
		S32 preedit_right_local = llmin(findPixelNearestPos(preedit_right_column - getCursor()), getRect().getWidth() - mBorderThickness);
		if (preedit_left_local > preedit_right_local)
		{
			// Is this condition possible?
			preedit_right_local = preedit_left_local;
		}

		LLRect preedit_rect_local(preedit_left_local, getRect().getHeight(), preedit_right_local, 0);
		LLRect preedit_rect_screen;
		localRectToScreen(preedit_rect_local, &preedit_rect_screen);
		LLUI::getInstance()->screenRectToGL(preedit_rect_screen, bounds);
	}

	return TRUE;
}

void LLLineEditor::getPreeditRange(S32 *position, S32 *length) const
{
	if (hasPreeditString())
	{
		*position = mPreeditPositions.front();
		*length = mPreeditPositions.back() - mPreeditPositions.front();
	}
	else
	{
		*position = mCursorPos;
		*length = 0;
	}
}

void LLLineEditor::getSelectionRange(S32 *position, S32 *length) const
{
	if (hasSelection())
	{
		*position = llmin(mSelectionStart, mSelectionEnd);
		*length = llabs(mSelectionStart - mSelectionEnd);
	}
	else
	{
		*position = mCursorPos;
		*length = 0;
	}
}

void LLLineEditor::markAsPreedit(S32 position, S32 length)
{
	deselect();
	setCursor(position);
	if (hasPreeditString())
	{
		LL_WARNS() << "markAsPreedit invoked when hasPreeditString is true." << LL_ENDL;
	}
	mPreeditWString.assign( LLWString( mText.getWString(), position, length ) );
	if (length > 0)
	{
		mPreeditPositions.resize(2);
		mPreeditPositions[0] = position;
		mPreeditPositions[1] = position + length;
		mPreeditStandouts.resize(1);
		mPreeditStandouts[0] = FALSE;
	}
	else
	{
		mPreeditPositions.clear();
		mPreeditStandouts.clear();
	}
	if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
	{
		mPreeditOverwrittenWString = mPreeditWString;
	}
	else
	{
		mPreeditOverwrittenWString.clear();
	}
}

S32 LLLineEditor::getPreeditFontSize() const
{
	return ll_round(mGLFont->getLineHeight() * LLUI::getScaleFactor().mV[VY]);
}

void LLLineEditor::setReplaceNewlinesWithSpaces(BOOL replace)
{
	mReplaceNewlinesWithSpaces = replace;
}

LLWString LLLineEditor::getConvertedText() const
{
	LLWString text = getWText();
	LLWStringUtil::trim(text);
	if (!mReplaceNewlinesWithSpaces)
	{
		LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
	}
	return text;
}

// <FS:Ansariel> FIRE-19933: Open context menu on context menu key press
//void LLLineEditor::showContextMenu(S32 x, S32 y)
void LLLineEditor::showContextMenu(S32 x, S32 y, bool set_cursor_pos)
// </FS:Ansariel>
{
	//LLContextMenu* menu = static_cast<LLContextMenu*>(mContextMenuHandle.get());
	// <FS:Ansariel> Delay context menu initialization if LLMenuGL::sMenuContainer is still NULL
	LLContextMenu* menu = NULL;
	if (mDelayedInit && !mContextMenuHandle.get())
	{
		llassert(LLMenuGL::sMenuContainer != NULL);
		menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>
			("menu_text_editor.xml",
			 LLMenuGL::sMenuContainer,
			 LLMenuHolderGL::child_registry_t::instance());
		setContextMenu(menu);
	}
	else
	{
		menu = static_cast<LLContextMenu*>(mContextMenuHandle.get());
	}
	mDelayedInit = false;
	// </FS:Ansariel>

	if (menu)
	{
		gEditMenuHandler = this;

		S32 screen_x, screen_y;
		localPointToScreen(x, y, &screen_x, &screen_y);

		if (set_cursor_pos) // <FS:Ansariel> FIRE-19933: Open context menu on context menu key press
			setCursorAtLocalPos(x);
		if (hasSelection())
		{
			if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
			{
				deselect();
			}
			else
			{
				setCursor(llmax(mSelectionStart, mSelectionEnd));
			}
		}

		bool use_spellcheck = getSpellCheck(), is_misspelled = false;
		if (use_spellcheck)
		{
			mSuggestionList.clear();

			// If the cursor is on a misspelled word, retrieve suggestions for it
			std::string misspelled_word = getMisspelledWord(mCursorPos);
			if ((is_misspelled = !misspelled_word.empty()) == true)
			{
				LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList);
			}
		}

		menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty()));
		menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled));
		menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled));
		menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled));
		menu->show(screen_x, screen_y, this);
	}
}

void LLLineEditor::setContextMenu(LLContextMenu* new_context_menu)
{
    LLContextMenu* menu = static_cast<LLContextMenu*>(mContextMenuHandle.get());
    if (menu)
    {
        menu->die();
        mContextMenuHandle.markDead();
		// <FS:Ansariel> Delay context menu initialization if LLMenuGL::sMenuContainer is still NULL
		mDelayedInit = false;
    }

    if (new_context_menu)
    {
        mContextMenuHandle = new_context_menu->getHandle();
    }
}

void LLLineEditor::setFont(const LLFontGL* font)
{
	mGLFont = font;
}
