diff options
Diffstat (limited to 'indra/llui')
-rw-r--r-- | indra/llui/lllineeditor.cpp | 54 | ||||
-rw-r--r-- | indra/llui/lllineeditor.h | 1 | ||||
-rw-r--r-- | indra/llui/llspellcheck.cpp | 12 | ||||
-rw-r--r-- | indra/llui/llspellcheck.h | 29 | ||||
-rw-r--r-- | indra/llui/lltextbase.cpp | 210 | ||||
-rw-r--r-- | indra/llui/lltextbase.h | 31 | ||||
-rw-r--r-- | indra/llui/lltexteditor.cpp | 34 |
7 files changed, 319 insertions, 52 deletions
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 5479c080bd..42cfc4cae9 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 @@ -617,6 +617,7 @@ bool LLLineEditor::isMisspelledWord(U32 pos) const void LLLineEditor::onSpellCheckSettingsChange() { // Recheck the spelling on every change + mMisspellRanges.clear(); mSpellCheckStart = mSpellCheckEnd = -1; } @@ -1158,9 +1159,8 @@ void LLLineEditor::cut() LLUI::reportBadKeystroke(); } else - if( mKeystrokeCallback ) { - mKeystrokeCallback( this ); + onKeystroke(); } } } @@ -1294,9 +1294,8 @@ void LLLineEditor::pasteHelper(bool is_primary) LLUI::reportBadKeystroke(); } else - if( mKeystrokeCallback ) { - mKeystrokeCallback( this ); + onKeystroke(); } } } @@ -1549,10 +1548,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 +1604,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 +1637,7 @@ void LLLineEditor::doDelete() if (!prevalidateInput(text_to_delete)) { - if( mKeystrokeCallback ) - mKeystrokeCallback( this ); - + onKeystroke(); return; } setCursor(getCursor() + 1); @@ -1661,10 +1653,8 @@ void LLLineEditor::doDelete() } else { - if( mKeystrokeCallback ) - { - mKeystrokeCallback( this ); - } + onKeystroke(); + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); } } @@ -2296,6 +2286,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 +2417,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 +2572,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 +2580,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<void (LLLineEditor* caller, void* user_data)> callback_t; void setKeystrokeCallback(callback_t callback, void* user_data); 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<std::string> dict_list) +void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list) { if (!getUseSpellCheck()) { @@ -194,11 +194,11 @@ void LLSpellChecker::setSecondaryDictionaries(std::list<std::string> dict_list) } // Check if we're only adding secondary dictionaries, or removing them - std::list<std::string> dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size())); + dict_list_t dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size())); dict_list.sort(); mDictSecondary.sort(); - std::list<std::string>::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin()); - std::list<std::string>::iterator end_removed = std::set_difference(mDictSecondary.begin(), mDictSecondary.end(), dict_list.begin(), dict_list.end(), dict_rem.begin()); + 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<std::string> dict_list) { const std::string app_path = getDictionaryAppPath(); const std::string user_path = getDictionaryUserPath(); - for (std::list<std::string>::const_iterator it_added = dict_add.begin(); it_added != end_added; ++it_added) + 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<std::string>::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<std::string>& suggestions) const; - -public: - const LLSD getDictionaryData(const std::string& dict_name) const; - const LLSD& getDictionaryMap() const { return mDictMap; } - void refreshDictionaryMap(); - void setSecondaryDictionaries(std::list<std::string> dictList); protected: - void addToDictFile(const std::string& dict_path, const std::string& word); - void initHunspell(const std::string& dict_name); + void addToDictFile(const std::string& dict_path, const std::string& word); + void initHunspell(const std::string& dict_name); public: + typedef std::list<std::string> 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<std::string> mDictSecondary; + Hunspell* mHunspell; + std::string mDictName; + std::string mDictFile; + LLSD mDictMap; + dict_list_t mDictSecondary; std::vector<std::string> mIgnoreList; static settings_change_signal_t sSettingsChangeSignal; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0040be45c7..2231d9b983 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<U32, U32>(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<std::pair<U32, U32> >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair<U32, U32>(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,95 @@ 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<std::pair<U32, U32> >::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<std::pair<U32, U32> >::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<std::pair<U32, U32> >::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 + mMisspellRanges.clear(); + 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<LLTextSegment> 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<std::pair<U32, U32> > mMisspellRanges; + std::vector<std::string> 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<class LLTextEditor>( 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 |