diff options
| author | Oz Linden <oz@lindenlab.com> | 2012-03-30 12:45:38 -0400 | 
|---|---|---|
| committer | Oz Linden <oz@lindenlab.com> | 2012-03-30 12:45:38 -0400 | 
| commit | 6f71df192720ec3f16e7e7102ad21f4d140b8e07 (patch) | |
| tree | 35e9865e19498f45ee7a700f564c2e4b1f2d5033 | |
| parent | 04636effcffde85a3b7005491a1fd51395cb37d1 (diff) | |
| parent | 2114e88cd1291ef1dba4b79bcdcca4b2d134262f (diff) | |
merge updates
| -rw-r--r-- | indra/llui/lllineeditor.cpp | 54 | ||||
| -rw-r--r-- | indra/llui/lllineeditor.h | 1 | ||||
| -rw-r--r-- | indra/llui/llspellcheck.cpp | 12 | ||||
| -rw-r--r-- | indra/llui/llspellcheck.h | 29 | ||||
| -rw-r--r-- | indra/llui/lltextbase.cpp | 210 | ||||
| -rw-r--r-- | indra/llui/lltextbase.h | 31 | ||||
| -rw-r--r-- | indra/llui/lltexteditor.cpp | 34 | ||||
| -rwxr-xr-x | indra/newview/llfloaterpreference.cpp | 95 | ||||
| -rw-r--r-- | indra/newview/llfloaterpreference.h | 2 | ||||
| -rw-r--r-- | indra/newview/skins/default/textures/textures.xml | 2 | ||||
| -rw-r--r-- | indra/newview/skins/default/textures/widgets/Arrow_Left.png | bin | 0 -> 311 bytes | |||
| -rw-r--r-- | indra/newview/skins/default/textures/widgets/Arrow_Right.png | bin | 0 -> 313 bytes | |||
| -rw-r--r-- | indra/newview/skins/default/xui/en/floater_preferences.xml | 6 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/floater_preview_notecard.xml | 1 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/panel_edit_pick.xml | 1 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/panel_group_notices.xml | 4 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/panel_preferences_spellcheck.xml | 132 | 
17 files changed, 562 insertions, 52 deletions
| diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 5479c080bd..42cfc4cae9 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -66,7 +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 showing spell checking feedback for the word the cursor is on +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 @@ -617,6 +617,7 @@ bool LLLineEditor::isMisspelledWord(U32 pos) const  void LLLineEditor::onSpellCheckSettingsChange()  {  	// Recheck the spelling on every change +	mMisspellRanges.clear();  	mSpellCheckStart = mSpellCheckEnd = -1;  } @@ -1158,9 +1159,8 @@ void LLLineEditor::cut()  			LLUI::reportBadKeystroke();  		}  		else -		if( mKeystrokeCallback )  		{ -			mKeystrokeCallback( this ); +			onKeystroke();  		}  	}  } @@ -1294,9 +1294,8 @@ void LLLineEditor::pasteHelper(bool is_primary)  				LLUI::reportBadKeystroke();  			}  			else -			if( mKeystrokeCallback )  			{ -				mKeystrokeCallback( this ); +				onKeystroke();  			}  		}  	} @@ -1549,10 +1548,7 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask )  			// Notify owner if requested  			if (!need_to_rollback && handled)  			{ -				if (mKeystrokeCallback) -				{ -					mKeystrokeCallback(this); -				} +				onKeystroke();  				if ( (!selection_modified) && (KEY_BACKSPACE == key) )  				{  					mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); @@ -1608,12 +1604,10 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)  		// 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);  		}  	} @@ -1643,9 +1637,7 @@ void LLLineEditor::doDelete()  			if (!prevalidateInput(text_to_delete))  			{ -				if( mKeystrokeCallback ) -					mKeystrokeCallback( this ); - +				onKeystroke();  				return;  			}  			setCursor(getCursor() + 1); @@ -1661,10 +1653,8 @@ void LLLineEditor::doDelete()  		}  		else  		{ -			if( mKeystrokeCallback ) -			{ -				mKeystrokeCallback( this ); -			} +			onKeystroke(); +  			mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);  		}  	} @@ -2296,6 +2286,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)  { @@ -2418,10 +2417,8 @@ 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);  } @@ -2575,6 +2572,7 @@ void LLLineEditor::showContextMenu(S32 x, S32 y)  		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)) ) @@ -2582,10 +2580,6 @@ void LLLineEditor::showContextMenu(S32 x, S32 y)  			else  				setCursor(llmax(mSelectionStart, mSelectionEnd));  		} -		else -		{ -			setCursorAtLocalPos(x); -		}  		bool use_spellcheck = getSpellCheck(), is_misspelled = false;  		if (use_spellcheck) diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 9513274f21..40f931ecc1 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -243,6 +243,7 @@ public:  	void			setSelectAllonFocusReceived(BOOL b);  	void			setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; } +	void			onKeystroke();  	typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t;  	void			setKeystrokeCallback(callback_t callback, void* user_data); diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 433ca02852..65207144f8 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -186,7 +186,7 @@ void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::stri  	}  } -void LLSpellChecker::setSecondaryDictionaries(std::list<std::string> dict_list) +void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list)  {  	if (!getUseSpellCheck())  	{ @@ -194,11 +194,11 @@ void LLSpellChecker::setSecondaryDictionaries(std::list<std::string> dict_list)  	}  	// Check if we're only adding secondary dictionaries, or removing them -	std::list<std::string> dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size())); +	dict_list_t dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size()));  	dict_list.sort();  	mDictSecondary.sort(); -	std::list<std::string>::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin()); -	std::list<std::string>::iterator end_removed = std::set_difference(mDictSecondary.begin(), mDictSecondary.end(), dict_list.begin(), dict_list.end(), dict_rem.begin()); +	dict_list_t::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin()); +	dict_list_t::iterator end_removed = std::set_difference(mDictSecondary.begin(), mDictSecondary.end(), dict_list.begin(), dict_list.end(), dict_rem.begin());  	if (end_removed != dict_rem.begin())		// We can't remove secondary dictionaries so we need to recreate the Hunspell instance  	{ @@ -211,7 +211,7 @@ void LLSpellChecker::setSecondaryDictionaries(std::list<std::string> dict_list)  	{  		const std::string app_path = getDictionaryAppPath();  		const std::string user_path = getDictionaryUserPath(); -		for (std::list<std::string>::const_iterator it_added = dict_add.begin(); it_added != end_added; ++it_added) +		for (dict_list_t::const_iterator it_added = dict_add.begin(); it_added != end_added; ++it_added)  		{  			const LLSD dict_entry = getDictionaryData(*it_added);  			if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) ) @@ -287,7 +287,7 @@ void LLSpellChecker::initHunspell(const std::string& dict_name)  			}  		} -		for (std::list<std::string>::const_iterator it = mDictSecondary.begin(); it != mDictSecondary.end(); ++it) +		for (dict_list_t::const_iterator it = mDictSecondary.begin(); it != mDictSecondary.end(); ++it)  		{  			const LLSD dict_entry = getDictionaryData(*it);  			if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) ) diff --git a/indra/llui/llspellcheck.h b/indra/llui/llspellcheck.h index affdac2907..8351655b49 100644 --- a/indra/llui/llspellcheck.h +++ b/indra/llui/llspellcheck.h @@ -44,17 +44,20 @@ public:  	void addToIgnoreList(const std::string& word);  	bool checkSpelling(const std::string& word) const;  	S32  getSuggestions(const std::string& word, std::vector<std::string>& suggestions) const; - -public: -	const LLSD	getDictionaryData(const std::string& dict_name) const; -	const LLSD&	getDictionaryMap() const { return mDictMap; } -	void		refreshDictionaryMap(); -	void		setSecondaryDictionaries(std::list<std::string> dictList);  protected: -	void		addToDictFile(const std::string& dict_path, const std::string& word); -	void		initHunspell(const std::string& dict_name); +	void addToDictFile(const std::string& dict_path, const std::string& word); +	void initHunspell(const std::string& dict_name);  public: +	typedef std::list<std::string> dict_list_t; + +	const std::string&	getActiveDictionary() const { return mDictName; } +	const LLSD			getDictionaryData(const std::string& dict_name) const; +	const LLSD&			getDictionaryMap() const { return mDictMap; } +	const dict_list_t&	getSecondaryDictionaries() const { return mDictSecondary; } +	void				refreshDictionaryMap(); +	void				setSecondaryDictionaries(dict_list_t dict_list); +  	static const std::string getDictionaryAppPath();  	static const std::string getDictionaryUserPath();  	static bool				 getUseSpellCheck(); @@ -64,11 +67,11 @@ public:  	static boost::signals2::connection setSettingsChangeCallback(const settings_change_signal_t::slot_type& cb);  protected: -	Hunspell*				 mHunspell; -	std::string				 mDictName; -	std::string				 mDictFile; -	LLSD					 mDictMap; -	std::list<std::string>	 mDictSecondary; +	Hunspell*	mHunspell; +	std::string	mDictName; +	std::string	mDictFile; +	LLSD		mDictMap; +	dict_list_t	mDictSecondary;  	std::vector<std::string> mIgnoreList;  	static settings_change_signal_t	sSettingsChangeSignal; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0040be45c7..2231d9b983 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,95 @@ 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 +	mMisspellRanges.clear(); +	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 diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index a333989e7e..29b07d2479 100755 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -66,6 +66,7 @@  #include "llsky.h"  #include "llscrolllistctrl.h"  #include "llscrolllistitem.h" +#include "llspellcheck.h"  #include "llsliderctrl.h"  #include "lltabcontainer.h"  #include "lltrans.h" @@ -110,6 +111,8 @@  #include "lllogininstance.h"        // to check if logged in yet  #include "llsdserialize.h" +#include <boost/algorithm/string.hpp> +  const F32 MAX_USER_FAR_CLIP = 512.f;  const F32 MIN_USER_FAR_CLIP = 64.f;  const F32 BANDWIDTH_UPDATER_TIMEOUT = 0.5f; @@ -445,6 +448,9 @@ BOOL LLFloaterPreference::postBuild()  	getChild<LLComboBox>("language_combobox")->setCommitCallback(boost::bind(&LLFloaterPreference::onLanguageChange, this)); +	getChild<LLUICtrl>("btn_spellcheck_moveleft")->setCommitCallback(boost::bind(&LLFloaterPreference::onClickDictMove, this, "list_spellcheck_active", "list_spellcheck_available")); +	getChild<LLUICtrl>("btn_spellcheck_moveright")->setCommitCallback(boost::bind(&LLFloaterPreference::onClickDictMove, this, "list_spellcheck_available", "list_spellcheck_active")); +  	// if floater is opened before login set default localized busy message  	if (LLStartUp::getStartupState() < STATE_STARTED)  	{ @@ -577,6 +583,19 @@ void LLFloaterPreference::apply()  		}  	} +	if (hasChild("check_spellcheck"), TRUE) +	{ +		LLScrollListCtrl* list_ctrl = findChild<LLScrollListCtrl>("list_spellcheck_active"); +		std::vector<LLScrollListItem*> list_items = list_ctrl->getAllData(); + +		std::list<std::string> list_dict; +		list_dict.push_back(LLSpellChecker::instance().getActiveDictionary()); +		for (std::vector<LLScrollListItem*>::const_iterator item_it = list_items.begin(); item_it != list_items.end(); ++item_it) +			list_dict.push_back((*item_it)->getColumn(0)->getValue().asString()); + +		gSavedSettings.setString("SpellCheckDictionary", boost::join(list_dict, ",")); +	} +  	saveAvatarProperties();  	if (mClickActionDirty) @@ -687,6 +706,8 @@ void LLFloaterPreference::onOpen(const LLSD& key)  	// Load (double-)click to walk/teleport settings.  	updateClickActionControls(); +	buildDictLists(); +  	// Enabled/disabled popups, might have been changed by user actions  	// while preferences floater was closed.  	buildPopupLists(); @@ -865,6 +886,25 @@ void LLFloaterPreference::onNameTagOpacityChange(const LLSD& newvalue)  	}  } +void LLFloaterPreference::onClickDictMove(const std::string& from, const std::string& to) +{ +	LLScrollListCtrl* from_ctrl = findChild<LLScrollListCtrl>(from); +	LLScrollListCtrl* to_ctrl = findChild<LLScrollListCtrl>(to); + +	LLSD row; +	row["columns"][0]["column"] = "name"; +	row["columns"][0]["font"]["name"] = "SANSSERIF_SMALL"; +	row["columns"][0]["font"]["style"] = "NORMAL"; + +	std::vector<LLScrollListItem*> sel_items = from_ctrl->getAllSelected(); +	for (std::vector<LLScrollListItem*>::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) +	{ +		row["columns"][0]["value"] = (*sel_it)->getColumn(0)->getValue(); +		to_ctrl->addElement(row); +	} +	from_ctrl->deleteSelectedItems(); +} +  void LLFloaterPreference::onClickSetCache()  {  	std::string cur_name(gSavedSettings.getString("CacheLocation")); @@ -930,6 +970,61 @@ void LLFloaterPreference::refreshSkin(void* data)  	self->getChild<LLRadioGroup>("skin_selection", true)->setValue(sSkin);  } +void LLFloaterPreference::buildDictLists() +{ +	LLComboBox* dict_combo = findChild<LLComboBox>("combo_spellcheck_dict"); +	dict_combo->clearRows(); + +	LLScrollListCtrl* active_ctrl = findChild<LLScrollListCtrl>("list_spellcheck_active"); +	active_ctrl->clearRows(); + +	LLScrollListCtrl* avail_ctrl = findChild<LLScrollListCtrl>("list_spellcheck_available"); +	avail_ctrl->clearRows(); + +	if (LLSpellChecker::getUseSpellCheck()) +	{ +		// Populate the main dictionary combobox +		const LLSD& dict_map = LLSpellChecker::instance().getDictionaryMap(); +		if (dict_map.size()) +		{ +			for (LLSD::array_const_iterator dict_it = dict_map.beginArray(); dict_it != dict_map.endArray(); ++dict_it) +			{ +				const LLSD& dict = *dict_it; +				if ( (dict["installed"].asBoolean()) && (dict.has("language")) ) +					dict_combo->add(dict["language"].asString()); +			} +			dict_combo->selectByValue(LLSpellChecker::instance().getActiveDictionary()); +		} + +		LLSD row; +		row["columns"][0]["column"] = "name"; +		row["columns"][0]["font"]["name"] = "SANSSERIF_SMALL"; +		row["columns"][0]["font"]["style"] = "NORMAL"; + +		// Populate the active dictionary list +		LLSpellChecker::dict_list_t active_list = LLSpellChecker::instance().getSecondaryDictionaries(); +		active_ctrl->sortByColumnIndex(0, true); +		for (LLSpellChecker::dict_list_t::const_iterator it = active_list.begin(); it != active_list.end(); ++it) +		{ +			row["columns"][0]["value"] = *it; +			active_ctrl->addElement(row); +		} +		active_list.push_back(LLSpellChecker::instance().getActiveDictionary()); + +		// Populate the available dictionary list +		avail_ctrl->sortByColumnIndex(0, true); +		for (LLSD::array_const_iterator dict_it = dict_map.beginArray(); dict_it != dict_map.endArray(); ++dict_it) +		{ +			const LLSD& dict = *dict_it; +			if ( (dict["installed"].asBoolean()) && (dict.has("language")) &&  +				 (active_list.end() == std::find(active_list.begin(), active_list.end(), dict["language"].asString())) ) +			{ +				row["columns"][0]["value"] = dict["language"].asString(); +				avail_ctrl->addElement(row); +			} +		} +	} +}  void LLFloaterPreference::buildPopupLists()  { diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 7ee3294478..cd258b5614 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -121,6 +121,7 @@ public:  	void setCacheLocation(const LLStringExplicit& location); +	void onClickDictMove(const std::string& from, const std::string& to);  	void onClickSetCache();  	void onClickResetCache();  	void onClickSkin(LLUICtrl* ctrl,const LLSD& userdata); @@ -160,6 +161,7 @@ public:  	void applyUIColor(LLUICtrl* ctrl, const LLSD& param);  	void getUIColor(LLUICtrl* ctrl, const LLSD& param); +	void buildDictLists();  	void buildPopupLists();  	static void refreshSkin(void* data);  private: diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 8702ebde2a..bc0363014a 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -54,6 +54,8 @@ with the same filename but different name    <texture name="Arrow_Down" file_name="widgets/Arrow_Down.png"	preload="true" />    <texture name="Arrow_Up" file_name="widgets/Arrow_Up.png" preload="true" /> +  <texture name="Arrow_Left" file_name="widgets/Arrow_Left.png" preload="true" /> +  <texture name="Arrow_Right" file_name="widgets/Arrow_Right.png" preload="true" />    <texture name="AudioMute_Off" file_name="icons/AudioMute_Off.png" preload="false" />    <texture name="AudioMute_Over" file_name="icons/AudioMute_Over.png" preload="false" /> diff --git a/indra/newview/skins/default/textures/widgets/Arrow_Left.png b/indra/newview/skins/default/textures/widgets/Arrow_Left.pngBinary files differ new file mode 100644 index 0000000000..a424282839 --- /dev/null +++ b/indra/newview/skins/default/textures/widgets/Arrow_Left.png diff --git a/indra/newview/skins/default/textures/widgets/Arrow_Right.png b/indra/newview/skins/default/textures/widgets/Arrow_Right.pngBinary files differ new file mode 100644 index 0000000000..e32bee8f34 --- /dev/null +++ b/indra/newview/skins/default/textures/widgets/Arrow_Right.png diff --git a/indra/newview/skins/default/xui/en/floater_preferences.xml b/indra/newview/skins/default/xui/en/floater_preferences.xml index 402868bb97..eebc3a9cca 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences.xml @@ -120,6 +120,12 @@           layout="topleft"           help_topic="preferences_advanced1_tab"           name="advanced1" /> +        <panel +         class="panel_preference" +         filename="panel_preferences_spellcheck.xml" +         label="Spell Check" +         layout="topleft" +         name="spell_check" />      </tab_container>  </floater> diff --git a/indra/newview/skins/default/xui/en/floater_preview_notecard.xml b/indra/newview/skins/default/xui/en/floater_preview_notecard.xml index be3b2d179d..2e1c8ce670 100644 --- a/indra/newview/skins/default/xui/en/floater_preview_notecard.xml +++ b/indra/newview/skins/default/xui/en/floater_preview_notecard.xml @@ -70,6 +70,7 @@       max_length="65536"       name="Notecard Editor"       parse_urls="false"  +     spellcheck="true"       tab_group="1"       top="46"       width="392" diff --git a/indra/newview/skins/default/xui/en/panel_edit_pick.xml b/indra/newview/skins/default/xui/en/panel_edit_pick.xml index 2ec2e03e8c..6d0be7fdec 100644 --- a/indra/newview/skins/default/xui/en/panel_edit_pick.xml +++ b/indra/newview/skins/default/xui/en/panel_edit_pick.xml @@ -134,6 +134,7 @@           top_pad="2"           max_length="1023"           name="pick_desc" +         spellcheck="true"           text_color="black"           word_wrap="true" />          <text diff --git a/indra/newview/skins/default/xui/en/panel_group_notices.xml b/indra/newview/skins/default/xui/en/panel_group_notices.xml index 607e1bb213..6d5fb51e85 100644 --- a/indra/newview/skins/default/xui/en/panel_group_notices.xml +++ b/indra/newview/skins/default/xui/en/panel_group_notices.xml @@ -141,6 +141,7 @@ Maximum 200 per group daily           max_length_bytes="63"           name="create_subject"           prevalidate_callback="ascii" +         spellcheck="true"           width="218" />          <text           follows="left|top" @@ -161,6 +162,7 @@ Maximum 200 per group daily           left_pad="3"           max_length="511"           name="create_message" +         spellcheck="true"           top_delta="0"           width="218"           word_wrap="true" /> @@ -309,6 +311,7 @@ Maximum 200 per group daily           left_pad="3"           max_length_bytes="63"           name="view_subject" +         spellcheck="true"           top_delta="-1"           visible="false"           width="200" /> @@ -333,6 +336,7 @@ Maximum 200 per group daily           right="-1"           max_length="511"           name="view_message" +         spellcheck="true"           top_delta="-40"           width="313"           word_wrap="true" /> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_spellcheck.xml b/indra/newview/skins/default/xui/en/panel_preferences_spellcheck.xml new file mode 100644 index 0000000000..9b5d429846 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_preferences_spellcheck.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + border="true" + follows="left|top|right|bottom" + height="408" + label="Spell Check" + layout="topleft" + left="102" + name="spellcheck" + top="1" + width="517"> +    <check_box +     control_name="SpellCheck" +     enabled="true" +     follows="top|left" +     height="16" +     label="Enable spell checking" +     layout="topleft" +     left="30" +     name="check_spellcheck" +     top="30" +     width="250" +    /> +    <text +     enabled_control="SpellCheck" +     follows="top|left" +     height="10" +     label="Logs:" +     layout="topleft" +     left="55" +     mouse_opaque="false" +     name="text_spellcheck_dict" +     top_pad="15" +     type="string" +     width="90" +    > +      Main dictionary : +    </text> +    <combo_box +     enabled_control="SpellCheck" +     follows="top|left" +     height="23" +     layout="topleft" +     left_pad="10" +     name="combo_spellcheck_dict" +     top_pad="-15" +     width="175" +    /> + +    <text +     enabled_control="SpellCheck" +     follows="top|left" +     height="10" +     label="Logs:" +     layout="topleft" +     left="55" +     mouse_opaque="false" +     name="text_spellcheck_additional" +     top_pad="15" +     type="string" +     width="190" +    > +      Additional dictionaries : +    </text> +    <text +     follows="top|left" +     height="12" +     layout="topleft" +     left="80" +     length="1" +     name="text_spellcheck_available" +     top_pad="10" +     type="string" +     width="175"> +        Available +    </text> +    <text +     follows="top|left" +     height="12" +     type="string" +     left_pad="45" +     length="1" +     layout="topleft" +     name="text_spellcheck_active" +     width="175"> +        Active +    </text> +    <scroll_list +     follows="top|left" +     height="155" +     layout="topleft" +     left="80" +     multi_select="true" +     name="list_spellcheck_available" +     sort_column="0" +     sort_ascending="true"  +     width="175" /> +    <button +     follows="top|left" +     height="26" +     image_overlay="Arrow_Right" +     hover_glow_amount="0.15" +     layout="topleft" +     left_pad="10" +     name="btn_spellcheck_moveright" +     top_delta="50" +     width="25"> +    </button> +    <button +     follows="top|left" +     height="26" +     image_overlay="Arrow_Left" +     hover_glow_amount="0.15" +     layout="topleft" +     name="btn_spellcheck_moveleft" +     top_delta="30" +     width="25"> +    </button> +    <scroll_list +     follows="top|left" +     height="155" +     layout="topleft" +     left_pad="10" +     multi_select="true" +     name="list_spellcheck_active" +     sort_column="0" +     sort_ascending="true"  +     top_pad="-105" +     width="175" +    /> + +</panel> | 
