Commits

Scott Lawrence committed 4d4aea1 Merge

merge spelling changes for UI review

  • Participants
  • Parent commits 6a35dd7, 5730b11

Comments (0)

Files changed (11)

 indra/lib/mono/indra/*.pdb
 indra/lib/python/eventlet/
 indra/llwindow/glh/glh_linear.h
+indra/newview/app_settings/dictionaries
 indra/newview/app_settings/mozilla
 indra/newview/app_settings/mozilla-runtime-*
 indra/newview/app_settings/mozilla_debug

indra/llui/lllineeditor.cpp

 	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;
 }
 	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;
 }
 			// 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();
 					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;
 		{
 			// 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());
 
 			{
 				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 (hasSelection())
 		{
 			if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
+			{
 				deselect();
+			}
 			else
+			{
 				setCursor(llmax(mSelectionStart, mSelectionEnd));
+			}
 		}
 
 		bool use_spellcheck = getSpellCheck(), is_misspelled = false;

indra/llui/llmenugl.cpp

 	arrange();
 
 	if (spawning_view)
+	{
 		mSpawningViewHandle = spawning_view->getHandle();
+	}
 	else
+	{
 		mSpawningViewHandle.markDead();
+	}
 	LLView::setVisible(TRUE);
 }
 

indra/llui/llspellcheck.cpp

 static const std::string DICT_CUSTOM_SUFFIX = "_custom";
 static const std::string DICT_IGNORE_SUFFIX = "_ignore";
 
+static const std::string DICT_FILE_MAIN = "dictionaries.xml";
+static const std::string DICT_FILE_USER = "user_dictionaries.xml";
+
 LLSD LLSpellChecker::sDictMap;
 LLSpellChecker::settings_change_signal_t LLSpellChecker::sSettingsChangeSignal;
 
 {
 	suggestions.clear();
 	if ( (!mHunspell) || (word.length() < 3) )
+	{
 		return 0;
+	}
 
 	char** suggestion_list; int suggestion_cnt = 0;
 	if ( (suggestion_cnt = mHunspell->suggest(&suggestion_list, word.c_str())) != 0 )
 	{
 		for (int suggestion_index = 0; suggestion_index < suggestion_cnt; suggestion_index++)
+		{
 			suggestions.push_back(suggestion_list[suggestion_index]);
+		}
 		mHunspell->free_list(&suggestion_list, suggestion_cnt);	
 	}
 	return suggestions.size();
 	{
 		const LLSD& dict_entry = *it;
 		if (dict_language == dict_entry["language"].asString())
+		{
 			return dict_entry;
+		}
 	}
 	return LLSD();
 }
 
 // static
+bool LLSpellChecker::hasDictionary(const std::string& dict_language, bool check_installed)
+{
+	const LLSD dict_info = getDictionaryData(dict_language);
+	return dict_info.has("language") && ( (!check_installed) || (dict_info["installed"].asBoolean()) );
+}
+
+// static
 void LLSpellChecker::setDictionaryData(const LLSD& dict_info)
 {
 	const std::string dict_language = dict_info["language"].asString();
 	if (dict_language.empty())
+	{
 		return;
+	}
 
 	for (LLSD::array_iterator it = sDictMap.beginArray(); it != sDictMap.endArray(); ++it)
 	{
 	const std::string user_path = getDictionaryUserPath();
 
 	// Load dictionary information (file name, friendly name, ...)
-	llifstream user_file(user_path + "dictionaries.xml", std::ios::binary);
+	llifstream user_file(user_path + DICT_FILE_MAIN, std::ios::binary);
 	if ( (!user_file.is_open()) || (0 == LLSDSerialize::fromXMLDocument(sDictMap, user_file)) || (0 == sDictMap.size()) )
 	{
-		llifstream app_file(app_path + "dictionaries.xml", std::ios::binary);
+		llifstream app_file(app_path + DICT_FILE_MAIN, std::ios::binary);
 		if ( (!app_file.is_open()) || (0 == LLSDSerialize::fromXMLDocument(sDictMap, app_file)) || (0 == sDictMap.size()) )
+		{
 			return;
+		}
 	}
 
 	// Load user installed dictionary information
-	llifstream custom_file(user_path + "user_dictionaries.xml", std::ios::binary);
+	llifstream custom_file(user_path + DICT_FILE_USER, std::ios::binary);
 	if (custom_file.is_open())
 	{
 		LLSD custom_dict_map;
 		LLSDSerialize::fromXMLDocument(custom_dict_map, custom_file);
-		for (LLSD::array_const_iterator it = custom_dict_map.beginArray(); it != custom_dict_map.endArray(); ++it)
-			setDictionaryData(*it);
+		for (LLSD::array_iterator it = custom_dict_map.beginArray(); it != custom_dict_map.endArray(); ++it)
+		{
+			LLSD& dict_info = *it;
+			dict_info["user_installed"] = true;
+			setDictionaryData(dict_info);
+		}
 		custom_file.close();
 	}
 
 			{
 				// Skip over the first line since that's just a line count
 				if (0 != line_num)
+				{
 					word_list.push_back(word);
+				}
 				line_num++;
 			}
 		}
 	{
 		file_out << word_list.size() << std::endl;
 		for (std::vector<std::string>::const_iterator itWord = word_list.begin(); itWord != word_list.end(); ++itWord)
+		{
 			file_out << *itWord << std::endl;
+		}
 		file_out.close();
 	}
 }
 
+bool LLSpellChecker::isActiveDictionary(const std::string& dict_language) const
+{
+	return
+		(mDictLanguage == dict_language) || 
+		(mDictSecondary.end() != std::find(mDictSecondary.begin(), mDictSecondary.end(), dict_language));
+}
+
 void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list)
 {
 	if (!getUseSpellCheck())
 	{
 		mDictSecondary = dict_list;
 
-		std::string dict_name = mDictName;
-		initHunspell(dict_name);
+		std::string dict_language = mDictLanguage;
+		initHunspell(dict_language);
 	}
 	else if (end_added != dict_add.begin())		// Add the new secondary dictionaries one by one
 	{
 		{
 			const LLSD dict_entry = getDictionaryData(*it_added);
 			if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) )
+			{
 				continue;
+			}
 
 			const std::string strFileDic = dict_entry["name"].asString() + ".dic";
 			if (gDirUtilp->fileExists(user_path + strFileDic))
+			{
 				mHunspell->add_dic((user_path + strFileDic).c_str());
+			}
 			else if (gDirUtilp->fileExists(app_path + strFileDic))
+			{
 				mHunspell->add_dic((app_path + strFileDic).c_str());
+			}
 		}
 		mDictSecondary = dict_list;
 		sSettingsChangeSignal();
 	}
 }
 
-void LLSpellChecker::initHunspell(const std::string& dict_name)
+void LLSpellChecker::initHunspell(const std::string& dict_language)
 {
 	if (mHunspell)
 	{
 		delete mHunspell;
 		mHunspell = NULL;
-		mDictName.clear();
+		mDictLanguage.clear();
 		mDictFile.clear();
 		mIgnoreList.clear();
 	}
 
-	const LLSD dict_entry = (!dict_name.empty()) ? getDictionaryData(dict_name) : LLSD();
+	const LLSD dict_entry = (!dict_language.empty()) ? getDictionaryData(dict_language) : LLSD();
 	if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) || (!dict_entry["is_primary"].asBoolean()))
 	{
 		sSettingsChangeSignal();
 		const std::string filename_aff = dict_entry["name"].asString() + ".aff";
 		const std::string filename_dic = dict_entry["name"].asString() + ".dic";
 		if ( (gDirUtilp->fileExists(user_path + filename_aff)) && (gDirUtilp->fileExists(user_path + filename_dic)) )
+		{
 			mHunspell = new Hunspell((user_path + filename_aff).c_str(), (user_path + filename_dic).c_str());
+		}
 		else if ( (gDirUtilp->fileExists(app_path + filename_aff)) && (gDirUtilp->fileExists(app_path + filename_dic)) )
+		{
 			mHunspell = new Hunspell((app_path + filename_aff).c_str(), (app_path + filename_dic).c_str());
+		}
 		if (!mHunspell)
+		{
 			return;
+		}
 
-		mDictName = dict_name;
+		mDictLanguage = dict_language;
 		mDictFile = dict_entry["name"].asString();
 
 		if (dict_entry["has_custom"].asBoolean())
 		{
 			const LLSD dict_entry = getDictionaryData(*it);
 			if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) )
+			{
 				continue;
+			}
 
 			const std::string filename_dic = dict_entry["name"].asString() + ".dic";
 			if (gDirUtilp->fileExists(user_path + filename_dic))
+			{
 				mHunspell->add_dic((user_path + filename_dic).c_str());
+			}
 			else if (gDirUtilp->fileExists(app_path + filename_dic))
+			{
 				mHunspell->add_dic((app_path + filename_dic).c_str());
+			}
 		}
 	}
 
 }
 
 // static
+bool LLSpellChecker::canRemoveDictionary(const std::string& dict_language)
+{
+	// Only user-installed inactive dictionaries can be removed
+	const LLSD dict_info = getDictionaryData(dict_language);
+	return 
+		(dict_info["user_installed"].asBoolean()) && 
+		( (!getUseSpellCheck()) || (!LLSpellChecker::instance().isActiveDictionary(dict_language)) );
+}
+
+// static
+void LLSpellChecker::removeDictionary(const std::string& dict_language)
+{
+	if (!canRemoveDictionary(dict_language))
+	{
+		return;
+	}
+
+	LLSD dict_map = loadUserDictionaryMap();
+	for (LLSD::array_const_iterator it = dict_map.beginArray(); it != dict_map.endArray(); ++it)
+	{
+		const LLSD& dict_info = *it;
+		if (dict_info["language"].asString() == dict_language)
+		{
+			const std::string dict_dic = getDictionaryUserPath() + dict_info["name"].asString() + ".dic";
+			if (gDirUtilp->fileExists(dict_dic))
+			{
+				LLFile::remove(dict_dic);
+			}
+			const std::string dict_aff = getDictionaryUserPath() + dict_info["name"].asString() + ".aff";
+			if (gDirUtilp->fileExists(dict_aff))
+			{
+				LLFile::remove(dict_aff);
+			}
+			dict_map.erase(it - dict_map.beginArray());
+			break;
+		}
+	}
+	saveUserDictionaryMap(dict_map);
+
+	refreshDictionaryMap();
+}
+
+// static
+LLSD LLSpellChecker::loadUserDictionaryMap()
+{
+	LLSD dict_map;
+	llifstream dict_file(getDictionaryUserPath() + DICT_FILE_USER, std::ios::binary);
+	if (dict_file.is_open())
+	{
+		LLSDSerialize::fromXMLDocument(dict_map, dict_file);
+		dict_file.close();
+	}
+	return dict_map;
+}
+
+// static
+void LLSpellChecker::saveUserDictionaryMap(const LLSD& dict_map)
+{
+	llofstream dict_file(getDictionaryUserPath() + DICT_FILE_USER, std::ios::trunc);
+	if (dict_file.is_open())
+	{
+		LLSDSerialize::toPrettyXML(dict_map, dict_file);
+		dict_file.close();
+	}
+}
+
+// static
 boost::signals2::connection LLSpellChecker::setSettingsChangeCallback(const settings_change_signal_t::slot_type& cb)
 {
 	return sSettingsChangeSignal.connect(cb);
 }
 
 // static
-void LLSpellChecker::setUseSpellCheck(const std::string& dict_name)
+void LLSpellChecker::setUseSpellCheck(const std::string& dict_language)
 {
-	if ( (((dict_name.empty()) && (getUseSpellCheck())) || (!dict_name.empty())) && 
-		 (LLSpellChecker::instance().mDictName != dict_name) )
+	if ( (((dict_language.empty()) && (getUseSpellCheck())) || (!dict_language.empty())) && 
+		 (LLSpellChecker::instance().mDictLanguage != dict_language) )
 	{
-		LLSpellChecker::instance().initHunspell(dict_name);
+		LLSpellChecker::instance().initHunspell(dict_language);
 	}
 }
 
 void LLSpellChecker::initClass()
 {
 	if (sDictMap.isUndefined())
+	{
 		refreshDictionaryMap();
+	}
 }

indra/llui/llspellcheck.h

 	S32  getSuggestions(const std::string& word, std::vector<std::string>& suggestions) const;
 protected:
 	void addToDictFile(const std::string& dict_path, const std::string& word);
-	void initHunspell(const std::string& dict_name);
+	void initHunspell(const std::string& dict_language);
 
 public:
 	typedef std::list<std::string> dict_list_t;
 
-	const std::string&	getActiveDictionary() const { return mDictName; }
+	const std::string&	getPrimaryDictionary() const { return mDictLanguage; }
 	const dict_list_t&	getSecondaryDictionaries() const { return mDictSecondary; }
+	bool				isActiveDictionary(const std::string& dict_language) const;
 	void				setSecondaryDictionaries(dict_list_t dict_list);
 
+	static bool				 canRemoveDictionary(const std::string& dict_language);
 	static const std::string getDictionaryAppPath();
 	static const std::string getDictionaryUserPath();
 	static const LLSD		 getDictionaryData(const std::string& dict_language);
 	static const LLSD&		 getDictionaryMap() { return sDictMap; }
 	static bool				 getUseSpellCheck();
+	static bool				 hasDictionary(const std::string& dict_language, bool check_installed = false);
 	static void				 refreshDictionaryMap();
-	static void				 setUseSpellCheck(const std::string& dict_name);
+	static void				 removeDictionary(const std::string& dict_language);
+	static void				 setUseSpellCheck(const std::string& dict_language);
 protected:
+	static LLSD				 loadUserDictionaryMap();
 	static void				 setDictionaryData(const LLSD& dict_info);
+	static void				 saveUserDictionaryMap(const LLSD& dict_map);
 
 public:
 	typedef boost::signals2::signal<void()> settings_change_signal_t;
 
 protected:
 	Hunspell*	mHunspell;
-	std::string	mDictName;
+	std::string	mDictLanguage;
 	std::string	mDictFile;
 	dict_list_t	mDictSecondary;
 	std::vector<std::string> mIgnoreList;

indra/llui/lltextbase.cpp

 				// 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)
 						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));
 					// Find the start of the next word
 					word_start = word_end + 1;
 					while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) )
+					{
 						word_start++;
+					}
 				}
 			}
 
 				{
 					gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2);
 					if (squiggle_start + 3 < squiggle_end)
+					{
 						gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1);
+					}
 					squiggle_start += 4;
 				}
 
 				if (misspell_it->second > seg_end)
+				{
 					break;
+				}
 				++misspell_it;
 			}
 
 	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;
 }
 	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;
 }

indra/llui/lltexteditor.cpp

 	if (hasSelection())
 	{
 		if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
+		{
 			deselect();
+		}
 		else
+		{
 			setCursorPos(llmax(mSelectionStart, mSelectionEnd));
+		}
 	}
 
 	bool use_spellcheck = getSpellCheck(), is_misspelled = false;

indra/newview/llfloaterspellchecksettings.cpp

 #include "llscrolllistctrl.h"
 #include "llsdserialize.h"
 #include "llspellcheck.h"
+#include "lltrans.h"
 #include "llviewercontrol.h"
 
 #include <boost/algorithm/string.hpp>
 {
 }
 
+void LLFloaterSpellCheckerSettings::draw()
+{
+	LLFloater::draw();
+
+	std::vector<LLScrollListItem*> sel_items = getChild<LLScrollListCtrl>("spellcheck_available_list")->getAllSelected();
+	bool enable_remove = !sel_items.empty();
+	for (std::vector<LLScrollListItem*>::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it)
+	{
+		enable_remove &= LLSpellChecker::canRemoveDictionary((*sel_it)->getValue().asString());
+	}
+	getChild<LLUICtrl>("spellcheck_remove_btn")->setEnabled(enable_remove);
+}
+
 BOOL LLFloaterSpellCheckerSettings::postBuild(void)
 {
-	gSavedSettings.getControl("SpellCheck")->getSignal()->connect(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaryLists, this, false));
+	gSavedSettings.getControl("SpellCheck")->getSignal()->connect(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaries, this, false));
 	LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLFloaterSpellCheckerSettings::onSpellCheckSettingsChange, this));
+	getChild<LLUICtrl>("spellcheck_remove_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnRemove, this));
 	getChild<LLUICtrl>("spellcheck_import_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnImport, this));
-	getChild<LLUICtrl>("spellcheck_main_combo")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaryLists, this, false));
+	getChild<LLUICtrl>("spellcheck_main_combo")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaries, this, false));
 	getChild<LLUICtrl>("spellcheck_moveleft_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnMove, this, "spellcheck_active_list", "spellcheck_available_list"));
 	getChild<LLUICtrl>("spellcheck_moveright_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnMove, this, "spellcheck_available_list", "spellcheck_active_list"));
 	getChild<LLUICtrl>("spellcheck_ok")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnOK, this));
 	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["value"] = (*sel_it)->getValue();
 		row["columns"][0]["value"] = (*sel_it)->getColumn(0)->getValue();
 		to_ctrl->addElement(row);
 	}
 		LLScrollListCtrl* list_ctrl = findChild<LLScrollListCtrl>("spellcheck_active_list");
 		std::vector<LLScrollListItem*> list_items = list_ctrl->getAllData();
 		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());
+		{
+			const std::string language = (*item_it)->getValue().asString();
+			if (LLSpellChecker::hasDictionary(language, true))
+			{
+				list_dict.push_back(language);
+			}
+		}
 	}
 	gSavedSettings.setString("SpellCheckDictionary", boost::join(list_dict, ","));
 
 
 void LLFloaterSpellCheckerSettings::onOpen(const LLSD& key)
 {
-	refreshDictionaryLists(true);
+	refreshDictionaries(true);
+}
+
+void LLFloaterSpellCheckerSettings::onBtnRemove()
+{
+	std::vector<LLScrollListItem*> sel_items = getChild<LLScrollListCtrl>("spellcheck_available_list")->getAllSelected();
+	for (std::vector<LLScrollListItem*>::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it)
+	{
+		LLSpellChecker::instance().removeDictionary((*sel_it)->getValue().asString());
+	}
 }
 
 void LLFloaterSpellCheckerSettings::onSpellCheckSettingsChange()
 {
-	refreshDictionaryLists(true);
+	refreshDictionaries(true);
 }
 
-void LLFloaterSpellCheckerSettings::refreshDictionaryLists(bool from_settings)
+void LLFloaterSpellCheckerSettings::refreshDictionaries(bool from_settings)
 {
 	bool enabled = gSavedSettings.getBOOL("SpellCheck");
 	getChild<LLUICtrl>("spellcheck_moveleft_btn")->setEnabled(enabled);
 	LLComboBox* dict_combo = findChild<LLComboBox>("spellcheck_main_combo");
 	std::string dict_cur = dict_combo->getSelectedItemLabel();
 	if ((dict_cur.empty() || from_settings) && (LLSpellChecker::getUseSpellCheck()))
-		dict_cur = LLSpellChecker::instance().getActiveDictionary();
+	{
+		dict_cur = LLSpellChecker::instance().getPrimaryDictionary();
+	}
 	dict_combo->clearRows();
 	dict_combo->setEnabled(enabled);
 
 		{
 			const LLSD& dict = *dict_it;
 			if ( (dict["installed"].asBoolean()) && (dict["is_primary"].asBoolean()) && (dict.has("language")) )
+			{
 				dict_combo->add(dict["language"].asString());
+			}
 		}
 		if (!dict_combo->selectByValue(dict_cur))
+		{
 			dict_combo->clear();
+		}
 	}
 
 	// Populate the available and active dictionary list
 	if ( ((!avail_ctrl->getItemCount()) && (!active_ctrl->getItemCount())) || (from_settings) )
 	{
 		if (LLSpellChecker::getUseSpellCheck())
+		{
 			active_list = LLSpellChecker::instance().getSecondaryDictionaries();
+		}
 	}
 	else
 	{
 		std::vector<LLScrollListItem*> active_items = active_ctrl->getAllData();
 		for (std::vector<LLScrollListItem*>::const_iterator item_it = active_items.begin(); item_it != active_items.end(); ++item_it)
 		{
-			std::string dict = (*item_it)->getColumn(0)->getValue().asString();
+			std::string dict = (*item_it)->getValue().asString();
 			if (dict_cur != dict)
+			{
 				active_list.push_back(dict);
+			}
 		}
 	}
 
 
 	active_ctrl->clearRows();
 	active_ctrl->setEnabled(enabled);
-	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;
+		const std::string language = *it;
+		const LLSD dict = LLSpellChecker::getDictionaryData(language);
+		row["value"] = language;
+		row["columns"][0]["value"] = (!dict["user_installed"].asBoolean()) ? language : language + " " + LLTrans::getString("UserDictionary");
 		active_ctrl->addElement(row);
 	}
+	active_ctrl->sortByColumnIndex(0, true);
 	active_list.push_back(dict_cur);
 
 	avail_ctrl->clearRows();
 	avail_ctrl->setEnabled(enabled);
-	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())) )
+		const std::string language = dict["language"].asString();
+		if ( (dict["installed"].asBoolean()) && (active_list.end() == std::find(active_list.begin(), active_list.end(), language)) )
 		{
-			row["columns"][0]["value"] = dict["language"].asString();
+			row["value"] = language;
+			row["columns"][0]["value"] = (!dict["user_installed"].asBoolean()) ? language : language + " " + LLTrans::getString("UserDictionary");
 			avail_ctrl->addElement(row);
 		}
 	}
+	avail_ctrl->sortByColumnIndex(0, true);
 }
 
 ///----------------------------------------------------------------------------

indra/newview/llfloaterspellchecksettings.h

 public:
 	LLFloaterSpellCheckerSettings(const LLSD& key);
 
+	/*virtual*/ void draw();
 	/*virtual*/ BOOL postBuild();
 	/*virtual*/ void onOpen(const LLSD& key);
 
 	void onBtnImport();
 	void onBtnMove(const std::string& from, const std::string& to);
 	void onBtnOK();
+	void onBtnRemove();
 	void onSpellCheckSettingsChange();
-	void refreshDictionaryLists(bool from_settings);
+	void refreshDictionaries(bool from_settings);
 };
 
 class LLFloaterSpellCheckerImport : public LLFloater

indra/newview/skins/default/xui/en/floater_spellcheck.xml

  border="true"
  can_close="true"
  can_minimize="true"
- bottom="275"
+ bottom="300"
  left="300"
  can_resize="false"
- height="330"
+ height="355"
  width="490"
  name="spellcheck_floater"
  title="Spell Checker Settings">
    top_pad="-15"
    width="175"
   />
-  <button
-   follows="left|top"
-   height="23"
-   label="Import"
-   label_selected="Import"
-   layout="topleft"
-   left_pad="5"
-   name="spellcheck_import_btn"
-   top_delta="0"
-   width="75" />
   <text
    enabled_control="SpellCheck"
    follows="top|left"
    top_pad="-105"
    width="175"
   />
+  <button
+   enabled="false"
+   follows="left|top"
+   height="23"
+   label="Remove"
+   layout="topleft"
+   left="55"
+   name="spellcheck_remove_btn"
+   top_pad="5"
+   width="80" />
+  <button
+   follows="left|top"
+   height="23"
+   label="Import..."
+   layout="topleft"
+   left_pad="15"
+   name="spellcheck_import_btn"
+   top_delta="0"
+   width="80" />
   <view_border
    top_pad="10"
    left="2"
    mouse_opaque="false"
    name="divisor4"/>
   <button
-   top_pad="10"
+   top_pad="8"
    right="380"
    height="22"
    width="90"

indra/newview/skins/default/xui/en/strings.xml

   <string name="snapshot_quality_high">High</string>
   <string name="snapshot_quality_very_high">Very High</string>
 
+  <!-- Spell check settings floater -->
+  <string name="UserDictionary">[User]</string>
+  
   </strings>