/**
 * @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"),
    prevalidator("prevalidator"),
    input_prevalidator("input_prevalidator"),
    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),
    show_label_focused("show_label_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),
    allow_emoji("allow_emoji", true),
    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(prevalidator, "prevalidate_callback");
    addSynonym(input_prevalidator, "prevalidate_input_callback");
    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 ),
    mAllowEmoji( p.allow_emoji ),
    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 ),
    mShowLabelFocused( p.show_label_focused ),
    mUseBgColor(p.use_bg_color),
    mHaveHistory(FALSE),
    mReplaceNewlinesWithSpaces( TRUE ),
    mPrevalidator(p.prevalidator()),
    mInputPrevalidator(p.input_prevalidator()),
    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),
    mContextMenuHandle(),
    mShowContextMenu(true)
{
    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();

    updateAllowingLanguageInput();
}

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

    // 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 (!mAllowEmoji)
    {
        // Cut emoji symbols if exist
        utf8str_remove_emojis(truncated_utf8);
    }
    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;
    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) )
        {
            LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
            if (!mAllowEmoji)
            {
                // Cut emoji symbols if exist
                wstring_remove_emojis(suggestion);
            }
            if (suggestion.empty())
                return;

            deselect();

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

            // Insert the suggestion in its place
            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
    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) && getShowContextMenu())
    {
        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();
    }
}

void LLLineEditor::addChar(const llwchar uni_char)
{
    if (!mAllowEmoji && LLStringOps::isEmoji(uni_char))
        return;

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

    getWindow()->hideCursorUntilMouseMove();
}

void LLLineEditor::addString(char *s, bool editing)
{
	if (hasSelection())
		deleteSelection();
	else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) {
		if (!prevalidateInput(mText.getWString()
					.substr(getCursor(), 1)))
			return;
		mText.erase(getCursor(), 1);
	} else if (editing) {
		mText.clear();
		setCursor(0);
	}
	mText.insert(getCursor(), utf8str_to_wstring(s));
	if (editing) setCursor(strlen(s));
	else setCursor(getCursor() + 1);
	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;
        }
    }

    if(handled)
    {
        // take selection to 'primary' clipboard
        updatePrimary();
    }

    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 = mPrevalidator && !mPrevalidator.validate(mText.getWString());
        if (need_to_rollback)
        {
            rollback.doRollback( this );
            LLUI::getInstance()->reportBadKeystroke();
            mPrevalidator.showLastErrorUsingTimeout();
        }
        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 (!mAllowEmoji)
            {
                wstring_remove_emojis(paste);
            }

            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 = mPrevalidator && !mPrevalidator.validate(mText.getWString());
            if (need_to_rollback)
            {
                rollback.doRollback( this );
                LLUI::getInstance()->reportBadKeystroke();
                mPrevalidator.showLastErrorUsingTimeout();
            }
            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() )
            {
                removeChar();
            }
            else
            {
                LLUI::getInstance()->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 && 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() )
            {
                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 );

        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 != key)
            {
                deselect();
            }

            bool prevalidator_failed = false;

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

            // Validate new string and rollback the keystroke if needed.
            if (!need_to_rollback && mPrevalidator)
            {
                prevalidator_failed = !mPrevalidator.validate(mText.getWString());
                need_to_rollback |= prevalidator_failed;
            }

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

                LLUI::getInstance()->reportBadKeystroke();
                if (prevalidator_failed)
                {
                    mPrevalidator.showLastErrorUsingTimeout();
                }
            }

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

        // Validate new string and rollback the keystroke if needed.
        bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString());
        if (need_to_rollback)
        {
            rollback.doRollback( this );

            LLUI::getInstance()->reportBadKeystroke();
            mPrevalidator.showLastErrorUsingTimeout();
        }

        // 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::handleUnicodeStringHere(char *uni_str, bool editing)
{
	auto handled = FALSE;

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

		addString(uni_str, editing);

		mKeystrokeTimer.reset();
		deselect();
		auto need_to_rollback = mPrevalidator
			&& !mPrevalidator.validate(mText.getWString());

		if (need_to_rollback) {
			rollback.doRollback(this);
			LLUI::getInstance()->reportBadKeystroke();
			mPrevalidator.showLastErrorUsingTimeout();
		}

		if (!need_to_rollback && handled) {
			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 = mPrevalidator && !mPrevalidator.validate(mText.getWString());
        if (need_to_rollback)
        {
            rollback.doRollback(this);
            LLUI::getInstance()->reportBadKeystroke();
            mPrevalidator.showLastErrorUsingTimeout();
        }
        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);
    }
}

//virtual
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 || mShowLabelFocused))
        {
            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);
        }
        // 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, !mPrevalidator);
    }
}

//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::Validator validator)
{
    mPrevalidator = validator;
    updateAllowingLanguageInput();
}

void LLLineEditor::setPrevalidateInput(LLTextValidate::Validator validator)
{
    mInputPrevalidator = validator;
    updateAllowingLanguageInput();
}

bool LLLineEditor::prevalidateInput(const LLWString& wstr)
{
    return mInputPrevalidator.validate(wstr);
}

// 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 && !mPrevalidator)
    {
        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;
}

void LLLineEditor::showContextMenu(S32 x, S32 y)
{
    LLContextMenu* menu = static_cast<LLContextMenu*>(mContextMenuHandle.get());
    if (!menu)
    {
        llassert(LLMenuGL::sMenuContainer != NULL);
        menu = LLUICtrlFactory::createFromFile<LLContextMenu>
            ("menu_text_editor.xml",
                LLMenuGL::sMenuContainer,
                LLMenuHolderGL::child_registry_t::instance());
        setContextMenu(menu);
    }

    if (menu)
    {
        gEditMenuHandler = this;

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

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

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

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