summaryrefslogtreecommitdiff
path: root/indra/llui/lllineeditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui/lllineeditor.cpp')
-rw-r--r--indra/llui/lllineeditor.cpp5470
1 files changed, 2735 insertions, 2735 deletions
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 8c5e69fbb2..5ec4c34f57 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -1,2735 +1,2735 @@
-/**
- * @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();
-}
-
-// 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 || (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 || (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::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()))
- {
- 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;
-}
+/**
+ * @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();
+}
+
+// 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 || (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 || (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::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()))
+ {
+ 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;
+}