diff options
Diffstat (limited to 'indra/llui/llspellcheck.cpp')
-rw-r--r-- | indra/llui/llspellcheck.cpp | 345 |
1 files changed, 345 insertions, 0 deletions
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); + } +} |