From 1cc154166fe504e17735669b7b9e366f93e34d64 Mon Sep 17 00:00:00 2001 From: Tank_Master Date: Tue, 20 Dec 2011 22:17:20 -0800 Subject: STORM-1738 - Add autocorrect functionality Ported with owner permission from Firestorm, inital work done by LordGregGreg Back --- indra/llui/lllineeditor.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++++ indra/llui/lllineeditor.h | 1 + 2 files changed, 58 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 06dfc90d83..cee84cc53f 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -51,6 +51,8 @@ #include "lluictrlfactory.h" #include "llclipboard.h" #include "llmenugl.h" +#include "../newview/llautocorrect.h" +#include "../newview/llviewercontrol.h" // // Imported globals @@ -194,7 +196,60 @@ LLLineEditor::~LLLineEditor() // calls onCommit() while LLLineEditor still valid gFocusMgr.releaseFocusIfNeeded( this ); } +void LLLineEditor::autoCorrectText() +{ + static LLCachedControl doAnything(gSavedSettings, "EnableAutoCorrect"); + if( (!mReadOnly) && (doAnything))// && (isDirty())) + { + S32 wordStart = 0; + S32 wordEnd = mCursorPos-1; + //llinfos <<"Checking Word, Cursor is at "< 0) && (' '!=text[wordEnd-1])) + { + wordEnd--; + } + + wordStart=wordEnd; + + while ((wordEnd < (S32)text.length()) && (' '!=text[wordEnd] ) ) + { + wordEnd++; + } + + std::string strLastWord = std::string(text.begin(), text.end()); + std::string lastTypedWord = strLastWord.substr( wordStart, wordEnd-wordStart); + std::string correctedWord( AutoCorrect::getInstance()->replaceWord(lastTypedWord)); + if(correctedWord!=lastTypedWord) + { + LLWString strNew = utf8str_to_wstring( correctedWord ); + LLWString strOld = utf8str_to_wstring( lastTypedWord ); + int nDiff = strNew.size() - strOld.size(); + + //int wordStart = regText.find(lastTypedWord); + text.replace(wordStart,lastTypedWord.length(),strNew); + mText = wstring_to_utf8str(text); + mCursorPos+=nDiff; + } + } + } +} void LLLineEditor::onFocusReceived() { @@ -866,6 +921,8 @@ void LLLineEditor::addChar(const llwchar uni_char) LLUI::reportBadKeystroke(); } + autoCorrectText(); + getWindow()->hideCursorUntilMouseMove(); } diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 2518dbe3c7..8dcc801b62 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -169,6 +169,7 @@ public: virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); + void autoCorrectText(); void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } const std::string& getLabel() { return mLabel.getString(); } -- cgit v1.2.3 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/CMakeLists.txt | 3 + indra/llui/llmenugl.cpp | 6 +- indra/llui/llmenugl.h | 6 +- indra/llui/llspellcheck.cpp | 345 +++++++++++++++++++++++++++++++++++ indra/llui/llspellcheck.h | 77 ++++++++ indra/llui/llspellcheckmenuhandler.h | 46 +++++ 6 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 indra/llui/llspellcheck.cpp create mode 100644 indra/llui/llspellcheck.h create mode 100644 indra/llui/llspellcheckmenuhandler.h (limited to 'indra/llui') 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 getHandle() { return getDerivedHandle(); } + LLView* getSpawningView() const { return mSpawningViewHandle.get(); } + void setSpawningView(LLHandle spawning_view) { mSpawningViewHandle = spawning_view; } + protected: BOOL mHoveredAnyItem; LLMenuItemGL* mHoverItem; LLRootHandle mHandle; + LLHandle 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 + #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); + } +} 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 + +class Hunspell; + +class LLSpellChecker : public LLSingleton +{ + friend class LLSingleton; +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& suggestions) const; + +public: + const LLSD getDictionaryData(const std::string& dict_name) const; + const LLSD& getDictionaryMap() const { return mDictMap; } + void refreshDictionaryMap(); + void setSecondaryDictionaries(std::list 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 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 mDictSecondary; + std::vector 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 -- cgit v1.2.3 From f0d1afc2266e13b4f36f750ba5e721e427caf7b8 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Fri, 20 Jan 2012 18:07:35 +0100 Subject: STORM-276 Added spellcheck functionality to the LLLineEditor control --- indra/llui/lllineeditor.cpp | 217 +++++++++++++++++++++++++++++++++++++++++++- indra/llui/lllineeditor.h | 29 +++++- 2 files changed, 242 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 06dfc90d83..5479c080bd 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -45,6 +45,7 @@ #include "llkeyboard.h" #include "llrect.h" #include "llresmgr.h" +#include "llspellcheck.h" #include "llstring.h" #include "llwindow.h" #include "llui.h" @@ -65,6 +66,7 @@ const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing const F32 AUTO_SCROLL_TIME = 0.05f; const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. *TODO: make this equal to the double click interval? +const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and showing spell checking feedback for the word the cursor is on const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET @@ -88,6 +90,7 @@ LLLineEditor::Params::Params() background_image_focused("background_image_focused"), select_on_focus("select_on_focus", false), revert_on_esc("revert_on_esc", true), + spellcheck("spellcheck", false), commit_on_focus_lost("commit_on_focus_lost", true), ignore_tab("ignore_tab", true), is_password("is_password", false), @@ -134,6 +137,9 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mIgnoreArrowKeys( FALSE ), mIgnoreTab( p.ignore_tab ), mDrawAsterixes( p.is_password ), + mSpellCheck( p.spellcheck ), + mSpellCheckStart(-1), + mSpellCheckEnd(-1), mSelectAllonFocusReceived( p.select_on_focus ), mSelectAllonCommit( TRUE ), mPassDelete(FALSE), @@ -177,6 +183,12 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) updateTextPadding(); setCursor(mText.length()); + if (mSpellCheck) + { + LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this)); + } + mSpellCheckTimer.reset(); + setPrevalidateInput(p.prevalidate_input_callback()); setPrevalidate(p.prevalidate_callback()); @@ -519,6 +531,94 @@ void LLLineEditor::selectAll() updatePrimary(); } +bool LLLineEditor::getSpellCheck() const +{ + return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); +} + +const std::string& LLLineEditor::getSuggestion(U32 index) const +{ + return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; +} + +U32 LLLineEditor::getSuggestionCount() const +{ + return mSuggestionList.size(); +} + +void LLLineEditor::replaceWithSuggestion(U32 index) +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + { + deselect(); + + // Delete the misspelled word + mText.erase(it->first, it->second - it->first); + + // Insert the suggestion in its place + LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); + mText.insert(it->first, suggestion); + setCursor(it->first + (S32)suggestion.length()); + + break; + } + } + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLLineEditor::addToDictionary() +{ + if (canAddToDictionary()) + { + LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); + } +} + +bool LLLineEditor::canAddToDictionary() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +void LLLineEditor::addToIgnore() +{ + if (canAddToIgnore()) + { + LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); + } +} + +bool LLLineEditor::canAddToIgnore() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +std::string LLLineEditor::getMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first)); + } + return LLStringUtil::null; +} + +bool LLLineEditor::isMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + return true; + } + return false; +} + +void LLLineEditor::onSpellCheckSettingsChange() +{ + // Recheck the spelling on every change + mSpellCheckStart = mSpellCheckEnd = -1; +} BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { @@ -1453,6 +1553,10 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask ) { mKeystrokeCallback(this); } + if ( (!selection_modified) && (KEY_BACKSPACE == key) ) + { + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); + } } } } @@ -1510,6 +1614,7 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) // We'll have to do something about this if something ever changes! - Doug mKeystrokeCallback( this ); } + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } } return handled; @@ -1560,6 +1665,7 @@ void LLLineEditor::doDelete() { mKeystrokeCallback( this ); } + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } } } @@ -1756,7 +1862,7 @@ void LLLineEditor::draw() if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) { // unselected, right side - mGLFont->render( + rendered_text += mGLFont->render( mText, mScrollHPos + rendered_text, rendered_pixels_right, text_bottom, text_color, @@ -1770,7 +1876,7 @@ void LLLineEditor::draw() } else { - mGLFont->render( + rendered_text = mGLFont->render( mText, mScrollHPos, rendered_pixels_right, text_bottom, text_color, @@ -1785,6 +1891,80 @@ void LLLineEditor::draw() mBorder->setVisible(FALSE); // no more programmatic art. #endif + if ( (getSpellCheck()) && (mText.length() > 2) ) + { + // Calculate start and end indices for the first and last visible word + U32 start = prevWordPos(mScrollHPos), end = nextWordPos(mScrollHPos + rendered_text); + + if ( (mSpellCheckStart != start) || (mSpellCheckEnd = end) ) + { + const LLWString& text = mText.getWString().substr(start, end); + + // Find the start of the first word + U32 word_start = 0, word_end = 0; + while ( (word_start < text.length()) && (!LLStringOps::isAlpha(text[word_start])) ) + word_start++; + + // Iterate over all words in the text block and check them one by one + mMisspellRanges.clear(); + while (word_start < text.length()) + { + // Find the end of the current word (special case handling for "'" when it's used as a contraction) + word_end = word_start + 1; + while ( (word_end < text.length()) && + ((LLWStringUtil::isPartOfWord(text[word_end])) || + ((L'\'' == text[word_end]) && (word_end + 1 < text.length()) && + (LLStringOps::isAlnum(text[word_end - 1])) && (LLStringOps::isAlnum(text[word_end + 1])))) ) + { + word_end++; + } + if (word_end >= text.length()) + break; + + // Don't process words shorter than 3 characters + std::string word = wstring_to_utf8str(text.substr(word_start, word_end - word_start)); + if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) + mMisspellRanges.push_back(std::pair(start + word_start, start + word_end)); + + // Find the start of the next word + word_start = word_end + 1; + while ( (word_start < text.length()) && (!LLWStringUtil::isPartOfWord(text[word_start])) ) + word_start++; + } + + mSpellCheckStart = start; + mSpellCheckEnd = end; + } + + // Draw squiggly lines under any (visible) misspelled words + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + // Skip over words that aren't (partially) visible + if ( ((it->first < start) && (it->second < start)) || (it->first > end) ) + continue; + + // Skip the current word if the user is still busy editing it + if ( (!mSpellCheckTimer.hasExpired()) && (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + continue; + + S32 pxWidth = getRect().getWidth(); + S32 pxStart = findPixelNearestPos(it->first - getCursor()); + if (pxStart > pxWidth) + continue; + S32 pxEnd = findPixelNearestPos(it->second - getCursor()); + if (pxEnd > pxWidth) + pxEnd = pxWidth; + + gGL.color4ub(255, 0, 0, 200); + while (pxStart < pxEnd) + { + gl_line_2d(pxStart, text_bottom - 2, pxStart + 3, text_bottom + 1); + gl_line_2d(pxStart + 3, text_bottom + 1, pxStart + 6, text_bottom - 2); + pxStart += 6; + } + } + } + // If we're editing... if( hasFocus()) { @@ -2242,6 +2422,7 @@ void LLLineEditor::updatePreedit(const LLWString &preedit_string, { mKeystrokeCallback( this ); } + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const @@ -2393,7 +2574,37 @@ void LLLineEditor::showContextMenu(S32 x, S32 y) S32 screen_x, screen_y; localPointToScreen(x, y, &screen_x, &screen_y); - menu->show(screen_x, screen_y); + + if (hasSelection()) + { + if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) + deselect(); + else + setCursor(llmax(mSelectionStart, mSelectionEnd)); + } + else + { + setCursorAtLocalPos(x); + } + + bool use_spellcheck = getSpellCheck(), is_misspelled = false; + if (use_spellcheck) + { + mSuggestionList.clear(); + + // If the cursor is on a misspelled word, retrieve suggestions for it + std::string misspelled_word = getMisspelledWord(mCursorPos); + if ((is_misspelled = !misspelled_word.empty()) == true) + { + LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList); + } + } + + menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); + menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); + menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); + menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); + menu->show(screen_x, screen_y, this); } } diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 2518dbe3c7..9513274f21 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -40,6 +40,7 @@ #include "llframetimer.h" #include "lleditmenuhandler.h" +#include "llspellcheckmenuhandler.h" #include "lluictrl.h" #include "lluiimage.h" #include "lluistring.h" @@ -54,7 +55,7 @@ class LLButton; class LLContextMenu; class LLLineEditor -: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor +: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor, public LLSpellCheckMenuHandler { public: @@ -86,6 +87,7 @@ public: Optional select_on_focus, revert_on_esc, + spellcheck, commit_on_focus_lost, ignore_tab, is_password; @@ -146,6 +148,24 @@ public: virtual void deselect(); virtual BOOL canDeselect() const; + // LLSpellCheckMenuHandler overrides + /*virtual*/ bool getSpellCheck() const; + + /*virtual*/ const std::string& getSuggestion(U32 index) const; + /*virtual*/ U32 getSuggestionCount() const; + /*virtual*/ void replaceWithSuggestion(U32 index); + + /*virtual*/ void addToDictionary(); + /*virtual*/ bool canAddToDictionary() const; + + /*virtual*/ void addToIgnore(); + /*virtual*/ bool canAddToIgnore() const; + + // Spell checking helper functions + std::string getMisspelledWord(U32 pos) const; + bool isMisspelledWord(U32 pos) const; + void onSpellCheckSettingsChange(); + // view overrides virtual void draw(); virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); @@ -322,6 +342,13 @@ protected: S32 mLastSelectionStart; S32 mLastSelectionEnd; + bool mSpellCheck; + S32 mSpellCheckStart; + S32 mSpellCheckEnd; + LLTimer mSpellCheckTimer; + std::list > mMisspellRanges; + std::vector mSuggestionList; + LLTextValidate::validate_func_t mPrevalidateFunc; LLTextValidate::validate_func_t mPrevalidateInputFunc; -- cgit v1.2.3 From 057da807ac55f9b0583ff334cd12b3568ab81a18 Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Fri, 20 Jan 2012 13:51:46 -0800 Subject: removed LLXUIXML library moved LLInitParam, and LLRegistry to llcommon moved LLUIColor, LLTrans, and LLXUIParser to llui reviewed by Nat --- indra/llui/CMakeLists.txt | 8 +- indra/llui/lltrans.cpp | 295 ++++++ indra/llui/lltrans.h | 133 +++ indra/llui/lluicolor.cpp | 87 ++ indra/llui/lluicolor.h | 71 ++ indra/llui/llxuiparser.cpp | 1756 ++++++++++++++++++++++++++++++++++ indra/llui/llxuiparser.h | 242 +++++ indra/llui/tests/llurlentry_stub.cpp | 22 - indra/llui/tests/llurlentry_test.cpp | 15 - indra/llui/tests/llurlmatch_test.cpp | 34 - 10 files changed, 2590 insertions(+), 73 deletions(-) create mode 100644 indra/llui/lltrans.cpp create mode 100644 indra/llui/lltrans.h create mode 100644 indra/llui/lluicolor.cpp create mode 100644 indra/llui/lluicolor.h create mode 100644 indra/llui/llxuiparser.cpp create mode 100644 indra/llui/llxuiparser.h (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 772f173f17..9226f36e73 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -12,7 +12,6 @@ include(LLRender) include(LLWindow) include(LLVFS) include(LLXML) -include(LLXUIXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} @@ -24,7 +23,6 @@ include_directories( ${LLWINDOW_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLXUIXML_INCLUDE_DIRS} ) set(llui_SOURCE_FILES @@ -100,11 +98,13 @@ set(llui_SOURCE_FILES lltextutil.cpp lltextvalidate.cpp lltimectrl.cpp + lltrans.cpp lltransutil.cpp lltoggleablemenu.cpp lltoolbar.cpp lltooltip.cpp llui.cpp + lluicolor.cpp lluicolortable.cpp lluictrl.cpp lluictrlfactory.cpp @@ -121,6 +121,7 @@ set(llui_SOURCE_FILES llview.cpp llviewquery.cpp llwindowshade.cpp + llxuiparser.cpp ) set(llui_HEADER_FILES @@ -208,6 +209,7 @@ set(llui_HEADER_FILES lltoggleablemenu.h lltoolbar.h lltooltip.h + lltrans.h lltransutil.h lluicolortable.h lluiconstants.h @@ -215,6 +217,7 @@ set(llui_HEADER_FILES lluictrl.h lluifwd.h llui.h + lluicolor.h lluiimage.h lluistring.h llundo.h @@ -228,6 +231,7 @@ set(llui_HEADER_FILES llview.h llviewquery.h llwindowshade.h + llxuiparser.h ) set_source_files_properties(${llui_HEADER_FILES} diff --git a/indra/llui/lltrans.cpp b/indra/llui/lltrans.cpp new file mode 100644 index 0000000000..5388069c24 --- /dev/null +++ b/indra/llui/lltrans.cpp @@ -0,0 +1,295 @@ +/** + * @file lltrans.cpp + * @brief LLTrans implementation + * + * $LicenseInfo:firstyear=2000&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 "lltrans.h" + +#include "llfasttimer.h" // for call count statistics +#include "llxuiparser.h" +#include "llsd.h" +#include "llxmlnode.h" + +#include + +LLTrans::template_map_t LLTrans::sStringTemplates; +LLStringUtil::format_map_t LLTrans::sDefaultArgs; + +struct StringDef : public LLInitParam::Block +{ + Mandatory name; + Mandatory value; + + StringDef() + : name("name"), + value("value") + {} +}; + +struct StringTable : public LLInitParam::Block +{ + Multiple strings; + StringTable() + : strings("string") + {} +}; + +//static +bool LLTrans::parseStrings(LLXMLNodePtr &root, const std::set& default_args) +{ + std::string xml_filename = "(strings file)"; + if (!root->hasName("strings")) + { + llerrs << "Invalid root node name in " << xml_filename + << ": was " << root->getName() << ", expected \"strings\"" << llendl; + } + + StringTable string_table; + LLXUIParser parser; + parser.readXUI(root, string_table, xml_filename); + + if (!string_table.validateBlock()) + { + llerrs << "Problem reading strings: " << xml_filename << llendl; + return false; + } + + sStringTemplates.clear(); + sDefaultArgs.clear(); + + for(LLInitParam::ParamIterator::const_iterator it = string_table.strings.begin(); + it != string_table.strings.end(); + ++it) + { + LLTransTemplate xml_template(it->name, it->value); + sStringTemplates[xml_template.mName] = xml_template; + + std::set::const_iterator iter = default_args.find(xml_template.mName); + if (iter != default_args.end()) + { + std::string name = *iter; + if (name[0] != '[') + name = llformat("[%s]",name.c_str()); + sDefaultArgs[name] = xml_template.mText; + } + } + + return true; +} + + +//static +bool LLTrans::parseLanguageStrings(LLXMLNodePtr &root) +{ + std::string xml_filename = "(language strings file)"; + if (!root->hasName("strings")) + { + llerrs << "Invalid root node name in " << xml_filename + << ": was " << root->getName() << ", expected \"strings\"" << llendl; + } + + StringTable string_table; + LLXUIParser parser; + parser.readXUI(root, string_table, xml_filename); + + if (!string_table.validateBlock()) + { + llerrs << "Problem reading strings: " << xml_filename << llendl; + return false; + } + + for(LLInitParam::ParamIterator::const_iterator it = string_table.strings.begin(); + it != string_table.strings.end(); + ++it) + { + // share the same map with parseStrings() so we can search the strings using the same getString() function.- angela + LLTransTemplate xml_template(it->name, it->value); + sStringTemplates[xml_template.mName] = xml_template; + } + + return true; +} + + + +static LLFastTimer::DeclareTimer FTM_GET_TRANS("Translate string"); + +//static +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ + // Don't care about time as much as call count. Make sure we're not + // calling LLTrans::getString() in an inner loop. JC + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format_map_t args = sDefaultArgs; + args.insert(msg_args.begin(), msg_args.end()); + LLStringUtil::format(text, args); + + return text; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return "MissingString("+xml_desc+")"; + } +} + +//static +std::string LLTrans::getString(const std::string &xml_desc, const LLSD& msg_args) +{ + // Don't care about time as much as call count. Make sure we're not + // calling LLTrans::getString() in an inner loop. JC + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + return text; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return "MissingString("+xml_desc+")"; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format_map_t args = sDefaultArgs; + args.insert(msg_args.begin(), msg_args.end()); + LLStringUtil::format(text, args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLSD& msg_args) +{ + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + +//static +std::string LLTrans::getCountString(const std::string& language, const std::string& xml_desc, S32 count) +{ + // Compute which string identifier to use + const char* form = ""; + if (language == "ru") // Russian + { + // From GNU ngettext() + // Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + if (count % 10 == 1 + && count % 100 != 11) + { + // singular, "1 item" + form = "A"; + } + else if (count % 10 >= 2 + && count % 10 <= 4 + && (count % 100 < 10 || count % 100 >= 20) ) + { + // special case "2 items", "23 items", but not "13 items" + form = "B"; + } + else + { + // English-style plural, "5 items" + form = "C"; + } + } + else if (language == "fr" || language == "pt") // French, Brazilian Portuguese + { + // French and Portuguese treat zero as a singular "0 item" not "0 items" + if (count == 0 || count == 1) + { + form = "A"; + } + else + { + // English-style plural + form = "B"; + } + } + else // default + { + // languages like English with 2 forms, singular and plural + if (count == 1) + { + // "1 item" + form = "A"; + } + else + { + // "2 items", also use plural for "0 items" + form = "B"; + } + } + + // Translate that string + LLStringUtil::format_map_t args; + args["[COUNT]"] = llformat("%d", count); + + // Look up "AgeYearsB" or "AgeWeeksC" including the "form" + std::string key = llformat("%s%s", xml_desc.c_str(), form); + return getString(key, args); +} + +void LLTrans::setDefaultArg(const std::string& name, const std::string& value) +{ + sDefaultArgs[name] = value; +} diff --git a/indra/llui/lltrans.h b/indra/llui/lltrans.h new file mode 100644 index 0000000000..128b51d383 --- /dev/null +++ b/indra/llui/lltrans.h @@ -0,0 +1,133 @@ +/** + * @file lltrans.h + * @brief LLTrans definition + * + * $LicenseInfo:firstyear=2000&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 LL_TRANS_H +#define LL_TRANS_H + +#include + +#include "llpointer.h" +#include "llstring.h" + +class LLXMLNode; + +class LLSD; + +/** + * @brief String template loaded from strings.xml + */ +class LLTransTemplate +{ +public: + LLTransTemplate(const std::string& name = LLStringUtil::null, const std::string& text = LLStringUtil::null) : mName(name), mText(text) {} + + std::string mName; + std::string mText; +}; + +/** + * @brief Localized strings class + * This class is used to retrieve translations of strings used to build larger ones, as well as + * strings with a general usage that don't belong to any specific floater. For example, + * "Owner:", "Retrieving..." used in the place of a not yet known name, etc. + */ +class LLTrans +{ +public: + LLTrans(); + + /** + * @brief Parses the xml root that holds the strings. Used once on startup +// *FIXME * @param xml_filename Filename to parse + * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" + * @returns true if the file was parsed successfully, true if something went wrong + */ + static bool parseStrings(LLPointer & root, const std::set& default_args); + + static bool parseLanguageStrings(LLPointer & root); + + /** + * @brief Returns a translated string + * @param xml_desc String's description + * @param args A list of substrings to replace in the string + * @returns Translated string + */ + static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static std::string getString(const std::string &xml_desc, const LLSD& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLSD& args); + + // Returns translated string with [COUNT] replaced with a number, following + // special per-language logic for plural nouns. For example, some languages + // may have different plurals for 0, 1, 2 and > 2. + // See "AgeWeeksA", "AgeWeeksB", etc. in strings.xml for examples. + static std::string getCountString(const std::string& language, const std::string& xml_desc, S32 count); + + /** + * @brief Returns a translated string + * @param xml_desc String's description + * @returns Translated string + */ + static std::string getString(const std::string &xml_desc) + { + LLStringUtil::format_map_t empty; + return getString(xml_desc, empty); + } + + static bool findString(std::string &result, const std::string &xml_desc) + { + LLStringUtil::format_map_t empty; + return findString(result, xml_desc, empty); + } + + static std::string getKeyboardString(const char* keystring) + { + std::string key_str(keystring); + std::string trans_str; + return findString(trans_str, key_str) ? trans_str : key_str; + } + + // get the default args + static const LLStringUtil::format_map_t& getDefaultArgs() + { + return sDefaultArgs; + } + + static void setDefaultArg(const std::string& name, const std::string& value); + + // insert default args into an arg list + static void getArgs(LLStringUtil::format_map_t& args) + { + args.insert(sDefaultArgs.begin(), sDefaultArgs.end()); + } + +private: + typedef std::map template_map_t; + static template_map_t sStringTemplates; + static LLStringUtil::format_map_t sDefaultArgs; +}; + +#endif diff --git a/indra/llui/lluicolor.cpp b/indra/llui/lluicolor.cpp new file mode 100644 index 0000000000..f9bb80f8c5 --- /dev/null +++ b/indra/llui/lluicolor.cpp @@ -0,0 +1,87 @@ +/** + * @file lluicolor.cpp + * @brief brief LLUIColor class implementation file + * + * $LicenseInfo:firstyear=2009&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 "lluicolor.h" + +LLUIColor::LLUIColor() + :mColorPtr(NULL) +{ +} + + +LLUIColor::LLUIColor(const LLColor4& color) +: mColor(color), + mColorPtr(NULL) +{ +} + +LLUIColor::LLUIColor(const LLUIColor* color) +: mColorPtr(color) +{ +} + +void LLUIColor::set(const LLColor4& color) +{ + mColor = color; + mColorPtr = NULL; +} + +void LLUIColor::set(const LLUIColor* color) +{ + mColorPtr = color; +} + +const LLColor4& LLUIColor::get() const +{ + return (mColorPtr == NULL ? mColor : mColorPtr->get()); +} + +LLUIColor::operator const LLColor4& () const +{ + return get(); +} + +const LLColor4& LLUIColor::operator()() const +{ + return get(); +} + +bool LLUIColor::isReference() const +{ + return mColorPtr != NULL; +} + +namespace LLInitParam +{ + // used to detect equivalence with default values on export + bool ParamCompare::equals(const LLUIColor &a, const LLUIColor &b) + { + // do not detect value equivalence, treat pointers to colors as distinct from color values + return (a.mColorPtr == NULL && b.mColorPtr == NULL ? a.mColor == b.mColor : a.mColorPtr == b.mColorPtr); + } +} diff --git a/indra/llui/lluicolor.h b/indra/llui/lluicolor.h new file mode 100644 index 0000000000..97ebea854a --- /dev/null +++ b/indra/llui/lluicolor.h @@ -0,0 +1,71 @@ +/** + * @file lluicolor.h + * @brief brief LLUIColor class header file + * + * $LicenseInfo:firstyear=2009&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 LL_LLUICOLOR_H_ +#define LL_LLUICOLOR_H_ + +#include "v4color.h" + +namespace LLInitParam +{ + template + struct ParamCompare; +} + +class LLUIColor +{ +public: + LLUIColor(); + LLUIColor(const LLColor4& color); + LLUIColor(const LLUIColor* color); + + void set(const LLColor4& color); + void set(const LLUIColor* color); + + const LLColor4& get() const; + + operator const LLColor4& () const; + const LLColor4& operator()() const; + + bool isReference() const; + +private: + friend struct LLInitParam::ParamCompare; + + const LLUIColor* mColorPtr; + LLColor4 mColor; +}; + +namespace LLInitParam +{ + template<> + struct ParamCompare + { + static bool equals(const LLUIColor& a, const LLUIColor& b); + }; +} + +#endif diff --git a/indra/llui/llxuiparser.cpp b/indra/llui/llxuiparser.cpp new file mode 100644 index 0000000000..afc76024d1 --- /dev/null +++ b/indra/llui/llxuiparser.cpp @@ -0,0 +1,1756 @@ +/** + * @file llxuiparser.cpp + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&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 "llxuiparser.h" + +#include "llxmlnode.h" + +#ifdef LL_STANDALONE +#include +#else +#include "expat/expat.h" +#endif + +#include +#include +//#include +#include + +#include "lluicolor.h" + +using namespace BOOST_SPIRIT_CLASSIC_NS; + +const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; + +static LLInitParam::Parser::parser_read_func_map_t sXSDReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sXSDWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sXSDInspectFuncs; + +static LLInitParam::Parser::parser_read_func_map_t sSimpleXUIReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sSimpleXUIWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sSimpleXUIInspectFuncs; + +const char* NO_VALUE_MARKER = "no_value"; + +const S32 LINE_NUMBER_HERE = 0; + +struct MaxOccursValues : public LLInitParam::TypeValuesHelper +{ + static void declareValues() + { + declare("unbounded", U32_MAX); + } +}; + +struct Occurs : public LLInitParam::Block +{ + Optional minOccurs; + Optional maxOccurs; + + Occurs() + : minOccurs("minOccurs", 0), + maxOccurs("maxOccurs", U32_MAX) + + {} +}; + + +typedef enum +{ + USE_REQUIRED, + USE_OPTIONAL +} EUse; + +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues() + { + declare("required", USE_REQUIRED); + declare("optional", USE_OPTIONAL); + } + }; +} + +struct Element; +struct Group; +struct Choice; +struct Sequence; +struct Any; + +struct Attribute : public LLInitParam::Block +{ + Mandatory name; + Mandatory type; + Mandatory use; + + Attribute() + : name("name"), + type("type"), + use("use") + {} +}; + +struct Any : public LLInitParam::Block +{ + Optional _namespace; + + Any() + : _namespace("namespace") + {} +}; + +struct All : public LLInitParam::Block +{ + Multiple< Lazy > elements; + + All() + : elements("element") + { + maxOccurs = 1; + } +}; + +struct Choice : public LLInitParam::ChoiceBlock +{ + Alternative< Lazy > element; + Alternative< Lazy > group; + Alternative< Lazy > choice; + Alternative< Lazy > sequence; + Alternative< Lazy > any; + + Choice() + : element("element"), + group("group"), + choice("choice"), + sequence("sequence"), + any("any") + {} + +}; + +struct Sequence : public LLInitParam::ChoiceBlock +{ + Alternative< Lazy > element; + Alternative< Lazy > group; + Alternative< Lazy > choice; + Alternative< Lazy > sequence; + Alternative< Lazy > any; +}; + +struct GroupContents : public LLInitParam::ChoiceBlock +{ + Alternative all; + Alternative choice; + Alternative sequence; + + GroupContents() + : all("all"), + choice("choice"), + sequence("sequence") + {} +}; + +struct Group : public LLInitParam::Block +{ + Optional name, + ref; + + Group() + : name("name"), + ref("ref") + {} +}; + +struct Restriction : public LLInitParam::Block +{ +}; + +struct Extension : public LLInitParam::Block +{ +}; + +struct SimpleContent : public LLInitParam::ChoiceBlock +{ + Alternative restriction; + Alternative extension; + + SimpleContent() + : restriction("restriction"), + extension("extension") + {} +}; + +struct SimpleType : public LLInitParam::Block +{ + // TODO +}; + +struct ComplexContent : public LLInitParam::Block +{ + Optional mixed; + + ComplexContent() + : mixed("mixed", true) + {} +}; + +struct ComplexTypeContents : public LLInitParam::ChoiceBlock +{ + Alternative simple_content; + Alternative complex_content; + Alternative group; + Alternative all; + Alternative choice; + Alternative sequence; + + ComplexTypeContents() + : simple_content("simpleContent"), + complex_content("complexContent"), + group("group"), + all("all"), + choice("choice"), + sequence("sequence") + {} +}; + +struct ComplexType : public LLInitParam::Block +{ + Optional name; + Optional mixed; + + Multiple attribute; + Multiple< Lazy > elements; + + ComplexType() + : name("name"), + attribute("xs:attribute"), + elements("xs:element"), + mixed("mixed") + { + } +}; + +struct ElementContents : public LLInitParam::ChoiceBlock +{ + Alternative simpleType; + Alternative complexType; + + ElementContents() + : simpleType("simpleType"), + complexType("complexType") + {} +}; + +struct Element : public LLInitParam::Block +{ + Optional name, + ref, + type; + + Element() + : name("xs:name"), + ref("xs:ref"), + type("xs:type") + {} +}; + +struct Schema : public LLInitParam::Block +{ +private: + Mandatory targetNamespace, + xmlns, + xs; + +public: + Optional attributeFormDefault, + elementFormDefault; + + Mandatory root_element; + + void setNameSpace(const std::string& ns) {targetNamespace = ns; xmlns = ns;} + + Schema(const std::string& ns = LLStringUtil::null) + : attributeFormDefault("attributeFormDefault"), + elementFormDefault("elementFormDefault"), + xs("xmlns:xs"), + targetNamespace("targetNamespace"), + xmlns("xmlns"), + root_element("xs:element") + { + attributeFormDefault = "unqualified"; + elementFormDefault = "qualified"; + xs = "http://www.w3.org/2001/XMLSchema"; + if (!ns.empty()) + { + setNameSpace(ns); + }; + } + +}; + +// +// LLXSDWriter +// +LLXSDWriter::LLXSDWriter() +: Parser(sXSDReadFuncs, sXSDWriteFuncs, sXSDInspectFuncs) +{ + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +} + +void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) +{ + Schema schema(xml_namespace); + + schema.root_element.name = type_name; + Choice& choice = schema.root_element.complexType.choice; + + choice.minOccurs = 0; + choice.maxOccurs = "unbounded"; + + mSchemaNode = node; + //node->setName("xs:schema"); + //node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); + //node->createChild("elementFormDefault", true)->setStringValue("qualified"); + //node->createChild("targetNamespace", true)->setStringValue(xml_namespace); + //node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); + //node->createChild("xmlns", true)->setStringValue(xml_namespace); + + //node = node->createChild("xs:complexType", false); + //node->createChild("name", true)->setStringValue(type_name); + //node->createChild("mixed", true)->setStringValue("true"); + + //mAttributeNode = node; + //mElementNode = node->createChild("xs:choice", false); + //mElementNode->createChild("minOccurs", true)->setStringValue("0"); + //mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); + block.inspectBlock(*this); + + // duplicate element choices + LLXMLNodeList children; + mElementNode->getChildren("xs:element", children, FALSE); + for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) + { + LLXMLNodePtr child_copy = child_it->second->deepCopy(); + std::string child_name; + child_copy->getAttributeString("name", child_name); + child_copy->setAttributeString("name", type_name + "." + child_name); + mElementNode->addChild(child_copy); + } + + LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); + element_declaration_node->createChild("name", true)->setStringValue(type_name); + element_declaration_node->createChild("type", true)->setStringValue(type_name); +} + +void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector* possible_values) +{ + name_stack_t non_empty_names; + std::string attribute_name; + for (name_stack_t::const_iterator it = stack.begin(); + it != stack.end(); + ++it) + { + const std::string& name = it->first; + if (!name.empty()) + { + non_empty_names.push_back(*it); + } + } + + for (name_stack_t::const_iterator it = non_empty_names.begin(); + it != non_empty_names.end(); + ++it) + { + if (!attribute_name.empty()) + { + attribute_name += "."; + } + attribute_name += it->first; + } + + // only flag non-nested attributes as mandatory, nested attributes have variant syntax + // that can't be properly constrained in XSD + // e.g. vs + bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; + + // don't bother supporting "Multiple" params as xml attributes + if (max_count <= 1) + { + // add compound attribute to root node + addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); + } + + // now generated nested elements for compound attributes + if (non_empty_names.size() > 1 && !attribute_mandatory) + { + std::string element_name; + + // traverse all but last element, leaving that as an attribute name + name_stack_t::const_iterator end_it = non_empty_names.end(); + end_it--; + + for (name_stack_t::const_iterator it = non_empty_names.begin(); + it != end_it; + ++it) + { + if (it != non_empty_names.begin()) + { + element_name += "."; + } + element_name += it->first; + } + + std::string short_attribute_name = non_empty_names.back().first; + + LLXMLNodePtr complex_type_node; + + // find existing element node here, starting at tail of child list + if (mElementNode->mChildren.notNull()) + { + for(LLXMLNodePtr element = mElementNode->mChildren->tail; + element.notNull(); + element = element->mPrev) + { + std::string name; + if(element->getAttributeString("name", name) && name == element_name) + { + complex_type_node = element->mChildren->head; + break; + } + } + } + //create complex_type node + // + // + // + // + // + if(complex_type_node.isNull()) + { + complex_type_node = mElementNode->createChild("xs:element", false); + + complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); + complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); + complex_type_node->createChild("name", true)->setStringValue(element_name); + complex_type_node = complex_type_node->createChild("xs:complexType", false); + } + + addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); + } +} + +void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values) +{ + if (!attribute_name.empty()) + { + LLXMLNodePtr new_enum_type_node; + if (possible_values != NULL) + { + // custom attribute type, for example + // + // + // + // + // + // + new_enum_type_node = new LLXMLNode("xs:simpleType", false); + + LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); + restriction_node->createChild("base", true)->setStringValue("xs:string"); + + for (std::vector::const_iterator it = possible_values->begin(); + it != possible_values->end(); + ++it) + { + LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); + enum_node->createChild("value", true)->setStringValue(*it); + } + } + + string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; + + string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); + + // attribute not yet declared + if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) + { + attributes_written.insert(found_it, attribute_name); + + LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); + + // attribute name + attribute_node->createChild("name", true)->setStringValue(attribute_name); + + if (new_enum_type_node.notNull()) + { + attribute_node->addChild(new_enum_type_node); + } + else + { + // simple attribute type + attribute_node->createChild("type", true)->setStringValue(type); + } + + // required or optional + attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); + } + // attribute exists...handle collision of same name attributes with potentially different types + else + { + LLXMLNodePtr attribute_declaration; + if (type_declaration_node.notNull()) + { + for(LLXMLNodePtr node = type_declaration_node->mChildren->tail; + node.notNull(); + node = node->mPrev) + { + std::string name; + if (node->getAttributeString("name", name) && name == attribute_name) + { + attribute_declaration = node; + break; + } + } + } + + bool new_type_is_enum = new_enum_type_node.notNull(); + bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); + + // either type is enum, revert to string in collision + // don't bother to check for enum equivalence + if (new_type_is_enum || existing_type_is_enum) + { + if (attribute_declaration->hasAttribute("type")) + { + attribute_declaration->setAttributeString("type", "xs:string"); + } + else + { + attribute_declaration->createChild("type", true)->setStringValue("xs:string"); + } + attribute_declaration->deleteChildren("xs:simpleType"); + } + else + { + // check for collision of different standard types + std::string existing_type; + attribute_declaration->getAttributeString("type", existing_type); + // if current type is not the same as the new type, revert to strnig + if (existing_type != type) + { + // ...than use most general type, string + attribute_declaration->setAttributeString("type", "string"); + } + } + } + } +} + +// +// LLXUIXSDWriter +// +void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) +{ + std::string file_name(path); + file_name += type_name + ".xsd"; + LLXMLNodePtr root_nodep = new LLXMLNode(); + + LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); + + // add includes for all possible children + const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); + const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); + + // add choices for valid children + if (widget_registryp) + { + // add include declarations for all valid children + for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); + it != widget_registryp->currentRegistrar().endItems(); + ++it) + { + std::string widget_name = it->first; + if (widget_name == type_name) + { + continue; + } + LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); + nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); + + // add to front of schema + mSchemaNode->addChild(nodep, mSchemaNode); + } + + for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); + it != widget_registryp->currentRegistrar().endItems(); + ++it) + { + std::string widget_name = it->first; + // + LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); + widget_node->createChild("name", true)->setStringValue(widget_name); + widget_node->createChild("type", true)->setStringValue(widget_name); + } + } + + LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); + LLXMLNode::writeHeaderToFile(xsd_file); + root_nodep->writeToFile(xsd_file); + fclose(xsd_file); +} + +static LLInitParam::Parser::parser_read_func_map_t sXUIReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sXUIWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sXUIInspectFuncs; + +// +// LLXUIParser +// +LLXUIParser::LLXUIParser() +: Parser(sXUIReadFuncs, sXUIWriteFuncs, sXUIInspectFuncs), + mCurReadDepth(0) +{ + if (sXUIReadFuncs.empty()) + { + registerParserFuncs(readFlag, writeFlag); + registerParserFuncs(readBoolValue, writeBoolValue); + registerParserFuncs(readStringValue, writeStringValue); + registerParserFuncs(readU8Value, writeU8Value); + registerParserFuncs(readS8Value, writeS8Value); + registerParserFuncs(readU16Value, writeU16Value); + registerParserFuncs(readS16Value, writeS16Value); + registerParserFuncs(readU32Value, writeU32Value); + registerParserFuncs(readS32Value, writeS32Value); + registerParserFuncs(readF32Value, writeF32Value); + registerParserFuncs(readF64Value, writeF64Value); + registerParserFuncs(readColor4Value, writeColor4Value); + registerParserFuncs(readUIColorValue, writeUIColorValue); + registerParserFuncs(readUUIDValue, writeUUIDValue); + registerParserFuncs(readSDValue, writeSDValue); + } +} + +static LLFastTimer::DeclareTimer FTM_PARSE_XUI("XUI Parsing"); +const LLXMLNodePtr DUMMY_NODE = new LLXMLNode(); + +void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename, bool silent) +{ + LLFastTimer timer(FTM_PARSE_XUI); + mNameStack.clear(); + mRootNodeName = node->getName()->mString; + mCurFileName = filename; + mCurReadDepth = 0; + setParseSilently(silent); + + if (node.isNull()) + { + parserWarning("Invalid node"); + } + else + { + readXUIImpl(node, block); + } +} + +bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool values_parsed = false; + bool silent = mCurReadDepth > 0; + + if (nodep->getFirstChild().isNull() + && nodep->mAttributes.empty() + && nodep->getSanitizedValue().empty()) + { + // empty node, just parse as flag + mCurReadNode = DUMMY_NODE; + return block.submitValue(mNameStack, *this, silent); + } + + // submit attributes for current node + values_parsed |= readAttributes(nodep, block); + + // treat text contents of xml node as "value" parameter + std::string text_contents = nodep->getSanitizedValue(); + if (!text_contents.empty()) + { + mCurReadNode = nodep; + mNameStack.push_back(std::make_pair(std::string("value"), true)); + // child nodes are not necessarily valid parameters (could be a child widget) + // so don't complain once we've recursed + if (!block.submitValue(mNameStack, *this, true)) + { + mNameStack.pop_back(); + block.submitValue(mNameStack, *this, silent); + } + else + { + mNameStack.pop_back(); + } + } + + // then traverse children + // child node must start with last name of parent node (our "scope") + // for example: "" + // which equates to the following nesting: + // button + // param + // nested_param1 + // nested_param2 + // nested_param3 + mCurReadDepth++; + for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) + { + std::string child_name(childp->getName()->mString); + S32 num_tokens_pushed = 0; + + // for non "dotted" child nodes check to see if child node maps to another widget type + // and if not, treat as a child element of the current node + // e.g. will interpret as "button.rect" + // since there is no widget named "rect" + if (child_name.find(".") == std::string::npos) + { + mNameStack.push_back(std::make_pair(child_name, true)); + num_tokens_pushed++; + } + else + { + // parse out "dotted" name into individual tokens + tokenizer name_tokens(child_name, sep); + + tokenizer::iterator name_token_it = name_tokens.begin(); + if(name_token_it == name_tokens.end()) + { + childp = childp->getNextSibling(); + continue; + } + + // check for proper nesting + if (mNameStack.empty()) + { + if (*name_token_it != mRootNodeName) + { + childp = childp->getNextSibling(); + continue; + } + } + else if(mNameStack.back().first != *name_token_it) + { + childp = childp->getNextSibling(); + continue; + } + + // now ignore first token + ++name_token_it; + + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + } + + // recurse and visit children XML nodes + if(readXUIImpl(childp, block)) + { + // child node successfully parsed, remove from DOM + + values_parsed = true; + LLXMLNodePtr node_to_remove = childp; + childp = childp->getNextSibling(); + + nodep->deleteChild(node_to_remove); + } + else + { + childp = childp->getNextSibling(); + } + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + mCurReadDepth--; + return values_parsed; +} + +bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool any_parsed = false; + bool silent = mCurReadDepth > 0; + + for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin(); + attribute_it != nodep->mAttributes.end(); + ++attribute_it) + { + S32 num_tokens_pushed = 0; + std::string attribute_name(attribute_it->first->mString); + mCurReadNode = attribute_it->second; + + tokenizer name_tokens(attribute_name, sep); + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + + // child nodes are not necessarily valid attributes, so don't complain once we've recursed + any_parsed |= block.submitValue(mNameStack, *this, silent); + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + + return any_parsed; +} + +void LLXUIParser::writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::BaseBlock* diff_block) +{ + mWriteRootNode = node; + name_stack_t name_stack = Parser::name_stack_t(); + block.serializeBlock(*this, name_stack, diff_block); + mOutNodes.clear(); +} + +// go from a stack of names to a specific XML node +LLXMLNodePtr LLXUIParser::getNode(name_stack_t& stack) +{ + LLXMLNodePtr out_node = mWriteRootNode; + + name_stack_t::iterator next_it = stack.begin(); + for (name_stack_t::iterator it = stack.begin(); + it != stack.end(); + it = next_it) + { + ++next_it; + if (it->first.empty()) + { + it->second = false; + continue; + } + + out_nodes_t::iterator found_it = mOutNodes.find(it->first); + + // node with this name not yet written + if (found_it == mOutNodes.end() || it->second) + { + // make an attribute if we are the last element on the name stack + bool is_attribute = next_it == stack.end(); + LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); + out_node->addChild(new_node); + mOutNodes[it->first] = new_node; + out_node = new_node; + it->second = false; + } + else + { + out_node = found_it->second; + } + } + + return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); +} + +bool LLXUIParser::readFlag(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode == DUMMY_NODE; +} + +bool LLXUIParser::writeFlag(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + // just create node + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + return node.notNull(); +} + +bool LLXUIParser::readBoolValue(Parser& parser, void* val_ptr) +{ + S32 value; + LLXUIParser& self = static_cast(parser); + bool success = self.mCurReadNode->getBoolValue(1, &value); + *((bool*)val_ptr) = (value != FALSE); + return success; +} + +bool LLXUIParser::writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setBoolValue(*((bool*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readStringValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + *((std::string*)val_ptr) = self.mCurReadNode->getSanitizedValue(); + return true; +} + +bool LLXUIParser::writeStringValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + const std::string* string_val = reinterpret_cast(val_ptr); + if (string_val->find('\n') != std::string::npos + || string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) + { + // don't write strings with newlines into attributes + std::string attribute_name = node->getName()->mString; + LLXMLNodePtr parent_node = node->mParent; + parent_node->deleteChild(node); + // write results in text contents of node + if (attribute_name == "value") + { + // "value" is implicit, just write to parent + node = parent_node; + } + else + { + // create a child that is not an attribute, but with same name + node = parent_node->createChild(attribute_name.c_str(), false); + } + } + node->setStringValue(*string_val); + return true; + } + return false; +} + +bool LLXUIParser::readU8Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getByteValue(1, (U8*)val_ptr); +} + +bool LLXUIParser::writeU8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U8*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS8Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + S32 value; + if(self.mCurReadNode->getIntValue(1, &value)) + { + *((S8*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeS8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S8*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readU16Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + U32 value; + if(self.mCurReadNode->getUnsignedValue(1, &value)) + { + *((U16*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeU16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U16*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS16Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + S32 value; + if(self.mCurReadNode->getIntValue(1, &value)) + { + *((S16*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeS16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S16*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readU32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); +} + +bool LLXUIParser::writeU32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getIntValue(1, (S32*)val_ptr); +} + +bool LLXUIParser::writeS32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readF32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getFloatValue(1, (F32*)val_ptr); +} + +bool LLXUIParser::writeF32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setFloatValue(*((F32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readF64Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getDoubleValue(1, (F64*)val_ptr); +} + +bool LLXUIParser::writeF64Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setDoubleValue(*((F64*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readColor4Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLColor4* colorp = (LLColor4*)val_ptr; + if(self.mCurReadNode->getFloatValue(4, colorp->mV) >= 3) + { + return true; + } + + return false; +} + +bool LLXUIParser::writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLColor4 color = *((LLColor4*)val_ptr); + node->setFloatValue(4, color.mV); + return true; + } + return false; +} + +bool LLXUIParser::readUIColorValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLUIColor* param = (LLUIColor*)val_ptr; + LLColor4 color; + bool success = self.mCurReadNode->getFloatValue(4, color.mV) >= 3; + if (success) + { + param->set(color); + return true; + } + return false; +} + +bool LLXUIParser::writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLUIColor color = *((LLUIColor*)val_ptr); + //RN: don't write out the color that is represented by a function + // rely on param block exporting to get the reference to the color settings + if (color.isReference()) return false; + node->setFloatValue(4, color.get().mV); + return true; + } + return false; +} + +bool LLXUIParser::readUUIDValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLUUID temp_id; + // LLUUID::set is destructive, so use temporary value + if (temp_id.set(self.mCurReadNode->getSanitizedValue())) + { + *(LLUUID*)(val_ptr) = temp_id; + return true; + } + return false; +} + +bool LLXUIParser::writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setStringValue(((LLUUID*)val_ptr)->asString()); + return true; + } + return false; +} + +bool LLXUIParser::readSDValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + *((LLSD*)val_ptr) = LLSD(self.mCurReadNode->getSanitizedValue()); + return true; +} + +bool LLXUIParser::writeSDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + std::string string_val = ((LLSD*)val_ptr)->asString(); + if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) + { + // don't write strings with newlines into attributes + std::string attribute_name = node->getName()->mString; + LLXMLNodePtr parent_node = node->mParent; + parent_node->deleteChild(node); + // write results in text contents of node + if (attribute_name == "value") + { + // "value" is implicit, just write to parent + node = parent_node; + } + else + { + node = parent_node->createChild(attribute_name.c_str(), false); + } + } + + node->setStringValue(string_val); + return true; + } + return false; +} + +/*virtual*/ std::string LLXUIParser::getCurrentElementName() +{ + std::string full_name; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." + } + + return full_name; +} + +void LLXUIParser::parserWarning(const std::string& message) +{ +#ifdef LL_WINDOWS + // use Visual Studo friendly formatting of output message for easy access to originating xml + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserWarning(message); +#endif +} + +void LLXUIParser::parserError(const std::string& message) +{ +#ifdef LL_WINDOWS + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserError(message); +#endif +} + + +// +// LLSimpleXUIParser +// + +struct ScopedFile +{ + ScopedFile( const std::string& filename, const char* accessmode ) + { + mFile = LLFile::fopen(filename, accessmode); + } + + ~ScopedFile() + { + fclose(mFile); + mFile = NULL; + } + + S32 getRemainingBytes() + { + if (!isOpen()) return 0; + + S32 cur_pos = ftell(mFile); + fseek(mFile, 0L, SEEK_END); + S32 file_size = ftell(mFile); + fseek(mFile, cur_pos, SEEK_SET); + return file_size - cur_pos; + } + + bool isOpen() { return mFile != NULL; } + + LLFILE* mFile; +}; +LLSimpleXUIParser::LLSimpleXUIParser(LLSimpleXUIParser::element_start_callback_t element_cb) +: Parser(sSimpleXUIReadFuncs, sSimpleXUIWriteFuncs, sSimpleXUIInspectFuncs), + mCurReadDepth(0), + mElementCB(element_cb) +{ + if (sSimpleXUIReadFuncs.empty()) + { + registerParserFuncs(readFlag); + registerParserFuncs(readBoolValue); + registerParserFuncs(readStringValue); + registerParserFuncs(readU8Value); + registerParserFuncs(readS8Value); + registerParserFuncs(readU16Value); + registerParserFuncs(readS16Value); + registerParserFuncs(readU32Value); + registerParserFuncs(readS32Value); + registerParserFuncs(readF32Value); + registerParserFuncs(readF64Value); + registerParserFuncs(readColor4Value); + registerParserFuncs(readUIColorValue); + registerParserFuncs(readUUIDValue); + registerParserFuncs(readSDValue); + } +} + +LLSimpleXUIParser::~LLSimpleXUIParser() +{ +} + + +bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent) +{ + LLFastTimer timer(FTM_PARSE_XUI); + + mParser = XML_ParserCreate(NULL); + XML_SetUserData(mParser, this); + XML_SetElementHandler( mParser, startElementHandler, endElementHandler); + XML_SetCharacterDataHandler( mParser, characterDataHandler); + + mOutputStack.push_back(std::make_pair(&block, 0)); + mNameStack.clear(); + mCurFileName = filename; + mCurReadDepth = 0; + setParseSilently(silent); + + ScopedFile file(filename, "rb"); + if( !file.isOpen() ) + { + LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + S32 bytes_read = 0; + + S32 buffer_size = file.getRemainingBytes(); + void* buffer = XML_GetBuffer(mParser, buffer_size); + if( !buffer ) + { + LL_WARNS("ReadXUI") << "Unable to allocate XML buffer while reading file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + bytes_read = (S32)fread(buffer, 1, buffer_size, file.mFile); + if( bytes_read <= 0 ) + { + LL_WARNS("ReadXUI") << "Error while reading file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + mEmptyLeafNode.push_back(false); + + if( !XML_ParseBuffer(mParser, bytes_read, TRUE ) ) + { + LL_WARNS("ReadXUI") << "Error while parsing file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + mEmptyLeafNode.pop_back(); + + XML_ParserFree( mParser ); + return true; +} + +void LLSimpleXUIParser::startElementHandler(void *userData, const char *name, const char **atts) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->startElement(name, atts); +} + +void LLSimpleXUIParser::endElementHandler(void *userData, const char *name) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->endElement(name); +} + +void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int len) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->characterData(s, len); +} + +void LLSimpleXUIParser::characterData(const char *s, int len) +{ + mTextContents += std::string(s, len); +} + +void LLSimpleXUIParser::startElement(const char *name, const char **atts) +{ + processText(); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + if (mElementCB) + { + LLInitParam::BaseBlock* blockp = mElementCB(*this, name); + if (blockp) + { + mOutputStack.push_back(std::make_pair(blockp, 0)); + } + } + + mOutputStack.back().second++; + S32 num_tokens_pushed = 0; + std::string child_name(name); + + if (mOutputStack.back().second == 1) + { // root node for this block + mScope.push_back(child_name); + } + else + { // compound attribute + if (child_name.find(".") == std::string::npos) + { + mNameStack.push_back(std::make_pair(child_name, true)); + num_tokens_pushed++; + mScope.push_back(child_name); + } + else + { + // parse out "dotted" name into individual tokens + tokenizer name_tokens(child_name, sep); + + tokenizer::iterator name_token_it = name_tokens.begin(); + if(name_token_it == name_tokens.end()) + { + return; + } + + // check for proper nesting + if(!mScope.empty() && *name_token_it != mScope.back()) + { + return; + } + + // now ignore first token + ++name_token_it; + + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + mScope.push_back(mNameStack.back().first); + } + } + + // parent node is not empty + mEmptyLeafNode.back() = false; + // we are empty if we have no attributes + mEmptyLeafNode.push_back(atts[0] == NULL); + + mTokenSizeStack.push_back(num_tokens_pushed); + readAttributes(atts); + +} + +void LLSimpleXUIParser::endElement(const char *name) +{ + bool has_text = processText(); + + // no text, attributes, or children + if (!has_text && mEmptyLeafNode.back()) + { + // submit this as a valueless name (even though there might be text contents we haven't seen yet) + mCurAttributeValueBegin = NO_VALUE_MARKER; + mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + } + + if (--mOutputStack.back().second == 0) + { + if (mOutputStack.empty()) + { + LL_ERRS("ReadXUI") << "Parameter block output stack popped while empty." << LL_ENDL; + } + mOutputStack.pop_back(); + } + + S32 num_tokens_to_pop = mTokenSizeStack.back(); + mTokenSizeStack.pop_back(); + while(num_tokens_to_pop-- > 0) + { + mNameStack.pop_back(); + } + mScope.pop_back(); + mEmptyLeafNode.pop_back(); +} + +bool LLSimpleXUIParser::readAttributes(const char **atts) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool any_parsed = false; + for(S32 i = 0; atts[i] && atts[i+1]; i += 2 ) + { + std::string attribute_name(atts[i]); + mCurAttributeValueBegin = atts[i+1]; + + S32 num_tokens_pushed = 0; + tokenizer name_tokens(attribute_name, sep); + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + + // child nodes are not necessarily valid attributes, so don't complain once we've recursed + any_parsed |= mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + return any_parsed; +} + +bool LLSimpleXUIParser::processText() +{ + if (!mTextContents.empty()) + { + LLStringUtil::trim(mTextContents); + if (!mTextContents.empty()) + { + mNameStack.push_back(std::make_pair(std::string("value"), true)); + mCurAttributeValueBegin = mTextContents.c_str(); + mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + mNameStack.pop_back(); + } + mTextContents.clear(); + return true; + } + return false; +} + +/*virtual*/ std::string LLSimpleXUIParser::getCurrentElementName() +{ + std::string full_name; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." + } + + return full_name; +} + +void LLSimpleXUIParser::parserWarning(const std::string& message) +{ +#ifdef LL_WINDOWS + // use Visual Studo friendly formatting of output message for easy access to originating xml + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserWarning(message); +#endif +} + +void LLSimpleXUIParser::parserError(const std::string& message) +{ +#ifdef LL_WINDOWS + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserError(message); +#endif +} + +bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return self.mCurAttributeValueBegin == NO_VALUE_MARKER; +} + +bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + if (!strcmp(self.mCurAttributeValueBegin, "true")) + { + *((bool*)val_ptr) = true; + return true; + } + else if (!strcmp(self.mCurAttributeValueBegin, "false")) + { + *((bool*)val_ptr) = false; + return true; + } + + return false; +} + +bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + *((std::string*)val_ptr) = self.mCurAttributeValueBegin; + return true; +} + +bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLColor4 value; + + if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + { + *(LLColor4*)(val_ptr) = value; + return true; + } + return false; +} + +bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLColor4 value; + LLUIColor* colorp = (LLUIColor*)val_ptr; + + if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + { + colorp->set(value); + return true; + } + return false; +} + +bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLUUID temp_id; + // LLUUID::set is destructive, so use temporary value + if (temp_id.set(std::string(self.mCurAttributeValueBegin))) + { + *(LLUUID*)(val_ptr) = temp_id; + return true; + } + return false; +} + +bool LLSimpleXUIParser::readSDValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + *((LLSD*)val_ptr) = LLSD(self.mCurAttributeValueBegin); + return true; +} diff --git a/indra/llui/llxuiparser.h b/indra/llui/llxuiparser.h new file mode 100644 index 0000000000..d7cd256967 --- /dev/null +++ b/indra/llui/llxuiparser.h @@ -0,0 +1,242 @@ +/** + * @file llxuiparser.h + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&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 LLXUIPARSER_H +#define LLXUIPARSER_H + +#include "llinitparam.h" +#include "llregistry.h" +#include "llpointer.h" + +#include +#include +#include +#include + + + +class LLView; + + +typedef LLPointer LLXMLNodePtr; + + +// lookup widget type by name +class LLWidgetTypeRegistry +: public LLRegistrySingleton +{}; + + +// global static instance for registering all widget types +typedef boost::function LLWidgetCreatorFunc; + +typedef LLRegistry widget_registry_t; + +class LLChildRegistryRegistry +: public LLRegistrySingleton +{}; + + + +class LLXSDWriter : public LLInitParam::Parser +{ + LOG_CLASS(LLXSDWriter); +public: + void writeXSD(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace); + + /*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; } + + LLXSDWriter(); + +protected: + void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector* possible_values); + void addAttributeToSchema(LLXMLNodePtr nodep, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values); + LLXMLNodePtr mAttributeNode; + LLXMLNodePtr mElementNode; + LLXMLNodePtr mSchemaNode; + + typedef std::set string_set_t; + typedef std::map attributes_map_t; + attributes_map_t mAttributesWritten; +}; + + + +// NOTE: DOES NOT WORK YET +// should support child widgets for XUI +class LLXUIXSDWriter : public LLXSDWriter +{ +public: + void writeXSD(const std::string& name, const std::string& path, const LLInitParam::BaseBlock& block); +}; + + +class LLXUIParserImpl; + +class LLXUIParser : public LLInitParam::Parser +{ +LOG_CLASS(LLXUIParser); + +public: + LLXUIParser(); + typedef LLInitParam::Parser::name_stack_t name_stack_t; + + /*virtual*/ std::string getCurrentElementName(); + /*virtual*/ void parserWarning(const std::string& message); + /*virtual*/ void parserError(const std::string& message); + + void readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename = LLStringUtil::null, bool silent=false); + void writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const LLInitParam::BaseBlock* diff_block = NULL); + +private: + bool readXUIImpl(LLXMLNodePtr node, LLInitParam::BaseBlock& block); + bool readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block); + + //reader helper functions + static bool readFlag(Parser& parser, void* val_ptr); + static bool readBoolValue(Parser& parser, void* val_ptr); + static bool readStringValue(Parser& parser, void* val_ptr); + static bool readU8Value(Parser& parser, void* val_ptr); + static bool readS8Value(Parser& parser, void* val_ptr); + static bool readU16Value(Parser& parser, void* val_ptr); + static bool readS16Value(Parser& parser, void* val_ptr); + static bool readU32Value(Parser& parser, void* val_ptr); + static bool readS32Value(Parser& parser, void* val_ptr); + static bool readF32Value(Parser& parser, void* val_ptr); + static bool readF64Value(Parser& parser, void* val_ptr); + static bool readColor4Value(Parser& parser, void* val_ptr); + static bool readUIColorValue(Parser& parser, void* val_ptr); + static bool readUUIDValue(Parser& parser, void* val_ptr); + static bool readSDValue(Parser& parser, void* val_ptr); + + //writer helper functions + static bool writeFlag(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeStringValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeU8Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeS8Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeU16Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeS16Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeU32Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeS32Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeF32Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeF64Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeSDValue(Parser& parser, const void* val_ptr, name_stack_t&); + + LLXMLNodePtr getNode(name_stack_t& stack); + +private: + Parser::name_stack_t mNameStack; + LLXMLNodePtr mCurReadNode; + // Root of the widget XML sub-tree, for example, "line_editor" + LLXMLNodePtr mWriteRootNode; + + typedef std::map out_nodes_t; + out_nodes_t mOutNodes; + LLXMLNodePtr mLastWrittenChild; + S32 mCurReadDepth; + std::string mCurFileName; + std::string mRootNodeName; +}; + +// LLSimpleXUIParser is a streamlined SAX-based XUI parser that does not support localization +// or parsing of a tree of independent param blocks, such as child widgets. +// Use this for reading non-localized files that only need a single param block as a result. +// +// NOTE: In order to support nested block parsing, we need callbacks for start element that +// push new blocks contexts on the mScope stack. +// NOTE: To support localization without building a DOM, we need to enforce consistent +// ordering of child elements from base file to localized diff file. Then we can use a pair +// of coroutines to perform matching of xml nodes during parsing. Not sure if the overhead +// of coroutines would offset the gain from SAX parsing +class LLSimpleXUIParserImpl; + +class LLSimpleXUIParser : public LLInitParam::Parser +{ +LOG_CLASS(LLSimpleXUIParser); +public: + typedef LLInitParam::Parser::name_stack_t name_stack_t; + typedef LLInitParam::BaseBlock* (*element_start_callback_t)(LLSimpleXUIParser&, const char* block_name); + + LLSimpleXUIParser(element_start_callback_t element_cb = NULL); + virtual ~LLSimpleXUIParser(); + + /*virtual*/ std::string getCurrentElementName(); + /*virtual*/ void parserWarning(const std::string& message); + /*virtual*/ void parserError(const std::string& message); + + bool readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent=false); + + +private: + //reader helper functions + static bool readFlag(Parser&, void* val_ptr); + static bool readBoolValue(Parser&, void* val_ptr); + static bool readStringValue(Parser&, void* val_ptr); + static bool readU8Value(Parser&, void* val_ptr); + static bool readS8Value(Parser&, void* val_ptr); + static bool readU16Value(Parser&, void* val_ptr); + static bool readS16Value(Parser&, void* val_ptr); + static bool readU32Value(Parser&, void* val_ptr); + static bool readS32Value(Parser&, void* val_ptr); + static bool readF32Value(Parser&, void* val_ptr); + static bool readF64Value(Parser&, void* val_ptr); + static bool readColor4Value(Parser&, void* val_ptr); + static bool readUIColorValue(Parser&, void* val_ptr); + static bool readUUIDValue(Parser&, void* val_ptr); + static bool readSDValue(Parser&, void* val_ptr); + +private: + static void startElementHandler(void *userData, const char *name, const char **atts); + static void endElementHandler(void *userData, const char *name); + static void characterDataHandler(void *userData, const char *s, int len); + + void startElement(const char *name, const char **atts); + void endElement(const char *name); + void characterData(const char *s, int len); + bool readAttributes(const char **atts); + bool processText(); + + Parser::name_stack_t mNameStack; + struct XML_ParserStruct* mParser; + LLXMLNodePtr mLastWrittenChild; + S32 mCurReadDepth; + std::string mCurFileName; + std::string mTextContents; + const char* mCurAttributeValueBegin; + std::vector mTokenSizeStack; + std::vector mScope; + std::vector mEmptyLeafNode; + element_start_callback_t mElementCB; + + std::vector > mOutputStack; +}; + + +#endif //LLXUIPARSER_H diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index c75df86891..cb3b7abb14 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -105,28 +105,6 @@ LLStyle::Params::Params() namespace LLInitParam { - Param::Param(BaseBlock* enclosing_block) - : mIsProvided(false) - { - const U8* my_addr = reinterpret_cast(this); - const U8* block_addr = reinterpret_cast(enclosing_block); - mEnclosingBlockOffset = (U16)(my_addr - block_addr); - } - - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){} - void BaseBlock::addSynonym(Param& param, const std::string& synonym) {} - param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} - - void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) - { - descriptor.mCurrentBlockPtr = this; - } - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; } - void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {} - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_value, S32 max_value) const { return true; } - bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } - bool BaseBlock::validateBlock(bool emit_errors) const { return true; } - ParamValue >::ParamValue(const LLUIColor& color) : super_t(color) {} diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index c1fb050206..8f0a48018f 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -70,21 +70,6 @@ S32 LLUIImage::getHeight() const return 0; } -namespace LLInitParam -{ - BlockDescriptor::BlockDescriptor() {} - ParamDescriptor::ParamDescriptor(param_handle_t p, - merge_func_t merge_func, - deserialize_func_t deserialize_func, - serialize_func_t serialize_func, - validation_func_t validation_func, - inspect_func_t inspect_func, - S32 min_count, - S32 max_count){} - ParamDescriptor::~ParamDescriptor() {} - -} - namespace tut { struct LLUrlEntryData diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index 7183413463..963473c92a 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -63,40 +63,6 @@ S32 LLUIImage::getHeight() const namespace LLInitParam { - BlockDescriptor::BlockDescriptor() {} - ParamDescriptor::ParamDescriptor(param_handle_t p, - merge_func_t merge_func, - deserialize_func_t deserialize_func, - serialize_func_t serialize_func, - validation_func_t validation_func, - inspect_func_t inspect_func, - S32 min_count, - S32 max_count){} - ParamDescriptor::~ParamDescriptor() {} - - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){} - param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} - void BaseBlock::addSynonym(Param& param, const std::string& synonym) {} - - void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) - { - descriptor.mCurrentBlockPtr = this; - } - - Param::Param(BaseBlock* enclosing_block) - : mIsProvided(false) - { - const U8* my_addr = reinterpret_cast(this); - const U8* block_addr = reinterpret_cast(enclosing_block); - mEnclosingBlockOffset = 0x7FFFffff & ((U32)(my_addr - block_addr)); - } - - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; } - void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {} - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const { return true; } - bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } - bool BaseBlock::validateBlock(bool emit_errors) const { return true; } - ParamValue >::ParamValue(const LLUIColor& color) : super_t(color) {} -- cgit v1.2.3 From 4287dcaacf0804a5a73dbf37c629471e2855733c Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Fri, 20 Jan 2012 14:55:39 -0800 Subject: moved LLSDParam to llcommon so that LLSD<->Param Block conversion are usable by everyone --- indra/llui/CMakeLists.txt | 2 - indra/llui/llsdparam.cpp | 342 ---------------------------------------------- indra/llui/llsdparam.h | 126 ----------------- 3 files changed, 470 deletions(-) delete mode 100644 indra/llui/llsdparam.cpp delete mode 100644 indra/llui/llsdparam.h (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 9226f36e73..20c3456a56 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -81,7 +81,6 @@ set(llui_SOURCE_FILES llscrolllistcolumn.cpp llscrolllistctrl.cpp llscrolllistitem.cpp - llsdparam.cpp llsearcheditor.cpp llslider.cpp llsliderctrl.cpp @@ -190,7 +189,6 @@ set(llui_HEADER_FILES llscrolllistcolumn.h llscrolllistctrl.h llscrolllistitem.h - llsdparam.h llsliderctrl.h llslider.h llspinctrl.h diff --git a/indra/llui/llsdparam.cpp b/indra/llui/llsdparam.cpp deleted file mode 100644 index 0e29873bb0..0000000000 --- a/indra/llui/llsdparam.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/** - * @file llsdparam.cpp - * @brief parameter block abstraction for creating complex objects and - * parsing construction parameters from xml and LLSD - * - * $LicenseInfo:firstyear=2008&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" - -// Project includes -#include "llsdparam.h" -#include "llsdutil.h" - -static LLInitParam::Parser::parser_read_func_map_t sReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs; -static const LLSD NO_VALUE_MARKER; - -LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR("LLSD to LLInitParam conversion"); - -// -// LLParamSDParser -// -LLParamSDParser::LLParamSDParser() -: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs) -{ - using boost::bind; - - if (sReadFuncs.empty()) - { - registerParserFuncs(readFlag, &LLParamSDParser::writeFlag); - registerParserFuncs(readS32, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readU32, &LLParamSDParser::writeU32Param); - registerParserFuncs(readF32, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readF64, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readBool, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readString, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readUUID, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readDate, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readURI, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readSD, &LLParamSDParser::writeTypedValue); - } -} - -// special case handling of U32 due to ambiguous LLSD::assign overload -bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) -{ - LLParamSDParser& sdparser = static_cast(parser); - if (!sdparser.mWriteRootSD) return false; - - parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); - LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - sd_to_write.assign((S32)*((const U32*)val_ptr)); - - return true; -} - -bool LLParamSDParser::writeFlag(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) -{ - LLParamSDParser& sdparser = static_cast(parser); - if (!sdparser.mWriteRootSD) return false; - - parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); - LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - - return true; -} - -void LLParamSDParser::submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack) -{ - mCurReadSD = &sd; - block.submitValue(name_stack, *this); -} - -void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) -{ - mCurReadSD = NULL; - mNameStack.clear(); - setParseSilently(silent); - - LLParamSDParserUtilities::readSDValues(boost::bind(&LLParamSDParser::submit, this, boost::ref(block), _1, _2), sd, mNameStack); - //readSDValues(sd, block); -} - -void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) -{ - mNameStack.clear(); - mWriteRootSD = &sd; - - name_stack_t name_stack; - block.serializeBlock(*this, name_stack); -} - -/*virtual*/ std::string LLParamSDParser::getCurrentElementName() -{ - std::string full_name = "sd"; - for (name_stack_t::iterator it = mNameStack.begin(); - it != mNameStack.end(); - ++it) - { - full_name += llformat("[%s]", it->first.c_str()); - } - - return full_name; -} - - -bool LLParamSDParser::readFlag(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - return self.mCurReadSD == &NO_VALUE_MARKER; -} - - -bool LLParamSDParser::readS32(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((S32*)val_ptr) = self.mCurReadSD->asInteger(); - return true; -} - -bool LLParamSDParser::readU32(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((U32*)val_ptr) = self.mCurReadSD->asInteger(); - return true; -} - -bool LLParamSDParser::readF32(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((F32*)val_ptr) = self.mCurReadSD->asReal(); - return true; -} - -bool LLParamSDParser::readF64(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((F64*)val_ptr) = self.mCurReadSD->asReal(); - return true; -} - -bool LLParamSDParser::readBool(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((bool*)val_ptr) = self.mCurReadSD->asBoolean(); - return true; -} - -bool LLParamSDParser::readString(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((std::string*)val_ptr) = self.mCurReadSD->asString(); - return true; -} - -bool LLParamSDParser::readUUID(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLUUID*)val_ptr) = self.mCurReadSD->asUUID(); - return true; -} - -bool LLParamSDParser::readDate(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLDate*)val_ptr) = self.mCurReadSD->asDate(); - return true; -} - -bool LLParamSDParser::readURI(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLURI*)val_ptr) = self.mCurReadSD->asURI(); - return true; -} - -bool LLParamSDParser::readSD(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLSD*)val_ptr) = *self.mCurReadSD; - return true; -} - -// static -LLSD& LLParamSDParserUtilities::getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range) -{ - LLSD* sd_to_write = &input; - - for (LLInitParam::Parser::name_stack_t::iterator it = name_stack_range.first; - it != name_stack_range.second; - ++it) - { - bool new_traversal = it->second; - - LLSD* child_sd = it->first.empty() ? sd_to_write : &(*sd_to_write)[it->first]; - - if (child_sd->isArray()) - { - if (new_traversal) - { - // write to new element at end - sd_to_write = &(*child_sd)[child_sd->size()]; - } - else - { - // write to last of existing elements, or first element if empty - sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)]; - } - } - else - { - if (new_traversal - && child_sd->isDefined() - && !child_sd->isArray()) - { - // copy child contents into first element of an array - LLSD new_array = LLSD::emptyArray(); - new_array.append(*child_sd); - // assign array to slot that previously held the single value - *child_sd = new_array; - // return next element in that array - sd_to_write = &((*child_sd)[1]); - } - else - { - sd_to_write = child_sd; - } - } - it->second = false; - } - - return *sd_to_write; -} - -//static -void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack) -{ - if (sd.isMap()) - { - for (LLSD::map_const_iterator it = sd.beginMap(); - it != sd.endMap(); - ++it) - { - stack.push_back(make_pair(it->first, true)); - readSDValues(cb, it->second, stack); - stack.pop_back(); - } - } - else if (sd.isArray()) - { - for (LLSD::array_const_iterator it = sd.beginArray(); - it != sd.endArray(); - ++it) - { - stack.back().second = true; - readSDValues(cb, *it, stack); - } - } - else if (sd.isUndefined()) - { - if (!cb.empty()) - { - cb(NO_VALUE_MARKER, stack); - } - } - else - { - if (!cb.empty()) - { - cb(sd, stack); - } - } -} - -//static -void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd) -{ - LLInitParam::Parser::name_stack_t stack = LLInitParam::Parser::name_stack_t(); - readSDValues(cb, sd, stack); -} -namespace LLInitParam -{ - // LLSD specialization - // block param interface - bool ParamValue, false>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name) - { - LLSD& sd = LLParamSDParserUtilities::getSDWriteNode(mValue, name_stack); - - LLSD::String string; - - if (p.readValue(string)) - { - sd = string; - return true; - } - return false; - } - - //static - void ParamValue, false>::serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack) - { - p.writeValue(sd.asString(), name_stack); - } - - void ParamValue, false>::serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block) const - { - // read from LLSD value and serialize out to parser (which could be LLSD, XUI, etc) - Parser::name_stack_t stack; - LLParamSDParserUtilities::readSDValues(boost::bind(&serializeElement, boost::ref(p), _1, _2), mValue, stack); - } -} diff --git a/indra/llui/llsdparam.h b/indra/llui/llsdparam.h deleted file mode 100644 index 3dfc6d020e..0000000000 --- a/indra/llui/llsdparam.h +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @file llsdparam.h - * @brief parameter block abstraction for creating complex objects and - * parsing construction parameters from xml and LLSD - * - * $LicenseInfo:firstyear=2008&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 LL_LLSDPARAM_H -#define LL_LLSDPARAM_H - -#include "llinitparam.h" -#include "boost/function.hpp" - -struct LLParamSDParserUtilities -{ - static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range); - - typedef boost::function read_sd_cb_t; - static void readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack); - static void readSDValues(read_sd_cb_t cb, const LLSD& sd); -}; - -class LLParamSDParser -: public LLInitParam::Parser -{ -LOG_CLASS(LLParamSDParser); - -typedef LLInitParam::Parser parser_t; - -public: - LLParamSDParser(); - void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false); - void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block); - - /*virtual*/ std::string getCurrentElementName(); - -private: - void submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack); - - template - static bool writeTypedValue(Parser& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) - { - LLParamSDParser& sdparser = static_cast(parser); - if (!sdparser.mWriteRootSD) return false; - - LLInitParam::Parser::name_stack_range_t range(name_stack.begin(), name_stack.end()); - LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - - sd_to_write.assign(*((const T*)val_ptr)); - return true; - } - - static bool writeU32Param(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); - static bool writeFlag(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); - - static bool readFlag(Parser& parser, void* val_ptr); - static bool readS32(Parser& parser, void* val_ptr); - static bool readU32(Parser& parser, void* val_ptr); - static bool readF32(Parser& parser, void* val_ptr); - static bool readF64(Parser& parser, void* val_ptr); - static bool readBool(Parser& parser, void* val_ptr); - static bool readString(Parser& parser, void* val_ptr); - static bool readUUID(Parser& parser, void* val_ptr); - static bool readDate(Parser& parser, void* val_ptr); - static bool readURI(Parser& parser, void* val_ptr); - static bool readSD(Parser& parser, void* val_ptr); - - Parser::name_stack_t mNameStack; - const LLSD* mCurReadSD; - LLSD* mWriteRootSD; - LLSD* mCurWriteSD; -}; - - -extern LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR; -template -class LLSDParamAdapter : public T -{ -public: - LLSDParamAdapter() {} - LLSDParamAdapter(const LLSD& sd) - { - LLFastTimer _(FTM_SD_PARAM_ADAPTOR); - LLParamSDParser parser; - // don't spam for implicit parsing of LLSD, as we want to allow arbitrary freeform data and ignore most of it - bool parse_silently = true; - parser.readSD(sd, *this, parse_silently); - } - - operator LLSD() const - { - LLParamSDParser parser; - LLSD sd; - parser.writeSD(sd, *this); - return sd; - } - - LLSDParamAdapter(const T& val) - : T(val) - { - T::operator=(val); - } -}; - -#endif // LL_LLSDPARAM_H - -- cgit v1.2.3 From 59dfcba33de76bfc998b9aa2121c53dff75024f7 Mon Sep 17 00:00:00 2001 From: Jonathan Yap Date: Wed, 1 Feb 2012 10:28:45 -0500 Subject: STORM-1738 Slight adjustment to callback --- indra/llui/lllineeditor.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index ba50728e89..de951d3b56 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -151,7 +151,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mHighlightColor(p.highlight_color()), mPreeditBgColor(p.preedit_bg_color()), mGLFont(p.font), - mContextMenuHandle() + mContextMenuHandle(), + mAutocorrectCallback() { llassert( mMaxLengthBytes > 0 ); @@ -866,10 +867,10 @@ void LLLineEditor::addChar(const llwchar uni_char) } // *TODO implement callback routine - if (!mReadOnly /*&& autocorrectCallbackRoutine != NULL */) + if (!mReadOnly && mAutocorrectCallback != NULL) { // call callback - // autotocorrectCallbackRoutine(mText&, mCursorPos&); + // mAutotocorrectCallback(mText&, mCursorPos&); } getWindow()->hideCursorUntilMouseMove(); -- cgit v1.2.3 From a9d11219779473c7d0c06bd0e56b0f068a487ad0 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Fri, 3 Feb 2012 15:20:34 +0100 Subject: STORM-276 Added spellcheck functionality to the LLTextEditor control --- indra/llui/lltextbase.cpp | 209 +++++++++++++++++++++++++++++++++++++++++++- indra/llui/lltextbase.h | 31 ++++++- indra/llui/lltexteditor.cpp | 34 ++++++- 3 files changed, 271 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0040be45c7..7eee1d39c4 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -32,6 +32,7 @@ #include "lllocalcliprect.h" #include "llmenugl.h" #include "llscrollcontainer.h" +#include "llspellcheck.h" #include "llstl.h" #include "lltextparser.h" #include "lltextutil.h" @@ -155,6 +156,7 @@ LLTextBase::Params::Params() plain_text("plain_text",false), track_end("track_end", false), read_only("read_only", false), + spellcheck("spellcheck", false), v_pad("v_pad", 0), h_pad("h_pad", 0), clip("clip", true), @@ -181,6 +183,9 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mFontShadow(p.font_shadow), mPopupMenu(NULL), mReadOnly(p.read_only), + mSpellCheck(p.spellcheck), + mSpellCheckStart(-1), + mSpellCheckEnd(-1), mCursorColor(p.cursor_color), mFgColor(p.text_color), mBorderVisible( p.border_visible ), @@ -246,6 +251,12 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) addChild(mDocumentView); } + if (mSpellCheck) + { + LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this)); + } + mSpellCheckTimer.reset(); + createDefaultSegment(); updateRects(); @@ -530,8 +541,86 @@ void LLTextBase::drawText() return; } + // Perform spell check if needed + if ( (getSpellCheck()) && (getWText().length() > 2) ) + { + // Calculate start and end indices for the spell checking range + S32 start = line_start, end = getLineEnd(last_line); + + if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) + { + const LLWString& wstrText = getWText(); + mMisspellRanges.clear(); + + segment_set_t::iterator seg_it = getSegIterContaining(start); + while (mSegments.end() != seg_it) + { + LLTextSegmentPtr text_segment = *seg_it; + if ( (text_segment.isNull()) || (text_segment->getStart() >= end) ) + { + break; + } + + if (!text_segment->canEdit()) + { + ++seg_it; + continue; + } + + // Combine adjoining text segments into one + U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end); + while (mSegments.end() != ++seg_it) + { + text_segment = *seg_it; + if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) ) + { + break; + } + seg_end = llmin(text_segment->getEnd(), end); + } + + // Find the start of the first word + U32 word_start = seg_start, word_end = -1; + while ( (word_start < wstrText.length()) && (!LLStringOps::isAlpha(wstrText[word_start])) ) + word_start++; + + // Iterate over all words in the text block and check them one by one + while (word_start < seg_end) + { + // Find the end of the current word (special case handling for "'" when it's used as a contraction) + word_end = word_start + 1; + while ( (word_end < seg_end) && + ((LLWStringUtil::isPartOfWord(wstrText[word_end])) || + ((L'\'' == wstrText[word_end]) && + (LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) ) + { + word_end++; + } + if (word_end > seg_end) + break; + + // Don't process words shorter than 3 characters + std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start)); + if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) + { + mMisspellRanges.push_back(std::pair(word_start, word_end)); + } + + // Find the start of the next word + word_start = word_end + 1; + while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) ) + word_start++; + } + } + + mSpellCheckStart = start; + mSpellCheckEnd = end; + } + } + LLTextSegmentPtr cur_segment = *seg_iter; + std::list >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair(line_start, 0)); for (S32 cur_line = first_line; cur_line < last_line; cur_line++) { S32 next_line = cur_line + 1; @@ -566,7 +655,8 @@ void LLTextBase::drawText() cur_segment = *seg_iter; } - S32 clipped_end = llmin( line_end, cur_segment->getEnd() ) - cur_segment->getStart(); + S32 seg_end = llmin(line_end, cur_segment->getEnd()); + S32 clipped_end = seg_end - cur_segment->getStart(); if (mUseEllipses // using ellipses && clipped_end == line_end // last segment on line @@ -578,6 +668,35 @@ void LLTextBase::drawText() text_rect.mRight -= 2; } + // Draw squiggly lines under any visible misspelled words + while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) ) + { + // Skip the current word if the user is still busy editing it + if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) ) + continue; + + S32 squiggle_start = 0, squiggle_end = 0, pony = 0; + cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_it->first - seg_start, squiggle_start, pony); + cur_segment->getDimensions(misspell_it->first - cur_segment->getStart(), misspell_it->second - misspell_it->first, squiggle_end, pony); + squiggle_start += text_rect.mLeft; + + pony = (squiggle_end + 3) / 6; + squiggle_start += squiggle_end / 2 - pony * 3; + squiggle_end = squiggle_start + pony * 6; + + gGL.color4ub(255, 0, 0, 200); + while (squiggle_start < squiggle_end) + { + gl_line_2d(squiggle_start, text_rect.mBottom - 2, squiggle_start + 3, text_rect.mBottom + 1); + gl_line_2d(squiggle_start + 3, text_rect.mBottom + 1, squiggle_start + 6, text_rect.mBottom - 2); + squiggle_start += 6; + } + + if (misspell_it->second > seg_end) + break; + ++misspell_it; + } + text_rect.mLeft = (S32)(cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect)); seg_start = clipped_end + cur_segment->getStart(); @@ -1103,6 +1222,94 @@ void LLTextBase::deselect() mIsSelecting = FALSE; } +bool LLTextBase::getSpellCheck() const +{ + return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); +} + +const std::string& LLTextBase::getSuggestion(U32 index) const +{ + return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; +} + +U32 LLTextBase::getSuggestionCount() const +{ + return mSuggestionList.size(); +} + +void LLTextBase::replaceWithSuggestion(U32 index) +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + { + deselect(); + + // Delete the misspelled word + removeStringNoUndo(it->first, it->second - it->first); + + // Insert the suggestion in its place + LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); + insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index])); + setCursorPos(it->first + (S32)suggestion.length()); + + break; + } + } + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLTextBase::addToDictionary() +{ + if (canAddToDictionary()) + { + LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); + } +} + +bool LLTextBase::canAddToDictionary() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +void LLTextBase::addToIgnore() +{ + if (canAddToIgnore()) + { + LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); + } +} + +bool LLTextBase::canAddToIgnore() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +std::string LLTextBase::getMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + return wstring_to_utf8str(getWText().substr(it->first, it->second - it->first)); + } + return LLStringUtil::null; +} + +bool LLTextBase::isMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + return true; + } + return false; +} + +void LLTextBase::onSpellCheckSettingsChange() +{ + // Recheck the spelling on every change + mSpellCheckStart = mSpellCheckEnd = -1; +} // Sets the scrollbar from the cursor position void LLTextBase::updateScrollFromCursor() diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 0549141b72..90b147cee1 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -30,6 +30,7 @@ #include "v4color.h" #include "lleditmenuhandler.h" +#include "llspellcheckmenuhandler.h" #include "llstyle.h" #include "llkeywords.h" #include "llpanel.h" @@ -230,7 +231,8 @@ typedef LLPointer LLTextSegmentPtr; /// class LLTextBase : public LLUICtrl, - protected LLEditMenuHandler + protected LLEditMenuHandler, + public LLSpellCheckMenuHandler { public: friend class LLTextSegment; @@ -259,6 +261,7 @@ public: border_visible, track_end, read_only, + spellcheck, allow_scroll, plain_text, wrap, @@ -311,6 +314,24 @@ public: /*virtual*/ BOOL canDeselect() const; /*virtual*/ void deselect(); + // LLSpellCheckMenuHandler overrides + /*virtual*/ bool getSpellCheck() const; + + /*virtual*/ const std::string& getSuggestion(U32 index) const; + /*virtual*/ U32 getSuggestionCount() const; + /*virtual*/ void replaceWithSuggestion(U32 index); + + /*virtual*/ void addToDictionary(); + /*virtual*/ bool canAddToDictionary() const; + + /*virtual*/ void addToIgnore(); + /*virtual*/ bool canAddToIgnore() const; + + // Spell checking helper functions + std::string getMisspelledWord(U32 pos) const; + bool isMisspelledWord(U32 pos) const; + void onSpellCheckSettingsChange(); + // used by LLTextSegment layout code bool getWordWrap() { return mWordWrap; } bool getUseEllipses() { return mUseEllipses; } @@ -540,6 +561,14 @@ protected: BOOL mIsSelecting; // Are we in the middle of a drag-select? + // spell checking + bool mSpellCheck; + S32 mSpellCheckStart; + S32 mSpellCheckEnd; + LLTimer mSpellCheckTimer; + std::list > mMisspellRanges; + std::vector mSuggestionList; + // configuration S32 mHPad; // padding on left of text S32 mVPad; // padding above text diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3a23ce1cac..c5957838ba 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -54,6 +54,7 @@ #include "llwindow.h" #include "lltextparser.h" #include "llscrollcontainer.h" +#include "llspellcheck.h" #include "llpanel.h" #include "llurlregistry.h" #include "lltooltip.h" @@ -77,6 +78,7 @@ template class LLTextEditor* LLView::getChild( const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32; const S32 UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4; const S32 SPACES_PER_TAB = 4; +const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on /////////////////////////////////////////////////////////////////// @@ -1961,7 +1963,34 @@ void LLTextEditor::showContextMenu(S32 x, S32 y) S32 screen_x, screen_y; localPointToScreen(x, y, &screen_x, &screen_y); - mContextMenu->show(screen_x, screen_y); + + setCursorAtLocalPos(x, y, false); + if (hasSelection()) + { + if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) + deselect(); + else + setCursorPos(llmax(mSelectionStart, mSelectionEnd)); + } + + bool use_spellcheck = getSpellCheck(), is_misspelled = false; + if (use_spellcheck) + { + mSuggestionList.clear(); + + // If the cursor is on a misspelled word, retrieve suggestions for it + std::string misspelled_word = getMisspelledWord(mCursorPos); + if ((is_misspelled = !misspelled_word.empty()) == true) + { + LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList); + } + } + + mContextMenu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); + mContextMenu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); + mContextMenu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); + mContextMenu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); + mContextMenu->show(screen_x, screen_y, this); } @@ -2846,6 +2875,9 @@ void LLTextEditor::setKeystrokeCallback(const keystroke_signal_t::slot_type& cal void LLTextEditor::onKeyStroke() { mKeystrokeSignal(this); + + mSpellCheckStart = mSpellCheckEnd = -1; + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } //virtual -- cgit v1.2.3 From 41e11a508379d8f6d2b95f835d2df9f4e2bbea01 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Fri, 3 Feb 2012 15:34:52 +0100 Subject: STORM-276 FIXED Selecting a character in a line editor and replacing it doesn't trigger a spell check --- indra/llui/lllineeditor.cpp | 53 ++++++++++++++++++++------------------------- indra/llui/lllineeditor.h | 1 + 2 files changed, 24 insertions(+), 30 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 5479c080bd..e67753292e 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -66,7 +66,7 @@ const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing const F32 AUTO_SCROLL_TIME = 0.05f; const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. *TODO: make this equal to the double click interval? -const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and showing spell checking feedback for the word the cursor is on +const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET @@ -1158,9 +1158,8 @@ void LLLineEditor::cut() LLUI::reportBadKeystroke(); } else - if( mKeystrokeCallback ) { - mKeystrokeCallback( this ); + onKeystroke(); } } } @@ -1294,9 +1293,8 @@ void LLLineEditor::pasteHelper(bool is_primary) LLUI::reportBadKeystroke(); } else - if( mKeystrokeCallback ) { - mKeystrokeCallback( this ); + onKeystroke(); } } } @@ -1549,10 +1547,7 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask ) // Notify owner if requested if (!need_to_rollback && handled) { - if (mKeystrokeCallback) - { - mKeystrokeCallback(this); - } + onKeystroke(); if ( (!selection_modified) && (KEY_BACKSPACE == key) ) { mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); @@ -1608,12 +1603,10 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) // Notify owner if requested if( !need_to_rollback && handled ) { - if( mKeystrokeCallback ) - { - // HACK! The only usage of this callback doesn't do anything with the character. - // We'll have to do something about this if something ever changes! - Doug - mKeystrokeCallback( this ); - } + // HACK! The only usage of this callback doesn't do anything with the character. + // We'll have to do something about this if something ever changes! - Doug + onKeystroke(); + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } } @@ -1643,9 +1636,7 @@ void LLLineEditor::doDelete() if (!prevalidateInput(text_to_delete)) { - if( mKeystrokeCallback ) - mKeystrokeCallback( this ); - + onKeystroke(); return; } setCursor(getCursor() + 1); @@ -1661,10 +1652,8 @@ void LLLineEditor::doDelete() } else { - if( mKeystrokeCallback ) - { - mKeystrokeCallback( this ); - } + onKeystroke(); + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } } @@ -2296,6 +2285,15 @@ void LLLineEditor::setSelectAllonFocusReceived(BOOL b) mSelectAllonFocusReceived = b; } +void LLLineEditor::onKeystroke() +{ + if (mKeystrokeCallback) + { + mKeystrokeCallback(this); + } + + mSpellCheckStart = mSpellCheckEnd = -1; +} void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data) { @@ -2418,10 +2416,8 @@ void LLLineEditor::updatePreedit(const LLWString &preedit_string, // Update of the preedit should be caused by some key strokes. mKeystrokeTimer.reset(); - if( mKeystrokeCallback ) - { - mKeystrokeCallback( this ); - } + onKeystroke(); + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } @@ -2575,6 +2571,7 @@ void LLLineEditor::showContextMenu(S32 x, S32 y) S32 screen_x, screen_y; localPointToScreen(x, y, &screen_x, &screen_y); + setCursorAtLocalPos(x); if (hasSelection()) { if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) @@ -2582,10 +2579,6 @@ void LLLineEditor::showContextMenu(S32 x, S32 y) else setCursor(llmax(mSelectionStart, mSelectionEnd)); } - else - { - setCursorAtLocalPos(x); - } bool use_spellcheck = getSpellCheck(), is_misspelled = false; if (use_spellcheck) diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 9513274f21..40f931ecc1 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -243,6 +243,7 @@ public: void setSelectAllonFocusReceived(BOOL b); void setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; } + void onKeystroke(); typedef boost::function callback_t; void setKeystrokeCallback(callback_t callback, void* user_data); -- 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/lllineeditor.cpp | 1 + indra/llui/llspellcheck.cpp | 12 ++++++------ indra/llui/llspellcheck.h | 29 ++++++++++++++++------------- indra/llui/lltextbase.cpp | 1 + 4 files changed, 24 insertions(+), 19 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index e67753292e..42cfc4cae9 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -617,6 +617,7 @@ bool LLLineEditor::isMisspelledWord(U32 pos) const void LLLineEditor::onSpellCheckSettingsChange() { // Recheck the spelling on every change + mMisspellRanges.clear(); mSpellCheckStart = mSpellCheckEnd = -1; } 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()) ) diff --git a/indra/llui/llspellcheck.h b/indra/llui/llspellcheck.h index affdac2907..8351655b49 100644 --- a/indra/llui/llspellcheck.h +++ b/indra/llui/llspellcheck.h @@ -44,17 +44,20 @@ public: void addToIgnoreList(const std::string& word); bool checkSpelling(const std::string& word) const; S32 getSuggestions(const std::string& word, std::vector& suggestions) const; - -public: - const LLSD getDictionaryData(const std::string& dict_name) const; - const LLSD& getDictionaryMap() const { return mDictMap; } - void refreshDictionaryMap(); - void setSecondaryDictionaries(std::list dictList); protected: - void addToDictFile(const std::string& dict_path, const std::string& word); - void initHunspell(const std::string& dict_name); + void addToDictFile(const std::string& dict_path, const std::string& word); + void initHunspell(const std::string& dict_name); public: + typedef std::list dict_list_t; + + const std::string& getActiveDictionary() const { return mDictName; } + const LLSD getDictionaryData(const std::string& dict_name) const; + const LLSD& getDictionaryMap() const { return mDictMap; } + const dict_list_t& getSecondaryDictionaries() const { return mDictSecondary; } + void refreshDictionaryMap(); + void setSecondaryDictionaries(dict_list_t dict_list); + static const std::string getDictionaryAppPath(); static const std::string getDictionaryUserPath(); static bool getUseSpellCheck(); @@ -64,11 +67,11 @@ public: 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 mDictSecondary; + Hunspell* mHunspell; + std::string mDictName; + std::string mDictFile; + LLSD mDictMap; + dict_list_t mDictSecondary; std::vector mIgnoreList; static settings_change_signal_t sSettingsChangeSignal; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 7eee1d39c4..2231d9b983 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1308,6 +1308,7 @@ bool LLTextBase::isMisspelledWord(U32 pos) const void LLTextBase::onSpellCheckSettingsChange() { // Recheck the spelling on every change + mMisspellRanges.clear(); mSpellCheckStart = mSpellCheckEnd = -1; } -- 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 +++++++++++++++----- indra/llui/llspellcheck.h | 14 +++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) (limited to 'indra/llui') 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 class Hunspell; -class LLSpellChecker : public LLSingleton +class LLSpellChecker : public LLSingleton, public LLInitClass { friend class LLSingleton; + friend class LLInitClass; protected: LLSpellChecker(); ~LLSpellChecker(); @@ -52,28 +54,30 @@ public: typedef std::list dict_list_t; const std::string& getActiveDictionary() const { return mDictName; } - const LLSD getDictionaryData(const std::string& dict_name) const; - const LLSD& getDictionaryMap() const { return mDictMap; } const dict_list_t& getSecondaryDictionaries() const { return mDictSecondary; } - void refreshDictionaryMap(); void setSecondaryDictionaries(dict_list_t dict_list); static const std::string getDictionaryAppPath(); static const std::string getDictionaryUserPath(); + static const LLSD getDictionaryData(const std::string& dict_name); + static const LLSD& getDictionaryMap() { return sDictMap; } static bool getUseSpellCheck(); + static void refreshDictionaryMap(); static void setUseSpellCheck(const std::string& dict_name); typedef boost::signals2::signal settings_change_signal_t; static boost::signals2::connection setSettingsChangeCallback(const settings_change_signal_t::slot_type& cb); +protected: + static void initClass(); protected: Hunspell* mHunspell; std::string mDictName; std::string mDictFile; - LLSD mDictMap; dict_list_t mDictSecondary; std::vector mIgnoreList; + static LLSD sDictMap; static settings_change_signal_t sSettingsChangeSignal; }; -- cgit v1.2.3 From c34b440fec36248ba63a5e85b47caa7271d31bec Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 14 Feb 2012 21:57:58 +0100 Subject: STORM-276 FIXED Typing in an LLTextEditor freezes the viewer --- indra/llui/lltextbase.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 2231d9b983..2d7516a1cd 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -552,7 +552,7 @@ void LLTextBase::drawText() const LLWString& wstrText = getWText(); mMisspellRanges.clear(); - segment_set_t::iterator seg_it = getSegIterContaining(start); + segment_set_t::const_iterator seg_it = getSegIterContaining(start); while (mSegments.end() != seg_it) { LLTextSegmentPtr text_segment = *seg_it; @@ -673,7 +673,10 @@ void LLTextBase::drawText() { // Skip the current word if the user is still busy editing it if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) ) + { + ++misspell_it; continue; + } S32 squiggle_start = 0, squiggle_end = 0, pony = 0; cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_it->first - seg_start, squiggle_start, pony); -- cgit v1.2.3 From 9c792e337695b904e4f52fce83b293a2e97fdeaf Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 14 Feb 2012 22:33:28 +0100 Subject: STORM-276 FIXED Misspellings that span multiple lines don't have their squiggly lines drawn correctly --- indra/llui/lltextbase.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 2d7516a1cd..ce405cd4a4 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -678,9 +678,10 @@ void LLTextBase::drawText() continue; } + U32 misspell_start = llmax(misspell_it->first, seg_start), misspell_end = llmin(misspell_it->second, seg_end); S32 squiggle_start = 0, squiggle_end = 0, pony = 0; - cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_it->first - seg_start, squiggle_start, pony); - cur_segment->getDimensions(misspell_it->first - cur_segment->getStart(), misspell_it->second - misspell_it->first, squiggle_end, pony); + cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_start - seg_start, squiggle_start, pony); + cur_segment->getDimensions(misspell_start - cur_segment->getStart(), misspell_end - misspell_start, squiggle_end, pony); squiggle_start += text_rect.mLeft; pony = (squiggle_end + 3) / 6; -- cgit v1.2.3 From e615660823e680e824d2db0f1a59917597e64a13 Mon Sep 17 00:00:00 2001 From: Kitty Barnett 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') 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 59bfc3f608bed806e03898b833a0fda9f95d42b9 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 25 Mar 2012 19:50:46 +0200 Subject: Minor fix for GCC --- indra/llui/lllineeditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 42cfc4cae9..0dc381231d 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -1948,8 +1948,8 @@ void LLLineEditor::draw() gGL.color4ub(255, 0, 0, 200); while (pxStart < pxEnd) { - gl_line_2d(pxStart, text_bottom - 2, pxStart + 3, text_bottom + 1); - gl_line_2d(pxStart + 3, text_bottom + 1, pxStart + 6, text_bottom - 2); + gl_line_2d(pxStart, (S32)text_bottom - 2, pxStart + 3, (S32)text_bottom + 1); + gl_line_2d(pxStart + 3, (S32)text_bottom + 1, pxStart + 6, (S32)text_bottom - 2); pxStart += 6; } } -- cgit v1.2.3 From 63196a9c08c94ced5bb9ff5b9e623564d956bcd8 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Tue, 10 Apr 2012 15:57:39 -0400 Subject: add hunspell libs --- indra/llui/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 1377336bb4..2208a2f327 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories( ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LLXUIXML_INCLUDE_DIRS} + ${LIBS_PREBUILD_DIR}/include/hunspell ) set(llui_SOURCE_FILES @@ -252,6 +253,7 @@ add_library (llui ${llui_SOURCE_FILES}) # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level target_link_libraries(llui + ${HUNSPELL_LIBRARY} ${LLMESSAGE_LIBRARIES} ${LLRENDER_LIBRARIES} ${LLWINDOW_LIBRARIES} -- cgit v1.2.3 From 51ceacbb45802209c36c555c03eeeb2f5fd1d3b4 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Wed, 11 Apr 2012 12:14:35 -0400 Subject: fix ordering problem in libraries? --- indra/llui/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 2208a2f327..d0fd37a5b4 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -253,7 +253,6 @@ add_library (llui ${llui_SOURCE_FILES}) # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level target_link_libraries(llui - ${HUNSPELL_LIBRARY} ${LLMESSAGE_LIBRARIES} ${LLRENDER_LIBRARIES} ${LLWINDOW_LIBRARIES} @@ -263,6 +262,7 @@ target_link_libraries(llui ${LLXUIXML_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} + ${HUNSPELL_LIBRARY} ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender ) -- cgit v1.2.3 From 22f26725b902cb2e942048d059710169666e4a45 Mon Sep 17 00:00:00 2001 From: Jonathan Yap Date: Fri, 10 Feb 2012 17:40:58 -0500 Subject: STORM-1738 Autocorrect working for nearby chat and IM input boxes warn-on-failure:open-license --- indra/llui/lllineeditor.cpp | 3 +-- indra/llui/lllineeditor.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index de951d3b56..4d0b972bf1 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -866,11 +866,10 @@ void LLLineEditor::addChar(const llwchar uni_char) LLUI::reportBadKeystroke(); } -// *TODO implement callback routine if (!mReadOnly && mAutocorrectCallback != NULL) { // call callback - // mAutotocorrectCallback(mText&, mCursorPos&); + mAutocorrectCallback(mText, mCursorPos); } getWindow()->hideCursorUntilMouseMove(); diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 9394700e8c..53af9ac996 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -169,8 +169,7 @@ public: virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); - // *TODO Make this work - typedef boost::function autocorrect_callback_t; + typedef boost::function autocorrect_callback_t; autocorrect_callback_t mAutocorrectCallback; void setAutocorrectCallback(autocorrect_callback_t cb) { mAutocorrectCallback = cb; } void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } -- cgit v1.2.3 From 3efa013ee4e037c2ba20aaf550aa06bcf578b145 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 13 Apr 2012 11:55:44 -0500 Subject: MAINT-939 Potential fix for crash when editing objects. --- indra/llui/llui.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index a38d0a0b0b..49666a991d 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -831,7 +831,11 @@ void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LL gGL.flush(); glLineWidth(2.5f); - glLineStipple(2, 0x3333 << shift); + + if (!LLGLSLShader::sNoFixedFunction) + { + glLineStipple(2, 0x3333 << shift); + } gGL.begin(LLRender::LINES); { -- cgit v1.2.3 From 4472354b5454f758c467bb23190d1a84cf61d909 Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Mon, 23 Apr 2012 18:48:43 -0700 Subject: SEC-995 FIX viewer is easily spammed to death by chat removed unnecessary string copies from chat message handling --- indra/llui/lltextbase.cpp | 25 ++++++++++++------------- indra/llui/lltextbox.cpp | 2 +- indra/llui/llviewmodel.h | 1 + 3 files changed, 14 insertions(+), 14 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0040be45c7..c2cd590acb 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -280,12 +280,11 @@ bool LLTextBase::truncate() if (getLength() >= S32(mMaxTextByteLength / 4)) { // Have to check actual byte size - LLWString text(getWText()); - S32 utf8_byte_size = wstring_utf8_length(text); + S32 utf8_byte_size = getViewModel()->getValue().asString().size(); if ( utf8_byte_size > mMaxTextByteLength ) { // Truncate safely in UTF-8 - std::string temp_utf8_text = wstring_to_utf8str(text); + std::string temp_utf8_text = getViewModel()->getValue().asString(); temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); LLWString text = utf8str_to_wstring( temp_utf8_text ); // remove extra bit of current string, to preserve formatting, etc. @@ -592,8 +591,7 @@ void LLTextBase::drawText() S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments ) { - LLWString text(getWText()); - S32 old_len = text.length(); // length() returns character length + S32 old_len = getLength(); // length() returns character length S32 insert_len = wstr.length(); pos = getEditableIndex(pos, true); @@ -653,8 +651,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } } - text.insert(pos, wstr); - getViewModel()->setDisplay(text); + getViewModel()->getEditableDisplay().insert(pos, wstr); if ( truncate() ) { @@ -669,7 +666,6 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) { - LLWString text(getWText()); segment_set_t::iterator seg_iter = getSegIterContaining(pos); while(seg_iter != mSegments.end()) { @@ -715,8 +711,7 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) ++seg_iter; } - text.erase(pos, length); - getViewModel()->setDisplay(text); + getViewModel()->getEditableDisplay().erase(pos, length); // recreate default segment in case we erased everything createDefaultSegment(); @@ -733,9 +728,7 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) { return 0; } - LLWString text(getWText()); - text[pos] = wc; - getViewModel()->setDisplay(text); + getViewModel()->getEditableDisplay()[pos] = wc; onValueChange(pos, pos + 1); needsReflow(pos); @@ -1682,6 +1675,8 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name) } } +static LLFastTimer::DeclareTimer FTM_PARSE_HTML("Parse HTML"); + void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) { LLStyle::Params style_params(input_params); @@ -1690,6 +1685,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para S32 part = (S32)LLTextParser::WHOLE; if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { + LLFastTimer _(FTM_PARSE_HTML); S32 start=0,end=0; LLUrlMatch match; std::string text = new_text; @@ -1757,8 +1753,11 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para } } +static LLFastTimer::DeclareTimer FTM_APPEND_TEXT("Append Text"); + void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) { + LLFastTimer _(FTM_APPEND_TEXT); if (new_text.empty()) return; diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index 6a905b7ec0..11cfa1d263 100644 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -150,7 +150,7 @@ S32 LLTextBox::getTextPixelHeight() LLSD LLTextBox::getValue() const { - return LLSD(getText()); + return getViewModel()->getValue(); } BOOL LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text ) diff --git a/indra/llui/llviewmodel.h b/indra/llui/llviewmodel.h index 763af5d8a2..ef2e314799 100644 --- a/indra/llui/llviewmodel.h +++ b/indra/llui/llviewmodel.h @@ -102,6 +102,7 @@ public: // New functions /// Get the stored value in string form const LLWString& getDisplay() const { return mDisplay; } + LLWString& getEditableDisplay() { mDirty = true; mUpdateFromDisplay = true; return mDisplay; } /** * Set the display string directly (see LLTextEditor). What the user is -- cgit v1.2.3 From 6d3be57d334f02f4781dcaede9746976bd3643a4 Mon Sep 17 00:00:00 2001 From: Kelly Washington Date: Wed, 25 Apr 2012 11:26:54 -0700 Subject: SEC-995 FIX viewer is easily spammed to death by chat removed a couple more unnecessary string copies from unfortunate LLSD behavior. reviewed with simon, post review from Richard. --- indra/llui/lltextbase.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index c2cd590acb..813f919505 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -280,11 +280,23 @@ bool LLTextBase::truncate() if (getLength() >= S32(mMaxTextByteLength / 4)) { // Have to check actual byte size - S32 utf8_byte_size = getViewModel()->getValue().asString().size(); + S32 utf8_byte_size = 0; + LLSD value = getViewModel()->getValue(); + if (value.type() == LLSD::TypeString) + { + // save a copy for strings. + utf8_byte_size = value.size(); + } + else + { + // non string LLSDs need explicit conversion to string + utf8_byte_size = value.asString().size(); + } + if ( utf8_byte_size > mMaxTextByteLength ) { // Truncate safely in UTF-8 - std::string temp_utf8_text = getViewModel()->getValue().asString(); + std::string temp_utf8_text = value.asString(); temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); LLWString text = utf8str_to_wstring( temp_utf8_text ); // remove extra bit of current string, to preserve formatting, etc. -- cgit v1.2.3 From bc4d0b0b0f0cf8ee305598d66c78f4a5a723a955 Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Thu, 23 Feb 2012 21:02:46 +0200 Subject: Linux build fix. Moved type casts from protected base classes to derived LLCoord. (transplanted from fa4c01adac7224b909ab1dbfc90416b633e6d6c2) --- indra/llui/llfloater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 22b20969fc..51611d547c 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -3306,7 +3306,7 @@ bool LLCoordFloater::operator==(const LLCoordFloater& other) const LLCoordCommon LL_COORD_FLOATER::convertToCommon() const { - const LLCoordFloater& self = static_cast(*this); + const LLCoordFloater& self = static_cast(LLCoordFloater::getTypedCoords(*this)); LLRect snap_rect = gFloaterView->getSnapRect(); LLFloater* floaterp = mFloater.get(); @@ -3348,7 +3348,7 @@ LLCoordCommon LL_COORD_FLOATER::convertToCommon() const void LL_COORD_FLOATER::convertFromCommon(const LLCoordCommon& from) { - LLCoordFloater& self = static_cast(*this); + LLCoordFloater& self = static_cast(LLCoordFloater::getTypedCoords(*this)); LLRect snap_rect = gFloaterView->getSnapRect(); LLFloater* floaterp = mFloater.get(); S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; -- cgit v1.2.3 From 80e64f26a13a8cbfe32d94b33fff57ddc7f21731 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Tue, 6 Mar 2012 15:30:08 -0500 Subject: via richard - fix to keep texture debug console anchored at the top --- indra/llui/llcontainerview.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llcontainerview.cpp b/indra/llui/llcontainerview.cpp index e01e331acf..e08ccb0b78 100644 --- a/indra/llui/llcontainerview.cpp +++ b/indra/llui/llcontainerview.cpp @@ -196,24 +196,24 @@ void LLContainerView::arrange(S32 width, S32 height, BOOL called_from_parent) if (total_height < height) total_height = height; + LLRect my_rect = getRect(); if (followsTop()) { - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast(getRect()).mBottom = getRect().mTop - total_height; + my_rect.mBottom = my_rect.mTop - total_height; } else { - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast(getRect()).mTop = getRect().mBottom + total_height; + my_rect.mTop = my_rect.mBottom + total_height; } - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast(getRect()).mRight = getRect().mLeft + width; + + my_rect.mRight = my_rect.mLeft + width; + setRect(my_rect); top = total_height; if (mShowLabel) - { - top -= 20; - } + { + top -= 20; + } bottom = top; -- cgit v1.2.3 From 7bc112ac9f5bf15b35c494a44e8c85767b1994a6 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Tue, 6 Mar 2012 16:53:31 -0500 Subject: texture view console tweaks --- indra/llui/llcontainerview.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 indra/llui/llcontainerview.cpp (limited to 'indra/llui') diff --git a/indra/llui/llcontainerview.cpp b/indra/llui/llcontainerview.cpp old mode 100644 new mode 100755 -- cgit v1.2.3 From 3672956f0da4020335fc8c3066f1099c1ceaa00d Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Thu, 15 Mar 2012 11:57:29 -0700 Subject: added stats tracking for texture/object cache hit rates and texture cache read latency --- indra/llui/llstatbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llstatbar.cpp b/indra/llui/llstatbar.cpp index ec4db14790..04cce7878e 100644 --- a/indra/llui/llstatbar.cpp +++ b/indra/llui/llstatbar.cpp @@ -272,7 +272,7 @@ LLRect LLStatBar::getRequiredRect() { if (mDisplayHistory) { - rect.mTop = 67; + rect.mTop = 35 + mStatp->getNumBins(); } else { -- cgit v1.2.3 From fc95597c63166c527f92f9170ccdc67a51f01481 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Wed, 28 Mar 2012 21:19:33 +0300 Subject: MAINT-405 FIXED ([PUBLIC]Ctrl W does not properly close IM tabs using tabbed IM in 2.x/3.x) - Now tabs are closed with Ctrl+W as expected --- indra/llui/llmultifloater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp index f3a48835b1..aa5f577897 100644 --- a/indra/llui/llmultifloater.cpp +++ b/indra/llui/llmultifloater.cpp @@ -349,7 +349,7 @@ void LLMultiFloater::setVisible(BOOL visible) BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask) { - if (key == 'W' && mask == (MASK_CONTROL|MASK_SHIFT)) + if (key == 'W' && mask == MASK_CONTROL) { LLFloater* floater = getActiveFloater(); // is user closeable and is system closeable -- cgit v1.2.3 From 9bdb1d82f867147af44def8c1cca3dfb8259b99c Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Fri, 11 May 2012 12:22:43 -0400 Subject: rename feature from "autocorrect" to "autoreplace", change names accordingly --- indra/llui/lllineeditor.cpp | 6 +++--- indra/llui/lllineeditor.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 1f7843b79d..0f328aa065 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -152,7 +152,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mPreeditBgColor(p.preedit_bg_color()), mGLFont(p.font), mContextMenuHandle(), - mAutocorrectCallback() + mAutoreplaceCallback() { llassert( mMaxLengthBytes > 0 ); @@ -866,10 +866,10 @@ void LLLineEditor::addChar(const llwchar uni_char) LLUI::reportBadKeystroke(); } - if (!mReadOnly && mAutocorrectCallback != NULL) + if (!mReadOnly && mAutoreplaceCallback != NULL) { // call callback - mAutocorrectCallback(mText, mCursorPos); + mAutoreplaceCallback(mText, mCursorPos); } getWindow()->hideCursorUntilMouseMove(); diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 53af9ac996..cd4be17f37 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -169,9 +169,9 @@ public: virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); - typedef boost::function autocorrect_callback_t; - autocorrect_callback_t mAutocorrectCallback; - void setAutocorrectCallback(autocorrect_callback_t cb) { mAutocorrectCallback = cb; } + typedef boost::function autoreplace_callback_t; + autoreplace_callback_t mAutoreplaceCallback; + void setAutoreplaceCallback(autoreplace_callback_t cb) { mAutoreplaceCallback = cb; } void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } const std::string& getLabel() { return mLabel.getString(); } -- cgit v1.2.3 From 34e7226ac88e14d4cfed6bc0d63da215afe0ac88 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 15 May 2012 13:13:44 +0200 Subject: STORM-276 FIXED Squiggles overflow line editor height when font size is set to large - Also reduced squiggle width from 6 to 4 and prevented running past the end of a word --- indra/llui/lllineeditor.cpp | 15 +++++++++++---- indra/llui/lltextbase.cpp | 11 +++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index f119b0d9bc..d87b9d930c 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -1727,6 +1727,10 @@ void LLLineEditor::draw() background.stretch( -mBorderThickness ); S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2; + if (mSpellCheck) + { + lineeditor_v_pad += 1; + } drawBackground(); @@ -1945,12 +1949,15 @@ void LLLineEditor::draw() if (pxEnd > pxWidth) pxEnd = pxWidth; + S32 pxBottom = (S32)(text_bottom + mGLFont->getDescenderHeight()); + gGL.color4ub(255, 0, 0, 200); - while (pxStart < pxEnd) + while (pxStart + 1 < pxEnd) { - gl_line_2d(pxStart, (S32)text_bottom - 2, pxStart + 3, (S32)text_bottom + 1); - gl_line_2d(pxStart + 3, (S32)text_bottom + 1, pxStart + 6, (S32)text_bottom - 2); - pxStart += 6; + gl_line_2d(pxStart, pxBottom, pxStart + 2, pxBottom - 2); + if (pxStart + 3 < pxEnd) + gl_line_2d(pxStart + 2, pxBottom - 3, pxStart + 4, pxBottom - 1); + pxStart += 4; } } } diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 990c442b73..4db1efdd20 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -688,12 +688,15 @@ void LLTextBase::drawText() squiggle_start += squiggle_end / 2 - pony * 3; squiggle_end = squiggle_start + pony * 6; + S32 squiggle_bottom = text_rect.mBottom + (S32)cur_segment->getStyle()->getFont()->getDescenderHeight(); + gGL.color4ub(255, 0, 0, 200); - while (squiggle_start < squiggle_end) + while (squiggle_start + 1 < squiggle_end) { - gl_line_2d(squiggle_start, text_rect.mBottom - 2, squiggle_start + 3, text_rect.mBottom + 1); - gl_line_2d(squiggle_start + 3, text_rect.mBottom + 1, squiggle_start + 6, text_rect.mBottom - 2); - squiggle_start += 6; + gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2); + if (squiggle_start + 3 < squiggle_end) + gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1); + squiggle_start += 4; } if (misspell_it->second > seg_end) -- cgit v1.2.3 From 8199cc997aeaccc72b5bd5389ded8afc7b9c379c Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 15 May 2012 14:09:13 +0200 Subject: STORM-276 FIXED Right-to-left line editor selection becomes invisible if the cursor moves --- indra/llui/lllineeditor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index d87b9d930c..83527ae5ad 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -1805,14 +1805,14 @@ void LLLineEditor::draw() { S32 select_left; S32 select_right; - if( mSelectionStart < getCursor() ) + if (mSelectionStart < mSelectionEnd) { select_left = mSelectionStart; - select_right = getCursor(); + select_right = mSelectionEnd; } else { - select_left = getCursor(); + select_left = mSelectionEnd; select_right = mSelectionStart; } -- cgit v1.2.3 From f4edcdd4090cb6704603d5ef0dc8cf7a3f67399e Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Thu, 17 May 2012 21:02:53 +0300 Subject: MAINT-938 FIXED (Make debit-permissions dialog give a stronger warning) - Added new tag called "footer" to the notification. This tag allows to display messages under the buttons in toasts. - Created new class LLToastScriptQuestion for the "ScriptQuestionCaution" notification. This notification supports tag