From 9c66ac87fd46db3987e60ae50989b2497099480b Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Fri, 20 Jan 2012 18:06:32 +0100 Subject: STORM-276 Basic spellchecking framework --- indra/llui/llspellcheck.cpp | 345 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 indra/llui/llspellcheck.cpp (limited to 'indra/llui/llspellcheck.cpp') 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 + #pragma comment(lib, "libhunspell.lib") +#else + #include +#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& 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 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::const_iterator itWord = word_list.begin(); itWord != word_list.end(); ++itWord) + file_out << *itWord << std::endl; + file_out.close(); + } +} + +void LLSpellChecker::setSecondaryDictionaries(std::list dict_list) +{ + if (!getUseSpellCheck()) + { + return; + } + + // Check if we're only adding secondary dictionaries, or removing them + std::list dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size())); + dict_list.sort(); + mDictSecondary.sort(); + std::list::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin()); + std::list::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::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::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); + } +} -- cgit v1.2.3 From 49e0c38ee85214eb7d0e7e995d1a380ee2f60720 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Fri, 3 Feb 2012 19:45:00 +0100 Subject: STORM-276 Added preferences panel --- indra/llui/llspellcheck.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 433ca02852..65207144f8 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -186,7 +186,7 @@ void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::stri } } -void LLSpellChecker::setSecondaryDictionaries(std::list dict_list) +void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list) { if (!getUseSpellCheck()) { @@ -194,11 +194,11 @@ void LLSpellChecker::setSecondaryDictionaries(std::list dict_list) } // Check if we're only adding secondary dictionaries, or removing them - std::list dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size())); + dict_list_t dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size())); dict_list.sort(); mDictSecondary.sort(); - std::list::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin()); - std::list::iterator end_removed = std::set_difference(mDictSecondary.begin(), mDictSecondary.end(), dict_list.begin(), dict_list.end(), dict_rem.begin()); + dict_list_t::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin()); + dict_list_t::iterator end_removed = std::set_difference(mDictSecondary.begin(), mDictSecondary.end(), dict_list.begin(), dict_list.end(), dict_rem.begin()); if (end_removed != dict_rem.begin()) // We can't remove secondary dictionaries so we need to recreate the Hunspell instance { @@ -211,7 +211,7 @@ void LLSpellChecker::setSecondaryDictionaries(std::list dict_list) { const std::string app_path = getDictionaryAppPath(); const std::string user_path = getDictionaryUserPath(); - for (std::list::const_iterator it_added = dict_add.begin(); it_added != end_added; ++it_added) + for (dict_list_t::const_iterator it_added = dict_add.begin(); it_added != end_added; ++it_added) { const LLSD dict_entry = getDictionaryData(*it_added); if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) ) @@ -287,7 +287,7 @@ void LLSpellChecker::initHunspell(const std::string& dict_name) } } - for (std::list::const_iterator it = mDictSecondary.begin(); it != mDictSecondary.end(); ++it) + for (dict_list_t::const_iterator it = mDictSecondary.begin(); it != mDictSecondary.end(); ++it) { const LLSD dict_entry = getDictionaryData(*it); if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) ) -- cgit v1.2.3 From 60e39343b17f29c65e8a60a415f7ed83ff069a12 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 14 Feb 2012 21:48:22 +0100 Subject: STORM-276 Reworked the spell check preferences to be more robust and less error-prone --- indra/llui/llspellcheck.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 65207144f8..46df44cdba 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -41,6 +41,7 @@ static const std::string DICT_DIR = "dictionaries"; static const std::string DICT_CUSTOM_SUFFIX = "_custom"; static const std::string DICT_IGNORE_SUFFIX = "_ignore"; +LLSD LLSpellChecker::sDictMap; LLSpellChecker::settings_change_signal_t LLSpellChecker::sSettingsChangeSignal; LLSpellChecker::LLSpellChecker() @@ -86,9 +87,10 @@ S32 LLSpellChecker::getSuggestions(const std::string& word, std::vector Date: Thu, 9 Feb 2012 22:02:32 +0100 Subject: STORM-276 Differentiate between primary and secondary dictionaries --- indra/llui/llspellcheck.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 46df44cdba..aa39e21a96 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -122,9 +122,7 @@ void LLSpellChecker::refreshDictionaryMap() 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"))) ); + (!tmp_app_path.empty()) && ((gDirUtilp->fileExists(tmp_user_path + ".dic")) || (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")); } @@ -243,7 +241,7 @@ void LLSpellChecker::initHunspell(const std::string& dict_name) } const LLSD dict_entry = (!dict_name.empty()) ? getDictionaryData(dict_name) : LLSD(); - if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) ) + if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) || (!dict_entry["is_primary"].asBoolean())) { sSettingsChangeSignal(); return; -- cgit v1.2.3 From 76234b48256a93c6a0f9dd1d3807706b1039a181 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Thu, 31 May 2012 17:08:14 +0200 Subject: STORM-276 FIXED "Add to Ignore" doesn't trigger --- indra/llui/llspellcheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index aa39e21a96..bde3b56741 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -142,7 +142,7 @@ 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)) + 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); -- cgit v1.2.3 From 20210455f5a350b3e8e24515ba7af71db0eece0b Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Mon, 4 Jun 2012 16:10:32 +0200 Subject: STORM-276 Dictionary import functionality and floater --- indra/llui/llspellcheck.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index bde3b56741..04c8a4fed0 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -88,17 +88,37 @@ S32 LLSpellChecker::getSuggestions(const std::string& word, std::vectorfileExists(tmp_user_path + DICT_CUSTOM_SUFFIX + ".dic")); sdDict["has_ignore"] = (!tmp_user_path.empty()) && (gDirUtilp->fileExists(tmp_user_path + DICT_IGNORE_SUFFIX + ".dic")); } + + sSettingsChangeSignal(); } void LLSpellChecker::addToCustomDictionary(const std::string& word) -- cgit v1.2.3 From 80b1a2c0a8f7b4444dc1588ba5f71bbb69b40acd Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Mon, 11 Jun 2012 13:39:45 +0200 Subject: STORM-276 Insert braces around if/for/while loops --- indra/llui/llspellcheck.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 04c8a4fed0..7e6d594249 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -75,13 +75,17 @@ S32 LLSpellChecker::getSuggestions(const std::string& word, std::vectorsuggest(&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(); @@ -94,7 +98,9 @@ const LLSD LLSpellChecker::getDictionaryData(const std::string& dict_language) { const LLSD& dict_entry = *it; if (dict_language == dict_entry["language"].asString()) + { return dict_entry; + } } return LLSD(); } @@ -104,7 +110,9 @@ 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) { @@ -131,7 +139,9 @@ void LLSpellChecker::refreshDictionaryMap() { llifstream app_file(app_path + "dictionaries.xml", std::ios::binary); if ( (!app_file.is_open()) || (0 == LLSDSerialize::fromXMLDocument(sDictMap, app_file)) || (0 == sDictMap.size()) ) + { return; + } } // Load user installed dictionary information @@ -141,7 +151,9 @@ void LLSpellChecker::refreshDictionaryMap() 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); + } custom_file.close(); } @@ -197,7 +209,9 @@ void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::stri { // Skip over the first line since that's just a line count if (0 != line_num) + { word_list.push_back(word); + } line_num++; } } @@ -215,7 +229,9 @@ void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::stri { file_out << word_list.size() << std::endl; for (std::vector::const_iterator itWord = word_list.begin(); itWord != word_list.end(); ++itWord) + { file_out << *itWord << std::endl; + } file_out.close(); } } @@ -249,13 +265,19 @@ void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list) { 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(); @@ -287,11 +309,17 @@ void LLSpellChecker::initHunspell(const std::string& dict_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(); @@ -325,13 +353,19 @@ void LLSpellChecker::initHunspell(const std::string& dict_name) { 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()); + } } } @@ -382,5 +416,7 @@ void LLSpellChecker::setUseSpellCheck(const std::string& dict_name) void LLSpellChecker::initClass() { if (sDictMap.isUndefined()) + { refreshDictionaryMap(); + } } -- cgit v1.2.3 From 53bd3ada86faf8c470989c809c2baeb3a3e7770c Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Mon, 11 Jun 2012 16:04:24 +0200 Subject: STORM-276 Distinguish between default dictionaries and user-installed dictionaries --- indra/llui/llspellcheck.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 7e6d594249..8b3ca50c29 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -105,6 +105,13 @@ const LLSD LLSpellChecker::getDictionaryData(const std::string& dict_language) 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) { @@ -150,9 +157,11 @@ void LLSpellChecker::refreshDictionaryMap() { 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) + for (LLSD::array_iterator it = custom_dict_map.beginArray(); it != custom_dict_map.endArray(); ++it) { - setDictionaryData(*it); + LLSD& dict_info = *it; + dict_info["user_installed"] = true; + setDictionaryData(dict_info); } custom_file.close(); } -- cgit v1.2.3 From d96d3d525677ae5bebab1908394854b8fb79cdfb Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Mon, 11 Jun 2012 17:37:06 +0200 Subject: STORM-276 Added the ability to remove (user-installed) dictionaries --- indra/llui/llspellcheck.cpp | 103 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 13 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 8b3ca50c29..887714d720 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -41,6 +41,9 @@ static const std::string DICT_DIR = "dictionaries"; 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; @@ -141,10 +144,10 @@ void LLSpellChecker::refreshDictionaryMap() 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; @@ -152,7 +155,7 @@ void LLSpellChecker::refreshDictionaryMap() } // 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; @@ -245,6 +248,13 @@ void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::stri } } +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()) @@ -263,8 +273,8 @@ void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list) { 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 { @@ -293,18 +303,18 @@ void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list) } } -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(); @@ -330,7 +340,7 @@ void LLSpellChecker::initHunspell(const std::string& dict_name) return; } - mDictName = dict_name; + mDictLanguage = dict_language; mDictFile = dict_entry["name"].asString(); if (dict_entry["has_custom"].asBoolean()) @@ -405,6 +415,73 @@ bool LLSpellChecker::getUseSpellCheck() return (LLSpellChecker::instanceExists()) && (LLSpellChecker::instance().mHunspell); } +// 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) { @@ -412,12 +489,12 @@ boost::signals2::connection LLSpellChecker::setSettingsChangeCallback(const sett } // 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); } } -- cgit v1.2.3 From 8ef14ffb299a55d29284111899415d824db89263 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 19 Jun 2012 21:48:45 +0200 Subject: STORM-1887 FIXED Added words are not saved in dictionaries if main dictionary was reselected The user's custom dictionary and ignore list are now independent of the primary dictionary. --- indra/llui/llspellcheck.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'indra/llui/llspellcheck.cpp') diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp index 887714d720..a189375fbe 100644 --- a/indra/llui/llspellcheck.cpp +++ b/indra/llui/llspellcheck.cpp @@ -38,8 +38,8 @@ #endif static const std::string DICT_DIR = "dictionaries"; -static const std::string DICT_CUSTOM_SUFFIX = "_custom"; -static const std::string DICT_IGNORE_SUFFIX = "_ignore"; +static const std::string DICT_FILE_CUSTOM = "user_custom.dic"; +static const std::string DICT_FILE_IGNORE = "user_ignore.dic"; static const std::string DICT_FILE_MAIN = "dictionaries.xml"; static const std::string DICT_FILE_USER = "user_dictionaries.xml"; @@ -178,8 +178,6 @@ void LLSpellChecker::refreshDictionaryMap() tmp_user_path = (sdDict.has("name")) ? user_path + sdDict["name"].asString() : LLStringUtil::null; sdDict["installed"] = (!tmp_app_path.empty()) && ((gDirUtilp->fileExists(tmp_user_path + ".dic")) || (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")); } sSettingsChangeSignal(); @@ -191,7 +189,7 @@ void LLSpellChecker::addToCustomDictionary(const std::string& word) { mHunspell->add(word.c_str()); } - addToDictFile(getDictionaryUserPath() + mDictFile + DICT_CUSTOM_SUFFIX + ".dic", word); + addToDictFile(getDictionaryUserPath() + DICT_FILE_CUSTOM, word); sSettingsChangeSignal(); } @@ -202,7 +200,7 @@ void LLSpellChecker::addToIgnoreList(const std::string& word) 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); + addToDictFile(getDictionaryUserPath() + DICT_FILE_IGNORE, word_lower); sSettingsChangeSignal(); } } @@ -343,15 +341,14 @@ void LLSpellChecker::initHunspell(const std::string& dict_language) mDictLanguage = dict_language; mDictFile = dict_entry["name"].asString(); - if (dict_entry["has_custom"].asBoolean()) + if (gDirUtilp->fileExists(user_path + DICT_FILE_CUSTOM)) { - const std::string filename_dic = user_path + mDictFile + DICT_CUSTOM_SUFFIX + ".dic"; - mHunspell->add_dic(filename_dic.c_str()); + mHunspell->add_dic((user_path + DICT_FILE_CUSTOM).c_str()); } - if (dict_entry["has_ignore"].asBoolean()) + if (gDirUtilp->fileExists(user_path + DICT_FILE_IGNORE)) { - llifstream file_in(user_path + mDictFile + DICT_IGNORE_SUFFIX + ".dic", std::ios::in); + llifstream file_in(user_path + DICT_FILE_IGNORE, std::ios::in); if (file_in.is_open()) { std::string word; int idxLine = 0; -- cgit v1.2.3