diff options
| -rw-r--r-- | indra/llui/lltextbase.cpp | 209 | ||||
| -rw-r--r-- | indra/llui/lltextbase.h | 31 | ||||
| -rw-r--r-- | indra/llui/lltexteditor.cpp | 34 | 
3 files changed, 271 insertions, 3 deletions
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0040be45c7..7eee1d39c4 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -32,6 +32,7 @@  #include "lllocalcliprect.h"  #include "llmenugl.h"  #include "llscrollcontainer.h" +#include "llspellcheck.h"  #include "llstl.h"  #include "lltextparser.h"  #include "lltextutil.h" @@ -155,6 +156,7 @@ LLTextBase::Params::Params()  	plain_text("plain_text",false),  	track_end("track_end", false),  	read_only("read_only", false), +	spellcheck("spellcheck", false),  	v_pad("v_pad", 0),  	h_pad("h_pad", 0),  	clip("clip", true), @@ -181,6 +183,9 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)  	mFontShadow(p.font_shadow),  	mPopupMenu(NULL),  	mReadOnly(p.read_only), +	mSpellCheck(p.spellcheck), +	mSpellCheckStart(-1), +	mSpellCheckEnd(-1),  	mCursorColor(p.cursor_color),  	mFgColor(p.text_color),  	mBorderVisible( p.border_visible ), @@ -246,6 +251,12 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)  		addChild(mDocumentView);  	} +	if (mSpellCheck) +	{ +		LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this)); +	} +	mSpellCheckTimer.reset(); +  	createDefaultSegment();  	updateRects(); @@ -530,8 +541,86 @@ void LLTextBase::drawText()  		return;  	} +	// Perform spell check if needed +	if ( (getSpellCheck()) && (getWText().length() > 2) ) +	{ +		// Calculate start and end indices for the spell checking range +		S32 start = line_start, end = getLineEnd(last_line); + +		if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) +		{ +			const LLWString& wstrText = getWText();  +			mMisspellRanges.clear(); + +			segment_set_t::iterator seg_it = getSegIterContaining(start); +			while (mSegments.end() != seg_it) +			{ +				LLTextSegmentPtr text_segment = *seg_it; +				if ( (text_segment.isNull()) || (text_segment->getStart() >= end) ) +				{ +					break; +				} + +				if (!text_segment->canEdit()) +				{ +					++seg_it; +					continue; +				} + +				// Combine adjoining text segments into one +				U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end); +				while (mSegments.end() != ++seg_it) +				{ +					text_segment = *seg_it; +					if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) ) +					{ +						break; +					} +					seg_end = llmin(text_segment->getEnd(), end); +				} + +				// Find the start of the first word +				U32 word_start = seg_start, word_end = -1; +				while ( (word_start < wstrText.length()) && (!LLStringOps::isAlpha(wstrText[word_start])) ) +					word_start++; + +				// Iterate over all words in the text block and check them one by one +				while (word_start < seg_end) +				{ +					// 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 < seg_end) &&  +							((LLWStringUtil::isPartOfWord(wstrText[word_end])) || +								((L'\'' == wstrText[word_end]) &&  +								(LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) ) +					{ +						word_end++; +					} +					if (word_end > seg_end) +						break; + +					// Don't process words shorter than 3 characters +					std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start)); +					if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) +					{ +						mMisspellRanges.push_back(std::pair<U32, U32>(word_start, word_end)); +					} + +					// Find the start of the next word +					word_start = word_end + 1; +					while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) ) +						word_start++; +				} +			} + +			mSpellCheckStart = start; +			mSpellCheckEnd = end; +		} +	} +  	LLTextSegmentPtr cur_segment = *seg_iter; +	std::list<std::pair<U32, U32> >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair<U32, U32>(line_start, 0));  	for (S32 cur_line = first_line; cur_line < last_line; cur_line++)  	{  		S32 next_line = cur_line + 1; @@ -566,7 +655,8 @@ void LLTextBase::drawText()  				cur_segment = *seg_iter;  			} -			S32 clipped_end	=	llmin( line_end, cur_segment->getEnd() )  - cur_segment->getStart(); +			S32 seg_end = llmin(line_end, cur_segment->getEnd()); +			S32 clipped_end	= seg_end - cur_segment->getStart();  			if (mUseEllipses								// using ellipses  				&& clipped_end == line_end					// last segment on line @@ -578,6 +668,35 @@ void LLTextBase::drawText()  				text_rect.mRight -= 2;  			} +			// Draw squiggly lines under any visible misspelled words +			while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) ) +			{ +				// Skip the current word if the user is still busy editing it +				if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) ) + 					continue; + +				S32 squiggle_start = 0, squiggle_end = 0, pony = 0; +				cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_it->first - seg_start, squiggle_start, pony); +				cur_segment->getDimensions(misspell_it->first - cur_segment->getStart(), misspell_it->second - misspell_it->first, squiggle_end, pony); +				squiggle_start += text_rect.mLeft; + +				pony = (squiggle_end + 3) / 6; +				squiggle_start += squiggle_end / 2 - pony * 3; +				squiggle_end = squiggle_start + pony * 6; + +				gGL.color4ub(255, 0, 0, 200); +				while (squiggle_start < squiggle_end) +				{ +					gl_line_2d(squiggle_start, text_rect.mBottom - 2, squiggle_start + 3, text_rect.mBottom + 1); +					gl_line_2d(squiggle_start + 3, text_rect.mBottom + 1, squiggle_start + 6, text_rect.mBottom - 2); +					squiggle_start += 6; +				} + +				if (misspell_it->second > seg_end) +					break; +				++misspell_it; +			} +  			text_rect.mLeft = (S32)(cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect));  			seg_start = clipped_end + cur_segment->getStart(); @@ -1103,6 +1222,94 @@ void LLTextBase::deselect()  	mIsSelecting = FALSE;  } +bool LLTextBase::getSpellCheck() const +{ +	return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); +} + +const std::string& LLTextBase::getSuggestion(U32 index) const +{ +	return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; +} + +U32 LLTextBase::getSuggestionCount() const +{ +	return mSuggestionList.size(); +} + +void LLTextBase::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 +			removeStringNoUndo(it->first, it->second - it->first); + +			// Insert the suggestion in its place +			LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); +			insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index])); +			setCursorPos(it->first + (S32)suggestion.length()); + +			break; +		} +	} +	mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLTextBase::addToDictionary() +{ +	if (canAddToDictionary()) +	{ +		LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); +	} +} + +bool LLTextBase::canAddToDictionary() const +{ +	return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +void LLTextBase::addToIgnore() +{ +	if (canAddToIgnore()) +	{ +		LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); +	} +} + +bool LLTextBase::canAddToIgnore() const +{ +	return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +std::string LLTextBase::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(getWText().substr(it->first, it->second - it->first)); +	} +	return LLStringUtil::null; +} + +bool LLTextBase::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 LLTextBase::onSpellCheckSettingsChange() +{ +	// Recheck the spelling on every change +	mSpellCheckStart = mSpellCheckEnd = -1; +}  // Sets the scrollbar from the cursor position  void LLTextBase::updateScrollFromCursor() diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 0549141b72..90b147cee1 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -30,6 +30,7 @@  #include "v4color.h"  #include "lleditmenuhandler.h" +#include "llspellcheckmenuhandler.h"  #include "llstyle.h"  #include "llkeywords.h"  #include "llpanel.h" @@ -230,7 +231,8 @@ typedef LLPointer<LLTextSegment> LLTextSegmentPtr;  ///  class LLTextBase   :	public LLUICtrl, -	protected LLEditMenuHandler +	protected LLEditMenuHandler, +	public LLSpellCheckMenuHandler  {  public:  	friend class LLTextSegment; @@ -259,6 +261,7 @@ public:  								border_visible,  								track_end,  								read_only, +								spellcheck,  								allow_scroll,  								plain_text,  								wrap, @@ -311,6 +314,24 @@ public:  	/*virtual*/ BOOL		canDeselect() const;  	/*virtual*/ void		deselect(); +	// LLSpellCheckMenuHandler overrides +	/*virtual*/ bool		getSpellCheck() const; + +	/*virtual*/ const std::string& getSuggestion(U32 index) const; +	/*virtual*/ U32			getSuggestionCount() const; +	/*virtual*/ void		replaceWithSuggestion(U32 index); + +	/*virtual*/ void		addToDictionary(); +	/*virtual*/ bool		canAddToDictionary() const; + +	/*virtual*/ void		addToIgnore(); +	/*virtual*/ bool		canAddToIgnore() const; + +	// Spell checking helper functions +	std::string				getMisspelledWord(U32 pos) const; +	bool					isMisspelledWord(U32 pos) const; +	void					onSpellCheckSettingsChange(); +  	// used by LLTextSegment layout code  	bool					getWordWrap() { return mWordWrap; }  	bool					getUseEllipses() { return mUseEllipses; } @@ -540,6 +561,14 @@ protected:  	BOOL						mIsSelecting;		// Are we in the middle of a drag-select?  +	// spell checking +	bool						mSpellCheck; +	S32							mSpellCheckStart; +	S32							mSpellCheckEnd; +	LLTimer						mSpellCheckTimer; +	std::list<std::pair<U32, U32> > mMisspellRanges; +	std::vector<std::string>		mSuggestionList; +  	// configuration  	S32							mHPad;				// padding on left of text  	S32							mVPad;				// padding above text diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3a23ce1cac..c5957838ba 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -54,6 +54,7 @@  #include "llwindow.h"  #include "lltextparser.h"  #include "llscrollcontainer.h" +#include "llspellcheck.h"  #include "llpanel.h"  #include "llurlregistry.h"  #include "lltooltip.h" @@ -77,6 +78,7 @@ template class LLTextEditor* LLView::getChild<class LLTextEditor>(  const S32	UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32;  const S32	UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4;  const S32	SPACES_PER_TAB = 4; +const F32	SPELLCHECK_DELAY = 0.5f;	// delay between the last keypress and spell checking the word the cursor is on  /////////////////////////////////////////////////////////////////// @@ -1961,7 +1963,34 @@ void LLTextEditor::showContextMenu(S32 x, S32 y)  	S32 screen_x, screen_y;  	localPointToScreen(x, y, &screen_x, &screen_y); -	mContextMenu->show(screen_x, screen_y); + +	setCursorAtLocalPos(x, y, false); +	if (hasSelection()) +	{ +		if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) +			deselect(); +		else +			setCursorPos(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); +		} +	} + +	mContextMenu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); +	mContextMenu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); +	mContextMenu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); +	mContextMenu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); +	mContextMenu->show(screen_x, screen_y, this);  } @@ -2846,6 +2875,9 @@ void LLTextEditor::setKeystrokeCallback(const keystroke_signal_t::slot_type& cal  void LLTextEditor::onKeyStroke()  {  	mKeystrokeSignal(this); + +	mSpellCheckStart = mSpellCheckEnd = -1; +	mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);  }  //virtual  | 
