diff options
author | Kitty Barnett <develop@catznip.com> | 2012-01-20 18:06:32 +0100 |
---|---|---|
committer | Kitty Barnett <develop@catznip.com> | 2012-01-20 18:06:32 +0100 |
commit | 9c66ac87fd46db3987e60ae50989b2497099480b (patch) | |
tree | 0b2e6d98f3790ee2c1394017567ef75b9cd426ab /indra/llui | |
parent | 4c5141c5677a2e98c1331026d4e119abee6ab2ae (diff) |
STORM-276 Basic spellchecking framework
Diffstat (limited to 'indra/llui')
-rw-r--r-- | indra/llui/CMakeLists.txt | 3 | ||||
-rw-r--r-- | indra/llui/llmenugl.cpp | 6 | ||||
-rw-r--r-- | indra/llui/llmenugl.h | 6 | ||||
-rw-r--r-- | indra/llui/llspellcheck.cpp | 345 | ||||
-rw-r--r-- | indra/llui/llspellcheck.h | 77 | ||||
-rw-r--r-- | indra/llui/llspellcheckmenuhandler.h | 46 |
6 files changed, 481 insertions, 2 deletions
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 |