summaryrefslogtreecommitdiff
path: root/indra/llui/lllineeditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui/lllineeditor.cpp')
-rwxr-xr-x[-rw-r--r--]indra/llui/lllineeditor.cpp750
1 files changed, 533 insertions, 217 deletions
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 483a394bbd..a08cf91a69 100644..100755
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -2,31 +2,25 @@
* @file lllineeditor.cpp
* @brief LLLineEditor base class
*
- * $LicenseInfo:firstyear=2001&license=viewergpl$
- *
- * Copyright (c) 2001-2009, Linden Research, Inc.
- *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 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.
*
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 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.
*
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
+ * 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
*
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -43,6 +37,7 @@
#include "llgl.h"
#include "lltimer.h"
+#include "llcalc.h"
//#include "llclipboard.h"
#include "llcontrol.h"
#include "llbutton.h"
@@ -50,6 +45,7 @@
#include "llkeyboard.h"
#include "llrect.h"
#include "llresmgr.h"
+#include "llspellcheck.h"
#include "llstring.h"
#include "llwindow.h"
#include "llui.h"
@@ -70,6 +66,7 @@ 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
@@ -84,17 +81,20 @@ template class LLLineEditor* LLView::getChild<class LLLineEditor>(
//
LLLineEditor::Params::Params()
-: max_length_bytes("max_length", 254),
+: max_length(""),
keystroke_callback("keystroke_callback"),
prevalidate_callback("prevalidate_callback"),
+ prevalidate_input_callback("prevalidate_input_callback"),
background_image("background_image"),
background_image_disabled("background_image_disabled"),
background_image_focused("background_image_focused"),
+ bg_image_always_focused("bg_image_always_focused", false),
select_on_focus("select_on_focus", false),
- handle_edit_keys_directly("handle_edit_keys_directly", false),
revert_on_esc("revert_on_esc", true),
+ spellcheck("spellcheck", false),
commit_on_focus_lost("commit_on_focus_lost", true),
ignore_tab("ignore_tab", true),
+ is_password("is_password", false),
cursor_color("cursor_color"),
text_color("text_color"),
text_readonly_color("text_readonly_color"),
@@ -107,15 +107,17 @@ LLLineEditor::Params::Params()
text_pad_right("text_pad_right"),
default_text("default_text")
{
- mouse_opaque = true;
+ changeDefault(mouse_opaque, true);
addSynonym(select_on_focus, "select_all_on_focus_received");
addSynonym(border, "border");
addSynonym(label, "watermark_text");
+ addSynonym(max_length.chars, "max_length");
}
LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
: LLUICtrl(p),
- mMaxLengthBytes(p.max_length_bytes),
+ mMaxLengthBytes(p.max_length.bytes),
+ mMaxLengthChars(p.max_length.chars),
mCursorPos( 0 ),
mScrollHPos( 0 ),
mTextPadLeft(p.text_pad_left),
@@ -135,14 +137,18 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
mBorderThickness( 0 ),
mIgnoreArrowKeys( FALSE ),
mIgnoreTab( p.ignore_tab ),
- mDrawAsterixes( FALSE ),
- mHandleEditKeysDirectly(p.handle_edit_keys_directly),
+ mDrawAsterixes( p.is_password ),
+ mSpellCheck( p.spellcheck ),
+ mSpellCheckStart(-1),
+ mSpellCheckEnd(-1),
mSelectAllonFocusReceived( p.select_on_focus ),
+ mSelectAllonCommit( TRUE ),
mPassDelete(FALSE),
mReadOnly(FALSE),
mBgImage( p.background_image ),
mBgImageDisabled( p.background_image_disabled ),
mBgImageFocused( p.background_image_focused ),
+ mShowImageFocused( p.bg_image_always_focused ),
mHaveHistory(FALSE),
mReplaceNewlinesWithSpaces( TRUE ),
mLabel(p.label),
@@ -179,8 +185,16 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
updateTextPadding();
setCursor(mText.length());
+ if (mSpellCheck)
+ {
+ LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this));
+ }
+ mSpellCheckTimer.reset();
+
+ setPrevalidateInput(p.prevalidate_input_callback());
setPrevalidate(p.prevalidate_callback());
+ llassert(LLMenuGL::sMenuContainer != NULL);
LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>
("menu_text_editor.xml",
LLMenuGL::sMenuContainer,
@@ -191,18 +205,22 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
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 );
-
- if( gEditMenuHandler == this )
- {
- gEditMenuHandler = NULL;
- }
}
-
void LLLineEditor::onFocusReceived()
{
+ gEditMenuHandler = this;
LLUICtrl::onFocusReceived();
updateAllowingLanguageInput();
}
@@ -238,7 +256,11 @@ void LLLineEditor::onCommit()
setControlValue(getValue());
LLUICtrl::onCommit();
- selectAll();
+ 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
@@ -325,6 +347,12 @@ void LLLineEditor::setMaxTextLength(S32 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;
@@ -370,6 +398,16 @@ void LLLineEditor::setText(const LLStringExplicit &new_text)
}
mText.assign(truncated_utf8);
+ if (mMaxLengthChars)
+ {
+ LLWString truncated_wstring = utf8str_to_wstring(truncated_utf8);
+ if (truncated_wstring.size() > (U32)mMaxLengthChars)
+ {
+ truncated_wstring = truncated_wstring.substr(0, mMaxLengthChars);
+ }
+ mText.assign(wstring_to_utf8str(truncated_wstring));
+ }
+
if (all_selected)
{
// ...keep whole thing selected
@@ -383,7 +421,14 @@ void LLLineEditor::setText(const LLStringExplicit &new_text)
setCursor(llmin((S32)mText.length(), getCursor()));
// Set current history line to end of history.
- mCurrentHistoryLine = mLineHistory.end() - 1;
+ if (mLineHistory.empty())
+ {
+ mCurrentHistoryLine = mLineHistory.end();
+ }
+ else
+ {
+ mCurrentHistoryLine = mLineHistory.end() - 1;
+ }
mPrevText = mText;
}
@@ -392,23 +437,15 @@ void LLLineEditor::setText(const LLStringExplicit &new_text)
// Picks a new cursor position based on the actual screen size of text being drawn.
void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x )
{
- const llwchar* wtext = mText.getWString().c_str();
- LLWString asterix_text;
- if (mDrawAsterixes)
- {
- for (S32 i = 0; i < mText.length(); i++)
- {
- asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK);
- }
- wtext = asterix_text.c_str();
- }
+ 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;
- S32 cursor_pos =
- mScrollHPos +
- mGLFont->charFromPixelOffset(
- wtext, mScrollHPos,
- (F32)(local_mouse_x - mTextLeftEdge),
- (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive
setCursor(cursor_pos);
}
@@ -492,13 +529,112 @@ BOOL LLLineEditor::canSelectAll() const
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) )
+ {
+ deselect();
+
+ // Delete the misspelled word
+ mText.erase(it->first, it->second - it->first);
+
+ // Insert the suggestion in its place
+ LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
+ mText.insert(it->first, suggestion);
+ setCursor(it->first + (S32)suggestion.length());
+
+ break;
+ }
+ }
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
+
+void LLLineEditor::addToDictionary()
+{
+ if (canAddToDictionary())
+ {
+ LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos));
+ }
+}
+
+bool LLLineEditor::canAddToDictionary() const
+{
+ return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
}
+void LLLineEditor::addToIgnore()
+{
+ if (canAddToIgnore())
+ {
+ LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos));
+ }
+}
+
+bool LLLineEditor::canAddToIgnore() const
+{
+ return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
+}
+
+std::string LLLineEditor::getMisspelledWord(U32 pos) const
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= pos) && (it->second >= pos) )
+ {
+ return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first));
+ }
+ }
+ return LLStringUtil::null;
+}
+
+bool LLLineEditor::isMisspelledWord(U32 pos) const
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= pos) && (it->second >= pos) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void LLLineEditor::onSpellCheckSettingsChange()
+{
+ // Recheck the spelling on every change
+ mMisspellRanges.clear();
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
{
@@ -576,6 +712,9 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
if (mask & MASK_SHIFT)
{
+ // assume we're starting a drag select
+ mIsSelecting = TRUE;
+
// Handle selection extension
S32 old_cursor_pos = getCursor();
setCursorAtLocalPos(x);
@@ -610,8 +749,6 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
mSelectionStart = old_cursor_pos;
mSelectionEnd = getCursor();
}
- // assume we're starting a drag select
- mIsSelecting = TRUE;
}
else
{
@@ -652,7 +789,7 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
BOOL LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
{
- // llinfos << "MiddleMouseDown" << llendl;
+ // LL_INFOS() << "MiddleMouseDown" << LL_ENDL;
setFocus( TRUE );
if( canPastePrimary() )
{
@@ -694,7 +831,7 @@ BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask)
// Scroll if mouse cursor outside of bounds
if (mScrollTimer.hasExpired())
{
- S32 increment = llround(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME);
+ S32 increment = ll_round(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME);
mScrollTimer.reset();
mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME);
if( (x < mTextLeftEdge) && (mScrollHPos > 0 ) )
@@ -722,14 +859,14 @@ BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask)
mKeystrokeTimer.reset();
getWindow()->setCursor(UI_CURSOR_IBEAM);
- lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;
+ LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL;
handled = TRUE;
}
if( !handled )
{
getWindow()->setCursor(UI_CURSOR_IBEAM);
- lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;
+ LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL;
handled = TRUE;
}
@@ -782,13 +919,16 @@ void LLLineEditor::removeChar()
{
if( getCursor() > 0 )
{
+ if (!prevalidateInput(mText.getWString().substr(getCursor()-1, 1)))
+ return;
+
mText.erase(getCursor() - 1, 1);
setCursor(getCursor() - 1);
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
}
@@ -802,10 +942,14 @@ void LLLineEditor::addChar(const llwchar uni_char)
}
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;
@@ -815,6 +959,14 @@ void LLLineEditor::addChar(const llwchar uni_char)
{
allow_char = FALSE;
}
+ else if (mMaxLengthChars)
+ {
+ S32 wide_chars = mText.getWString().size();
+ if ((wide_chars + 1) > mMaxLengthChars)
+ {
+ allow_char = FALSE;
+ }
+ }
if (allow_char)
{
@@ -827,7 +979,7 @@ void LLLineEditor::addChar(const llwchar uni_char)
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
getWindow()->hideCursorUntilMouseMove();
@@ -841,6 +993,13 @@ void LLLineEditor::extendSelection( S32 new_cursor_pos )
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();
}
@@ -916,7 +1075,7 @@ BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask)
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
break;
@@ -932,7 +1091,7 @@ BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask)
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
break;
@@ -958,22 +1117,6 @@ BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask)
}
}
- if (!handled && mHandleEditKeysDirectly)
- {
- if( (MASK_CONTROL & mask) && ('A' == key) )
- {
- if( canSelectAll() )
- {
- selectAll();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = TRUE;
- }
- }
-
if(handled)
{
// take selection to 'primary' clipboard
@@ -987,8 +1130,12 @@ void LLLineEditor::deleteSelection()
{
if( !mReadOnly && hasSelection() )
{
- S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
- S32 selection_length = llabs( mSelectionStart - mSelectionEnd );
+ 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();
@@ -1006,13 +1153,17 @@ 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 );
-
- S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
- S32 length = llabs( mSelectionStart - mSelectionEnd );
- gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
+ LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length );
deleteSelection();
// Validate new string and rollback the if needed.
@@ -1020,12 +1171,11 @@ void LLLineEditor::cut()
if( need_to_rollback )
{
rollback.doRollback( this );
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
else
- if( mKeystrokeCallback )
{
- mKeystrokeCallback( this );
+ onKeystroke();
}
}
}
@@ -1043,13 +1193,13 @@ void LLLineEditor::copy()
{
S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
S32 length = llabs( mSelectionStart - mSelectionEnd );
- gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
+ LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length );
}
}
BOOL LLLineEditor::canPaste() const
{
- return !mReadOnly && gClipboard.canPasteString();
+ return !mReadOnly && LLClipboard::instance().isTextAvailable();
}
void LLLineEditor::paste()
@@ -1080,17 +1230,13 @@ void LLLineEditor::pasteHelper(bool is_primary)
if (can_paste_it)
{
LLWString paste;
- if (is_primary)
- {
- paste = gClipboard.getPastePrimaryWString();
- }
- else
- {
- paste = gClipboard.getPasteWString();
- }
+ LLClipboard::instance().pasteFromClipboard(paste, is_primary);
if (!paste.empty())
{
+ if (!prevalidateInput(paste))
+ return;
+
// Prepare for possible rollback
LLLineEditorRollback rollback(this);
@@ -1129,9 +1275,21 @@ void LLLineEditor::pasteHelper(bool is_primary)
}
// Truncate the clean string at the limit of what will fit
clean_string = clean_string.substr(0, wchars_that_fit);
- reportBadKeystroke();
+ LLUI::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::reportBadKeystroke();
+ }
+
mText.insert(getCursor(), clean_string);
setCursor( getCursor() + (S32)clean_string.length() );
deselect();
@@ -1141,12 +1299,11 @@ void LLLineEditor::pasteHelper(bool is_primary)
if( need_to_rollback )
{
rollback.doRollback( this );
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
else
- if( mKeystrokeCallback )
{
- mKeystrokeCallback( this );
+ onKeystroke();
}
}
}
@@ -1159,13 +1316,13 @@ void LLLineEditor::copyPrimary()
{
S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
S32 length = llabs( mSelectionStart - mSelectionEnd );
- gClipboard.copyFromPrimarySubstring( mText.getWString(), left_pos, length );
+ LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length, true);
}
}
BOOL LLLineEditor::canPastePrimary() const
{
- return !mReadOnly && gClipboard.canPastePrimaryString();
+ return !mReadOnly && LLClipboard::instance().isTextAvailable(true);
}
void LLLineEditor::updatePrimary()
@@ -1194,7 +1351,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
case KEY_BACKSPACE:
if (!mReadOnly)
{
- //llinfos << "Handling backspace" << llendl;
+ //LL_INFOS() << "Handling backspace" << LL_ENDL;
if( hasSelection() )
{
deleteSelection();
@@ -1206,7 +1363,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
}
handled = TRUE;
@@ -1255,7 +1412,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
handled = TRUE;
}
@@ -1282,7 +1439,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
handled = TRUE;
}
@@ -1290,33 +1447,33 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
// handle ctrl-uparrow if we have a history enabled line editor.
case KEY_UP:
- if( mHaveHistory && ( MASK_CONTROL == mask ) )
+ if( mHaveHistory && ((mIgnoreArrowKeys == false) || ( MASK_CONTROL == mask )) )
{
if( mCurrentHistoryLine > mLineHistory.begin() )
{
mText.assign( *(--mCurrentHistoryLine) );
- setCursor(llmin((S32)mText.length(), getCursor()));
+ setCursorToEnd();
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
handled = TRUE;
}
break;
- // handle ctrl-downarrow if we have a history enabled line editor
+ // handle [ctrl]-downarrow if we have a history enabled line editor
case KEY_DOWN:
- if( mHaveHistory && ( MASK_CONTROL == mask ) )
+ if( mHaveHistory && ((mIgnoreArrowKeys == false) || ( MASK_CONTROL == mask )) )
{
if( !mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.end() - 1 )
{
mText.assign( *(++mCurrentHistoryLine) );
- setCursor(llmin((S32)mText.length(), getCursor()));
+ setCursorToEnd();
}
else
{
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
handled = TRUE;
}
@@ -1339,64 +1496,6 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
break;
}
- if( !handled && mHandleEditKeysDirectly )
- {
- // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system.
- if( KEY_DELETE == key )
- {
- if( canDoDelete() )
- {
- doDelete();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = TRUE;
- }
- else
- if( MASK_CONTROL & mask )
- {
- if( 'C' == key )
- {
- if( canCopy() )
- {
- copy();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = TRUE;
- }
- else
- if( 'V' == key )
- {
- if( canPaste() )
- {
- paste();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = TRUE;
- }
- else
- if( 'X' == key )
- {
- if( canCut() )
- {
- cut();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = TRUE;
- }
- }
- }
return handled;
}
@@ -1451,15 +1550,16 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask )
{
rollback.doRollback(this);
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
// Notify owner if requested
if (!need_to_rollback && handled)
{
- if (mKeystrokeCallback)
+ onKeystroke();
+ if ( (!selection_modified) && (KEY_BACKSPACE == key) )
{
- mKeystrokeCallback(this);
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
}
}
@@ -1484,6 +1584,13 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
LLLineEditorRollback rollback( this );
+ {
+ LLWString u_char;
+ u_char.assign(1, uni_char);
+ if (!prevalidateInput(u_char))
+ return handled;
+ }
+
addChar(uni_char);
mKeystrokeTimer.reset();
@@ -1499,18 +1606,17 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
{
rollback.doRollback( this );
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
// Notify owner if requested
if( !need_to_rollback && handled )
{
- if( mKeystrokeCallback )
- {
- // HACK! The only usage of this callback doesn't do anything with the character.
- // We'll have to do something about this if something ever changes! - Doug
- mKeystrokeCallback( this );
- }
+ // 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;
@@ -1519,7 +1625,7 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
BOOL LLLineEditor::canDoDelete() const
{
- return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) );
+ return ( !mReadOnly && mText.length() > 0 && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) );
}
void LLLineEditor::doDelete()
@@ -1535,6 +1641,13 @@ void LLLineEditor::doDelete()
}
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();
}
@@ -1544,14 +1657,13 @@ void LLLineEditor::doDelete()
if( need_to_rollback )
{
rollback.doRollback( this );
- reportBadKeystroke();
+ LLUI::reportBadKeystroke();
}
else
{
- if( mKeystrokeCallback )
- {
- mKeystrokeCallback( this );
- }
+ onKeystroke();
+
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
}
}
@@ -1565,7 +1677,7 @@ void LLLineEditor::drawBackground()
{
image = mBgImageDisabled;
}
- else if ( has_focus )
+ else if ( has_focus || mShowImageFocused)
{
image = mBgImageFocused;
}
@@ -1573,8 +1685,11 @@ void LLLineEditor::drawBackground()
{
image = mBgImage;
}
+
+ if (!image) return;
- F32 alpha = getDrawContext().mAlpha;
+ F32 alpha = getCurrentTransparency();
+
// optionally draw programmatic border
if (has_focus)
{
@@ -1619,7 +1734,11 @@ void LLLineEditor::draw()
LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 );
background.stretch( -mBorderThickness );
- S32 lineeditor_v_pad = llround((background.getHeight() - mGLFont->getLineHeight())/2);
+ S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2;
+ if (mSpellCheck)
+ {
+ lineeditor_v_pad += 1;
+ }
drawBackground();
@@ -1694,14 +1813,14 @@ void LLLineEditor::draw()
{
S32 select_left;
S32 select_right;
- if( mSelectionStart < getCursor() )
+ if (mSelectionStart < mSelectionEnd)
{
select_left = mSelectionStart;
- select_right = getCursor();
+ select_right = mSelectionEnd;
}
else
{
- select_left = getCursor();
+ select_left = mSelectionEnd;
select_right = mSelectionStart;
}
@@ -1716,7 +1835,7 @@ void LLLineEditor::draw()
0,
LLFontGL::NO_SHADOW,
select_left - mScrollHPos,
- mTextRightEdge - llround(rendered_pixels_right),
+ mTextRightEdge - ll_round(rendered_pixels_right),
&rendered_pixels_right);
}
@@ -1726,8 +1845,8 @@ void LLLineEditor::draw()
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 - llround(rendered_pixels_right));
- gl_rect_2d(llround(rendered_pixels_right), cursor_top, llround(rendered_pixels_right)+width, cursor_bottom, color);
+ 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(
@@ -1738,14 +1857,14 @@ void LLLineEditor::draw()
0,
LLFontGL::NO_SHADOW,
select_right - mScrollHPos - rendered_text,
- mTextRightEdge - llround(rendered_pixels_right),
+ mTextRightEdge - ll_round(rendered_pixels_right),
&rendered_pixels_right);
}
if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) )
{
// unselected, right side
- mGLFont->render(
+ rendered_text += mGLFont->render(
mText, mScrollHPos + rendered_text,
rendered_pixels_right, text_bottom,
text_color,
@@ -1753,13 +1872,13 @@ void LLLineEditor::draw()
0,
LLFontGL::NO_SHADOW,
S32_MAX,
- mTextRightEdge - llround(rendered_pixels_right),
+ mTextRightEdge - ll_round(rendered_pixels_right),
&rendered_pixels_right);
}
}
else
{
- mGLFont->render(
+ rendered_text = mGLFont->render(
mText, mScrollHPos,
rendered_pixels_right, text_bottom,
text_color,
@@ -1767,13 +1886,108 @@ void LLLineEditor::draw()
0,
LLFontGL::NO_SHADOW,
S32_MAX,
- mTextRightEdge - llround(rendered_pixels_right),
+ 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())
{
@@ -1813,8 +2027,8 @@ void LLLineEditor::draw()
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::sGLScaleFactor.mV[VX]);
- ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]);
+ 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 );
}
}
@@ -1832,7 +2046,7 @@ void LLLineEditor::draw()
0,
LLFontGL::NO_SHADOW,
S32_MAX,
- mTextRightEdge - llround(rendered_pixels_right),
+ mTextRightEdge - ll_round(rendered_pixels_right),
&rendered_pixels_right, FALSE);
}
@@ -1857,7 +2071,7 @@ void LLLineEditor::draw()
0,
LLFontGL::NO_SHADOW,
S32_MAX,
- mTextRightEdge - llround(rendered_pixels_right),
+ mTextRightEdge - ll_round(rendered_pixels_right),
&rendered_pixels_right, FALSE);
}
// Draw children (border)
@@ -1879,11 +2093,27 @@ S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const
return result;
}
-void LLLineEditor::reportBadKeystroke()
+S32 LLLineEditor::calcCursorPos(S32 mouse_x)
{
- make_ui_sound("UISndBadKeystroke");
-}
+ 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()
{
@@ -1977,6 +2207,22 @@ void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func)
updateAllowingLanguageInput();
}
+void LLLineEditor::setPrevalidateInput(LLTextValidate::validate_func_t func)
+{
+ mPrevalidateInputFunc = func;
+ updateAllowingLanguageInput();
+}
+
+bool LLLineEditor::prevalidateInput(const LLWString& wstr)
+{
+ if (mPrevalidateInputFunc && !mPrevalidateInputFunc(wstr))
+ {
+ return false;
+ }
+
+ return true;
+}
+
// static
BOOL LLLineEditor::postvalidateFloat(const std::string &str)
{
@@ -2036,6 +2282,32 @@ BOOL LLLineEditor::postvalidateFloat(const std::string &str)
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();
@@ -2047,6 +2319,15 @@ 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)
{
@@ -2102,7 +2383,7 @@ void LLLineEditor::resetPreedit()
{
if (hasPreeditString())
{
- llwarns << "Preedit and selection!" << llendl;
+ LL_WARNS() << "Preedit and selection!" << LL_ENDL;
deselect();
}
else
@@ -2169,10 +2450,9 @@ void LLLineEditor::updatePreedit(const LLWString &preedit_string,
// Update of the preedit should be caused by some key strokes.
mKeystrokeTimer.reset();
- if( mKeystrokeCallback )
- {
- mKeystrokeCallback( this );
- }
+ onKeystroke();
+
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const
@@ -2267,7 +2547,7 @@ void LLLineEditor::markAsPreedit(S32 position, S32 length)
setCursor(position);
if (hasPreeditString())
{
- llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl;
+ LL_WARNS() << "markAsPreedit invoked when hasPreeditString is true." << LL_ENDL;
}
mPreeditWString.assign( LLWString( mText.getWString(), position, length ) );
if (length > 0)
@@ -2295,7 +2575,7 @@ void LLLineEditor::markAsPreedit(S32 position, S32 length)
S32 LLLineEditor::getPreeditFontSize() const
{
- return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]);
+ return ll_round(mGLFont->getLineHeight() * LLUI::getScaleFactor().mV[VY]);
}
void LLLineEditor::setReplaceNewlinesWithSpaces(BOOL replace)
@@ -2324,7 +2604,38 @@ void LLLineEditor::showContextMenu(S32 x, S32 y)
S32 screen_x, screen_y;
localPointToScreen(x, y, &screen_x, &screen_y);
- menu->show(screen_x, screen_y);
+
+ setCursorAtLocalPos(x);
+ if (hasSelection())
+ {
+ if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
+ {
+ deselect();
+ }
+ else
+ {
+ setCursor(llmax(mSelectionStart, mSelectionEnd));
+ }
+ }
+
+ bool use_spellcheck = getSpellCheck(), is_misspelled = false;
+ if (use_spellcheck)
+ {
+ mSuggestionList.clear();
+
+ // If the cursor is on a misspelled word, retrieve suggestions for it
+ std::string misspelled_word = getMisspelledWord(mCursorPos);
+ if ((is_misspelled = !misspelled_word.empty()) == true)
+ {
+ LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList);
+ }
+ }
+
+ menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty()));
+ menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled));
+ menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled));
+ menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled));
+ menu->show(screen_x, screen_y, this);
}
}
@@ -2335,3 +2646,8 @@ void LLLineEditor::setContextMenu(LLContextMenu* new_context_menu)
else
mContextMenuHandle.markDead();
}
+
+void LLLineEditor::setFont(const LLFontGL* font)
+{
+ mGLFont = font;
+}