diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index 394db362b1572414e80578e5f433589b6b2b9f77..ebeae3e5beb83ec89d20980dcec1d1755e7dfa7e 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -41,6 +41,7 @@ if(WINDOWS)
         libeay32.dll
         libcollada14dom22-d.dll
         glod.dll    
+        libhunspell.dll
         )
 
     set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}")
@@ -53,6 +54,7 @@ if(WINDOWS)
         libeay32.dll
         libcollada14dom22.dll
         glod.dll
+        libhunspell.dll
         )
 
     if(USE_GOOGLE_PERFTOOLS)
@@ -215,6 +217,7 @@ elseif(DARWIN)
     libllqtwebkit.dylib
     libminizip.a
         libndofdev.dylib
+        libhunspell-1.3.dylib
         libexception_handler.dylib
     libcollada14dom.dylib
        )
diff --git a/indra/cmake/ViewerMiscLibs.cmake b/indra/cmake/ViewerMiscLibs.cmake
index df013b1665058e458ede89172c2aa5281ea480a5..f907181929802ab0a27019b574a3ddab7d4e1794 100644
--- a/indra/cmake/ViewerMiscLibs.cmake
+++ b/indra/cmake/ViewerMiscLibs.cmake
@@ -2,6 +2,7 @@
 include(Prebuilt)
 
 if (NOT STANDALONE)
+  use_prebuilt_binary(libhunspell)
   use_prebuilt_binary(libuuid)
   use_prebuilt_binary(slvoice)
   use_prebuilt_binary(fontconfig)
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 772f173f1748e9edabc3281e2ed6b43feb0803d0..1377336bb4d74f8ea4f15d4e331d4034256aec10 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -87,6 +87,7 @@ set(llui_SOURCE_FILES
     llsearcheditor.cpp
     llslider.cpp
     llsliderctrl.cpp
+    llspellcheck.cpp
     llspinctrl.cpp
     llstatbar.cpp
     llstatgraph.cpp
@@ -192,6 +193,8 @@ set(llui_HEADER_FILES
     llsdparam.h
     llsliderctrl.h
     llslider.h
+    llspellcheck.h
+    llspellcheckmenuhandler.h
     llspinctrl.h
     llstatbar.h
     llstatgraph.h
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 95ecbb1c9479cb9a885ce9c10c448ace5e29e9ab..2a65262bbb6980a1155dd4b264be1e829278f45a 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -3854,7 +3854,7 @@ void LLContextMenu::setVisible(BOOL visible)
 }
 
 // Takes cursor position in screen space?
-void LLContextMenu::show(S32 x, S32 y)
+void LLContextMenu::show(S32 x, S32 y, LLView* spawning_view)
 {
 	if (getChildList()->empty())
 	{
@@ -3908,6 +3908,10 @@ void LLContextMenu::show(S32 x, S32 y)
 	setRect(rect);
 	arrange();
 
+	if (spawning_view)
+		mSpawningViewHandle = spawning_view->getHandle();
+	else
+		mSpawningViewHandle.markDead();
 	LLView::setVisible(TRUE);
 }
 
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index 36f3ba34b9b5e45c3271563e0efc7224acbb86fe..67b3e1fbe669587bc41ef47fb58cf9a262395dc1 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -670,7 +670,7 @@ class LLContextMenu
 	
 	virtual void	draw				();
 	
-	virtual void	show				(S32 x, S32 y);
+	virtual void	show				(S32 x, S32 y, LLView* spawning_view = NULL);
 	virtual void	hide				();
 
 	virtual BOOL	handleHover			( S32 x, S32 y, MASK mask );
@@ -683,10 +683,14 @@ class LLContextMenu
 
 			LLHandle<LLContextMenu> getHandle() { return getDerivedHandle<LLContextMenu>(); }
 
+			LLView*	getSpawningView() const		{ return mSpawningViewHandle.get(); }
+			void	setSpawningView(LLHandle<LLView> spawning_view) { mSpawningViewHandle = spawning_view; }
+
 protected:
 	BOOL						mHoveredAnyItem;
 	LLMenuItemGL*				mHoverItem;
 	LLRootHandle<LLContextMenu>	mHandle;
+	LLHandle<LLView>			mSpawningViewHandle;
 };
 
 
diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..433ca0285243a68c25312a02e154203e4520f7d3
--- /dev/null
+++ b/indra/llui/llspellcheck.cpp
@@ -0,0 +1,345 @@
+/** 
+ * @file llspellcheck.cpp
+ * @brief Spell checking functionality
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "lldir.h"
+#include "llsdserialize.h"
+
+#include "llspellcheck.h"
+#if LL_WINDOWS
+	#include <hunspell/hunspelldll.h>
+	#pragma comment(lib, "libhunspell.lib")
+#else
+	#include <hunspell/hunspell.hxx>
+#endif
+
+static const std::string DICT_DIR = "dictionaries";
+static const std::string DICT_CUSTOM_SUFFIX = "_custom";
+static const std::string DICT_IGNORE_SUFFIX = "_ignore";
+
+LLSpellChecker::settings_change_signal_t LLSpellChecker::sSettingsChangeSignal;
+
+LLSpellChecker::LLSpellChecker()
+	: mHunspell(NULL)
+{
+	// Load initial dictionary information
+	refreshDictionaryMap();
+}
+
+LLSpellChecker::~LLSpellChecker()
+{
+	delete mHunspell;
+}
+
+bool LLSpellChecker::checkSpelling(const std::string& word) const
+{
+	if ( (!mHunspell) || (word.length() < 3) || (0 != mHunspell->spell(word.c_str())) )
+	{
+		return true;
+	}
+	if (mIgnoreList.size() > 0)
+	{
+		std::string word_lower(word);
+		LLStringUtil::toLower(word_lower);
+		return (mIgnoreList.end() != std::find(mIgnoreList.begin(), mIgnoreList.end(), word_lower));
+	}
+	return false;
+}
+
+S32 LLSpellChecker::getSuggestions(const std::string& word, std::vector<std::string>& suggestions) const
+{
+	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 LLSpellChecker::getDictionaryData(const std::string& dict_name) const
+{
+	for (LLSD::array_const_iterator it = mDictMap.beginArray(); it != mDictMap.endArray(); ++it)
+	{
+		const LLSD& dict_entry = *it;
+		if (dict_name == dict_entry["language"].asString())
+			return dict_entry;
+	}
+	return LLSD();
+}
+
+void LLSpellChecker::refreshDictionaryMap()
+{
+	const std::string app_path = getDictionaryAppPath();
+	const std::string user_path = getDictionaryUserPath();
+
+	// Load dictionary information (file name, friendly name, ...)
+	llifstream user_map(user_path + "dictionaries.xml", std::ios::binary);
+	if ( (!user_map.is_open()) || (0 == LLSDSerialize::fromXMLDocument(mDictMap, user_map)) || (0 == mDictMap.size()) )
+	{
+		llifstream app_map(app_path + "dictionaries.xml", std::ios::binary);
+		if ( (!app_map.is_open()) || (0 == LLSDSerialize::fromXMLDocument(mDictMap, app_map)) || (0 == mDictMap.size()) )
+			return;
+	}
+
+	// Look for installed dictionaries
+	std::string tmp_app_path, tmp_user_path;
+	for (LLSD::array_iterator it = mDictMap.beginArray(); it != mDictMap.endArray(); ++it)
+	{
+		LLSD& sdDict = *it;
+		tmp_app_path = (sdDict.has("name")) ? app_path + sdDict["name"].asString() : LLStringUtil::null;
+		tmp_user_path = (sdDict.has("name")) ? user_path + sdDict["name"].asString() : LLStringUtil::null;
+		sdDict["installed"] = 
+			(!tmp_app_path.empty()) && 
+			( ((gDirUtilp->fileExists(tmp_user_path + ".aff")) && (gDirUtilp->fileExists(tmp_user_path + ".dic"))) ||
+			  ((gDirUtilp->fileExists(tmp_app_path + ".aff")) && (gDirUtilp->fileExists(tmp_app_path + ".dic"))) );
+		sdDict["has_custom"] = (!tmp_user_path.empty()) && (gDirUtilp->fileExists(tmp_user_path + DICT_CUSTOM_SUFFIX + ".dic"));
+		sdDict["has_ignore"] = (!tmp_user_path.empty()) && (gDirUtilp->fileExists(tmp_user_path + DICT_IGNORE_SUFFIX + ".dic"));
+	}
+}
+
+void LLSpellChecker::addToCustomDictionary(const std::string& word)
+{
+	if (mHunspell)
+	{
+		mHunspell->add(word.c_str());
+	}
+	addToDictFile(getDictionaryUserPath() + mDictFile + DICT_CUSTOM_SUFFIX + ".dic", word);
+	sSettingsChangeSignal();
+}
+
+void LLSpellChecker::addToIgnoreList(const std::string& word)
+{
+	std::string word_lower(word);
+	LLStringUtil::toLower(word_lower);
+	if (mIgnoreList.end() != std::find(mIgnoreList.begin(), mIgnoreList.end(), word_lower))
+	{
+		mIgnoreList.push_back(word_lower);
+		addToDictFile(getDictionaryUserPath() + mDictFile + DICT_IGNORE_SUFFIX + ".dic", word_lower);
+		sSettingsChangeSignal();
+	}
+}
+
+void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::string& word)
+{
+	std::vector<std::string> word_list;
+
+	if (gDirUtilp->fileExists(dict_path))
+	{
+		llifstream file_in(dict_path, std::ios::in);
+		if (file_in.is_open())
+		{
+			std::string word; int line_num = 0;
+			while (getline(file_in, word))
+			{
+				// Skip over the first line since that's just a line count
+				if (0 != line_num)
+					word_list.push_back(word);
+				line_num++;
+			}
+		}
+		else
+		{
+			// TODO: show error message?
+			return;
+		}
+	}
+
+	word_list.push_back(word);
+
+	llofstream file_out(dict_path, std::ios::out | std::ios::trunc);	
+	if (file_out.is_open())
+	{
+		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();
+	}
+}
+
+void LLSpellChecker::setSecondaryDictionaries(std::list<std::string> dict_list)
+{
+	if (!getUseSpellCheck())
+	{
+		return;
+	}
+
+	// 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.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());
+
+	if (end_removed != dict_rem.begin())		// We can't remove secondary dictionaries so we need to recreate the Hunspell instance
+	{
+		mDictSecondary = dict_list;
+
+		std::string dict_name = mDictName;
+		initHunspell(dict_name);
+	}
+	else if (end_added != dict_add.begin())		// Add the new secondary dictionaries one by one
+	{
+		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)
+		{
+			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)
+{
+	if (mHunspell)
+	{
+		delete mHunspell;
+		mHunspell = NULL;
+		mDictName.clear();
+		mDictFile.clear();
+		mIgnoreList.clear();
+	}
+
+	const LLSD dict_entry = (!dict_name.empty()) ? getDictionaryData(dict_name) : LLSD();
+	if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) )
+	{
+		sSettingsChangeSignal();
+		return;
+	}
+
+	const std::string app_path = getDictionaryAppPath();
+	const std::string user_path = getDictionaryUserPath();
+	if (dict_entry.has("name"))
+	{
+		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;
+		mDictFile = dict_entry["name"].asString();
+
+		if (dict_entry["has_custom"].asBoolean())
+		{
+			const std::string filename_dic = user_path + mDictFile + DICT_CUSTOM_SUFFIX + ".dic";
+			mHunspell->add_dic(filename_dic.c_str());
+		}
+
+		if (dict_entry["has_ignore"].asBoolean())
+		{
+			llifstream file_in(user_path + mDictFile + DICT_IGNORE_SUFFIX + ".dic", std::ios::in);
+			if (file_in.is_open())
+			{
+				std::string word; int idxLine = 0;
+				while (getline(file_in, word))
+				{
+					// Skip over the first line since that's just a line count
+					if (0 != idxLine)
+					{
+						LLStringUtil::toLower(word);
+						mIgnoreList.push_back(word);
+					}
+					idxLine++;
+				}
+			}
+		}
+
+		for (std::list<std::string>::const_iterator it = mDictSecondary.begin(); it != mDictSecondary.end(); ++it)
+		{
+			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());
+		}
+	}
+
+	sSettingsChangeSignal();
+}
+
+// static
+const std::string LLSpellChecker::getDictionaryAppPath()
+{
+	std::string dict_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, DICT_DIR, "");
+	return dict_path;
+}
+
+// static
+const std::string LLSpellChecker::getDictionaryUserPath()
+{
+	std::string dict_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, DICT_DIR, "");
+	if (!gDirUtilp->fileExists(dict_path))
+	{
+		LLFile::mkdir(dict_path);
+	}
+	return dict_path;
+}
+
+// static
+bool LLSpellChecker::getUseSpellCheck()
+{
+	return (LLSpellChecker::instanceExists()) && (LLSpellChecker::instance().mHunspell);
+}
+
+// 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)
+{
+	if ( (((dict_name.empty()) && (getUseSpellCheck())) || (!dict_name.empty())) && 
+		 (LLSpellChecker::instance().mDictName != dict_name) )
+	{
+		LLSpellChecker::instance().initHunspell(dict_name);
+	}
+}
diff --git a/indra/llui/llspellcheck.h b/indra/llui/llspellcheck.h
new file mode 100644
index 0000000000000000000000000000000000000000..affdac2907b4546450339a58cec2c756fde19ce5
--- /dev/null
+++ b/indra/llui/llspellcheck.h
@@ -0,0 +1,77 @@
+/** 
+ * @file llspellcheck.h
+ * @brief Spell checking functionality
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLSPELLCHECK_H
+#define LLSPELLCHECK_H
+
+#include "llsingleton.h"
+#include <boost/signals2.hpp>
+
+class Hunspell;
+
+class LLSpellChecker : public LLSingleton<LLSpellChecker>
+{
+	friend class LLSingleton<LLSpellChecker>;
+protected:
+	LLSpellChecker();
+	~LLSpellChecker();
+
+public:
+	void addToCustomDictionary(const std::string& word);
+	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);
+
+public:
+	static const std::string getDictionaryAppPath();
+	static const std::string getDictionaryUserPath();
+	static bool				 getUseSpellCheck();
+	static void				 setUseSpellCheck(const std::string& dict_name);
+
+	typedef boost::signals2::signal<void()> settings_change_signal_t;
+	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;
+	std::vector<std::string> mIgnoreList;
+
+	static settings_change_signal_t	sSettingsChangeSignal;
+};
+
+#endif // LLSPELLCHECK_H
diff --git a/indra/llui/llspellcheckmenuhandler.h b/indra/llui/llspellcheckmenuhandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5c95bad39a7586dddd1e81155ba223b7bac78c3
--- /dev/null
+++ b/indra/llui/llspellcheckmenuhandler.h
@@ -0,0 +1,46 @@
+/** 
+ * @file llspellcheckmenuhandler.h
+ * @brief Interface used by spell check menu handling
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLSPELLCHECKMENUHANDLER_H
+#define LLSPELLCHECKMENUHANDLER_H
+
+class LLSpellCheckMenuHandler
+{
+public:
+	virtual bool	getSpellCheck() const			{ return false; }
+
+	virtual const std::string& getSuggestion(U32 index) const	{ return LLStringUtil::null; }
+	virtual U32		getSuggestionCount() const		{ return 0; }
+	virtual void	replaceWithSuggestion(U32 index){}
+
+	virtual void	addToDictionary()				{}
+	virtual bool	canAddToDictionary() const		{ return false; }
+
+	virtual void	addToIgnore()					{}
+	virtual bool	canAddToIgnore() const			{ return false; }
+};
+
+#endif // LLSPELLCHECKMENUHANDLER_H
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 1ea623791d6570ee6cbb90db98e2382c34261f50..1ad3ee1dd1c1a7ca5312e1c1f095d50f75996e99 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -12082,6 +12082,28 @@
       <key>Value</key>
       <real>10.0</real>
     </map>
+    <key>SpellCheck</key>
+    <map>
+      <key>Comment</key>
+      <string>Enable spellchecking on line and text editors</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>1</integer>
+    </map>
+    <key>SpellCheckDictionary</key>
+    <map>
+      <key>Comment</key>
+      <string>Current primary and secondary dictionaries used for spell checking</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>String</string>
+      <key>Value</key>
+      <string>English (United States)</string>
+    </map>
     <key>UseNewWalkRun</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 0861fe85a84475ad45f3a32e6baa7a97c5d07e76..698f2469a3f451c06c918f30be87c8fbf2fcc39c 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -91,6 +91,7 @@
 #include "llsecondlifeurls.h"
 #include "llupdaterservice.h"
 #include "llcallfloater.h"
+#include "llspellcheck.h"
 
 // Linden library includes
 #include "llavatarnamecache.h"
@@ -112,6 +113,7 @@
 // Third party library includes
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
 
 
 
@@ -2488,6 +2490,18 @@ bool LLAppViewer::initConfiguration()
 		//gDirUtilp->setSkinFolder("default");
     }
 
+	if (gSavedSettings.getBOOL("SpellCheck"))
+	{
+		std::list<std::string> dict_list;
+		boost::split(dict_list, gSavedSettings.getString("SpellCheckDictionary"), boost::is_any_of(std::string(",")));
+		if (!dict_list.empty())
+		{
+			LLSpellChecker::setUseSpellCheck(dict_list.front());
+			dict_list.pop_front();
+			LLSpellChecker::instance().setSecondaryDictionaries(dict_list);
+		}
+	}
+
     mYieldTime = gSavedSettings.getS32("YieldTime");
 
 	// Read skin/branding settings if specified.
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 7e02a41e7e14ecb7eadc07e0207b930502f7cfc6..89360778d1304a391eeda6ed04063c12e2314fd5 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -739,6 +739,7 @@ bool idle_startup()
 		{
 			display_startup();
 			initialize_edit_menu();
+			initialize_spellcheck_menu();
 			display_startup();
 			init_menus();
 			display_startup();
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 093b84413a03cd4555d90178374df2a74e7d6e6d..7b6dbfaa0bf05e14f16b24bc4a0c85475cec1737 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -71,8 +71,12 @@
 #include "llpaneloutfitsinventory.h"
 #include "llpanellogin.h"
 #include "llpaneltopinfobar.h"
+#include "llspellcheck.h"
 #include "llupdaterservice.h"
 
+// Third party library includes
+#include <boost/algorithm/string.hpp>
+
 #ifdef TOGGLE_HACKED_GODLIKE_VIEWER
 BOOL 				gHackGodmode = FALSE;
 #endif
@@ -498,6 +502,24 @@ bool handleForceShowGrid(const LLSD& newvalue)
 	return true;
 }
 
+bool handleSpellCheckChanged()
+{
+	if (gSavedSettings.getBOOL("SpellCheck"))
+	{
+		std::list<std::string> dict_list;
+		boost::split(dict_list, gSavedSettings.getString("SpellCheckDictionary"), boost::is_any_of(std::string(",")));
+		if (!dict_list.empty())
+		{
+			LLSpellChecker::setUseSpellCheck(dict_list.front());
+			dict_list.pop_front();
+			LLSpellChecker::instance().setSecondaryDictionaries(dict_list);
+			return true;
+		}
+	}
+	LLSpellChecker::setUseSpellCheck(LLStringUtil::null);
+	return true;
+}
+
 bool toggle_agent_pause(const LLSD& newvalue)
 {
 	if ( newvalue.asBoolean() )
@@ -704,6 +726,8 @@ void settings_setup_listeners()
 	gSavedSettings.getControl("UpdaterServiceSetting")->getSignal()->connect(boost::bind(&toggle_updater_service_active, _2));
 	gSavedSettings.getControl("ForceShowGrid")->getSignal()->connect(boost::bind(&handleForceShowGrid, _2));
 	gSavedSettings.getControl("RenderTransparentWater")->getSignal()->connect(boost::bind(&handleRenderTransparentWaterChanged, _2));
+	gSavedSettings.getControl("SpellCheck")->getSignal()->connect(boost::bind(&handleSpellCheckChanged));
+	gSavedSettings.getControl("SpellCheckDictionary")->getSignal()->connect(boost::bind(&handleSpellCheckChanged));
 }
 
 #if TEST_CACHED_CONTROL
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 0104d35e53a96d4d10860ac7a4e40bd6bdd20215..2a11f3cc16d51ba456563d8f895ef2ab19670658 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -81,6 +81,7 @@
 #include "llrootview.h"
 #include "llsceneview.h"
 #include "llselectmgr.h"
+#include "llspellcheckmenuhandler.h"
 #include "llstatusbar.h"
 #include "lltextureview.h"
 #include "lltoolcomp.h"
@@ -4984,6 +4985,78 @@ class LLEditDelete : public view_listener_t
 	}
 };
 
+void handle_spellcheck_replace_with_suggestion(const LLUICtrl* ctrl, const LLSD& param)
+{
+	const LLContextMenu* menu = dynamic_cast<const LLContextMenu*>(ctrl->getParent());
+	LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast<LLSpellCheckMenuHandler*>(menu->getSpawningView()) : NULL;
+	if ( (!spellcheck_handler) || (!spellcheck_handler->getSpellCheck()) )
+	{
+		return;
+	}
+
+	U32 index = 0;
+	if ( (!LLStringUtil::convertToU32(param.asString(), index)) || (index >= spellcheck_handler->getSuggestionCount()) )
+	{
+		return;
+	}
+
+	spellcheck_handler->replaceWithSuggestion(index);
+}
+
+bool visible_spellcheck_suggestion(LLUICtrl* ctrl, const LLSD& param)
+{
+	LLMenuItemGL* item = dynamic_cast<LLMenuItemGL*>(ctrl);
+	const LLContextMenu* menu = (item) ? dynamic_cast<const LLContextMenu*>(item->getParent()) : NULL;
+	const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast<const LLSpellCheckMenuHandler*>(menu->getSpawningView()) : NULL;
+	if ( (!spellcheck_handler) || (!spellcheck_handler->getSpellCheck()) )
+	{
+		return false;
+	}
+
+	U32 index = 0;
+	if ( (!LLStringUtil::convertToU32(param.asString(), index)) || (index >= spellcheck_handler->getSuggestionCount()) )
+	{
+		return false;
+	}
+
+	item->setLabel(spellcheck_handler->getSuggestion(index));
+	return true;
+}
+
+void handle_spellcheck_add_to_dictionary(const LLUICtrl* ctrl)
+{
+	const LLContextMenu* menu = dynamic_cast<const LLContextMenu*>(ctrl->getParent());
+	LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast<LLSpellCheckMenuHandler*>(menu->getSpawningView()) : NULL;
+	if ( (spellcheck_handler) && (spellcheck_handler->canAddToDictionary()) )
+	{
+		spellcheck_handler->addToDictionary();
+	}
+}
+
+bool enable_spellcheck_add_to_dictionary(const LLUICtrl* ctrl)
+{
+	const LLContextMenu* menu = dynamic_cast<const LLContextMenu*>(ctrl->getParent());
+	const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast<const LLSpellCheckMenuHandler*>(menu->getSpawningView()) : NULL;
+	return (spellcheck_handler) && (spellcheck_handler->canAddToDictionary());
+}
+
+void handle_spellcheck_add_to_ignore(const LLUICtrl* ctrl)
+{
+	const LLContextMenu* menu = dynamic_cast<const LLContextMenu*>(ctrl->getParent());
+	LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast<LLSpellCheckMenuHandler*>(menu->getSpawningView()) : NULL;
+	if ( (spellcheck_handler) && (spellcheck_handler->canAddToIgnore()) )
+	{
+		spellcheck_handler->addToIgnore();
+	}
+}
+
+bool enable_spellcheck_add_to_ignore(const LLUICtrl* ctrl)
+{
+	const LLContextMenu* menu = dynamic_cast<const LLContextMenu*>(ctrl->getParent());
+	const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast<const LLSpellCheckMenuHandler*>(menu->getSpawningView()) : NULL;
+	return (spellcheck_handler) && (spellcheck_handler->canAddToIgnore());
+}
+
 bool enable_object_delete()
 {
 	bool new_value = 
@@ -7933,6 +8006,19 @@ void initialize_edit_menu()
 
 }
 
+void initialize_spellcheck_menu()
+{
+	LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar();
+	LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar();
+
+	commit.add("SpellCheck.ReplaceWithSuggestion", boost::bind(&handle_spellcheck_replace_with_suggestion, _1, _2));
+	enable.add("SpellCheck.VisibleSuggestion", boost::bind(&visible_spellcheck_suggestion, _1, _2));
+	commit.add("SpellCheck.AddToDictionary", boost::bind(&handle_spellcheck_add_to_dictionary, _1));
+	enable.add("SpellCheck.EnableAddToDictionary", boost::bind(&enable_spellcheck_add_to_dictionary, _1));
+	commit.add("SpellCheck.AddToIgnore", boost::bind(&handle_spellcheck_add_to_ignore, _1));
+	enable.add("SpellCheck.EnableAddToIgnore", boost::bind(&enable_spellcheck_add_to_ignore, _1));
+}
+
 void initialize_menus()
 {
 	// A parameterized event handler used as ctrl-8/9/0 zoom controls below.
diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h
index 87cb4efbc44fd2411f550bd76b320d8af6518773..8c40762865ea1b781198f41652112b0609a060a4 100644
--- a/indra/newview/llviewermenu.h
+++ b/indra/newview/llviewermenu.h
@@ -39,6 +39,7 @@ class LLObjectSelection;
 class LLSelectNode;
 
 void initialize_edit_menu();
+void initialize_spellcheck_menu();
 void init_menus();
 void cleanup_menus();
 
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 0931c4ec9b81b10557b1a3ecbd3d424a43180e0c..1b732676e4ab66ae8755e17ce52e999e8561c9cb 100644
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -91,6 +91,8 @@ def construct(self):
 
                 # ... and the entire windlight directory
                 self.path("windlight")
+                # ... and the pre-installed spell checking dictionaries
+                self.path("dictionaries")
                 self.end_prefix("app_settings")
 
             if self.prefix(src="character"):
@@ -393,6 +395,9 @@ def construct(self):
             self.path("ssleay32.dll")
             self.path("libeay32.dll")
 
+            # Hunspell
+            self.path("libhunspell.dll")
+
             # For google-perftools tcmalloc allocator.
             try:
                 if self.args['configuration'].lower() == 'debug':
@@ -659,6 +664,7 @@ def construct(self):
 
             # copy additional libs in <bundle>/Contents/MacOS/
             self.path("../packages/lib/release/libndofdev.dylib", dst="Resources/libndofdev.dylib")
+            self.path("../packages/lib/release/libhunspell-1.3.dylib", dst="Resources/libhunspell-1.3.dylib")
 
             self.path("../viewer_components/updater/scripts/darwin/update_install", "MacOS/update_install")
 
@@ -1053,6 +1059,8 @@ def construct(self):
             self.path("libopenjpeg.so.1.4.0")
             self.path("libopenjpeg.so.1")
             self.path("libopenjpeg.so")
+            self.path("libhunspell-1.3.so")
+            self.path("libhunspell-1.3.so.0")
             self.path("libalut.so")
             self.path("libopenal.so", "libopenal.so.1")
             self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname