From ded1245db74ae4c97d174c5779f8572ee2f032fa Mon Sep 17 00:00:00 2001 From: Patrick Sapinski Date: Tue, 24 Aug 2010 03:04:20 -0400 Subject: added spellcheck + translation from Emerald Viewer. references to modularsystems.sl should be changed! --- linden/indra/llui/lllineeditor.cpp | 521 +++++++++++++++++++++++++++--- linden/indra/llui/lllineeditor.h | 47 ++- linden/indra/llui/llmenugl.cpp | 25 ++ linden/indra/llui/llmenugl.h | 3 + linden/indra/llui/lltexteditor.cpp | 639 +++++++++++++++++++++++++++++++++---- linden/indra/llui/lltexteditor.h | 74 ++++- 6 files changed, 1193 insertions(+), 116 deletions(-) (limited to 'linden/indra/llui') diff --git a/linden/indra/llui/lllineeditor.cpp b/linden/indra/llui/lllineeditor.cpp index 21f0800..5441d0a 100644 --- a/linden/indra/llui/lllineeditor.cpp +++ b/linden/indra/llui/lllineeditor.cpp @@ -37,7 +37,6 @@ #include "lllineeditor.h" #include "lltexteditor.h" -#include "audioengine.h" #include "llmath.h" #include "llfontgl.h" #include "llgl.h" @@ -57,6 +56,11 @@ #include "lluictrlfactory.h" #include "llclipboard.h" +#include "../newview/lgghunspell_wrapper.h" +#include "../newview/lltranslate.h" +#include "../newview/llviewercontrol.h" +#include "../newview/lggautocorrect.h" + // // Imported globals // @@ -89,6 +93,34 @@ static LLRegisterWidget r1("line_editor"); // // Member functions // +class LineChatTranslationReceiver : public LLTranslate::TranslationReceiver +{ +public : + LineChatTranslationReceiver(const std::string &toLang, LLLineEditor* line): LLTranslate::TranslationReceiver("", toLang), + m_line(line) + { + } + + static boost::intrusive_ptr build(const std::string &toLang,LLLineEditor* line) + { + return boost::intrusive_ptr(new LineChatTranslationReceiver(toLang,line)); + } + +protected: + void handleResponse(const std::string &translation, const std::string &detectedLanguage) + { + static BOOL* rep = rebind_llcontrol("EmeraldTranslateReplace", &gSavedSettings, true); + if(*rep) + m_line->deleteSelection(); + m_line->insert(((*rep)?"":" (") + translation + ((*rep)?"":")"),m_line->getCursor()); + } + void handleFailure() + { + LLTranslate::TranslationReceiver::handleFailure(); + } +private: + LLLineEditor* m_line; +}; LLLineEditor::LLLineEditor(const std::string& name, const LLRect& rect, const std::string& default_text, const LLFontGL* font, @@ -104,6 +136,7 @@ LLLineEditor::LLLineEditor(const std::string& name, const LLRect& rect, : LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ), mMaxLengthBytes(max_length_bytes), + mPopupMenuHandle(), mCursorPos( 0 ), mScrollHPos( 0 ), mTextPadLeft(0), @@ -137,7 +170,8 @@ LLLineEditor::LLLineEditor(const std::string& name, const LLRect& rect, mReadOnly(FALSE), mHaveHistory(FALSE), mImage( sImage ), - mReplaceNewlinesWithSpaces( TRUE ) + mReplaceNewlinesWithSpaces( TRUE ), + mOverRideAndShowMisspellings( FALSE ) { llassert( max_length_bytes > 0 ); @@ -175,6 +209,59 @@ LLLineEditor::LLLineEditor(const std::string& name, const LLRect& rect, sImage = LLUI::getUIImage("sm_rounded_corners_simple.tga"); } mImage = sImage; + // make the popup menu available + //LLMenuGL* menu = LLUICtrlFactory::getInstance()->buildMenu("menu_texteditor.xml", parent_view); + LLMenuGL* menu = new LLMenuGL("wot"); + /*if (!menu) + { + menu = new LLMenuGL(LLStringUtil::null); + }*/ + menu->append(new LLMenuItemCallGL("Cut", context_cut, NULL, this)); + menu->append(new LLMenuItemCallGL("Copy", context_copy, NULL, this)); + menu->append(new LLMenuItemCallGL("Paste", context_paste, NULL, this)); + menu->append(new LLMenuItemCallGL("Delete", context_delete, NULL, this)); + menu->append(new LLMenuItemCallGL("Select All", context_selectall, NULL, this)); + menu->appendSeparator("Transep"); + LLMenuGL* translatemenu = new LLMenuGL("Translate To"); + translatemenu->setCanTearOff(FALSE); + SpellMenuBind* t=new SpellMenuBind;t->origin=this;t->word="en"; + translatemenu->append(new LLMenuItemCallGL("English",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="da"; + translatemenu->append(new LLMenuItemCallGL("Danish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="de"; + translatemenu->append(new LLMenuItemCallGL("Deutsch(German)",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="es"; + translatemenu->append(new LLMenuItemCallGL("Spanish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="fr"; + translatemenu->append(new LLMenuItemCallGL("French",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="it"; + translatemenu->append(new LLMenuItemCallGL("Italian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="hu"; + translatemenu->append(new LLMenuItemCallGL("Hungarian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="nl"; + translatemenu->append(new LLMenuItemCallGL("Dutch",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="pl"; + translatemenu->append(new LLMenuItemCallGL("Polish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="pt"; + translatemenu->append(new LLMenuItemCallGL("Portugese",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="ru"; + translatemenu->append(new LLMenuItemCallGL("Russian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="tr"; + translatemenu->append(new LLMenuItemCallGL("Turkish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="uk"; + translatemenu->append(new LLMenuItemCallGL("Ukrainian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="zh"; + translatemenu->append(new LLMenuItemCallGL("Chinese",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="ja"; + translatemenu->append(new LLMenuItemCallGL("Japanese",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="ko"; + translatemenu->append(new LLMenuItemCallGL("Korean",translateText, NULL, t)); + menu->appendMenu(translatemenu); + menu->appendSeparator("Spelsep"); + //menu->setBackgroundColor(gColors.getColor("MenuPopupBgColor")); + menu->setCanTearOff(FALSE); + menu->setVisible(FALSE); + mPopupMenuHandle = menu->getHandle(); } @@ -188,6 +275,7 @@ LLLineEditor::~LLLineEditor() { gEditMenuHandler = NULL; } + LLView::deleteViewByHandle(mPopupMenuHandle); } @@ -343,7 +431,7 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) // Picks a new cursor position based on the actual screen size of text being drawn. -void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) +S32 LLLineEditor::calculateCursorFromMouse( S32 local_mouse_x ) { const llwchar* wtext = mText.getWString().c_str(); LLWString asterix_text; @@ -351,18 +439,22 @@ void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) { for (S32 i = 0; i < mText.length(); i++) { - asterix_text += (llwchar) 0x2022L; + asterix_text += '*'; } wtext = asterix_text.c_str(); } - S32 cursor_pos = - mScrollHPos + + return mScrollHPos + mGLFont->charFromPixelOffset( wtext, mScrollHPos, (F32)(local_mouse_x - mMinHPixels), (F32)(mMaxHPixels - mMinHPixels + 1)); // min-max range is inclusive - setCursor(cursor_pos); + +} +// Picks a new cursor position based on the actual screen size of text being drawn. +void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) +{ + setCursor(calculateCursorFromMouse(local_mouse_x)); } void LLLineEditor::setCursor( S32 pos ) @@ -418,6 +510,125 @@ void LLLineEditor::deselect() } +void LLLineEditor::context_cut(void* data) +{ + LLLineEditor* line = (LLLineEditor*)data; + if(line)line->cut(); +} +void LLLineEditor::context_copy(void* data) +{ + LLLineEditor* line = (LLLineEditor*)data; + if(line)line->copy(); +} +void LLLineEditor::spell_correct(void* data) +{ + SpellMenuBind* tempBind = (SpellMenuBind*)data; + LLLineEditor* line = tempBind->origin; + if(tempBind && line) + { + llinfos << ((LLMenuItemCallGL *)(tempBind->menuItem))->getName() << " : " << tempBind->origin->getName() << " : " << tempBind->word << llendl; + if(line)line->spellReplace(tempBind); + + } +} +void LLLineEditor::translateText(void * data) +{ + SpellMenuBind* t = (SpellMenuBind*)data; + LLLineEditor* line = t->origin; + const std::string &toLang = t->word;//LLTranslate::getTranslateLanguage(); + LLHTTPClient::ResponderPtr result = LineChatTranslationReceiver::build(toLang,line); + S32 left_pos = llmin( line->mSelectionStart, line->mSelectionEnd ); + S32 length = abs( line->mSelectionStart - line->mSelectionEnd ); + LLTranslate::translateMessage(result,"", toLang, line->mText.getString().substr(left_pos, length)); +} +void LLLineEditor::spell_show(void * data) +{ + SpellMenuBind* tempBind = (SpellMenuBind*)data; + LLLineEditor* line = tempBind->origin; + + if(tempBind && line) + { + if(tempBind->word=="Show Misspellings") + { + line->setOverRideAndShowMisspellings(TRUE); + }else + { + line->setOverRideAndShowMisspellings(FALSE); + } + } + + +} +std::vector LLLineEditor::getMisspelledWordsPositions() +{ + std::vector thePosesOfBadWords; + const LLWString& text = mText.getWString(); + + //llinfos << "end of box is at " << cursorloc << " and end of text is at " << text.length() << llendl; + S32 wordStart=0; + S32 wordEnd=mStartSpellHere; + while(wordEnd < mEndSpellHere) + { + //go through all the chars... XD + if( LLTextEditor::isPartOfWord( text[wordEnd] ) ) + + { + // Select word the cursor is over + while ((wordEnd > 0) && LLTextEditor::isPartOfWord(text[wordEnd-1])) + { + wordEnd--; + } + wordStart=wordEnd; + while ((wordEnd < (S32)text.length()) && LLTextEditor::isPartOfWord( text[wordEnd] ) ) + { + wordEnd++; + } + //got a word :D + std::string selectedWord(std::string(text.begin(), + text.end()).substr(wordStart,wordEnd-wordStart)); + + if(!glggHunSpell->isSpelledRight(selectedWord)) + { + //misspelled word here, and you have just right clicked on it! + //get the center of this word.. + //S32 center = llround( (wordEnd-wordStart)/2 ) + wordStart; + //turn this cursor position into a pixel pos + //center = findPixelNearestPos(center-getCursor()); + + thePosesOfBadWords.push_back( + wordStart); + thePosesOfBadWords.push_back(wordEnd); + } + } + wordEnd++; + } + return thePosesOfBadWords; +} +void LLLineEditor::spell_add(void* data) +{ + SpellMenuBind* tempBind = (SpellMenuBind*)data; + if(tempBind) + { + glggHunSpell->addWordToCustomDictionary(tempBind->word); + tempBind->origin->mPrevSpelledText="";//make it update + } +} +void LLLineEditor::context_paste(void* data) +{ + LLLineEditor* line = (LLLineEditor*)data; + if(line)line->paste(); +} +void LLLineEditor::context_delete(void* data) +{ + LLLineEditor* line = (LLLineEditor*)data; + if(line)line->doDelete(); +} +void LLLineEditor::context_selectall(void* data) +{ + LLLineEditor* line = (LLLineEditor*)data; + if(line)line->selectAll(); +} + void LLLineEditor::startSelection() { mIsSelecting = TRUE; @@ -508,6 +719,112 @@ BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) return TRUE; } + +BOOL LLLineEditor::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + setFocus(TRUE); + + //setCursorAtLocalPos( x); + S32 wordStart = 0; + S32 wordEnd = calculateCursorFromMouse(x); + + + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu) + { + if(menu->isOpen()) + menu->setVisible(FALSE); + for(int i = 0;i<(int)suggestionMenuItems.size();i++) + { + SpellMenuBind * tempBind = suggestionMenuItems[i]; + if(tempBind) + { + menu->remove((LLMenuItemCallGL *)tempBind->menuItem); + ((LLMenuItemCallGL *)tempBind->menuItem)->die(); + //delete tempBind->menuItem; + //tempBind->menuItem = NULL; + delete tempBind; + } + } + suggestionMenuItems.clear(); + + menu->setItemVisible("Translate To",!mReadOnly); + menu->setItemVisible("Transep",!mReadOnly); + + const LLWString& text = mText.getWString(); + if(( LLTextEditor::isPartOfWord( text[wordEnd] ) ) + &&(!mReadOnly)) + { + // Select word the cursor is over + while ((wordEnd > 0) && LLTextEditor::isPartOfWord(text[wordEnd-1])) + { + wordEnd--; + } + wordStart=wordEnd; + //startSelection(); + + while ((wordEnd < (S32)text.length()) && LLTextEditor::isPartOfWord( text[wordEnd] ) ) + { + wordEnd++; + } + std::string selectedWord(std::string(text.begin(), + text.end()).substr(wordStart,wordEnd-wordStart)); + if(!glggHunSpell->isSpelledRight(selectedWord)) + { + //misspelled word here, and you have just right clicked on it! + std::vector suggs = glggHunSpell->getSuggestionList(selectedWord); + //menu->setItemVisible("Transep",(suggs.size()>0)); + + for(int i = 0;i<(int)suggs.size();i++) + { + SpellMenuBind * tempStruct = new SpellMenuBind; + tempStruct->origin = this; + tempStruct->word = suggs[i]; + tempStruct->wordPositionEnd = wordEnd; + tempStruct->wordPositionStart=wordStart; + LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL( + tempStruct->word, spell_correct, NULL, tempStruct); + //new LLMenuItemCallGL("Select All", context_selectall, NULL, this)); + tempStruct->menuItem = suggMenuItem; + suggestionMenuItems.push_back(tempStruct); + menu->append(suggMenuItem); + } + SpellMenuBind * tempStruct = new SpellMenuBind; + tempStruct->origin = this; + tempStruct->word = selectedWord; + tempStruct->wordPositionEnd = wordEnd; + tempStruct->wordPositionStart=wordStart; + LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL( + "Add Word", spell_add, NULL, tempStruct); + tempStruct->menuItem = suggMenuItem; + suggestionMenuItems.push_back(tempStruct); + menu->append(suggMenuItem); + } + + } + if((!mReadOnly)&&((!glggHunSpell->highlightInRed) + ||(mOverRideAndShowMisspellings))) + { + SpellMenuBind * tempStruct = new SpellMenuBind; + tempStruct->origin = this; + if(mOverRideAndShowMisspellings) + tempStruct->word = "Hide Misspellings"; + else + tempStruct->word = "Show Misspellings"; + LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL( + tempStruct->word, spell_show, NULL, tempStruct); + tempStruct->menuItem = suggMenuItem; + suggestionMenuItems.push_back(tempStruct); + menu->append(suggMenuItem); + } + + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + } + return TRUE; +} + BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) { // Check first whether the "clear search" button wants to deal with this. @@ -912,7 +1229,7 @@ void LLLineEditor::deleteSelection() if( !mReadOnly && hasSelection() ) { S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 selection_length = abs( mSelectionStart - mSelectionEnd ); + S32 selection_length = llabs( mSelectionStart - mSelectionEnd ); mText.erase(left_pos, selection_length); deselect(); @@ -935,7 +1252,7 @@ void LLLineEditor::cut() S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); deleteSelection(); @@ -966,11 +1283,38 @@ void LLLineEditor::copy() if( canCopy() ) { S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); } } +void LLLineEditor::spellReplace(SpellMenuBind* spellData) +{ + mText.erase(spellData->wordPositionStart, + spellData->wordPositionEnd - spellData->wordPositionStart); + insert(spellData->word,spellData->wordPositionStart); + mCursorPos+=spellData->word.length() - (spellData->wordPositionEnd-spellData->wordPositionStart); + + +} +void LLLineEditor::insert(std::string what, S32 wher) +{ + LLLineEditorRollback rollback(this); + LLWString clean_string(utf8str_to_wstring(what)); + LLWStringUtil::replaceTabsWithSpaces(clean_string, 4); + mText.insert(wher, clean_string); + //see if we should move over the cursor acordingly + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else if( mKeystrokeCallback ) + mKeystrokeCallback( this, mCallbackUserData ); +} + BOOL LLLineEditor::canPaste() const { return !mReadOnly && gClipboard.canPasteString(); @@ -993,17 +1337,25 @@ void LLLineEditor::pasteHelper(bool is_primary) { bool can_paste_it; if (is_primary) + { can_paste_it = canPastePrimary(); + } else + { can_paste_it = canPaste(); + } if (can_paste_it) { LLWString paste; if (is_primary) + { paste = gClipboard.getPastePrimaryWString(); + } else + { paste = gClipboard.getPasteWString(); + } if (!paste.empty()) { @@ -1018,7 +1370,7 @@ void LLLineEditor::pasteHelper(bool is_primary) // Clean up string (replace tabs and returns and remove characters that our fonts don't support.) LLWString clean_string(paste); - LLWStringUtil::replaceTabsWithSpaces(clean_string, 1); + LLWStringUtil::replaceTabsWithSpaces(clean_string, 4); //clean_string = wstring_detabify(paste, 1); LLWStringUtil::replaceChar(clean_string, '\n', mReplaceNewlinesWithSpaces ? ' ' : 182); // 182 == paragraph character @@ -1074,7 +1426,7 @@ void LLLineEditor::copyPrimary() if( canCopy() ) { S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); gClipboard.copyFromPrimarySubstring( mText.getWString(), left_pos, length ); } } @@ -1322,6 +1674,14 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask ) BOOL handled = FALSE; BOOL selection_modified = FALSE; + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + if ( gFocusMgr.getKeyboardFocus() == this ) { LLLineEditorRollback rollback( this ); @@ -1396,6 +1756,13 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly) { + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } handled = TRUE; LLLineEditorRollback rollback( this ); @@ -1471,10 +1838,97 @@ void LLLineEditor::doDelete() } } } +void LLLineEditor::autoCorrectText() +{ + static BOOL *doAnything = rebind_llcontrol("EmeraldEnableAutoCorrect", &gSavedSettings, true); + if( (!mReadOnly) && (*doAnything) && (isSpellDirty())) + { + 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 lastTypedWord(std::string(text.begin(), + text.end()).substr(wordStart,wordEnd-wordStart)); + //llinfos << " The last typed word has been chosen, it is "<replaceWord(lastTypedWord)); + if(correctedWord!=lastTypedWord) + { + int dif = correctedWord.length()-lastTypedWord.length(); + std::string regText(mText); + //int wordStart = regText.find(lastTypedWord); + regText.replace(wordStart,lastTypedWord.length(),correctedWord); + mText=regText; + mCursorPos+=dif; + } + } + } +} +void LLLineEditor::drawMisspelled(LLRect background) +{ + if((glggHunSpell->highlightInRed || mOverRideAndShowMisspellings) + &&(!mReadOnly)) + { + S32 newStartSpellHere =mScrollHPos; + S32 cursorloc =calculateCursorFromMouse(mMaxHPixels); + S32 newStopSpellHere = ( ((S32)mText.length())>cursorloc)?cursorloc:(S32)mText.length(); + + F32 elapsed = mSpellTimer.getElapsedTimeF32(); + if(S32(elapsed / 1) & 1) + { + if(isSpellDirty()||(newStartSpellHere!=mStartSpellHere)||(newStopSpellHere!=mEndSpellHere)) + { + mStartSpellHere=newStartSpellHere; + mEndSpellHere= newStopSpellHere; + resetSpellDirty(); + misspellLocations=getMisspelledWordsPositions(); + } + } + for(int i =0;i<(int)misspellLocations.size();i++) + { + S32 wstart =findPixelNearestPos( misspellLocations[i]-getCursor()); + S32 wend = findPixelNearestPos(misspellLocations[++i]-getCursor()); + S32 maxw = getRect().getWidth(); + + if(wend > maxw) + { + wend=maxw; + } + if(wstart > maxw) + { + wstart=maxw; + } + gGL.color4ub(255,0,0,200); + //3 line zig zags.. + while(wstartsetVisible(FALSE); // no more programmatic art. #endif + drawMisspelled(background); + resetSpellDirty(); + // If we're editing... if( gFocusMgr.getKeyboardFocus() == this) { @@ -2164,36 +2621,28 @@ BOOL LLLineEditor::prevalidateASCII(const LLWString &str) BOOL LLLineEditor::evaluateFloat() { - bool success = false; + bool success; + F32 result = 0.f; std::string expr = getText(); + LLStringUtil::toUpper(expr); - // user deleted the contents, nothing to evaluate -- MC - if (expr.empty()) + success = LLCalc::getInstance()->evalString(expr, result); + + if (!success) { - return success; + // Move the cursor to near the error on failure + setCursor(LLCalc::getInstance()->getLastErrorPos()); + // *TODO: Translated error message indicating the type of error? Select error text? } else { - F32 result = 0.f; - success = LLCalc::getInstance()->evalString(expr, result); - - if (!success) - { - // Move the cursor to near the error on failure - setCursor(LLCalc::getInstance()->getLastErrorPos()); - // *TODO: Translated error message indicating the type of error? Select error text? - } - else - { - // Replace the expression with the result - std::ostringstream result_str; - result_str << result; - setText(result_str.str()); - selectAll(); - } - - return success; + // Replace the expression with the result + std::string result_str = llformat("%f",result); + setText(result_str); + selectAll(); } + + return success; } void LLLineEditor::onMouseCaptureLost() diff --git a/linden/indra/llui/lllineeditor.h b/linden/indra/llui/lllineeditor.h index fc1b75f..6de57ec 100644 --- a/linden/indra/llui/lllineeditor.h +++ b/linden/indra/llui/lllineeditor.h @@ -78,6 +78,7 @@ public: LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, S32 border_thickness = 1); + virtual ~LLLineEditor(); virtual LLXMLNodePtr getXML(bool save_children = true) const; @@ -91,15 +92,30 @@ public: /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask); /*virtual*/ BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask); + /*virtual*/ BOOL handleRightMouseDown( S32 x, S32 y, MASK mask ); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char); /*virtual*/ void onMouseCaptureLost(); + struct SpellMenuBind + { + LLLineEditor* origin; + void * menuItem; + std::string word; + S32 wordPositionStart; + S32 wordPositionEnd; + }; + + virtual void spellReplace(SpellMenuBind* spellData); + virtual void insert(std::string what,S32 wher); + // LLEditMenuHandler overrides virtual void cut(); virtual BOOL canCut() const; + virtual void copy(); virtual BOOL canCopy() const; + virtual void paste(); virtual BOOL canPaste() const; @@ -117,8 +133,20 @@ public: virtual void deselect(); virtual BOOL canDeselect() const; + static void context_cut(void* data); + static void context_copy(void* data); + static void spell_correct(void* data); + static void spell_show(void* data); + static void translateText(void * data); + static void spell_add(void* data); + static void context_paste(void* data); + static void context_delete(void* data); + static void context_selectall(void* data); + std::vector getMisspelledWordsPositions(); // view overrides virtual void draw(); + void autoCorrectText(); + void drawMisspelled(LLRect background); virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); virtual void onFocusReceived(); virtual void onFocusLost(); @@ -133,6 +161,8 @@ public: virtual void onCommit(); virtual BOOL isDirty() const { return mText.getString() != mPrevText; } // Returns TRUE if user changed value at all virtual void resetDirty() { mPrevText = mText.getString(); } // Clear dirty state + virtual BOOL isSpellDirty() const { return mText.getString() != mPrevSpelledText; } // Returns TRUE if user changed value at all + virtual void resetSpellDirty() { mPrevSpelledText = mText.getString(); } // Clear dirty state // assumes UTF8 text virtual void setValue(const LLSD& value ) { setText(value.asString()); } @@ -168,6 +198,7 @@ public: void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } void setFocusBgColor(const LLColor4& c) { mFocusBgColor = c; } + void setOverRideAndShowMisspellings(BOOL b) { mOverRideAndShowMisspellings =b;} const LLColor4& getFgColor() const { return mFgColor; } const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor; } @@ -213,7 +244,7 @@ public: static BOOL prevalidateASCII(const LLWString &str); static BOOL postvalidateFloat(const std::string &str); - + BOOL evaluateFloat(); // line history support: @@ -225,11 +256,12 @@ public: private: // private helper methods - void pasteHelper(bool is_primary); + void pasteHelper(bool is_primary); void removeChar(); void addChar(const llwchar c); void setCursorAtLocalPos(S32 local_mouse_x); + S32 calculateCursorFromMouse(S32 local_mouse_x); S32 findPixelNearestPos(S32 cursor_offset = 0) const; void reportBadKeystroke(); BOOL handleSpecialKey(KEY key, MASK mask); @@ -253,9 +285,18 @@ private: virtual S32 getPreeditFontSize() const; protected: + LLHandle mPopupMenuHandle; LLUIString mText; // The string being edited. std::string mPrevText; // Saved string for 'ESC' revert LLUIString mLabel; // text label that is visible when no user text provided + std::string mPrevSpelledText; // saved string so we know whether to respell or not + std::vector misspellLocations; // where all the mispelled words are + S32 mStartSpellHere; // the position of the first char on the screen, stored so we know when to update + S32 mEndSpellHere; // the location of the last char on the screen + BOOL mOverRideAndShowMisspellings; + LLFrameTimer mSpellTimer; + //to keep track of what we have to remove before showing menu + std::vector suggestionMenuItems; // line history support: BOOL mHaveHistory; // flag for enabled line history @@ -364,6 +405,8 @@ private: BOOL mIsSelecting; S32 mSelectionStart; S32 mSelectionEnd; + + }; // end class LLLineEditorRollback }; // end class LLLineEditor diff --git a/linden/indra/llui/llmenugl.cpp b/linden/indra/llui/llmenugl.cpp index b70f98b..e00700a 100644 --- a/linden/indra/llui/llmenugl.cpp +++ b/linden/indra/llui/llmenugl.cpp @@ -2582,6 +2582,31 @@ BOOL LLMenuGL::appendMenu( LLMenuGL* menu ) return success; } +// Remove a menu item from this menu. +BOOL LLMenuGL::remove( LLMenuItemGL* item ) +{ + if (mSpilloverMenu) + { + cleanupSpilloverBranch(); + } + + item_list_t::iterator found_iter = std::find(mItems.begin(), mItems.end(), item); + if (found_iter != mItems.end()) + { + mItems.erase(found_iter); + } + + removeChild( item ); + + // We keep it around in case someone is pointing at it. + // The caller can delete it if it's safe. + // Note that getMenu() will still not work since its parent isn't a menu. + sMenuContainer->addChild( item ); + + arrange(); + return TRUE; +} + void LLMenuGL::setEnabledSubMenus(BOOL enable) { setEnabled(enable); diff --git a/linden/indra/llui/llmenugl.h b/linden/indra/llui/llmenugl.h index 26fc294..63f9d55 100644 --- a/linden/indra/llui/llmenugl.h +++ b/linden/indra/llui/llmenugl.h @@ -442,6 +442,9 @@ public: // Add the menu item to this menu. virtual BOOL append( LLMenuItemGL* item ); + // Remove a menu item from this menu. + virtual BOOL remove( LLMenuItemGL* item ); + // *NOTE:Mani - appendNoArrange() should be removed when merging to skinning/viewer2.0 // Its added as a fix to a viewer 1.23 bug that has already been address by skinning work. virtual BOOL appendNoArrange( LLMenuItemGL* item ); diff --git a/linden/indra/llui/lltexteditor.cpp b/linden/indra/llui/lltexteditor.cpp index 2d46943..be6d3ef 100644 --- a/linden/indra/llui/lltexteditor.cpp +++ b/linden/indra/llui/lltexteditor.cpp @@ -36,7 +36,6 @@ #include "lltexteditor.h" -#include "llerror.h" #include "llfontgl.h" #include "llrender.h" #include "llui.h" @@ -46,7 +45,6 @@ #include "lltimer.h" #include "llmath.h" -#include "audioengine.h" #include "llclipboard.h" #include "llscrollbar.h" #include "llstl.h" @@ -60,7 +58,13 @@ #include "llwindow.h" #include "lltextparser.h" #include -#include + +#include "llmenugl.h" +#include +#include "../newview/lgghunspell_wrapper.h" +#include "../newview/lltranslate.h" +#include "../newview/llviewercontrol.h" +#include "../newview/lggautocorrect.h" // // Globals @@ -96,7 +100,34 @@ LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; void (* LLTextEditor::mURLcallback)(const std::string&) = NULL; bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; bool (* LLTextEditor::mSecondlifeURLcallbackRightClick)(const std::string&) = NULL; +/////////////////////////////////////////////////////////////////// + +class TextChatTranslationReceiver : public LLTranslate::TranslationReceiver +{ +public : + TextChatTranslationReceiver(const std::string &toLang, LLTextEditor* line): LLTranslate::TranslationReceiver("", toLang), + m_line(line) + { + } + + static boost::intrusive_ptr build(const std::string &toLang,LLTextEditor* line) + { + return boost::intrusive_ptr(new TextChatTranslationReceiver(toLang,line)); + } +protected: + void handleResponse(const std::string &translation, const std::string &detectedLanguage) + { + BOOL rep = gSavedSettings.getBOOL("EmeraldTranslateReplace"); + m_line->insertText((rep?"":" (") + translation +(rep?"":")"),rep); + } + void handleFailure() + { + LLTranslate::TranslationReceiver::handleFailure(); + } +private: + LLTextEditor* m_line; +}; /////////////////////////////////////////////////////////////////// @@ -257,6 +288,7 @@ LLTextEditor::LLTextEditor( LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), mTextIsUpToDate(TRUE), mMaxTextByteLength( max_length ), + mPopupMenuHandle(), mBaseDocIsPristine(TRUE), mPristineCmd( NULL ), mLastCmd( NULL ), @@ -290,7 +322,8 @@ LLTextEditor::LLTextEditor( mLastSelectionX(-1), mLastSelectionY(-1), mReflowNeeded(FALSE), - mScrollNeeded(FALSE) + mScrollNeeded(FALSE), + mOverRideAndShowMisspellings(FALSE) { mSourceID.generate(); @@ -342,6 +375,60 @@ LLTextEditor::LLTextEditor( mParseHTML=FALSE; mHTML.clear(); + // make the popup menu available + //LLMenuGL* menu = LLUICtrlFactory::getInstance()->buildMenu("menu_texteditor.xml", parent_view); + LLMenuGL* menu = new LLMenuGL("wot"); + /*if (!menu) + { + menu = new LLMenuGL(LLStringUtil::null); + }*/ + menu->append(new LLMenuItemCallGL("Cut", context_cut, NULL, this)); + menu->append(new LLMenuItemCallGL("Copy", context_copy, NULL, this)); + menu->append(new LLMenuItemCallGL("Paste", context_paste, NULL, this)); + menu->append(new LLMenuItemCallGL("Delete", context_delete, NULL, this)); + menu->append(new LLMenuItemCallGL("Select All", context_selectall, NULL, this)); + menu->appendSeparator("transep"); + LLMenuGL* translatemenu = new LLMenuGL("Translate To"); + translatemenu->setCanTearOff(FALSE); + SpellMenuBind* t=new SpellMenuBind;t->origin=this;t->word="en"; + translatemenu->append(new LLMenuItemCallGL("English",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="da"; + translatemenu->append(new LLMenuItemCallGL("Danish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="de"; + translatemenu->append(new LLMenuItemCallGL("Deutsch(German)",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="es"; + translatemenu->append(new LLMenuItemCallGL("Spanish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="fr"; + translatemenu->append(new LLMenuItemCallGL("French",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="it"; + translatemenu->append(new LLMenuItemCallGL("Italian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="hu"; + translatemenu->append(new LLMenuItemCallGL("Hungarian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="nl"; + translatemenu->append(new LLMenuItemCallGL("Dutch",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="pl"; + translatemenu->append(new LLMenuItemCallGL("Polish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="pt"; + translatemenu->append(new LLMenuItemCallGL("Portugese",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="ru"; + translatemenu->append(new LLMenuItemCallGL("Russian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="tr"; + translatemenu->append(new LLMenuItemCallGL("Turkish",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="uk"; + translatemenu->append(new LLMenuItemCallGL("Ukrainian",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="zh"; + translatemenu->append(new LLMenuItemCallGL("Chinese",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="ja"; + translatemenu->append(new LLMenuItemCallGL("Japanese",translateText, NULL, t)); + t=new SpellMenuBind;t->origin=this;t->word="ko"; + translatemenu->append(new LLMenuItemCallGL("Korean",translateText, NULL, t)); + + menu->appendMenu(translatemenu); + menu->appendSeparator("Spelsep"); + //menu->setBackgroundColor(gColors.getColor("MenuPopupBgColor")); + menu->setCanTearOff(FALSE); + menu->setVisible(FALSE); + mPopupMenuHandle = menu->getHandle(); } @@ -360,6 +447,119 @@ LLTextEditor::~LLTextEditor() std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); + LLView::deleteViewByHandle(mPopupMenuHandle); +} +void LLTextEditor::context_cut(void* data) +{ + LLTextEditor* line = (LLTextEditor*)data; + if(line)line->cut(); +} +void LLTextEditor::context_copy(void* data) +{ + LLTextEditor* line = (LLTextEditor*)data; + if(line)line->copy(); +} +void LLTextEditor::translateText(void * data) +{ + SpellMenuBind* t = (SpellMenuBind*)data; + LLTextEditor* line = t->origin; + const std::string &toLang = t->word;//LLTranslate::getTranslateLanguage(); + LLHTTPClient::ResponderPtr result = TextChatTranslationReceiver::build(toLang,line); + + S32 left_pos = llmin( line->mSelectionStart, line->mSelectionEnd ); + S32 length = abs( line->mSelectionStart - line->mSelectionEnd ); + LLTranslate::translateMessage(result,"", toLang, line->getText().substr(left_pos, length)); +} +void LLTextEditor::spell_correct(void* data) +{ + SpellMenuBind* tempBind = (SpellMenuBind*)data; + LLTextEditor* line = tempBind->origin; + if(tempBind && line) + { + llinfos << tempBind->menuItem->getName() << " : " << tempBind->origin->getName() << " : " << tempBind->word << llendl; + if(line)line->spellReplace(tempBind); + + } +} +void LLTextEditor::spell_show(void * data) +{ + SpellMenuBind* tempBind = (SpellMenuBind*)data; + LLTextEditor* line = tempBind->origin; + + if(tempBind && line) + { + if(tempBind->word=="Show Misspellings") + { + line->setOverRideAndShowMisspellings(TRUE); + }else + { + line->setOverRideAndShowMisspellings(FALSE); + } + } +} + +std::vector LLTextEditor::getMisspelledWordsPositions() +{ + resetSpellDirty(); + std::vector thePosesOfBadWords; + LLWString& text = mWText; + S32 wordStart=0; + S32 wordEnd=spellStart;//start at the scroll start + while(wordEnd < spellEnd) + { + //go through all the chars... XD + if( LLTextEditor::isPartOfWord( text[wordEnd] ) ) + { + // Select word the cursor is over + while ((wordEnd > 0) && LLTextEditor::isPartOfWord(text[wordEnd-1])) + { + wordEnd--; + } + wordStart=wordEnd; + while ((wordEnd < (S32)text.length()) && LLTextEditor::isPartOfWord( text[wordEnd] ) ) + { + wordEnd++; + } + //got a word :D + + std::string regText(text.begin(),text.end()); + std::string selectedWord(regText.substr(wordStart,wordEnd-wordStart)); + + if(!glggHunSpell->isSpelledRight(selectedWord)) + { + //misspelled word here, and you have just right clicked on it + + thePosesOfBadWords.push_back(wordStart); + thePosesOfBadWords.push_back(wordEnd); + } + } + wordEnd++; + } + return thePosesOfBadWords; +} +void LLTextEditor::spell_add(void* data) +{ + SpellMenuBind* tempBind = (SpellMenuBind*)data; + if(tempBind) + { + glggHunSpell->addWordToCustomDictionary(tempBind->word); + tempBind->origin->mPrevSpelledText.erase();//make it update + } +} +void LLTextEditor::context_paste(void* data) +{ + LLTextEditor* line = (LLTextEditor*)data; + if(line)line->paste(); +} +void LLTextEditor::context_delete(void* data) +{ + LLTextEditor* line = (LLTextEditor*)data; + if(line)line->doDelete(); +} +void LLTextEditor::context_selectall(void* data) +{ + LLTextEditor* line = (LLTextEditor*)data; + if(line)line->selectAll(); } void LLTextEditor::setTrackColor( const LLColor4& color ) @@ -751,14 +951,7 @@ S32 LLTextEditor::getLineStart( S32 line ) const S32 segoffset = mLineStartList[line].mOffset; LLTextSegment* seg = mSegments[segidx]; S32 res = seg->getStart() + segoffset; - if (res > seg->getEnd()) - { - //llerrs << "wtf" << llendl; - // This happens when creating a new notecard using the AO on certain opensims. - // Play it safe instead of bringing down the viewer - MC - llwarns << "BAD JOOJOO! Text length (" << res << ") greater than text end (" << seg->getEnd() << "). Setting line start to " << seg->getEnd() << llendl; - res = seg->getEnd(); - } + if (res > seg->getEnd()) llerrs << "wtf" << llendl; return res; } @@ -886,11 +1079,6 @@ S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL rou void LLTextEditor::setCursor(S32 row, S32 column) { - // Make sure we're not trying to set the cursor anywhere - // it can't go by always setting the min to 0 -- MC - row = (row < 0) ? 0 : row; - column = (column < 0) ? 0 : column; - const llwchar* doc = mWText.c_str(); const char CR = 10; while(row--) @@ -1142,6 +1330,14 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + // Let scrollbar have first dibs handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; @@ -1216,6 +1412,107 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) return handled; } +BOOL LLTextEditor::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + + setFocus(TRUE); + + //setCursorAtLocalPos( x, y, TRUE ); + S32 wordStart = 0; + S32 wordEnd = getCursorPosFromLocalCoord(x,y,TRUE); + + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu) + { + for(int i = 0;i<(int)suggestionMenuItems.size();i++) + { + SpellMenuBind * tempBind = suggestionMenuItems[i]; + if(tempBind) + { + menu->remove(tempBind->menuItem); + tempBind->menuItem->die(); + //delete tempBind->menuItem; + //tempBind->menuItem = NULL; + delete tempBind; + } + } + suggestionMenuItems.clear(); + + menu->setItemVisible("Translate To",!mReadOnly); + menu->setItemVisible("Transep",!mReadOnly); + + const LLWString &text = mWText; + + if(( isPartOfWord( text[wordEnd] ) )&&(!mReadOnly)) + { + // Select word the cursor is over + while ((wordEnd > 0) && isPartOfWord(text[wordEnd-1])) + { + wordEnd--; + } + wordStart=wordEnd; + //startSelection(); + + while ((wordEnd < (S32)text.length()) && isPartOfWord( text[wordEnd] ) ) + { + wordEnd++; + } + std::string selectedWord(std::string(text.begin(), text.end()).substr(wordStart,wordEnd-wordStart)); + if(!glggHunSpell->isSpelledRight(selectedWord)) + { + //misspelled word here, and you have just right clicked on it! + std::vector suggs = glggHunSpell->getSuggestionList(selectedWord); + + //menu->setItemVisible("Transep",(suggs.size()>0)); + for(int i = 0;i<(int)suggs.size();i++) + { + SpellMenuBind * tempStruct = new SpellMenuBind; + tempStruct->origin = this; + tempStruct->word = suggs[i]; + tempStruct->wordPositionEnd = wordEnd; + tempStruct->wordPositionStart=wordStart; + tempStruct->wordY=y; + LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL( + tempStruct->word, spell_correct, NULL, tempStruct); + tempStruct->menuItem = suggMenuItem; + suggestionMenuItems.push_back(tempStruct); + menu->append(suggMenuItem); + } + SpellMenuBind * tempStruct = new SpellMenuBind; + tempStruct->origin = this; + tempStruct->word = selectedWord; + tempStruct->wordPositionEnd = wordEnd; + tempStruct->wordPositionStart=wordStart; + tempStruct->wordY=y; + LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL( + "Add Word", spell_add, NULL, tempStruct); + tempStruct->menuItem = suggMenuItem; + suggestionMenuItems.push_back(tempStruct); + menu->append(suggMenuItem); + } + + } + if((!mReadOnly)&&((!glggHunSpell->highlightInRed) + ||(mOverRideAndShowMisspellings)||(mShowLineNumbers))) + { + SpellMenuBind * tempStruct = new SpellMenuBind; + tempStruct->origin = this; + if(mOverRideAndShowMisspellings) + tempStruct->word = "Hide Misspellings"; + else + tempStruct->word = "Show Misspellings"; + LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL( + tempStruct->word, spell_show, NULL, tempStruct); + tempStruct->menuItem = suggMenuItem; + suggestionMenuItems.push_back(tempStruct); + menu->append(suggMenuItem); + } + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + } + return TRUE; +} BOOL LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) @@ -1350,11 +1647,6 @@ BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) setCursorAtLocalPos( x, y, TRUE ); endSelection(); - - updateScrollFromCursor(); - - // take selection to primary clipboard - updatePrimary(); } if( !hasSelection() ) @@ -1432,7 +1724,6 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) handled = TRUE; } - return handled; } @@ -1913,6 +2204,16 @@ BOOL LLTextEditor::canPaste() const return !mReadOnly && gClipboard.canPasteString(); } +void LLTextEditor::spellReplace(SpellMenuBind* spellData) +{ + remove( spellData->wordPositionStart, + spellData->wordPositionEnd - spellData->wordPositionStart, TRUE ); + LLWString clean_string = utf8str_to_wstring(spellData->word); + insert(spellData->wordPositionStart, clean_string, FALSE); + mCursorPos+=clean_string.length() - (spellData->wordPositionEnd-spellData->wordPositionStart); + needsReflow(); +} + // paste from clipboard void LLTextEditor::paste() { @@ -1932,25 +2233,35 @@ void LLTextEditor::pasteHelper(bool is_primary) { bool can_paste_it; if (is_primary) + { can_paste_it = canPastePrimary(); + } else + { can_paste_it = canPaste(); + } if (!can_paste_it) { return; } + LLUUID source_id; LLWString paste; if (is_primary) + { paste = gClipboard.getPastePrimaryWString(&source_id); + } else + { paste = gClipboard.getPasteWString(&source_id); + } if (paste.empty()) { return; } + // Delete any selected characters (the paste replaces them) if( (!is_primary) && hasSelection() ) { @@ -1995,7 +2306,7 @@ void LLTextEditor::copyPrimary() return; } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); gClipboard.copyFromPrimarySubstring(mWText, left_pos, length, mSourceID); } @@ -2262,6 +2573,13 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) BOOL selection_modified = FALSE; BOOL return_key_hit = FALSE; BOOL text_may_have_changed = TRUE; + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } if ( gFocusMgr.getKeyboardFocus() == this ) { @@ -2306,6 +2624,14 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) } } + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + // Handle most keys only if the text editor is writeable. if( !mReadOnly ) { @@ -2739,6 +3065,140 @@ void LLTextEditor::drawSelectionBackground() } } } +void LLTextEditor::autoCorrectText() +{ + + static BOOL *doAnything = rebind_llcontrol("EmeraldEnableAutoCorrect", &gSavedSettings, true); + if( (!mReadOnly) && (*doAnything) && (isSpellDirty()) ) + { + S32 wordStart = 0; + S32 wordEnd = mCursorPos-1; + if(wordEnd<1)return; + LLWString& text = mWText; + if(text.size()<1)return; + if( LLTextEditor::isPartOfWord( text[wordEnd] )) return;//we only check on word breaks + wordEnd--; + if( LLTextEditor::isPartOfWord( text[wordEnd] ) ) + { + while ((wordEnd > 0) && (text[wordEnd-1]!=' ')) + { + wordEnd--; + } + wordStart=wordEnd; + while ((wordEnd < (S32)text.length()) && (' '!= text[wordEnd] ) ) + { + wordEnd++; + } + std::string lastTypedWord(std::string(text.begin(), + text.end()).substr(wordStart,wordEnd-wordStart)); + + std::string regText(text.begin(),text.end()); + + std::string correctedWord(LGGAutoCorrect::getInstance()->replaceWord(lastTypedWord)); + if(correctedWord!=lastTypedWord) + { + int dif = correctedWord.length()-lastTypedWord.length(); + regText.replace(wordStart,lastTypedWord.length(),correctedWord); + mWText=utf8str_to_wstring(regText); + mCursorPos+=dif; + needsReflow(); + } + } + } +} +void LLTextEditor::drawMisspelled() +{ + if(mReadOnly)return; + if(glggHunSpell->highlightInRed || mOverRideAndShowMisspellings) + { + if( + ( ((getLength()<400)||(false)) &&( (S32(mSpellTimer.getElapsedTimeF32() / 1) & 1) )) + || + (S32(mKeystrokeTimer.getElapsedTimeF32() / 1) & 1) + ) + { + S32 newSpellStart = getLineStart(mScrollbar->getDocPos());//start at the scroll start + S32 newSpellEnd = getLineStart(mScrollbar->getDocPos() + 1 + mScrollbar->getDocSize()-mScrollbar->getDocPosMax());//end at the end o.o + + if(mScrollbar->getDocPos() == mScrollbar->getDocPosMax()) + { + newSpellEnd=(S32)mWText.length(); + } + if((isSpellDirty())||(newSpellEnd!=spellEnd || newSpellStart!=spellStart)) + { + spellEnd = newSpellEnd; + spellStart = newSpellStart; + misspellLocations=getMisspelledWordsPositions(); + } + } + //draw + for(int i =0;i<(int)misspellLocations.size();i++) + { + S32 wstart = misspellLocations[i]; + S32 wend = misspellLocations[++i]; + //start curor code mod + const LLWString &text = mWText; + const S32 text_len = getLength(); + // Skip through the lines we aren't drawing. + S32 search_pos = mScrollbar->getDocPos(); + S32 num_lines = getLineCount(); + if (search_pos >= num_lines)return; + S32 line_start = getLineStart(search_pos); + F32 line_height = mGLFont->getLineHeight(); + F32 text_y = (F32)(mTextRect.mTop) - line_height; + + F32 word_left = 0.f; + F32 word_right = 0.f; + F32 word_bottom = 0.f; + BOOL word_visible = FALSE; + + S32 line_end = 0; + // Determine if the cursor is visible and if so what its coordinates are. + while( (mTextRect.mBottom <= llround(text_y)) && (search_pos < num_lines)) + { + line_end = text_len + 1; + S32 next_line = -1; + + if ((search_pos + 1) < num_lines) + { + next_line = getLineStart(search_pos + 1); + line_end = next_line - 1; + } + const llwchar* line = text.c_str() + line_start; + // Find the cursor and selection bounds + if( line_start <= wstart && wend <= line_end ) + { + word_visible = TRUE; + word_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, wstart - line_start, mAllowEmbeddedItems )-1.f; + word_right = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, wend - line_start, mAllowEmbeddedItems )+1.f; + word_bottom = text_y; + break; + } + // move down one line + text_y -= line_height; + line_start = next_line; + search_pos++; + } + if(mShowLineNumbers) + { + word_left += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; + word_right += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; + } + // Draw the cursor + if( word_visible ) + { + //end cursos code mod + gGL.color4ub(255,0,0,200); + while(word_leftsetVisible(true); html->setColor(mLinkColor); @@ -3675,9 +4143,9 @@ void LLTextEditor::appendStyledText(const std::string &new_text, appendHighlightedText(subtext,allow_undo, prepend_newline, part, stylep); } - html->setLinkHREF(text.substr(start,end-start)); + html->setLinkHREF(url); appendText(text.substr(start, end-start),allow_undo, prepend_newline, html); - if (end < (S32)text.length()) + if (end < (S32)text.length()) { text = text.substr(end,text.length() - end); end=0; @@ -3688,6 +4156,7 @@ void LLTextEditor::appendStyledText(const std::string &new_text, break; } } + if (part != (S32)LLTextParser::WHOLE) part=(S32)LLTextParser::END; if (end < (S32)text.length()) appendHighlightedText(text,allow_undo, prepend_newline, part, stylep); } @@ -3784,6 +4253,10 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool { mSelectionStart = selection_start; mSelectionEnd = selection_end; + + + + mIsSelecting = was_selecting; setCursorPos(cursor_pos); } @@ -3952,6 +4425,15 @@ void LLTextEditor::loadKeywords(const std::string& filename, } } +void LLTextEditor::addToken(LLKeywordToken::TOKEN_TYPE type, + const std::string& key, + const LLColor3& color, + const std::string& tool_tip, + const std::string& delimiter) +{ + mKeywords.addToken(type,key,color,tool_tip); +} + void LLTextEditor::updateSegments() { if (mKeywords.isLoaded()) @@ -4417,11 +4899,9 @@ S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) std::string openers=" \t\n('\"[{<>"; std::string closers=" \t\n)'\"]}><;"; - S32 index = 0; - if (reverse) { - for (index=pos; index >= 0; index--) + for (int index=pos; index >= 0; index--) { char c = line[index]; S32 m2 = openers.find(c); @@ -4430,13 +4910,13 @@ S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) return index+1; } } - index = 0; // Can't be before first charater + return 0; // index is -1, don't want to return that. } else { // adjust the search slightly, to allow matching parenthesis inside the URL S32 paren_count = 0; - for (index=pos; index<(S32)line.length(); index++) + for (int index=pos; index<(S32)line.length(); index++) { char c = line[index]; @@ -4464,12 +4944,11 @@ S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) } } } + return line.length(); } - - return index; } -BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const +BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end, std::string& url) const { S32 m1,m2,m3; @@ -4481,34 +4960,21 @@ BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const { *begin = findHTMLToken(line, m1, TRUE); *end = findHTMLToken(line, m1, FALSE); - - // Can't start before the first char - if(*begin < 0) - { - //*begin = 0; - } //Load_url only handles http and https so don't hilite ftp, smb, etc. - try + m2 = line.substr(*begin,(m1 - *begin)).find("http"); + m3 = line.substr(*begin,(m1 - *begin)).find("secondlife"); + + std::string badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\"; + + if (m2 >= 0 || m3>=0) { - m2 = line.substr(*begin,(m1 - *begin)).find("http"); - m3 = line.substr(*begin,(m1 - *begin)).find("secondlife"); - - std::string badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\"; - - if (m2 >= 0 || m3>=0) - { - S32 bn = badneighbors.find(line.substr(m1+3,1)); - - if (bn < 0) - { - matched = TRUE; - } + S32 bn = badneighbors.find(line.substr(m1+3,1)); + + if (bn < 0) + { + matched = TRUE; } - } - catch ( std::out_of_range outOfRange ) - { - LL_WARNS("TextEditor") << "got std::out_of_range exception \"" << line << "\"" << LL_ENDL; } } /* matches things like secondlife.com (no http://) needs a whitelist to really be effective. @@ -4541,11 +5007,46 @@ BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const { S32 strpos, strpos2; - try + url = line.substr(*begin,*end - *begin); + std::string slurlID = "slurl.com/secondlife/"; + strpos = url.find(slurlID); + + if (strpos < 0) { - std::string url = line.substr(*begin,*end - *begin); - std::string slurlID = "slurl.com/secondlife/"; - strpos = url.find(slurlID); + slurlID="maps.secondlife.com/secondlife/"; + strpos = url.find(slurlID); + } + + if (strpos < 0) + { + slurlID="secondlife://"; + strpos = url.find(slurlID); + } + + if (strpos < 0) + { + slurlID="sl://"; + strpos = url.find(slurlID); + } + + if (strpos >= 0) + { + strpos+=slurlID.length(); + + while ( ( strpos2=url.find("/",strpos) ) == -1 ) + { + if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " ) + { + matched=FALSE; + break; + } + + strpos = (*end + 1) - *begin; + + *end = findHTMLToken(line,(*begin + strpos),FALSE); + url = line.substr(*begin,*end - *begin); + } + } if (strpos < 0) { diff --git a/linden/indra/llui/lltexteditor.h b/linden/indra/llui/lltexteditor.h index f26bf3b..6b372d7 100644 --- a/linden/indra/llui/lltexteditor.h +++ b/linden/indra/llui/lltexteditor.h @@ -45,6 +45,7 @@ #include "lldarray.h" #include "llpreeditor.h" +#include "llmenugl.h" class LLFontGL; class LLScrollbar; @@ -84,6 +85,7 @@ public: virtual BOOL handleHover(S32 x, S32 y, MASK mask); virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask ); + virtual BOOL handleRightMouseDown( S32 x, S32 y, MASK mask ); virtual BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask); virtual BOOL handleKeyHere(KEY key, MASK mask ); @@ -108,29 +110,55 @@ public: virtual void setFocus( BOOL b ); virtual BOOL acceptsTextInput() const; virtual BOOL isDirty() const { return( mLastCmd != NULL || (mPristineCmd && (mPristineCmd != mLastCmd)) ); } + BOOL isSpellDirty() const { return mWText != mPrevSpelledText; } // Returns TRUE if user changed value at all + void resetSpellDirty() { mPrevSpelledText = mWText; } // Clear dirty state + struct SpellMenuBind + { + LLTextEditor* origin; + LLMenuItemCallGL * menuItem; + std::string word; + S32 wordPositionStart; + S32 wordPositionEnd; + S32 wordY; + }; + // LLEditMenuHandler interface virtual void undo(); virtual BOOL canUndo() const; virtual void redo(); virtual BOOL canRedo() const; - virtual void cut(); virtual BOOL canCut() const; virtual void copy(); virtual BOOL canCopy() const; virtual void paste(); virtual BOOL canPaste() const; + + virtual void spellReplace(SpellMenuBind* spellData); + virtual void updatePrimary(); virtual void copyPrimary(); virtual void pastePrimary(); virtual BOOL canPastePrimary() const; + virtual void doDelete(); virtual BOOL canDoDelete() const; virtual void selectAll(); virtual BOOL canSelectAll() const; virtual void deselect(); virtual BOOL canDeselect() const; + static void context_cut(void* data); + + static void context_copy(void* data); + static void context_paste(void* data); + static void context_delete(void* data); + static void context_selectall(void* data); + static void translateText(void * data); + static void spell_correct(void* data); + static void spell_add(void* data); + static void spell_show(void* data); + std::vector getMisspelledWordsPositions(); void selectNext(const std::string& search_text_in, BOOL case_insensitive, BOOL wrap = TRUE); BOOL replaceText(const std::string& search_text, const std::string& replace_text, BOOL case_insensitive, BOOL wrap = TRUE); @@ -145,17 +173,17 @@ public: BOOL allowsEmbeddedItems() const { return mAllowEmbeddedItems; } // inserts text at cursor - void insertText(const std::string &text); + void insertText(const std::string &text, BOOL deleteSelection = TRUE); // appends text at end void appendText(const std::string &wtext, bool allow_undo, bool prepend_newline, const LLStyleSP stylep = NULL); - void appendColoredText(const std::string &wtext, bool allow_undo, + void appendColoredText(const std::string &wtext, bool allow_undo, bool prepend_newline, const LLColor4 &color, const std::string& font_name = LLStringUtil::null); // if styled text starts a line, you need to prepend a newline. - void appendStyledText(const std::string &new_text, bool allow_undo, + void appendStyledText(const std::string &new_text, bool allow_undo, bool prepend_newline, LLStyleSP stylep = NULL); void appendHighlightedText(const std::string &new_text, bool allow_undo, @@ -182,6 +210,11 @@ public: const std::vector& funcs, const std::vector& tooltips, const LLColor3& func_color); + void addToken(LLKeywordToken::TOKEN_TYPE type, + const std::string& key, + const LLColor3& color, + const std::string& tool_tip = LLStringUtil::null, + const std::string& delimiter = LLStringUtil::null); LLKeywords::keyword_iterator_t keywordsBegin() { return mKeywords.begin(); } LLKeywords::keyword_iterator_t keywordsEnd() { return mKeywords.end(); } @@ -196,6 +229,7 @@ public: void setThumbColor( const LLColor4& color ); void setHighlightColor( const LLColor4& color ); void setShadowColor( const LLColor4& color ); + void setOverRideAndShowMisspellings(BOOL b){ mOverRideAndShowMisspellings =b;} // Hacky methods to make it into a word-wrapping, potentially scrolling, // read-only text box. @@ -262,7 +296,7 @@ public: const LLTextSegment* getPreviousSegment() const; void getSelectedSegments(std::vector& segments) const; - static bool isPartOfWord(llwchar c) { return (c == '_') || LLStringOps::isAlnum((char)c); } + static bool isPartOfWord(llwchar c) { return ( (c == '_') || (c == '\'') || LLStringOps::isAlnum((char)c)); } BOOL isReadOnly() { return mReadOnly; } protected: @@ -270,11 +304,14 @@ protected: // Methods // + LLHandle mPopupMenuHandle; + S32 getLength() const { return mWText.length(); } void getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ) const; void drawPreeditMarker(); - +public: void updateLineStartList(S32 startpos = 0); +protected: void updateScrollFromCursor(); void updateTextRect(); const LLRect& getTextRect() const { return mTextRect; } @@ -301,8 +338,13 @@ protected: BOOL handleSelectionKey(const KEY key, const MASK mask); BOOL handleControlKey(const KEY key, const MASK mask); BOOL handleEditKey(const KEY key, const MASK mask); - + // +public: + // BOOL hasSelection() const { return (mSelectionStart !=mSelectionEnd); } + // + protected: + // BOOL selectionContainsLineBreaks(); void startSelection(); void endSelection(); @@ -330,7 +372,7 @@ protected: virtual void unbindEmbeddedChars(const LLFontGL* font) const {} S32 findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const; - BOOL findHTML(const std::string &line, S32 *begin, S32 *end) const; + BOOL findHTML(const std::string &line, S32 *begin, S32 *end, std::string& url) const; // Abstract inner base class representing an undoable editor command. // Concrete sub-classes can be defined for operations such as insert, remove, etc. @@ -400,8 +442,9 @@ protected: // // I-beam is just after the mCursorPos-th character. +public: S32 mCursorPos; - +protected: // Use these to determine if a click on an embedded item is a drag or not. S32 mMouseDownX; S32 mMouseDownY; @@ -447,6 +490,8 @@ private: void drawBackground(); void drawSelectionBackground(); void drawCursor(); + void autoCorrectText(); + void drawMisspelled(); void drawText(); void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyleSP& color, F32* right_x); @@ -477,6 +522,12 @@ private: mutable std::string mUTF8Text; mutable BOOL mTextIsUpToDate; + LLWString mPrevSpelledText; // saved string so we know whether to respell or not + S32 spellStart; + S32 spellEnd; + std::vector misspellLocations; // where all the mispelled words are + BOOL mOverRideAndShowMisspellings; + S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes const LLFontGL* mGLFont; @@ -513,11 +564,16 @@ private: } }; typedef std::vector line_list_t; + + //to keep track of what we have to remove before showing menu + std::vector suggestionMenuItems; + line_list_t mLineStartList; BOOL mReflowNeeded; BOOL mScrollNeeded; LLFrameTimer mKeystrokeTimer; + LLFrameTimer mSpellTimer; LLColor4 mCursorColor; -- cgit v1.1