summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorKitty Barnett <develop@catznip.com>2012-01-20 18:06:32 +0100
committerKitty Barnett <develop@catznip.com>2012-01-20 18:06:32 +0100
commit9c66ac87fd46db3987e60ae50989b2497099480b (patch)
tree0b2e6d98f3790ee2c1394017567ef75b9cd426ab /indra
parent4c5141c5677a2e98c1331026d4e119abee6ab2ae (diff)
STORM-276 Basic spellchecking framework
Diffstat (limited to 'indra')
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake3
-rw-r--r--indra/cmake/ViewerMiscLibs.cmake1
-rw-r--r--indra/llui/CMakeLists.txt3
-rw-r--r--indra/llui/llmenugl.cpp6
-rw-r--r--indra/llui/llmenugl.h6
-rw-r--r--indra/llui/llspellcheck.cpp345
-rw-r--r--indra/llui/llspellcheck.h77
-rw-r--r--indra/llui/llspellcheckmenuhandler.h46
-rw-r--r--indra/newview/app_settings/settings.xml22
-rw-r--r--indra/newview/llappviewer.cpp14
-rw-r--r--indra/newview/llstartup.cpp1
-rw-r--r--indra/newview/llviewercontrol.cpp24
-rw-r--r--indra/newview/llviewermenu.cpp86
-rw-r--r--indra/newview/llviewermenu.h1
-rw-r--r--indra/newview/viewer_manifest.py8
15 files changed, 641 insertions, 2 deletions
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index 394db362b1..ebeae3e5be 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 df013b1665..f907181929 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 772f173f17..1377336bb4 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 95ecbb1c94..2a65262bbb 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 36f3ba34b9..67b3e1fbe6 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -670,7 +670,7 @@ public:
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 @@ public:
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 0000000000..433ca02852
--- /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 0000000000..affdac2907
--- /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 0000000000..d5c95bad39
--- /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 1ea623791d..1ad3ee1dd1 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 0861fe85a8..698f2469a3 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 7e02a41e7e..89360778d1 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 093b84413a..7b6dbfaa0b 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 0104d35e53..2a11f3cc16 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 87cb4efbc4..8c40762865 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 0931c4ec9b..1b732676e4 100644
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -91,6 +91,8 @@ class ViewerManifest(LLManifest):
# ... 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 @@ class WindowsManifest(ViewerManifest):
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 @@ class DarwinManifest(ViewerManifest):
# 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 @@ class Linux_i686Manifest(LinuxManifest):
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