lltexteditor.cpp

Go to the documentation of this file.
00001 
00032 // Text editor widget to let users enter a a multi-line ASCII document.
00033 
00034 #include "linden_common.h"
00035 
00036 #include "lltexteditor.h"
00037 
00038 #include "llfontgl.h"
00039 #include "llgl.h"
00040 #include "llui.h"
00041 #include "lluictrlfactory.h"
00042 #include "llrect.h"
00043 #include "llfocusmgr.h"
00044 #include "sound_ids.h"
00045 #include "lltimer.h"
00046 #include "llmath.h"
00047 
00048 #include "audioengine.h"
00049 #include "llclipboard.h"
00050 #include "llscrollbar.h"
00051 #include "llstl.h"
00052 #include "llstring.h"
00053 #include "llkeyboard.h"
00054 #include "llkeywords.h"
00055 #include "llundo.h"
00056 #include "llviewborder.h"
00057 #include "llcontrol.h"
00058 #include "llimagegl.h"
00059 #include "llwindow.h"
00060 #include "llglheaders.h"
00061 #include <queue>
00062 
00063 // 
00064 // Globals
00065 //
00066 
00067 BOOL gDebugTextEditorTips = FALSE;
00068 
00069 //
00070 // Constants
00071 //
00072 
00073 const S32       UI_TEXTEDITOR_BUFFER_BLOCK_SIZE = 512;
00074 
00075 const S32       UI_TEXTEDITOR_BORDER = 1;
00076 const S32       UI_TEXTEDITOR_H_PAD = 4;
00077 const S32       UI_TEXTEDITOR_V_PAD_TOP = 4;
00078 const F32       CURSOR_FLASH_DELAY = 1.0f;  // in seconds
00079 const S32       CURSOR_THICKNESS = 2;
00080 const S32       SPACES_PER_TAB = 4;
00081 
00082 LLColor4 LLTextEditor::mLinkColor = LLColor4::blue;
00083 void (* LLTextEditor::mURLcallback)(const char*)              = NULL;
00084 bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&)   = NULL;
00085 bool (* LLTextEditor::mSecondlifeURLcallbackRightClick)(const std::string&)   = NULL;
00086 
00088 //virtuals
00089 BOOL LLTextCmd::canExtend(S32 pos)
00090 {
00091         return FALSE;
00092 }
00093 
00094 void LLTextCmd::blockExtensions()
00095 {
00096 }
00097 
00098 BOOL LLTextCmd::extendAndExecute( LLTextEditor* editor, S32 pos, llwchar c, S32* delta )
00099 {
00100         llassert(0);
00101         return 0;
00102 }
00103 
00104 BOOL LLTextCmd::hasExtCharValue( llwchar value )
00105 {
00106         return FALSE;
00107 }
00108 
00109 // Utility funcs
00110 S32 LLTextCmd::insert(LLTextEditor* editor, S32 pos, const LLWString &wstr)
00111 {
00112         return editor->insertStringNoUndo( pos, wstr );
00113 }
00114 S32 LLTextCmd::remove(LLTextEditor* editor, S32 pos, S32 length)
00115 {
00116         return editor->removeStringNoUndo( pos, length );
00117 }
00118 S32 LLTextCmd::overwrite(LLTextEditor* editor, S32 pos, llwchar wc)
00119 {
00120         return editor->overwriteCharNoUndo(pos, wc);
00121 }
00122 
00124 
00125 class LLTextCmdInsert : public LLTextCmd 
00126 {
00127 public:
00128         LLTextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws)
00129                 : LLTextCmd(pos, group_with_next), mWString(ws)
00130         {
00131         }
00132         virtual BOOL execute( LLTextEditor* editor, S32* delta )
00133         {
00134                 *delta = insert(editor, mPos, mWString );
00135                 LLWString::truncate(mWString, *delta);
00136                 //mWString = wstring_truncate(mWString, *delta);
00137                 return (*delta != 0);
00138         }       
00139         virtual S32 undo( LLTextEditor* editor )
00140         {
00141                 remove(editor, mPos, mWString.length() );
00142                 return mPos;
00143         }
00144         virtual S32 redo( LLTextEditor* editor )
00145         {
00146                 insert(editor, mPos, mWString );
00147                 return mPos + mWString.length();
00148         }
00149 
00150 private:
00151         LLWString mWString;
00152 };
00153 
00155 
00156 class LLTextCmdAddChar : public LLTextCmd
00157 {
00158 public:
00159         LLTextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc)
00160                 : LLTextCmd(pos, group_with_next), mWString(1, wc), mBlockExtensions(FALSE)
00161         {
00162         }
00163         virtual void blockExtensions()
00164         {
00165                 mBlockExtensions = TRUE;
00166         }
00167         virtual BOOL canExtend(S32 pos)
00168         {
00169                 return !mBlockExtensions && (pos == mPos + (S32)mWString.length());
00170         }
00171         virtual BOOL execute( LLTextEditor* editor, S32* delta )
00172         {
00173                 *delta = insert(editor, mPos, mWString);
00174                 LLWString::truncate(mWString, *delta);
00175                 //mWString = wstring_truncate(mWString, *delta);
00176                 return (*delta != 0);
00177         }
00178         virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar wc, S32* delta )  
00179         { 
00180                 LLWString ws;
00181                 ws += wc;
00182                 
00183                 *delta = insert(editor, pos, ws);
00184                 if( *delta > 0 )
00185                 {
00186                         mWString += wc;
00187                 }
00188                 return (*delta != 0);
00189         }
00190         virtual S32 undo( LLTextEditor* editor )
00191         {
00192                 remove(editor, mPos, mWString.length() );
00193                 return mPos;
00194         }
00195         virtual S32 redo( LLTextEditor* editor )
00196         {
00197                 insert(editor, mPos, mWString );
00198                 return mPos + mWString.length();
00199         }
00200 
00201 private:
00202         LLWString       mWString;
00203         BOOL            mBlockExtensions;
00204 
00205 };
00206 
00208 
00209 class LLTextCmdOverwriteChar : public LLTextCmd
00210 {
00211 public:
00212         LLTextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc)
00213                 : LLTextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {}
00214 
00215         virtual BOOL execute( LLTextEditor* editor, S32* delta )
00216         { 
00217                 mOldChar = editor->getWChar(mPos);
00218                 overwrite(editor, mPos, mChar);
00219                 *delta = 0;
00220                 return TRUE;
00221         }       
00222         virtual S32 undo( LLTextEditor* editor )
00223         {
00224                 overwrite(editor, mPos, mOldChar);
00225                 return mPos;
00226         }
00227         virtual S32 redo( LLTextEditor* editor )
00228         {
00229                 overwrite(editor, mPos, mChar);
00230                 return mPos+1;
00231         }
00232 
00233 private:
00234         llwchar         mChar;
00235         llwchar         mOldChar;
00236 };
00237 
00239 
00240 class LLTextCmdRemove : public LLTextCmd
00241 {
00242 public:
00243         LLTextCmdRemove( S32 pos, BOOL group_with_next, S32 len ) :
00244                 LLTextCmd(pos, group_with_next), mLen(len)
00245         {
00246         }
00247         virtual BOOL execute( LLTextEditor* editor, S32* delta )
00248         { 
00249                 mWString = editor->getWSubString(mPos, mLen);
00250                 *delta = remove(editor, mPos, mLen );
00251                 return (*delta != 0);
00252         }
00253         virtual S32 undo( LLTextEditor* editor )
00254         {
00255                 insert(editor, mPos, mWString );
00256                 return mPos + mWString.length();
00257         }
00258         virtual S32 redo( LLTextEditor* editor )
00259         {
00260                 remove(editor, mPos, mLen );
00261                 return mPos;
00262         }
00263 private:
00264         LLWString       mWString;
00265         S32                             mLen;
00266 };
00267 
00269 
00270 //
00271 // Member functions
00272 //
00273 
00274 LLTextEditor::LLTextEditor(     
00275         const LLString& name, 
00276         const LLRect& rect, 
00277         S32 max_length, 
00278         const LLString &default_text, 
00279         const LLFontGL* font,
00280         BOOL allow_embedded_items)
00281         :       
00282         LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ),
00283         mTextIsUpToDate(TRUE),
00284         mMaxTextLength( max_length ),
00285         mBaseDocIsPristine(TRUE),
00286         mPristineCmd( NULL ),
00287         mLastCmd( NULL ),
00288         mCursorPos( 0 ),
00289         mIsSelecting( FALSE ),
00290         mSelectionStart( 0 ),
00291         mSelectionEnd( 0 ),
00292         mOnScrollEndCallback( NULL ),
00293         mOnScrollEndData( NULL ),
00294         mCursorColor(           LLUI::sColorsGroup->getColor( "TextCursorColor" ) ),
00295         mFgColor(                       LLUI::sColorsGroup->getColor( "TextFgColor" ) ),
00296         mReadOnlyFgColor(       LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ),
00297         mWriteableBgColor(      LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ),
00298         mReadOnlyBgColor(       LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ),
00299         mFocusBgColor(          LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ),
00300         mReadOnly(FALSE),
00301         mWordWrap( FALSE ),
00302         mTabToNextField( TRUE ),
00303         mCommitOnFocusLost( FALSE ),
00304         mTakesFocus( TRUE ),
00305         mHideScrollbarForShortDocs( FALSE ),
00306         mTakesNonScrollClicks( TRUE ),
00307         mAllowEmbeddedItems( allow_embedded_items ),
00308         mAcceptCallingCardNames(FALSE),
00309         mHandleEditKeysDirectly( FALSE ),
00310         mMouseDownX(0),
00311         mMouseDownY(0),
00312         mLastSelectionX(-1),
00313         mLastSelectionY(-1),
00314         mLastIMEPosition(-1,-1)
00315 {
00316         mSourceID.generate();
00317 
00318         // reset desired x cursor position
00319         mDesiredXPixel = -1;
00320 
00321         if (font)
00322         {
00323                 mGLFont = font;
00324         }
00325         else
00326         {
00327                 mGLFont = LLFontGL::sSansSerif;
00328         }
00329 
00330         updateTextRect();
00331 
00332         S32 line_height = llround( mGLFont->getLineHeight() );
00333         S32 page_size = mTextRect.getHeight() / line_height;
00334 
00335         // Init the scrollbar
00336         LLRect scroll_rect;
00337         scroll_rect.setOriginAndSize( 
00338                 mRect.getWidth() - UI_TEXTEDITOR_BORDER - SCROLLBAR_SIZE,
00339                 UI_TEXTEDITOR_BORDER,
00340                 SCROLLBAR_SIZE,
00341                 mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER );
00342         S32 lines_in_doc = getLineCount();
00343         mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect,
00344                 LLScrollbar::VERTICAL,
00345                 lines_in_doc,                                           
00346                 0,                                              
00347                 page_size,
00348                 NULL, this );
00349         mScrollbar->setFollowsRight();
00350         mScrollbar->setFollowsTop();
00351         mScrollbar->setFollowsBottom();
00352         mScrollbar->setEnabled( TRUE );
00353         mScrollbar->setVisible( TRUE );
00354         mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData);
00355         addChild(mScrollbar);
00356 
00357         mBorder = new LLViewBorder( "text ed border", LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER );
00358         addChild( mBorder );
00359 
00360         appendText(default_text, FALSE, FALSE);
00361         
00362         resetDirty();           // Update saved text state
00363 
00364         mParseHTML=FALSE;
00365         mHTML="";
00366 }
00367 
00368 
00369 LLTextEditor::~LLTextEditor()
00370 {
00371         gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
00372 
00373         // Route menu back to the default
00374         if( gEditMenuHandler == this )
00375         {
00376                 gEditMenuHandler = NULL;
00377         }
00378 
00379         // Scrollbar is deleted by LLView
00380         mHoverSegment = NULL;
00381         std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
00382 
00383         std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
00384 }
00385 
00386 //virtual
00387 LLString LLTextEditor::getWidgetTag() const
00388 {
00389         return LL_TEXT_EDITOR_TAG;
00390 }
00391 
00392 void LLTextEditor::setTrackColor( const LLColor4& color )
00393 { 
00394         mScrollbar->setTrackColor(color); 
00395 }
00396 
00397 void LLTextEditor::setThumbColor( const LLColor4& color ) 
00398 { 
00399         mScrollbar->setThumbColor(color); 
00400 }
00401 
00402 void LLTextEditor::setHighlightColor( const LLColor4& color ) 
00403 { 
00404         mScrollbar->setHighlightColor(color); 
00405 }
00406 
00407 void LLTextEditor::setShadowColor( const LLColor4& color ) 
00408 { 
00409         mScrollbar->setShadowColor(color); 
00410 }
00411 
00412 void LLTextEditor::updateLineStartList(S32 startpos)
00413 {
00414         updateSegments();
00415         
00416         bindEmbeddedChars( mGLFont );
00417 
00418         S32 seg_num = mSegments.size();
00419         S32 seg_idx = 0;
00420         S32 seg_offset = 0;
00421 
00422         if (!mLineStartList.empty())
00423         {
00424                 getSegmentAndOffset(startpos, &seg_idx, &seg_offset);
00425                 line_info t(seg_idx, seg_offset);
00426                 line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), t, line_info_compare());
00427                 if (iter != mLineStartList.begin()) --iter;
00428                 seg_idx = iter->mSegment;
00429                 seg_offset = iter->mOffset;
00430                 mLineStartList.erase(iter, mLineStartList.end());
00431         }
00432         
00433         while( seg_idx < seg_num )
00434         {
00435                 mLineStartList.push_back(line_info(seg_idx,seg_offset));
00436                 BOOL line_ended = FALSE;
00437                 S32 line_width = 0;
00438                 while(!line_ended && seg_idx < seg_num)
00439                 {
00440                         LLTextSegment* segment = mSegments[seg_idx];
00441                         S32 start_idx = segment->getStart() + seg_offset;
00442                         S32 end_idx = start_idx;
00443                         while (end_idx < segment->getEnd() && mWText[end_idx] != '\n')
00444                         {
00445                                 end_idx++;
00446                         }
00447                         if (start_idx == end_idx)
00448                         {
00449                                 if (end_idx >= segment->getEnd())
00450                                 {
00451                                         // empty segment
00452                                         seg_idx++;
00453                                         seg_offset = 0;
00454                                 }
00455                                 else
00456                                 {
00457                                         // empty line
00458                                         line_ended = TRUE;
00459                                         seg_offset++;
00460                                 }
00461                         }
00462                         else
00463                         { 
00464                                 const llwchar* str = mWText.c_str() + start_idx;
00465                                 S32 drawn = mGLFont->maxDrawableChars(str, (F32)mTextRect.getWidth() - line_width,
00466                                                                                                           end_idx - start_idx, mWordWrap, mAllowEmbeddedItems );
00467                                 if( 0 == drawn && line_width == 0)
00468                                 {
00469                                         // If at the beginning of a line, draw at least one character, even if it doesn't all fit.
00470                                         drawn = 1;
00471                                 }
00472                                 seg_offset += drawn;
00473                                 line_width += mGLFont->getWidth(str, 0, drawn, mAllowEmbeddedItems);
00474                                 end_idx = segment->getStart() + seg_offset;
00475                                 if (end_idx < segment->getEnd())
00476                                 {
00477                                         line_ended = TRUE;
00478                                         if (mWText[end_idx] == '\n')
00479                                         {
00480                                                 seg_offset++; // skip newline
00481                                         }
00482                                 }
00483                                 else
00484                                 {
00485                                         // finished with segment
00486                                         seg_idx++;
00487                                         seg_offset = 0;
00488                                 }
00489                         }
00490                 }
00491         }
00492         
00493         unbindEmbeddedChars(mGLFont);
00494 
00495         mScrollbar->setDocSize( getLineCount() );
00496 
00497         if (mHideScrollbarForShortDocs)
00498         {
00499                 BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize());
00500                 mScrollbar->setVisible(!short_doc);
00501         }
00502 
00503 }
00504 
00506 // LLTextEditor
00507 // Public methods
00508 
00509 //static
00510 BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); }
00511 
00512 
00513 
00514 void LLTextEditor::truncate()
00515 {
00516         if (mWText.size() > (size_t)mMaxTextLength)
00517         {
00518                 LLWString::truncate(mWText, mMaxTextLength);
00519                 mTextIsUpToDate = FALSE;
00520         }
00521 }
00522 
00523 void LLTextEditor::setText(const LLStringExplicit &utf8str)
00524 {
00525         // LLString::removeCRLF(utf8str);
00526         mUTF8Text = utf8str_removeCRLF(utf8str);
00527         // mUTF8Text = utf8str;
00528         mWText = utf8str_to_wstring(mUTF8Text);
00529         mTextIsUpToDate = TRUE;
00530 
00531         truncate();
00532         blockUndo();
00533 
00534         setCursorPos(0);
00535         deselect();
00536 
00537         updateLineStartList();
00538         updateScrollFromCursor();
00539 
00540         resetDirty();
00541 }
00542 
00543 void LLTextEditor::setWText(const LLWString &wtext)
00544 {
00545         mWText = wtext;
00546         mUTF8Text.clear();
00547         mTextIsUpToDate = FALSE;
00548 
00549         truncate();
00550         blockUndo();
00551 
00552         setCursorPos(0);
00553         deselect();
00554 
00555         updateLineStartList();
00556         updateScrollFromCursor();
00557 
00558         resetDirty();
00559 }
00560 
00561 void LLTextEditor::setValue(const LLSD& value)
00562 {
00563         setText(value.asString());
00564 }
00565 
00566 const LLString& LLTextEditor::getText() const
00567 {
00568         if (!mTextIsUpToDate)
00569         {
00570                 if (mAllowEmbeddedItems)
00571                 {
00572                         llwarns << "getText() called on text with embedded items (not supported)" << llendl;
00573                 }
00574                 mUTF8Text = wstring_to_utf8str(mWText);
00575                 mTextIsUpToDate = TRUE;
00576         }
00577         return mUTF8Text;
00578 }
00579 
00580 LLSD LLTextEditor::getValue() const
00581 {
00582         return LLSD(getText());
00583 }
00584 
00585 void LLTextEditor::setWordWrap(BOOL b)
00586 {
00587         mWordWrap = b; 
00588 
00589         setCursorPos(0);
00590         deselect();
00591         
00592         updateLineStartList();
00593         updateScrollFromCursor();
00594 }
00595 
00596 
00597 void LLTextEditor::setBorderVisible(BOOL b)
00598 {
00599         mBorder->setVisible(b);
00600 }
00601 
00602 
00603 
00604 void LLTextEditor::setHideScrollbarForShortDocs(BOOL b)
00605 {
00606         mHideScrollbarForShortDocs = b;
00607 
00608         if (mHideScrollbarForShortDocs)
00609         {
00610                 BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize());
00611                 mScrollbar->setVisible(!short_doc);
00612         }
00613 }
00614 
00615 void LLTextEditor::selectNext(const LLString& search_text_in, BOOL case_insensitive, BOOL wrap)
00616 {
00617         if (search_text_in.empty())
00618         {
00619                 return;
00620         }
00621 
00622         LLWString text = getWText();
00623         LLWString search_text = utf8str_to_wstring(search_text_in);
00624         if (case_insensitive)
00625         {
00626                 LLWString::toLower(text);
00627                 LLWString::toLower(search_text);
00628         }
00629         
00630         if (mIsSelecting)
00631         {
00632                 LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd);
00633                 
00634                 if (selected_text == search_text)
00635                 {
00636                         // We already have this word selected, we are searching for the next.
00637                         mCursorPos += search_text.size();
00638                 }
00639         }
00640         
00641         S32 loc = text.find(search_text,mCursorPos);
00642         
00643         // If Maybe we wrapped, search again
00644         if (wrap && (-1 == loc))
00645         {       
00646                 loc = text.find(search_text);
00647         }
00648         
00649         // If still -1, then search_text just isn't found.
00650     if (-1 == loc)
00651         {
00652                 mIsSelecting = FALSE;
00653                 mSelectionEnd = 0;
00654                 mSelectionStart = 0;
00655                 return;
00656         }
00657 
00658         setCursorPos(loc);
00659         
00660         mIsSelecting = TRUE;
00661         mSelectionEnd = mCursorPos;
00662         mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size()));
00663 }
00664 
00665 BOOL LLTextEditor::replaceText(const LLString& search_text_in, const LLString& replace_text,
00666                                                            BOOL case_insensitive, BOOL wrap)
00667 {
00668         BOOL replaced = FALSE;
00669 
00670         if (search_text_in.empty())
00671         {
00672                 return replaced;
00673         }
00674 
00675         LLWString search_text = utf8str_to_wstring(search_text_in);
00676         if (mIsSelecting)
00677         {
00678                 LLWString text = getWText();
00679                 LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd);
00680 
00681                 if (case_insensitive)
00682                 {
00683                         LLWString::toLower(selected_text);
00684                         LLWString::toLower(search_text);
00685                 }
00686 
00687                 if (selected_text == search_text)
00688                 {
00689                         insertText(replace_text);
00690                         replaced = TRUE;
00691                 }
00692         }
00693 
00694         selectNext(search_text_in, case_insensitive, wrap);
00695         return replaced;
00696 }
00697 
00698 void LLTextEditor::replaceTextAll(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive)
00699 {
00700         S32 cur_pos = mScrollbar->getDocPos();
00701 
00702         setCursorPos(0);
00703         selectNext(search_text, case_insensitive, FALSE);
00704 
00705         BOOL replaced = TRUE;
00706         while ( replaced )
00707         {
00708                 replaced = replaceText(search_text,replace_text, case_insensitive, FALSE);
00709         }
00710 
00711         mScrollbar->setDocPos(cur_pos);
00712 }
00713 
00714 void LLTextEditor::setTakesNonScrollClicks(BOOL b)
00715 {
00716         mTakesNonScrollClicks = b;
00717 }
00718 
00719 
00720 // Picks a new cursor position based on the screen size of text being drawn.
00721 void LLTextEditor::setCursorAtLocalPos( S32 local_x, S32 local_y, BOOL round )
00722 {
00723         setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round));
00724 }
00725 
00726 S32 LLTextEditor::prevWordPos(S32 cursorPos) const
00727 {
00728         const LLWString& wtext = mWText;
00729         while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') )
00730         {
00731                 cursorPos--;
00732         }
00733         while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) )
00734         {
00735                 cursorPos--;
00736         }
00737         return cursorPos;
00738 }
00739 
00740 S32 LLTextEditor::nextWordPos(S32 cursorPos) const
00741 {
00742         const LLWString& wtext = mWText;
00743         while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos] ) )
00744         {
00745                 cursorPos++;
00746         } 
00747         while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') )
00748         {
00749                 cursorPos++;
00750         }
00751         return cursorPos;
00752 }
00753 
00754 S32 LLTextEditor::getLineCount()
00755 {
00756         return mLineStartList.size();
00757 }
00758 
00759 S32 LLTextEditor::getLineStart( S32 line )
00760 {
00761         S32 num_lines = getLineCount();
00762         if (num_lines == 0)
00763     {
00764                 return 0;
00765     }
00766         line = llclamp(line, 0, num_lines-1);
00767         S32 segidx = mLineStartList[line].mSegment;
00768         S32 segoffset = mLineStartList[line].mOffset;
00769         LLTextSegment* seg = mSegments[segidx];
00770         S32 res = seg->getStart() + segoffset;
00771         if (res > seg->getEnd()) llerrs << "wtf" << llendl;
00772         return res;
00773 }
00774 
00775 // Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line.
00776 void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp )
00777 {
00778         if (mLineStartList.empty())
00779         {
00780                 *linep = 0;
00781                 *offsetp = startpos;
00782         }
00783         else
00784         {
00785                 S32 seg_idx, seg_offset;
00786                 getSegmentAndOffset( startpos, &seg_idx, &seg_offset );
00787 
00788                 line_info tline(seg_idx, seg_offset);
00789                 line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), tline, line_info_compare());
00790                 if (iter != mLineStartList.begin()) --iter;
00791                 *linep = iter - mLineStartList.begin();
00792                 S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset;
00793                 *offsetp = startpos - line_start;
00794         }
00795 }
00796 
00797 void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp )
00798 {
00799         if (mSegments.empty())
00800         {
00801                 *segidxp = -1;
00802                 *offsetp = startpos;
00803         }
00804         
00805         LLTextSegment tseg(startpos);
00806         segment_list_t::iterator seg_iter;
00807         seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg, LLTextSegment::compare());
00808         if (seg_iter != mSegments.begin()) --seg_iter;
00809         *segidxp = seg_iter - mSegments.begin();
00810         *offsetp = startpos - (*seg_iter)->getStart();
00811 }
00812 
00813 const LLWString& LLTextEditor::getWText() const
00814 {
00815         return mWText;
00816 }
00817 
00818 S32 LLTextEditor::getLength() const
00819 {
00820         return mWText.length();
00821 }
00822 
00823 llwchar LLTextEditor::getWChar(S32 pos)
00824 {
00825         return mWText[pos];
00826 }
00827 
00828 LLWString LLTextEditor::getWSubString(S32 pos, S32 len)
00829 {
00830         return mWText.substr(pos, len);
00831 }
00832 
00833 LLTextSegment*  LLTextEditor::getCurrentSegment()
00834 {
00835         return getSegmentAtOffset(mCursorPos);
00836 }
00837 
00838 LLTextSegment*  LLTextEditor::getPreviousSegment()
00839 {
00840         // find segment index at character to left of cursor (or rightmost edge of selection)
00841         S32 idx = llmax(0, getSegmentIdxAtOffset(mCursorPos) - 1);
00842         return idx >= 0 ? mSegments[idx] : NULL;
00843 }
00844 
00845 void LLTextEditor::getSelectedSegments(std::vector<LLTextSegment*>& segments)
00846 {
00847         S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos;
00848         S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos;
00849         S32 first_idx = llmax(0, getSegmentIdxAtOffset(left));
00850         S32 last_idx = llmax(0, first_idx, getSegmentIdxAtOffset(right));
00851 
00852         for (S32 idx = first_idx; idx <= last_idx; ++idx)
00853         {
00854                 segments.push_back(mSegments[idx]);
00855         }
00856 }
00857 
00858 S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round )
00859 {
00860                 // If round is true, if the position is on the right half of a character, the cursor
00861         // will be put to its right.  If round is false, the cursor will always be put to the
00862         // character's left.
00863 
00864         // Figure out which line we're nearest to.
00865         S32 total_lines = getLineCount();
00866         S32 line_height = llround( mGLFont->getLineHeight() );
00867         S32 max_visible_lines = mTextRect.getHeight() / line_height;
00868         S32 scroll_lines = mScrollbar->getDocPos();
00869         S32 visible_lines = llmin( total_lines - scroll_lines, max_visible_lines );                     // Lines currently visible 
00870 
00871         //S32 line = S32( 0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()) );
00872         S32 line = (mTextRect.mTop - 1 - local_y) / line_height;
00873         if (line >= total_lines)
00874         {
00875                 return getLength(); // past the end
00876         }
00877         
00878         line = llclamp( line, 0, visible_lines ) + scroll_lines;
00879 
00880         S32 line_start = getLineStart(line);
00881         S32 next_start = getLineStart(line+1);
00882         S32     line_end = (next_start != line_start) ? next_start - 1 : getLength();
00883 
00884         if(line_start == -1)
00885         {
00886                 return 0;
00887         }
00888         else
00889         {
00890                 S32 line_len = line_end - line_start;
00891                 S32 pos;
00892 
00893                 if (mAllowEmbeddedItems)
00894                 {
00895                         // Figure out which character we're nearest to.
00896                         bindEmbeddedChars(mGLFont);
00897                         pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start,
00898                                                                                            (F32)(local_x - mTextRect.mLeft),
00899                                                                                            (F32)(mTextRect.getWidth()),
00900                                                                                            line_len,
00901                                                                                            round, TRUE);
00902                         unbindEmbeddedChars(mGLFont);
00903                 }
00904                 else
00905                 {
00906                         pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start,
00907                                                                                            (F32)(local_x - mTextRect.mLeft),
00908                                                                                            (F32)mTextRect.getWidth(),
00909                                                                                            line_len,
00910                                                                                            round);
00911                 }
00912 
00913                 return line_start + pos;
00914         }
00915 }
00916 
00917 void LLTextEditor::setCursor(S32 row, S32 column)
00918 {
00919         const llwchar* doc = mWText.c_str();
00920         const char CR = 10;
00921         while(row--)
00922         {
00923                 while (CR != *doc++);
00924         }
00925         doc += column;
00926         setCursorPos(doc - mWText.c_str());
00927         updateScrollFromCursor();
00928 }
00929 
00930 void LLTextEditor::setCursorPos(S32 offset)
00931 {
00932         mCursorPos = llclamp(offset, 0, (S32)getLength());
00933         updateScrollFromCursor();
00934         // reset desired x cursor position
00935         mDesiredXPixel = -1;
00936 }
00937 
00938 
00939 BOOL LLTextEditor::canDeselect()
00940 {
00941         return hasSelection(); 
00942 }
00943 
00944 
00945 void LLTextEditor::deselect()
00946 {
00947         mSelectionStart = 0;
00948         mSelectionEnd = 0;
00949         mIsSelecting = FALSE;
00950 }
00951 
00952 
00953 void LLTextEditor::startSelection()
00954 {
00955         if( !mIsSelecting )
00956         {
00957                 mIsSelecting = TRUE;
00958                 mSelectionStart = mCursorPos;
00959                 mSelectionEnd = mCursorPos;
00960         }
00961 }
00962 
00963 void LLTextEditor::endSelection()
00964 {
00965         if( mIsSelecting )
00966         {
00967                 mIsSelecting = FALSE;
00968                 mSelectionEnd = mCursorPos;
00969         }
00970 }
00971 
00972 BOOL LLTextEditor::selectionContainsLineBreaks()
00973 {
00974         if (hasSelection())
00975         {
00976                 S32 left = llmin(mSelectionStart, mSelectionEnd);
00977                 S32 right = left + abs(mSelectionStart - mSelectionEnd);
00978 
00979                 const LLWString &wtext = mWText;
00980                 for( S32 i = left; i < right; i++ )
00981                 {
00982                         if (wtext[i] == '\n')
00983                         {
00984                                 return TRUE;
00985                         }
00986                 }
00987         }
00988         return FALSE;
00989 }
00990 
00991 
00992 S32 LLTextEditor::indentLine( S32 pos, S32 spaces )
00993 {
00994         // Assumes that pos is at the start of the line
00995         // spaces may be positive (indent) or negative (unindent).
00996         // Returns the actual number of characters added or removed.
00997 
00998         llassert(pos >= 0);
00999         llassert(pos <= getLength() );
01000 
01001         S32 delta_spaces = 0;
01002 
01003         if (spaces >= 0)
01004         {
01005                 // Indent
01006                 for(S32 i=0; i < spaces; i++)
01007                 {
01008                         delta_spaces += addChar(pos, ' ');
01009                 }
01010         }
01011         else
01012         {
01013                 // Unindent
01014                 for(S32 i=0; i < -spaces; i++)
01015                 {
01016                         const LLWString &wtext = mWText;
01017                         if (wtext[pos] == ' ')
01018                         {
01019                                 delta_spaces += remove( pos, 1, FALSE );
01020                         }
01021                 }
01022         }
01023 
01024         return delta_spaces;
01025 }
01026 
01027 void LLTextEditor::indentSelectedLines( S32 spaces )
01028 {
01029         if( hasSelection() )
01030         {
01031                 const LLWString &text = mWText;
01032                 S32 left = llmin( mSelectionStart, mSelectionEnd );
01033                 S32 right = left + abs( mSelectionStart - mSelectionEnd );
01034                 BOOL cursor_on_right = (mSelectionEnd > mSelectionStart);
01035                 S32 cur = left;
01036 
01037                 // Expand left to start of line
01038                 while( (cur > 0) && (text[cur] != '\n') )
01039                 {
01040                         cur--;
01041                 }
01042                 left = cur;
01043                 if( cur > 0 )
01044                 {
01045                         left++;
01046                 }
01047 
01048                 // Expand right to end of line
01049                 if( text[right - 1] == '\n' )
01050                 {
01051                         right--;
01052                 }
01053                 else
01054                 {
01055                         while( (text[right] != '\n') && (right <= getLength() ) )
01056                         {
01057                                 right++;
01058                         }
01059                 }
01060 
01061                 // Find each start-of-line and indent it
01062                 do
01063                 {
01064                         if( text[cur] == '\n' )
01065                         {
01066                                 cur++;
01067                         }
01068 
01069                         S32 delta_spaces = indentLine( cur, spaces );
01070                         if( delta_spaces > 0 )
01071                         {
01072                                 cur += delta_spaces;
01073                         }
01074                         right += delta_spaces;
01075 
01076                         //text = mWText;
01077 
01078                         // Find the next new line
01079                         while( (cur < right) && (text[cur] != '\n') )
01080                         {
01081                                 cur++;
01082                         }
01083                 }
01084                 while( cur < right );
01085 
01086                 if( (right < getLength()) && (text[right] == '\n') )
01087                 {
01088                         right++;
01089                 }
01090 
01091                 // Set the selection and cursor
01092                 if( cursor_on_right )
01093                 {
01094                         mSelectionStart = left;
01095                         mSelectionEnd = right;
01096                 }
01097                 else
01098                 {
01099                         mSelectionStart = right;
01100                         mSelectionEnd = left;
01101                 }
01102                 mCursorPos = mSelectionEnd;
01103         }
01104 }
01105 
01106 
01107 BOOL LLTextEditor::canSelectAll()
01108 {
01109         return TRUE;
01110 }
01111 
01112 void LLTextEditor::selectAll()
01113 {
01114         mSelectionStart = getLength();
01115         mSelectionEnd = 0;
01116         mCursorPos = mSelectionEnd;
01117 }
01118 
01119 
01120 BOOL LLTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
01121 {
01122         if (pointInView(x, y) && getVisible())
01123         {
01124                 for ( child_list_const_iter_t child_it = getChildList()->begin();
01125                           child_it != getChildList()->end(); ++child_it)
01126                 {
01127                         LLView* viewp = *child_it;
01128                         S32 local_x = x - viewp->getRect().mLeft;
01129                         S32 local_y = y - viewp->getRect().mBottom;
01130                         if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) )
01131                         {
01132                                 return TRUE;
01133                         }
01134                 }
01135 
01136                 if( mSegments.empty() )
01137                 {
01138                         return TRUE;
01139                 }
01140 
01141                 LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
01142                 if( cur_segment )
01143                 {
01144                         BOOL has_tool_tip = FALSE;
01145                         has_tool_tip = cur_segment->getToolTip( msg );
01146 
01147                         if( has_tool_tip )
01148                         {
01149                                 // Just use a slop area around the cursor
01150                                 // Convert rect local to screen coordinates
01151                                 S32 SLOP = 8;
01152                                 localPointToScreen( 
01153                                         x - SLOP, y - SLOP, 
01154                                         &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
01155                                 sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP;
01156                                 sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP;
01157                         }
01158                 }
01159                 return TRUE;
01160         }
01161         return FALSE;
01162 }
01163 
01164 BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks)
01165 {
01166         // Pretend the mouse is over the scrollbar
01167         if (getVisible())
01168         {
01169                 return mScrollbar->handleScrollWheel( 0, 0, clicks );
01170         }
01171         else
01172         {
01173                 return FALSE;
01174         }
01175 }
01176 
01177 BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
01178 {
01179         BOOL    handled = FALSE;
01180 
01181         // Let scrollbar have first dibs
01182         handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
01183 
01184         if( !handled && mTakesNonScrollClicks)
01185         {
01186                 if (!(mask & MASK_SHIFT))
01187                 {
01188                         deselect();
01189                 }
01190 
01191                 BOOL start_select = TRUE;
01192                 if( start_select )
01193                 {
01194                         // If we're not scrolling (handled by child), then we're selecting
01195                         if (mask & MASK_SHIFT)
01196                         {
01197                                 S32 old_cursor_pos = mCursorPos;
01198                                 setCursorAtLocalPos( x, y, TRUE );
01199 
01200                                 if (hasSelection())
01201                                 {
01202                                         /* Mac-like behavior - extend selection towards the cursor
01203                                         if (mCursorPos < mSelectionStart
01204                                                 && mCursorPos < mSelectionEnd)
01205                                         {
01206                                                 // ...left of selection
01207                                                 mSelectionStart = llmax(mSelectionStart, mSelectionEnd);
01208                                                 mSelectionEnd = mCursorPos;
01209                                         }
01210                                         else if (mCursorPos > mSelectionStart
01211                                                 && mCursorPos > mSelectionEnd)
01212                                         {
01213                                                 // ...right of selection
01214                                                 mSelectionStart = llmin(mSelectionStart, mSelectionEnd);
01215                                                 mSelectionEnd = mCursorPos;
01216                                         }
01217                                         else
01218                                         {
01219                                                 mSelectionEnd = mCursorPos;
01220                                         }
01221                                         */
01222                                         // Windows behavior
01223                                         mSelectionEnd = mCursorPos;
01224                                 }
01225                                 else
01226                                 {
01227                                         mSelectionStart = old_cursor_pos;
01228                                         mSelectionEnd = mCursorPos;
01229                                 }
01230                                 // assume we're starting a drag select
01231                                 mIsSelecting = TRUE;
01232                         }
01233                         else
01234                         {
01235                                 setCursorAtLocalPos( x, y, TRUE );
01236                                 startSelection();
01237                         }
01238                         gFocusMgr.setMouseCapture( this );
01239                 }
01240 
01241                 handled = TRUE;
01242         }
01243 
01244         if (mTakesFocus)
01245         {
01246                 setFocus( TRUE );
01247                 handled = TRUE;
01248         }
01249 
01250         // Delay cursor flashing
01251         mKeystrokeTimer.reset();
01252 
01253         return handled;
01254 }
01255 
01256 
01257 BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask)
01258 {
01259         BOOL handled = FALSE;
01260 
01261         mHoverSegment = NULL;
01262         if( getVisible() )
01263         {
01264                 if(hasMouseCapture() )
01265                 {
01266                         if( mIsSelecting ) 
01267                         {
01268                                 if (x != mLastSelectionX || y != mLastSelectionY)
01269                                 {
01270                                         mLastSelectionX = x;
01271                                         mLastSelectionY = y;
01272                                 }
01273 
01274                                 if( y > mTextRect.mTop )
01275                                 {
01276                                         mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 );
01277                                 }
01278                                 else
01279                                 if( y < mTextRect.mBottom )
01280                                 {
01281                                         mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 );
01282                                 }
01283 
01284                                 setCursorAtLocalPos( x, y, TRUE );
01285                                 mSelectionEnd = mCursorPos;
01286                                 
01287                                 updateScrollFromCursor();
01288                         }
01289 
01290                         lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;               
01291                         getWindow()->setCursor(UI_CURSOR_IBEAM);
01292                         handled = TRUE;
01293                 }
01294 
01295                 if( !handled )
01296                 {
01297                         // Pass to children
01298                         handled = LLView::childrenHandleHover(x, y, mask) != NULL;
01299                 }
01300 
01301                 if( handled )
01302                 {
01303                         // Delay cursor flashing
01304                         mKeystrokeTimer.reset();
01305                 }
01306         
01307                 // Opaque
01308                 if( !handled && mTakesNonScrollClicks)
01309                 {
01310                         // Check to see if we're over an HTML-style link
01311                         if( !mSegments.empty() )
01312                         {
01313                                 LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
01314                                 if( cur_segment )
01315                                 {
01316                                         if(cur_segment->getStyle().isLink())
01317                                         {
01318                                                 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl;          
01319                                                 getWindow()->setCursor(UI_CURSOR_HAND);
01320                                                 handled = TRUE;
01321                                         }
01322                                         else
01323                                         if(cur_segment->getStyle().getIsEmbeddedItem())
01324                                         {
01325                                                 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl;         
01326                                                 getWindow()->setCursor(UI_CURSOR_HAND);
01327                                                 //getWindow()->setCursor(UI_CURSOR_ARROW);
01328                                                 handled = TRUE;
01329                                         }
01330                                         mHoverSegment = cur_segment;
01331                                 }
01332                         }
01333 
01334                         if( !handled )
01335                         {
01336                                 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;             
01337                                 if (!mScrollbar->getVisible() || x < mRect.getWidth() - SCROLLBAR_SIZE)
01338                                 {
01339                                         getWindow()->setCursor(UI_CURSOR_IBEAM);
01340                                 }
01341                                 else
01342                                 {
01343                                         getWindow()->setCursor(UI_CURSOR_ARROW);
01344                                 }
01345                                 handled = TRUE;
01346                         }
01347                 }
01348         }
01349 
01350         if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
01351         {
01352                 mOnScrollEndCallback(mOnScrollEndData);
01353         }
01354         return handled;
01355 }
01356 
01357 
01358 BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
01359 {
01360         BOOL    handled = FALSE;
01361 
01362         // let scrollbar have first dibs
01363         handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL;
01364 
01365         if( !handled && mTakesNonScrollClicks)
01366         {
01367                 if( mIsSelecting )
01368                 {
01369                         // Finish selection
01370                         if( y > mTextRect.mTop )
01371                         {
01372                                 mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 );
01373                         }
01374                         else
01375                         if( y < mTextRect.mBottom )
01376                         {
01377                                 mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 );
01378                         }
01379                         
01380                         setCursorAtLocalPos( x, y, TRUE );
01381                         endSelection();
01382 
01383                         updateScrollFromCursor();
01384                 }
01385                 
01386                 if( !hasSelection() )
01387                 {
01388                         handleMouseUpOverSegment( x, y, mask );
01389                 }
01390 
01391                 handled = TRUE;
01392         }
01393 
01394         // Delay cursor flashing
01395         mKeystrokeTimer.reset();
01396 
01397         if( hasMouseCapture()  )
01398         {
01399                 gFocusMgr.setMouseCapture( NULL );
01400                 
01401                 handled = TRUE;
01402         }
01403 
01404         return handled;
01405 }
01406 
01407 
01408 BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
01409 {
01410         BOOL    handled = FALSE;
01411 
01412         // let scrollbar have first dibs
01413         handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL;
01414 
01415         if( !handled && mTakesNonScrollClicks)
01416         {
01417                 if (mTakesFocus)
01418                 {
01419                         setFocus( TRUE );
01420                 }
01421                 
01422                 setCursorAtLocalPos( x, y, FALSE );
01423                 deselect();
01424 
01425                 const LLWString &text = mWText;
01426                 
01427                 if( isPartOfWord( text[mCursorPos] ) )
01428                 {
01429                         // Select word the cursor is over
01430                         while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1]))
01431                         {
01432                                 mCursorPos--;
01433                         }
01434                         startSelection();
01435 
01436                         while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) )
01437                         {
01438                                 mCursorPos++;
01439                         }
01440                 
01441                         mSelectionEnd = mCursorPos;
01442                 }
01443                 else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) )
01444                 {
01445                         // Select the character the cursor is over
01446                         startSelection();
01447                         mCursorPos++;
01448                         mSelectionEnd = mCursorPos;
01449                 }
01450 
01451                 // We don't want handleMouseUp() to "finish" the selection (and thereby
01452                 // set mSelectionEnd to where the mouse is), so we finish the selection here.
01453                 mIsSelecting = FALSE;  
01454 
01455                 // delay cursor flashing
01456                 mKeystrokeTimer.reset();
01457 
01458                 handled = TRUE;
01459         }
01460         return handled;
01461 }
01462 
01463 
01464 // Allow calling cards to be dropped onto text fields.  Append the name and
01465 // a carriage return.
01466 // virtual
01467 BOOL LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask,
01468                                           BOOL drop, EDragAndDropType cargo_type, void *cargo_data,
01469                                           EAcceptance *accept,
01470                                           LLString& tooltip_msg)
01471 {
01472         *accept = ACCEPT_NO;
01473 
01474         return TRUE;
01475 }
01476 
01477 //----------------------------------------------------------------------------
01478 // Returns change in number of characters in mText
01479 
01480 S32 LLTextEditor::execute( LLTextCmd* cmd )
01481 {
01482         S32 delta = 0;
01483         if( cmd->execute(this, &delta) )
01484         {
01485                 // Delete top of undo stack
01486                 undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
01487                 if (enditer != mUndoStack.begin())
01488                 {
01489                         --enditer;
01490                         std::for_each(mUndoStack.begin(), enditer, DeletePointer());
01491                         mUndoStack.erase(mUndoStack.begin(), enditer);
01492                 }
01493                 // Push the new command is now on the top (front) of the undo stack.
01494                 mUndoStack.push_front(cmd);
01495                 mLastCmd = cmd;
01496         }
01497         else
01498         {
01499                 // Operation failed, so don't put it on the undo stack.
01500                 delete cmd;
01501         }
01502 
01503         return delta;
01504 }
01505 
01506 S32 LLTextEditor::insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op)
01507 {
01508         return execute( new LLTextCmdInsert( pos, group_with_next_op, wstr ) );
01509 }
01510 
01511 S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_next_op)
01512 {
01513         return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) );
01514 }
01515 
01516 S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op)
01517 {
01518         return insert(mWText.length(), wstr, group_with_next_op);
01519 }
01520 
01521 S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc)
01522 {
01523         if ((S32)mWText.length() == pos)
01524         {
01525                 return addChar(pos, wc);
01526         }
01527         else
01528         {
01529                 return execute(new LLTextCmdOverwriteChar(pos, FALSE, wc));
01530         }
01531 }
01532 
01533 // Remove a single character from the text.  Tries to remove
01534 // a pseudo-tab (up to for spaces in a row)
01535 void LLTextEditor::removeCharOrTab()
01536 {
01537         if( getEnabled() )
01538         {
01539                 if( mCursorPos > 0 )
01540                 {
01541                         S32 chars_to_remove = 1;
01542 
01543                         const LLWString &text = mWText;
01544                         if (text[mCursorPos - 1] == ' ')
01545                         {
01546                                 // Try to remove a "tab"
01547                                 S32 line, offset;
01548                                 getLineAndOffset(mCursorPos, &line, &offset);
01549                                 if (offset > 0)
01550                                 {
01551                                         chars_to_remove = offset % SPACES_PER_TAB;
01552                                         if( chars_to_remove == 0 )
01553                                         {
01554                                                 chars_to_remove = SPACES_PER_TAB;
01555                                         }
01556 
01557                                         for( S32 i = 0; i < chars_to_remove; i++ )
01558                                         {
01559                                                 if (text[ mCursorPos - i - 1] != ' ')
01560                                                 {
01561                                                         // Fewer than a full tab's worth of spaces, so
01562                                                         // just delete a single character.
01563                                                         chars_to_remove = 1;
01564                                                         break;
01565                                                 }
01566                                         }
01567                                 }
01568                         }
01569                 
01570                         for (S32 i = 0; i < chars_to_remove; i++)
01571                         {
01572                                 setCursorPos(mCursorPos - 1);
01573                                 remove( mCursorPos, 1, FALSE );
01574                         }
01575                 }
01576                 else
01577                 {
01578                         reportBadKeystroke();
01579                 }
01580         }
01581 }
01582 
01583 // Remove a single character from the text
01584 S32 LLTextEditor::removeChar(S32 pos)
01585 {
01586         return remove( pos, 1, FALSE );
01587 }
01588 
01589 void LLTextEditor::removeChar()
01590 {
01591         if (getEnabled())
01592         {
01593                 if (mCursorPos > 0)
01594                 {
01595                         setCursorPos(mCursorPos - 1);
01596                         removeChar(mCursorPos);
01597                 }
01598                 else
01599                 {
01600                         reportBadKeystroke();
01601                 }
01602         }
01603 }
01604 
01605 // Add a single character to the text
01606 S32 LLTextEditor::addChar(S32 pos, llwchar wc)
01607 {
01608         if ((S32)mWText.length() == mMaxTextLength)
01609         {
01610                 make_ui_sound("UISndBadKeystroke");
01611                 return 0;
01612         }
01613 
01614         if (mLastCmd && mLastCmd->canExtend(pos))
01615         {
01616                 S32 delta = 0;
01617                 mLastCmd->extendAndExecute(this, pos, wc, &delta);
01618                 return delta;
01619         }
01620         else
01621         {
01622                 return execute(new LLTextCmdAddChar(pos, FALSE, wc));
01623         }
01624 }
01625 
01626 void LLTextEditor::addChar(llwchar wc)
01627 {
01628         if( getEnabled() )
01629         {
01630                 if( hasSelection() )
01631                 {
01632                         deleteSelection(TRUE);
01633                 }
01634                 else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
01635                 {
01636                         removeChar(mCursorPos);
01637                 }
01638 
01639                 setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
01640         }
01641 }
01642 
01643 
01644 BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask)
01645 {
01646         BOOL handled = FALSE;
01647 
01648         if( mask & MASK_SHIFT )
01649         {
01650                 handled = TRUE;
01651                 
01652                 switch( key )
01653                 {
01654                 case KEY_LEFT:
01655                         if( 0 < mCursorPos )
01656                         {
01657                                 startSelection();
01658                                 mCursorPos--;
01659                                 if( mask & MASK_CONTROL )
01660                                 {
01661                                         mCursorPos = prevWordPos(mCursorPos);
01662                                 }
01663                                 mSelectionEnd = mCursorPos;
01664                         }
01665                         break;
01666 
01667                 case KEY_RIGHT:
01668                         if( mCursorPos < getLength() )
01669                         {
01670                                 startSelection();
01671                                 mCursorPos++;
01672                                 if( mask & MASK_CONTROL )
01673                                 {
01674                                         mCursorPos = nextWordPos(mCursorPos);
01675                                 }
01676                                 mSelectionEnd = mCursorPos;
01677                         }
01678                         break;
01679 
01680                 case KEY_UP:
01681                         startSelection();
01682                         changeLine( -1 );
01683                         mSelectionEnd = mCursorPos;
01684                         break;
01685 
01686                 case KEY_PAGE_UP:
01687                         startSelection();
01688                         changePage( -1 );
01689                         mSelectionEnd = mCursorPos;
01690                         break;
01691 
01692                 case KEY_HOME:
01693                         startSelection();
01694                         if( mask & MASK_CONTROL )
01695                         {
01696                                 mCursorPos = 0;
01697                         }
01698                         else
01699                         {
01700                                 startOfLine();
01701                         }
01702                         mSelectionEnd = mCursorPos;
01703                         break;
01704 
01705                 case KEY_DOWN:
01706                         startSelection();
01707                         changeLine( 1 );
01708                         mSelectionEnd = mCursorPos;
01709                         break;
01710 
01711                 case KEY_PAGE_DOWN:
01712                         startSelection();
01713                         changePage( 1 );
01714                         mSelectionEnd = mCursorPos;
01715                         break;
01716 
01717                 case KEY_END:
01718                         startSelection();
01719                         if( mask & MASK_CONTROL )
01720                         {
01721                                 mCursorPos = getLength();
01722                         }
01723                         else
01724                         {
01725                                 endOfLine();
01726                         }
01727                         mSelectionEnd = mCursorPos;
01728                         break;
01729 
01730                 default:
01731                         handled = FALSE;
01732                         break;
01733                 }
01734         }
01735 
01736 
01737         if( !handled && mHandleEditKeysDirectly )
01738         {
01739                 if( (MASK_CONTROL & mask) && ('A' == key) )
01740                 {
01741                         if( canSelectAll() )
01742                         {
01743                                 selectAll();
01744                         }
01745                         else
01746                         {
01747                                 reportBadKeystroke();
01748                         }
01749                         handled = TRUE;
01750                 }
01751         }
01752 
01753         return handled;
01754 }
01755 
01756 BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask)
01757 {
01758         BOOL handled = FALSE;
01759 
01760         // Ignore capslock key
01761         if( MASK_NONE == mask )
01762         {
01763                 handled = TRUE;
01764                 switch( key )
01765                 {
01766                 case KEY_UP:
01767                         if (mReadOnly)
01768                         {
01769                                 mScrollbar->setDocPos(mScrollbar->getDocPos() - 1);
01770                         }
01771                         else
01772                         {
01773                                 changeLine( -1 );
01774                         }
01775                         break;
01776 
01777                 case KEY_PAGE_UP:
01778                         changePage( -1 );
01779                         break;
01780 
01781                 case KEY_HOME:
01782                         if (mReadOnly)
01783                         {
01784                                 mScrollbar->setDocPos(0);
01785                         }
01786                         else
01787                         {
01788                                 startOfLine();
01789                         }
01790                         break;
01791 
01792                 case KEY_DOWN:
01793                         if (mReadOnly)
01794                         {
01795                                 mScrollbar->setDocPos(mScrollbar->getDocPos() + 1);
01796                         }
01797                         else
01798                         {
01799                                 changeLine( 1 );
01800                         }
01801                         break;
01802 
01803                 case KEY_PAGE_DOWN:
01804                         changePage( 1 );
01805                         break;
01806  
01807                 case KEY_END:
01808                         if (mReadOnly)
01809                         {
01810                                 mScrollbar->setDocPos(mScrollbar->getDocPosMax());
01811                         }
01812                         else
01813                         {
01814                                 endOfLine();
01815                         }
01816                         break;
01817 
01818                 case KEY_LEFT:
01819                         if (mReadOnly)
01820                         {
01821                                 break;
01822                         }
01823                         if( hasSelection() )
01824                         {
01825                                 setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd ));
01826                         }
01827                         else
01828                         {
01829                                 if( 0 < mCursorPos )
01830                                 {
01831                                         setCursorPos(mCursorPos - 1);
01832                                 }
01833                                 else
01834                                 {
01835                                         reportBadKeystroke();
01836                                 }
01837                         }
01838                         break;
01839 
01840                 case KEY_RIGHT:
01841                         if (mReadOnly)
01842                         {
01843                                 break;
01844                         }
01845                         if( hasSelection() )
01846                         {
01847                                 setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd ));
01848                         }
01849                         else
01850                         {
01851                                 if( mCursorPos < getLength() )
01852                                 {
01853                                         setCursorPos(mCursorPos + 1);
01854                                 }
01855                                 else
01856                                 {
01857                                         reportBadKeystroke();
01858                                 }
01859                         }       
01860                         break;
01861                         
01862                 default:
01863                         handled = FALSE;
01864                         break;
01865                 }
01866         }
01867         
01868         if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
01869         {
01870                 mOnScrollEndCallback(mOnScrollEndData);
01871         }
01872         return handled;
01873 }
01874 
01875 void LLTextEditor::deleteSelection(BOOL group_with_next_op )
01876 {
01877         if( getEnabled() && hasSelection() )
01878         {
01879                 S32 pos = llmin( mSelectionStart, mSelectionEnd );
01880                 S32 length = abs( mSelectionStart - mSelectionEnd );
01881         
01882                 remove( pos, length, group_with_next_op );
01883 
01884                 deselect();
01885                 setCursorPos(pos);
01886         }
01887 }
01888 
01889 BOOL LLTextEditor::canCut()
01890 {
01891         return !mReadOnly && hasSelection();
01892 }
01893 
01894 // cut selection to clipboard
01895 void LLTextEditor::cut()
01896 {
01897         if( canCut() )
01898         {
01899                 S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
01900                 S32 length = abs( mSelectionStart - mSelectionEnd );
01901                 gClipboard.copyFromSubstring( mWText, left_pos, length, mSourceID );
01902                 deleteSelection( FALSE );
01903 
01904                 updateLineStartList();
01905                 updateScrollFromCursor();
01906         }
01907 }
01908 
01909 BOOL LLTextEditor::canCopy()
01910 {
01911         return hasSelection();
01912 }
01913 
01914 
01915 // copy selection to clipboard
01916 void LLTextEditor::copy()
01917 {
01918         if( canCopy() )
01919         {
01920                 S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
01921                 S32 length = abs( mSelectionStart - mSelectionEnd );
01922                 gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID);
01923         }
01924 }
01925 
01926 BOOL LLTextEditor::canPaste()
01927 {
01928         return !mReadOnly && gClipboard.canPasteString();
01929 }
01930 
01931 
01932 // paste from clipboard
01933 void LLTextEditor::paste()
01934 {
01935         if (canPaste())
01936         {
01937                 LLUUID source_id;
01938                 LLWString paste = gClipboard.getPasteWString(&source_id);
01939                 if (!paste.empty())
01940                 {
01941                         // Delete any selected characters (the paste replaces them)
01942                         if( hasSelection() )
01943                         {
01944                                 deleteSelection(TRUE);
01945                         }
01946 
01947                         // Clean up string (replace tabs and remove characters that our fonts don't support).
01948                         LLWString clean_string(paste);
01949                         LLWString::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB);
01950                         if( mAllowEmbeddedItems )
01951                         {
01952                                 const llwchar LF = 10;
01953                                 S32 len = clean_string.length();
01954                                 for( S32 i = 0; i < len; i++ )
01955                                 {
01956                                         llwchar wc = clean_string[i];
01957                                         if( (wc < LLFont::FIRST_CHAR) && (wc != LF) )
01958                                         {
01959                                                 clean_string[i] = LL_UNKNOWN_CHAR;
01960                                         }
01961                                         else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR)
01962                                         {
01963                                                 clean_string[i] = pasteEmbeddedItem(wc);
01964                                         }
01965                                 }
01966                         }
01967         
01968                         // Insert the new text into the existing text.
01969                         setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE));
01970                         deselect();
01971 
01972                         updateLineStartList();
01973                         updateScrollFromCursor();
01974                 }
01975         }
01976 }
01977 
01978 
01979 BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask)     
01980 {
01981         BOOL handled = FALSE;
01982 
01983         if( mask & MASK_CONTROL )
01984         {
01985                 handled = TRUE;
01986 
01987                 switch( key )
01988                 {
01989                 case KEY_HOME:
01990                         if( mask & MASK_SHIFT )
01991                         {
01992                                 startSelection();
01993                                 mCursorPos = 0;
01994                                 mSelectionEnd = mCursorPos;
01995                         }
01996                         else
01997                         {
01998                                 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
01999                                 // all move the cursor as if clicking, so should deselect.
02000                                 deselect();
02001                                 setCursorPos(0);
02002                         }
02003                         break;
02004 
02005                 case KEY_END:
02006                         {
02007                                 if( mask & MASK_SHIFT )
02008                                 {
02009                                         startSelection();
02010                                 }
02011                                 else
02012                                 {
02013                                         // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
02014                                         // all move the cursor as if clicking, so should deselect.
02015                                         deselect();
02016                                 }
02017                                 endOfDoc();
02018                                 if( mask & MASK_SHIFT )
02019                                 {
02020                                         mSelectionEnd = mCursorPos;
02021                                 }
02022                                 break;
02023                         }
02024 
02025                 case KEY_RIGHT:
02026                         if( mCursorPos < getLength() )
02027                         {
02028                                 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
02029                                 // all move the cursor as if clicking, so should deselect.
02030                                 deselect();
02031 
02032                                 setCursorPos(nextWordPos(mCursorPos + 1));
02033                         }
02034                         break;
02035 
02036 
02037                 case KEY_LEFT:
02038                         if( mCursorPos > 0 )
02039                         {
02040                                 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
02041                                 // all move the cursor as if clicking, so should deselect.
02042                                 deselect();
02043 
02044                                 setCursorPos(prevWordPos(mCursorPos - 1));
02045                         }
02046                         break;
02047 
02048                 default:
02049                         handled = FALSE;
02050                         break;
02051                 }
02052         }
02053 
02054         return handled;
02055 }
02056 
02057 BOOL LLTextEditor::handleEditKey(const KEY key, const MASK mask)
02058 {
02059         BOOL handled = FALSE;
02060 
02061         // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system.
02062         if( KEY_DELETE == key )
02063         {
02064                 if( canDoDelete() )
02065                 {
02066                         doDelete();
02067                 }
02068                 else
02069                 {
02070                         reportBadKeystroke();
02071                 }
02072                 handled = TRUE;
02073         }
02074         else
02075         if( MASK_CONTROL & mask )
02076         {
02077                 if( 'C' == key )
02078                 {
02079                         if( canCopy() )
02080                         {
02081                                 copy();
02082                         }
02083                         else
02084                         {
02085                                 reportBadKeystroke();
02086                         }
02087                         handled = TRUE;
02088                 }
02089                 else
02090                 if( 'V' == key )
02091                 {
02092                         if( canPaste() )
02093                         {
02094                                 paste();
02095                         }
02096                         else
02097                         {
02098                                 reportBadKeystroke();
02099                         }
02100                         handled = TRUE;
02101                 }
02102                 else
02103                 if( 'X' == key )
02104                 {
02105                         if( canCut() )
02106                         {
02107                                 cut();
02108                         }
02109                         else
02110                         {
02111                                 reportBadKeystroke();
02112                         }
02113                         handled = TRUE;
02114                 }
02115         }
02116 
02117         return handled;
02118 }
02119 
02120         
02121 BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit)       
02122 {
02123         *return_key_hit = FALSE;
02124         BOOL handled = TRUE;
02125 
02126         switch( key )
02127         {
02128         case KEY_INSERT:
02129                 if (mask == MASK_NONE)
02130                 {
02131                         gKeyboard->toggleInsertMode();
02132                 }
02133                 break;
02134 
02135         case KEY_BACKSPACE:
02136                 if( hasSelection() )
02137                 {
02138                         deleteSelection(FALSE);
02139                 }
02140                 else
02141                 if( 0 < mCursorPos )
02142                 {
02143                         removeCharOrTab();
02144                 }
02145                 else
02146                 {
02147                         reportBadKeystroke();
02148                 }
02149                 break;
02150 
02151 
02152         case KEY_RETURN:
02153                 if (mask == MASK_NONE)
02154                 {
02155                         if( hasSelection() )
02156                         {
02157                                 deleteSelection(FALSE);
02158                         }
02159                         autoIndent(); // TODO: make this optional
02160                 }
02161                 else
02162                 {
02163                         handled = FALSE;
02164                         break;
02165                 }
02166                 break;
02167 
02168         case KEY_TAB:
02169                 if (mask & MASK_CONTROL)
02170                 {
02171                         handled = FALSE;
02172                         break;
02173                 }
02174                 if( hasSelection() && selectionContainsLineBreaks() )
02175                 {
02176                         indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB );
02177                 }
02178                 else
02179                 {
02180                         if( hasSelection() )
02181                         {
02182                                 deleteSelection(FALSE);
02183                         }
02184                         
02185                         S32 line, offset;
02186                         getLineAndOffset( mCursorPos, &line, &offset );
02187 
02188                         S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
02189                         for( S32 i=0; i < spaces_needed; i++ )
02190                         {
02191                                 addChar( ' ' );
02192                         }
02193                 }
02194                 break;
02195                 
02196         default:
02197                 handled = FALSE;
02198                 break;
02199         }
02200 
02201         return handled;
02202 }
02203 
02204 
02205 void LLTextEditor::unindentLineBeforeCloseBrace()
02206 {
02207         if( mCursorPos >= 1 )
02208         {
02209                 const LLWString &text = mWText;
02210                 if( ' ' == text[ mCursorPos - 1 ] )
02211                 {
02212                         removeCharOrTab();
02213                 }
02214         }
02215 }
02216 
02217 
02218 BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent )
02219 {
02220         BOOL    handled = FALSE;
02221         BOOL    selection_modified = FALSE;
02222         BOOL    return_key_hit = FALSE;
02223         BOOL    text_may_have_changed = TRUE;
02224 
02225         if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible())
02226         {
02227                 // Special case for TAB.  If want to move to next field, report
02228                 // not handled and let the parent take care of field movement.
02229                 if (KEY_TAB == key && mTabToNextField)
02230                 {
02231                         return FALSE;
02232                 }
02233 
02234                 handled = handleNavigationKey( key, mask );
02235                 if( handled )
02236                 {
02237                         text_may_have_changed = FALSE;
02238                 }
02239                         
02240                 if( !handled )
02241                 {
02242                         handled = handleSelectionKey( key, mask );
02243                         if( handled )
02244                         {
02245                                 selection_modified = TRUE;
02246                         }
02247                 }
02248         
02249                 if( !handled )
02250                 {
02251                         handled = handleControlKey( key, mask );
02252                         if( handled )
02253                         {
02254                                 selection_modified = TRUE;
02255                         }
02256                 }
02257 
02258                 if( !handled && mHandleEditKeysDirectly )
02259                 {
02260                         handled = handleEditKey( key, mask );
02261                         if( handled )
02262                         {
02263                                 selection_modified = TRUE;
02264                                 text_may_have_changed = TRUE;
02265                         }
02266                 }
02267 
02268                 // Handle most keys only if the text editor is writeable.
02269                 if( !mReadOnly )
02270                 {
02271                         if( !handled )
02272                         {
02273                                 handled = handleSpecialKey( key, mask, &return_key_hit );
02274                                 if( handled )
02275                                 {
02276                                         selection_modified = TRUE;
02277                                         text_may_have_changed = TRUE;
02278                                 }
02279                         }
02280 
02281                 }
02282 
02283                 if( handled )
02284                 {
02285                         mKeystrokeTimer.reset();
02286 
02287                         // Most keystrokes will make the selection box go away, but not all will.
02288                         if( !selection_modified &&
02289                                 KEY_SHIFT != key &&
02290                                 KEY_CONTROL != key &&
02291                                 KEY_ALT != key &&
02292                                 KEY_CAPSLOCK )
02293                         {
02294                                 deselect();
02295                         }
02296 
02297                         if(text_may_have_changed)
02298                         {
02299                                 updateLineStartList();
02300                         }
02301                         updateScrollFromCursor();
02302                 }
02303         }
02304 
02305         return handled;
02306 }
02307 
02308 
02309 BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
02310 {
02311         if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
02312         {
02313                 return FALSE;
02314         }
02315 
02316         BOOL    handled = FALSE;
02317 
02318         if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible())
02319         {
02320                 // Handle most keys only if the text editor is writeable.
02321                 if( !mReadOnly )
02322                 {
02323                         if( '}' == uni_char )
02324                         {
02325                                 unindentLineBeforeCloseBrace();
02326                         }
02327 
02328                         // TODO: KLW Add auto show of tool tip on (
02329                         addChar( uni_char );
02330 
02331                         // Keys that add characters temporarily hide the cursor
02332                         getWindow()->hideCursorUntilMouseMove();
02333 
02334                         handled = TRUE;
02335                 }
02336 
02337                 if( handled )
02338                 {
02339                         mKeystrokeTimer.reset();
02340 
02341                         // Most keystrokes will make the selection box go away, but not all will.
02342                         deselect();
02343 
02344                         updateLineStartList();
02345                         updateScrollFromCursor();
02346                 }
02347         }
02348 
02349         return handled;
02350 }
02351 
02352 
02353 
02354 BOOL LLTextEditor::canDoDelete()
02355 {
02356         return !mReadOnly && ( hasSelection() || (mCursorPos < getLength()) );
02357 }
02358 
02359 void LLTextEditor::doDelete()
02360 {
02361         if( canDoDelete() )
02362         {
02363                 if( hasSelection() )
02364                 {
02365                         deleteSelection(FALSE);
02366                 }
02367                 else
02368                 if( mCursorPos < getLength() )
02369                 {       
02370                         S32 i;
02371                         S32 chars_to_remove = 1;
02372                         const LLWString &text = mWText;
02373                         if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) )
02374                         {
02375                                 // Try to remove a full tab's worth of spaces
02376                                 S32 line, offset;
02377                                 getLineAndOffset( mCursorPos, &line, &offset );
02378                                 chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
02379                                 if( chars_to_remove == 0 )
02380                                 {
02381                                         chars_to_remove = SPACES_PER_TAB;
02382                                 }
02383 
02384                                 for( i = 0; i < chars_to_remove; i++ )
02385                                 {
02386                                         if( text[mCursorPos + i] != ' ' )
02387                                         {
02388                                                 chars_to_remove = 1;
02389                                                 break;
02390                                         }
02391                                 }
02392                         }
02393 
02394 
02395                         for( i = 0; i < chars_to_remove; i++ )
02396                         {
02397                                 setCursorPos(mCursorPos + 1);
02398                                 removeChar();
02399                         }
02400                 }
02401 
02402                 updateLineStartList();
02403                 updateScrollFromCursor();               
02404         }
02405 }
02406 
02407 //----------------------------------------------------------------------------
02408 
02409 
02410 void LLTextEditor::blockUndo()
02411 {
02412         mBaseDocIsPristine = FALSE;
02413         mLastCmd = NULL;
02414         std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
02415         mUndoStack.clear();
02416 }
02417 
02418 
02419 BOOL LLTextEditor::canUndo()
02420 {
02421         return !mReadOnly && mLastCmd != NULL;
02422 }
02423 
02424 void LLTextEditor::undo()
02425 {
02426         if( canUndo() )
02427         {
02428                 deselect();
02429 
02430                 S32 pos = 0;
02431                 do
02432                 {
02433                         pos = mLastCmd->undo(this);
02434                         undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
02435                         if (iter != mUndoStack.end())
02436                                 ++iter;
02437                         if (iter != mUndoStack.end())
02438                                 mLastCmd = *iter;
02439                         else
02440                                 mLastCmd = NULL;
02441 
02442                 } while( mLastCmd && mLastCmd->groupWithNext() );
02443 
02444                 setCursorPos(pos);
02445 
02446                 updateLineStartList();
02447                 updateScrollFromCursor();
02448         }
02449 }
02450 
02451 BOOL LLTextEditor::canRedo()
02452 {
02453         return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front());
02454 }
02455 
02456 void LLTextEditor::redo()
02457 {
02458         if( canRedo() )
02459         {
02460                 deselect();
02461 
02462                 S32 pos = 0;
02463                 do
02464                 {
02465                         if( !mLastCmd )
02466                         {
02467                                 mLastCmd = mUndoStack.back();
02468                         }
02469                         else
02470                         {
02471                                 undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
02472                                 if (iter != mUndoStack.begin())
02473                                         mLastCmd = *(--iter);
02474                                 else
02475                                         mLastCmd = NULL;
02476                         }
02477 
02478                         if( mLastCmd )
02479                         {
02480                                 pos = mLastCmd->redo(this);
02481                         }
02482                 } while( 
02483                         mLastCmd &&
02484                         mLastCmd->groupWithNext() &&
02485                         (mLastCmd != mUndoStack.front()) );
02486                 
02487                 setCursorPos(pos);
02488 
02489                 updateLineStartList();
02490                 updateScrollFromCursor();
02491         }
02492 }
02493 
02494 
02495 // virtual, from LLView
02496 void LLTextEditor::onFocusLost()
02497 {
02498         getWindow()->allowLanguageTextInput(FALSE);
02499 
02500         // Route menu back to the default
02501         if( gEditMenuHandler == this )
02502         {
02503                 gEditMenuHandler = NULL;
02504         }
02505 
02506         if (mCommitOnFocusLost)
02507         {
02508                 onCommit();
02509         }
02510 
02511         // Make sure cursor is shown again
02512         getWindow()->showCursorFromMouseMove();
02513 
02514         LLUICtrl::onFocusLost();
02515 }
02516 
02517 void LLTextEditor::setEnabled(BOOL enabled)
02518 {
02519         // just treat enabled as read-only flag
02520         BOOL read_only = !enabled;
02521         if (read_only != mReadOnly)
02522         {
02523                 mReadOnly = read_only;
02524                 updateSegments();
02525         }
02526 }
02527 
02528 void LLTextEditor::drawBackground()
02529 {
02530         S32 left = 0;
02531         S32 top = mRect.getHeight();
02532         S32 right = mRect.getWidth();
02533         S32 bottom = 0;
02534 
02535         LLColor4 bg_color = mReadOnlyBgColor;
02536 
02537         if( !mReadOnly )
02538         {
02539                 if (gFocusMgr.getKeyboardFocus() == this)
02540                 {
02541                         bg_color = mFocusBgColor;
02542                 }
02543                 else
02544                 {
02545                         bg_color = mWriteableBgColor;
02546                 }
02547         }
02548         gl_rect_2d(left, top, right, bottom, bg_color);
02549 
02550         LLView::draw();
02551 }
02552 
02553 // Draws the black box behind the selected text
02554 void LLTextEditor::drawSelectionBackground()
02555 {
02556         // Draw selection even if we don't have keyboard focus for search/replace
02557         if( hasSelection() )
02558         {
02559                 const LLWString &text = mWText;
02560                 const S32 text_len = getLength();
02561                 std::queue<S32> line_endings;
02562 
02563                 S32 line_height = llround( mGLFont->getLineHeight() );
02564 
02565                 S32 selection_left              = llmin( mSelectionStart, mSelectionEnd );
02566                 S32 selection_right             = llmax( mSelectionStart, mSelectionEnd );
02567                 S32 selection_left_x    = mTextRect.mLeft;
02568                 S32 selection_left_y    = mTextRect.mTop - line_height;
02569                 S32 selection_right_x   = mTextRect.mRight;
02570                 S32 selection_right_y   = mTextRect.mBottom;
02571 
02572                 BOOL selection_left_visible = FALSE;
02573                 BOOL selection_right_visible = FALSE;
02574 
02575                 // Skip through the lines we aren't drawing.
02576                 S32 cur_line = mScrollbar->getDocPos();
02577 
02578                 S32 left_line_num = cur_line;
02579                 S32 num_lines = getLineCount();
02580                 S32 right_line_num = num_lines - 1;
02581 
02582                 S32 line_start = -1;
02583                 if (cur_line >= num_lines)
02584                 {
02585                         return;
02586                 }
02587 
02588                 line_start = getLineStart(cur_line);
02589 
02590                 S32 left_visible_pos    = line_start;
02591                 S32 right_visible_pos   = line_start;
02592 
02593                 S32 text_y = mTextRect.mTop - line_height;
02594 
02595                 // Find the coordinates of the selected area
02596                 while((cur_line < num_lines))
02597                 {
02598                         S32 next_line = -1;
02599                         S32 line_end = text_len;
02600                         
02601                         if ((cur_line + 1) < num_lines)
02602                         {
02603                                 next_line = getLineStart(cur_line + 1);
02604                                 line_end = next_line;
02605 
02606                                 line_end = ( (line_end - line_start)==0 || text[next_line-1] == '\n' || text[next_line-1] == '\0' || text[next_line-1] == ' ' || text[next_line-1] == '\t'  ) ? next_line-1 : next_line;
02607                         }
02608 
02609                         const llwchar* line = text.c_str() + line_start;
02610 
02611                         if( line_start <= selection_left && selection_left <= line_end )
02612                         {
02613                                 left_line_num = cur_line;
02614                                 selection_left_visible = TRUE;
02615                                 selection_left_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_left - line_start, mAllowEmbeddedItems);
02616                                 selection_left_y = text_y;
02617                         }
02618                         if( line_start <= selection_right && selection_right <= line_end )
02619                         {
02620                                 right_line_num = cur_line;
02621                                 selection_right_visible = TRUE;
02622                                 selection_right_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_right - line_start, mAllowEmbeddedItems);
02623                                 if (selection_right == line_end)
02624                                 {
02625                                         // add empty space for "newline"
02626                                         //selection_right_x += mGLFont->getWidth("n");
02627                                 }
02628                                 selection_right_y = text_y;
02629                         }
02630                         
02631                         // if selection spans end of current line...
02632                         if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right)
02633                         {
02634                                 // extend selection slightly beyond end of line
02635                                 // to indicate selection of newline character (use "n" character to determine width)
02636                                 const LLWString nstr(utf8str_to_wstring(LLString("n")));
02637                                 line_endings.push(mTextRect.mLeft + mGLFont->getWidth(line, 0, line_end - line_start, mAllowEmbeddedItems) + mGLFont->getWidth(nstr.c_str()));
02638                         }
02639                         
02640                         // move down one line
02641                         text_y -= line_height;
02642 
02643                         right_visible_pos = line_end;
02644                         line_start = next_line;
02645                         cur_line++;
02646 
02647                         if (selection_right_visible)
02648                         {
02649                                 break;
02650                         }
02651                 }
02652                 
02653                 // Draw the selection box (we're using a box instead of reversing the colors on the selected text).
02654                 BOOL selection_visible = (left_visible_pos <= selection_right) && (selection_left <= right_visible_pos);
02655                 if( selection_visible )
02656                 {
02657                         LLGLSNoTexture no_texture;
02658                         const LLColor4& color = mReadOnly ? mReadOnlyBgColor : mWriteableBgColor;
02659                         F32 alpha = hasFocus() ? 1.f : 0.5f;
02660                         glColor4f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], alpha );
02661 
02662                         if( selection_left_y == selection_right_y )
02663                         {
02664                                 // Draw from selection start to selection end
02665                                 gl_rect_2d( selection_left_x, selection_left_y + line_height + 1,
02666                                         selection_right_x, selection_right_y);
02667                         }
02668                         else
02669                         {
02670                                 // Draw from selection start to the end of the first line
02671                                 if( mTextRect.mRight == selection_left_x )
02672                                 {
02673                                         selection_left_x -= CURSOR_THICKNESS;
02674                                 }
02675                                 
02676                                 S32 line_end = line_endings.front();
02677                                 line_endings.pop();
02678                                 gl_rect_2d( selection_left_x, selection_left_y + line_height + 1,
02679                                         line_end, selection_left_y );
02680 
02681                                 S32 line_num = left_line_num + 1;
02682                                 while(line_endings.size())
02683                                 {
02684                                         S32 vert_offset = -(line_num - left_line_num) * line_height;
02685                                         // Draw the block between the two lines
02686                                         gl_rect_2d( mTextRect.mLeft, selection_left_y + vert_offset + line_height + 1,
02687                                                 line_endings.front(), selection_left_y + vert_offset);
02688                                         line_endings.pop();
02689                                         line_num++;
02690                                 }
02691 
02692                                 // Draw from the start of the last line to selection end
02693                                 if( mTextRect.mLeft == selection_right_x )
02694                                 {
02695                                         selection_right_x += CURSOR_THICKNESS;
02696                                 }
02697                                 gl_rect_2d( mTextRect.mLeft, selection_right_y + line_height + 1,
02698                                         selection_right_x, selection_right_y );
02699                         }
02700                 }
02701         }
02702 }
02703 
02704 void LLTextEditor::drawCursor()
02705 {
02706         if( gFocusMgr.getKeyboardFocus() == this
02707                 && gShowTextEditCursor && !mReadOnly)
02708         {
02709                 const LLWString &text = mWText;
02710                 const S32 text_len = getLength();
02711 
02712                 // Skip through the lines we aren't drawing.
02713                 S32 cur_pos = mScrollbar->getDocPos();
02714 
02715                 S32 num_lines = getLineCount();
02716                 if (cur_pos >= num_lines)
02717                 {
02718                         return;
02719                 }
02720                 S32 line_start = getLineStart(cur_pos);
02721 
02722                 F32 line_height = mGLFont->getLineHeight();
02723                 F32 text_y = (F32)(mTextRect.mTop) - line_height;
02724 
02725                 F32 cursor_left = 0.f; 
02726                 F32 next_char_left = 0.f;
02727                 F32 cursor_bottom = 0.f;
02728                 BOOL cursor_visible = FALSE;
02729 
02730                 S32 line_end = 0;
02731                 // Determine if the cursor is visible and if so what its coordinates are.
02732                 while( (mTextRect.mBottom <= llround(text_y)) && (cur_pos < num_lines))
02733                 {
02734                         line_end = text_len + 1;
02735                         S32 next_line = -1;
02736 
02737                         if ((cur_pos + 1) < num_lines)
02738                         {
02739                                 next_line = getLineStart(cur_pos + 1);
02740                                 line_end = next_line - 1;
02741                         }
02742 
02743                         const llwchar* line = text.c_str() + line_start;
02744 
02745                         // Find the cursor and selection bounds
02746                         if( line_start <= mCursorPos && mCursorPos <= line_end )
02747                         {
02748                                 cursor_visible = TRUE;
02749                                 next_char_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, mCursorPos - line_start, mAllowEmbeddedItems );
02750                                 cursor_left = next_char_left - 1.f;
02751                                 cursor_bottom = text_y;
02752                                 break;
02753                         }
02754 
02755                         // move down one line
02756                         text_y -= line_height;
02757                         line_start = next_line;
02758                         cur_pos++;
02759                 }
02760 
02761                 // Draw the cursor
02762                 if( cursor_visible )
02763                 {
02764                         // (Flash the cursor every half second starting a fixed time after the last keystroke)
02765                         F32 elapsed = mKeystrokeTimer.getElapsedTimeF32();
02766                         if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
02767                         {
02768                                 F32 cursor_top = cursor_bottom + line_height + 1.f;
02769                                 F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS;
02770                                 if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
02771                                 {
02772                                         cursor_left += CURSOR_THICKNESS;
02773                                         const LLWString space(utf8str_to_wstring(LLString(" ")));
02774                                         F32 spacew = mGLFont->getWidthF32(space.c_str());
02775                                         if (mCursorPos == line_end)
02776                                         {
02777                                                 cursor_right = cursor_left + spacew;
02778                                         }
02779                                         else
02780                                         {
02781                                                 F32 width = mGLFont->getWidthF32(text.c_str(), mCursorPos, 1, mAllowEmbeddedItems);
02782                                                 cursor_right = cursor_left + llmax(spacew, width);
02783                                         }
02784                                 }
02785                                 
02786                                 LLGLSNoTexture no_texture;
02787 
02788                                 glColor4fv( mCursorColor.mV );
02789                                 
02790                                 gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top),
02791                                         llfloor(cursor_right), llfloor(cursor_bottom));
02792 
02793                                 if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n')
02794                                 {
02795                                         LLTextSegment* segmentp = getSegmentAtOffset(mCursorPos);
02796                                         LLColor4 text_color;
02797                                         if (segmentp)
02798                                         {
02799                                                 text_color = segmentp->getColor();
02800                                         }
02801                                         else if (mReadOnly)
02802                                         {
02803                                                 text_color = mReadOnlyFgColor;
02804                                         }
02805                                         else
02806                                         {
02807                                                 text_color = mFgColor;
02808                                         }
02809                                         LLGLSTexture texture;
02810                                         mGLFont->render(text, mCursorPos, next_char_left, cursor_bottom + line_height, 
02811                                                 LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], 1.f),
02812                                                 LLFontGL::LEFT, LLFontGL::TOP,
02813                                                 LLFontGL::NORMAL,
02814                                                 1);
02815                                 }
02816 
02817                                 // Make sure the IME is in the right place
02818                                 LLRect screen_pos = getScreenRect();
02819                                 LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_left), screen_pos.mBottom + llfloor(cursor_top) );
02820                                 if ( ime_pos.mX != mLastIMEPosition.mX || ime_pos.mY != mLastIMEPosition.mY )
02821                                 {
02822                                         mLastIMEPosition.mX = ime_pos.mX;
02823                                         mLastIMEPosition.mY = ime_pos.mY;
02824 
02825                                         ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]);
02826                                         ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]);
02827                                         getWindow()->setLanguageTextInput( ime_pos  );
02828                                 }
02829                         }
02830                 }
02831         }
02832 }
02833 
02834 
02835 void LLTextEditor::drawText()
02836 {
02837         const LLWString &text = mWText;
02838         const S32 text_len = getLength();
02839 
02840         if( text_len > 0 )
02841         {
02842                 S32 selection_left = -1;
02843                 S32 selection_right = -1;
02844                 // Draw selection even if we don't have keyboard focus for search/replace
02845                 if( hasSelection())
02846                 {
02847                         selection_left = llmin( mSelectionStart, mSelectionEnd );
02848                         selection_right = llmax( mSelectionStart, mSelectionEnd );
02849                 }
02850 
02851                 LLGLSUIDefault gls_ui;
02852 
02853                 S32 cur_line = mScrollbar->getDocPos();
02854                 S32 num_lines = getLineCount();
02855                 if (cur_line >= num_lines)
02856                 {
02857                         return;
02858                 }
02859                 
02860                 S32 line_start = getLineStart(cur_line);
02861                 LLTextSegment t(line_start);
02862                 segment_list_t::iterator seg_iter;
02863                 seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t, LLTextSegment::compare());
02864                 if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start) --seg_iter;
02865                 LLTextSegment* cur_segment = *seg_iter;
02866                 
02867                 S32 line_height = llround( mGLFont->getLineHeight() );
02868                 F32 text_y = (F32)(mTextRect.mTop - line_height);
02869                 while((mTextRect.mBottom <= text_y) && (cur_line < num_lines))
02870                 {
02871                         S32 next_start = -1;
02872                         S32 line_end = text_len;
02873 
02874                         if ((cur_line + 1) < num_lines)
02875                         {
02876                                 next_start = getLineStart(cur_line + 1);
02877                                 line_end = next_start;
02878                         }
02879                         if ( text[line_end-1] == '\n' )
02880                         {
02881                                 --line_end;
02882                         }
02883                         
02884                         F32 text_x = (F32)mTextRect.mLeft;
02885 
02886                         S32 seg_start = line_start;
02887                         while( seg_start < line_end )
02888                         {
02889                                 while( cur_segment->getEnd() <= seg_start )
02890                                 {
02891                                         seg_iter++;
02892                                         if (seg_iter == mSegments.end())
02893                                         {
02894                                                 llwarns << "Ran off the segmentation end!" << llendl;
02895                                                 return;
02896                                         }
02897                                         cur_segment = *seg_iter;
02898                                 }
02899                                 
02900                                 // Draw a segment within the line
02901                                 S32 clipped_end =       llmin( line_end, cur_segment->getEnd() );
02902                                 S32 clipped_len =       clipped_end - seg_start;
02903                                 if( clipped_len > 0 )
02904                                 {
02905                                         LLStyle style = cur_segment->getStyle();
02906                                         if ( style.isImage() && (cur_segment->getStart() >= seg_start) && (cur_segment->getStart() <= clipped_end))
02907                                         {
02908                                                 LLImageGL *image = style.getImage();
02909 
02910                                                 gl_draw_scaled_image( llround(text_x), llround(text_y)+line_height-style.mImageHeight, style.mImageWidth, style.mImageHeight, image, LLColor4::white );
02911                                                 
02912                                         }
02913 
02914                                         if (cur_segment == mHoverSegment && style.getIsEmbeddedItem())
02915                                         {
02916                                                 style.mUnderline = TRUE;
02917                                         }
02918 
02919                                         S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
02920                                         
02921                                         if ( (mParseHTML) && (left_pos > seg_start) && (left_pos < clipped_end) &&  mIsSelecting && (mSelectionStart == mSelectionEnd) )
02922                                         {
02923                                                 mHTML = style.getLinkHREF();
02924                                         }
02925 
02926                                         drawClippedSegment( text, seg_start, clipped_end, text_x, text_y, selection_left, selection_right, style, &text_x );
02927 
02928                                         // Note: text_x is incremented by drawClippedSegment()
02929                                         seg_start += clipped_len;
02930                                 }
02931                         }
02932 
02933                         // move down one line
02934                         text_y -= (F32)line_height;
02935 
02936                         line_start = next_start;
02937                         cur_line++;
02938                 }
02939         }
02940 }
02941 
02942 // Draws a single text segment, reversing the color for selection if needed.
02943 void LLTextEditor::drawClippedSegment(const LLWString &text, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& style, F32* right_x )
02944 {
02945         const LLFontGL* font = mGLFont;
02946 
02947         LLColor4 color;
02948 
02949         if (!style.isVisible())
02950         {
02951                 return;
02952         }
02953 
02954         color = style.getColor();
02955 
02956         if ( style.getFontString()[0] )
02957         {
02958                 font = gResMgr->getRes(style.getFontID());
02959         }
02960 
02961         U8 font_flags = LLFontGL::NORMAL;
02962         
02963         if (style.mBold)
02964         {
02965                 font_flags |= LLFontGL::BOLD;
02966         }
02967         if (style.mItalic)
02968         {
02969                 font_flags |= LLFontGL::ITALIC;
02970         }
02971         if (style.mUnderline)
02972         {
02973                 font_flags |= LLFontGL::UNDERLINE;
02974         }
02975 
02976         if (style.getIsEmbeddedItem())
02977         {
02978                 if (mReadOnly)
02979                 {
02980                         color = LLUI::sColorsGroup->getColor("TextEmbeddedItemReadOnlyColor");
02981                 }
02982                 else
02983                 {
02984                         color = LLUI::sColorsGroup->getColor("TextEmbeddedItemColor");
02985                 }
02986         }
02987 
02988         F32 y_top = y + (F32)llround(font->getLineHeight());
02989 
02990         if( selection_left > seg_start )
02991         {
02992                 // Draw normally
02993                 S32 start = seg_start;
02994                 S32 end = llmin( selection_left, seg_end );
02995                 S32 length =  end - start;
02996                 font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems);
02997         }
02998         x = *right_x;
02999         
03000         if( (selection_left < seg_end) && (selection_right > seg_start) )
03001         {
03002                 // Draw reversed
03003                 S32 start = llmax( selection_left, seg_start );
03004                 S32 end = llmin( selection_right, seg_end );
03005                 S32 length = end - start;
03006 
03007                 font->render(text, start, x, y_top,
03008                                          LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
03009                                          LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems);
03010         }
03011         x = *right_x;
03012         if( selection_right < seg_end )
03013         {
03014                 // Draw normally
03015                 S32 start = llmax( selection_right, seg_start );
03016                 S32 end = seg_end;
03017                 S32 length = end - start;
03018                 font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems);
03019         }
03020  }
03021 
03022 
03023 void LLTextEditor::draw()
03024 {
03025         if( getVisible() )
03026         {
03027                 {
03028                         LLLocalClipRect clip(LLRect(0, mRect.getHeight(), mRect.getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0));
03029 
03030                         bindEmbeddedChars( mGLFont );
03031 
03032                         drawBackground();
03033                         drawSelectionBackground();
03034                         drawText();
03035                         drawCursor();
03036 
03037                         unbindEmbeddedChars( mGLFont );
03038 
03039                         //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret
03040                         // when in readonly mode
03041                         mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly);
03042                 }
03043                 LLView::draw();  // Draw children (scrollbar and border)
03044         }
03045 }
03046 
03047 void LLTextEditor::reportBadKeystroke()
03048 {
03049         make_ui_sound("UISndBadKeystroke");
03050 }
03051 
03052 
03053 void LLTextEditor::onTabInto()
03054 {
03055         // selecting all on tabInto causes users to hit tab twice and replace their text with a tab character
03056         // theoretically, one could selectAll if mTabToNextField is true, but we couldn't think of a use case
03057         // where you'd want to select all anyway
03058         // preserve insertion point when returning to the editor
03059         //selectAll();
03060 }
03061 
03062 void LLTextEditor::clear()
03063 {
03064         setText(LLString::null);
03065 }
03066 
03067 // Start or stop the editor from accepting text-editing keystrokes
03068 // see also LLLineEditor
03069 void LLTextEditor::setFocus( BOOL new_state )
03070 {
03071         BOOL old_state = hasFocus();
03072 
03073         // Don't change anything if the focus state didn't change
03074         if (new_state == old_state) return;
03075 
03076         // Notify early if we are loosing focus.
03077         if (!new_state)
03078         {
03079                 getWindow()->allowLanguageTextInput(FALSE);
03080         }
03081 
03082         LLUICtrl::setFocus( new_state );
03083 
03084         if( new_state )
03085         {
03086                 // Route menu to this class
03087                 gEditMenuHandler = this;
03088 
03089                 // Don't start the cursor flashing right away
03090                 mKeystrokeTimer.reset();
03091         }
03092         else
03093         {
03094                 // Route menu back to the default
03095                 if( gEditMenuHandler == this )
03096                 {
03097                         gEditMenuHandler = NULL;
03098                 }
03099 
03100                 endSelection();
03101         }
03102 
03103         // Notify late if we are gaining focus.
03104         if (new_state && !mReadOnly)
03105         {
03106                 getWindow()->allowLanguageTextInput(TRUE);
03107         }
03108 }
03109 
03110 BOOL LLTextEditor::acceptsTextInput() const
03111 {
03112         return !mReadOnly;
03113 }
03114 
03115 // Given a line (from the start of the doc) and an offset into the line, find the offset (pos) into text.
03116 S32 LLTextEditor::getPos( S32 line, S32 offset )
03117 {
03118         S32 line_start = getLineStart(line);
03119         S32 next_start = getLineStart(line+1);
03120         if (next_start == line_start)
03121         {
03122                 next_start = getLength() + 1;
03123         }
03124         S32 line_length = next_start - line_start - 1;
03125         line_length = llmax(line_length, 0);
03126         return line_start + llmin( offset, line_length );
03127 }
03128 
03129 
03130 void LLTextEditor::changePage( S32 delta )
03131 {
03132         S32 line, offset;
03133         getLineAndOffset( mCursorPos, &line, &offset );
03134 
03135         // get desired x position to remember previous position
03136         S32 desired_x_pixel = mDesiredXPixel;
03137 
03138         // allow one line overlap
03139         S32 page_size = mScrollbar->getPageSize() - 1;
03140         if( delta == -1 )
03141         {
03142                 line = llmax( line - page_size, 0);
03143                 setCursorPos(getPos( line, offset ));
03144                 mScrollbar->setDocPos( mScrollbar->getDocPos() - page_size );
03145         }
03146         else
03147         if( delta == 1 )
03148         {
03149                 setCursorPos(getPos( line + page_size, offset ));
03150                 mScrollbar->setDocPos( mScrollbar->getDocPos() + page_size );
03151         }
03152 
03153         // put desired position into remember-buffer after setCursorPos()
03154         mDesiredXPixel = desired_x_pixel;
03155 
03156         if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
03157         {
03158                 mOnScrollEndCallback(mOnScrollEndData);
03159         }
03160 }
03161 
03162 void LLTextEditor::changeLine( S32 delta )
03163 {
03164         bindEmbeddedChars( mGLFont );
03165 
03166         S32 line, offset;
03167         getLineAndOffset( mCursorPos, &line, &offset );
03168 
03169         S32  line_start = getLineStart(line);
03170 
03171         // set desired x position to remembered previous position
03172         S32 desired_x_pixel = mDesiredXPixel;
03173         // if remembered position was reset (thus -1), calculate new one here
03174         if( desired_x_pixel == -1 )
03175         {
03176                 desired_x_pixel = mGLFont->getWidth(mWText.c_str(), line_start, offset, mAllowEmbeddedItems );
03177         }
03178 
03179         S32 new_line = 0;
03180         if( (delta < 0) && (line > 0 ) )
03181         {
03182                 new_line = line - 1;
03183         }
03184         else
03185         if( (delta > 0) && (line < (getLineCount() - 1)) )
03186         {
03187                 new_line = line + 1;
03188         }
03189         else
03190         {
03191                 unbindEmbeddedChars( mGLFont );
03192                 return;
03193         }
03194 
03195         S32 num_lines = getLineCount();
03196         S32 new_line_start = getLineStart(new_line);
03197         S32 new_line_end = getLength();
03198         if (new_line + 1 < num_lines)
03199         {
03200                 new_line_end = getLineStart(new_line + 1) - 1;
03201         }
03202 
03203         S32 new_line_len = new_line_end - new_line_start;
03204 
03205         S32 new_offset;
03206         new_offset = mGLFont->charFromPixelOffset(mWText.c_str(), new_line_start,
03207                                                                                           (F32)desired_x_pixel,
03208                                                                                           (F32)mTextRect.getWidth(),
03209                                                                                           new_line_len,
03210                                                                                           mAllowEmbeddedItems);
03211 
03212         setCursorPos (getPos( new_line, new_offset ));
03213 
03214         // put desired position into remember-buffer after setCursorPos()
03215         mDesiredXPixel = desired_x_pixel;
03216         unbindEmbeddedChars( mGLFont );
03217 }
03218 
03219 void LLTextEditor::startOfLine()
03220 {
03221         S32 line, offset;
03222         getLineAndOffset( mCursorPos, &line, &offset );
03223         setCursorPos(mCursorPos - offset);
03224 }
03225 
03226 
03227 // public
03228 void LLTextEditor::setCursorAndScrollToEnd()
03229 {
03230         deselect();
03231         endOfDoc();
03232         updateScrollFromCursor();
03233 }
03234 
03235 
03236 void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap )
03237 {
03238         if( include_wordwrap )
03239         {
03240                 getLineAndOffset( mCursorPos, line, col );
03241         }
03242         else
03243         {
03244                 const LLWString &text = mWText;
03245                 S32 line_count = 0;
03246                 S32 line_start = 0;
03247                 S32 i;
03248                 for( i = 0; text[i] && (i < mCursorPos); i++ )
03249                 {
03250                         if( '\n' == text[i] )
03251                         {
03252                                 line_start = i + 1;
03253                                 line_count++;
03254                         }
03255                 }
03256                 *line = line_count;
03257                 *col = i - line_start;
03258         }
03259 }
03260 
03261 
03262 void LLTextEditor::endOfLine()
03263 {
03264         S32 line, offset;
03265         getLineAndOffset( mCursorPos, &line, &offset );
03266         S32 num_lines = getLineCount();
03267         if (line + 1 >= num_lines)
03268         {
03269                 setCursorPos(getLength());
03270         }
03271         else
03272         {
03273                 setCursorPos( getLineStart(line + 1) - 1 );
03274         }
03275 }
03276 
03277 void LLTextEditor::endOfDoc()
03278 {
03279         mScrollbar->setDocPos( mScrollbar->getDocPosMax() );
03280         S32 len = getLength();
03281         if( len )
03282         {
03283                 setCursorPos(len);
03284         }
03285         if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
03286         {
03287                 mOnScrollEndCallback(mOnScrollEndData);
03288         }
03289 }
03290 
03291 // Sets the scrollbar from the cursor position
03292 void LLTextEditor::updateScrollFromCursor()
03293 {
03294         mScrollbar->setDocSize( getLineCount() );
03295 
03296         if (mReadOnly)
03297         {
03298                 // no cursor in read only mode
03299                 return;
03300         }
03301 
03302         S32 line, offset;
03303         getLineAndOffset( mCursorPos, &line, &offset ); 
03304 
03305         S32 page_size = mScrollbar->getPageSize();
03306 
03307         if( line < mScrollbar->getDocPos() )
03308         {
03309                 // scroll so that the cursor is at the top of the page
03310                 mScrollbar->setDocPos( line );
03311         }
03312         else if( line >= mScrollbar->getDocPos() + page_size - 1 )
03313         {
03314                 S32 new_pos = 0;
03315                 if( line < mScrollbar->getDocSize() - 1 )
03316                 {
03317                         // scroll so that the cursor is one line above the bottom of the page,
03318                         new_pos = line - page_size + 1;
03319                 }
03320                 else
03321                 {
03322                         // if there is less than a page of text remaining, scroll so that the cursor is at the bottom
03323                         new_pos = mScrollbar->getDocPosMax();
03324                 }
03325                 mScrollbar->setDocPos( new_pos );
03326         }
03327 
03328         // Check if we've scrolled to bottom for callback if asked for callback
03329         if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
03330         {
03331                 mOnScrollEndCallback(mOnScrollEndData);
03332         }
03333 }
03334 
03335 void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent)
03336 {
03337         LLView::reshape( width, height, called_from_parent );
03338 
03339         updateTextRect();
03340 
03341         S32 line_height = llround( mGLFont->getLineHeight() );
03342         S32 page_lines = mTextRect.getHeight() / line_height;
03343         mScrollbar->setPageSize( page_lines );
03344 
03345         updateLineStartList();
03346 }
03347 
03348 void LLTextEditor::autoIndent()
03349 {
03350         // Count the number of spaces in the current line
03351         S32 line, offset;
03352         getLineAndOffset( mCursorPos, &line, &offset );
03353         S32 line_start = getLineStart(line);
03354         S32 space_count = 0;
03355         S32 i;
03356 
03357         const LLWString &text = mWText;
03358         while( ' ' == text[line_start] )
03359         {
03360                 space_count++;
03361                 line_start++;
03362         }
03363 
03364         // If we're starting a braced section, indent one level.
03365         if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') )
03366         {
03367                 space_count += SPACES_PER_TAB;
03368         }
03369 
03370         // Insert that number of spaces on the new line
03371         addChar( '\n' );
03372         for( i = 0; i < space_count; i++ )
03373         {
03374                 addChar( ' ' );
03375         }
03376 }
03377 
03378 // Inserts new text at the cursor position
03379 void LLTextEditor::insertText(const LLString &new_text)
03380 {
03381         BOOL enabled = getEnabled();
03382         setEnabled( TRUE );
03383 
03384         // Delete any selected characters (the insertion replaces them)
03385         if( hasSelection() )
03386         {
03387                 deleteSelection(TRUE);
03388         }
03389 
03390         setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE ));
03391         
03392         updateLineStartList();
03393         updateScrollFromCursor();
03394 
03395         setEnabled( enabled );
03396 }
03397 
03398 
03399 void LLTextEditor::appendColoredText(const LLString &new_text, 
03400                                                                          bool allow_undo, 
03401                                                                          bool prepend_newline,
03402                                                                          const LLColor4 &color,
03403                                                                          const LLString& font_name)
03404 {
03405         LLStyle style;
03406         style.setVisible(true);
03407         style.setColor(color);
03408         style.setFontName(font_name);
03409         appendStyledText(new_text, allow_undo, prepend_newline, &style);
03410 }
03411 
03412 void LLTextEditor::appendStyledText(const LLString &new_text, 
03413                                                                          bool allow_undo, 
03414                                                                          bool prepend_newline,
03415                                                                          const LLStyle* style)
03416 {
03417         if(mParseHTML)
03418         {
03419 
03420                 S32 start=0,end=0;
03421                 LLString text = new_text;
03422                 while ( findHTML(text, &start, &end) )
03423                 {
03424                         LLStyle html;
03425                         html.setVisible(true);
03426                         html.setColor(mLinkColor);
03427                         if (style)
03428                         {
03429                                 html.setFontName(style->getFontString());
03430                         }
03431                         html.mUnderline = TRUE;
03432 
03433                         if (start > 0) appendText(text.substr(0,start),allow_undo, prepend_newline, style);
03434                         html.setLinkHREF(text.substr(start,end-start));
03435                         appendText(text.substr(start, end-start),allow_undo, prepend_newline, &html);
03436                         if (end < (S32)text.length()) 
03437                         {
03438                                 text = text.substr(end,text.length() - end);
03439                                 end=0;
03440                         }
03441                         else
03442                         {
03443                                 break;
03444                         }
03445                 }
03446                 if (end < (S32)text.length()) appendText(text,allow_undo, prepend_newline, style);      
03447         }
03448         else
03449         {
03450                 appendText(new_text, allow_undo, prepend_newline, style);
03451         }
03452 }
03453 
03454 // Appends new text to end of document
03455 void LLTextEditor::appendText(const LLString &new_text, bool allow_undo, bool prepend_newline,
03456                                                           const LLStyle* segment_style)
03457 {
03458         // Save old state
03459         BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax());
03460         S32 selection_start = mSelectionStart;
03461         S32 selection_end = mSelectionEnd;
03462         BOOL was_selecting = mIsSelecting;
03463         S32 cursor_pos = mCursorPos;
03464         S32 old_length = getLength();
03465         BOOL cursor_was_at_end = (mCursorPos == old_length);
03466 
03467         deselect();
03468 
03469         setCursorPos(old_length);
03470 
03471         // Add carriage return if not first line
03472         if (getLength() != 0
03473                 && prepend_newline)
03474         {
03475                 LLString final_text = "\n";
03476                 final_text += new_text;
03477                 append(utf8str_to_wstring(final_text), TRUE);
03478         }
03479         else
03480         {
03481                 append(utf8str_to_wstring(new_text), TRUE );
03482         }
03483 
03484         if (segment_style)
03485         {
03486                 S32 segment_start = old_length;
03487                 S32 segment_end = getLength();
03488                 LLTextSegment* segment = new LLTextSegment(*segment_style, segment_start, segment_end );
03489                 mSegments.push_back(segment);
03490         }
03491         
03492         updateLineStartList(old_length);
03493         
03494         // Set the cursor and scroll position
03495         // Maintain the scroll position unless the scroll was at the end of the doc (in which 
03496         // case, move it to the new end of the doc) or unless the user was doing actively selecting
03497         if( was_scrolled_to_bottom && !was_selecting )
03498         {
03499                 if( selection_start != selection_end )
03500                 {
03501                         // maintain an existing non-active selection
03502                         mSelectionStart = selection_start;
03503                         mSelectionEnd = selection_end;
03504                 }       
03505                 endOfDoc();
03506         }
03507         else if( selection_start != selection_end )
03508         {
03509                 mSelectionStart = selection_start;
03510                 mSelectionEnd = selection_end;
03511                 mIsSelecting = was_selecting;
03512                 setCursorPos(cursor_pos);
03513         }
03514         else if( cursor_was_at_end )
03515         {
03516                 setCursorPos(getLength());
03517         }
03518         else
03519         {
03520                 setCursorPos(cursor_pos);
03521         }
03522 
03523         if( !allow_undo )
03524         {
03525                 blockUndo();
03526         }
03527 }
03528 
03529 void LLTextEditor::removeTextFromEnd(S32 num_chars)
03530 {
03531         if (num_chars <= 0) return;
03532 
03533         remove(getLength() - num_chars, num_chars, FALSE);
03534 
03535         S32 len = getLength();
03536         mCursorPos = llclamp(mCursorPos, 0, len);
03537         mSelectionStart = llclamp(mSelectionStart, 0, len);
03538         mSelectionEnd = llclamp(mSelectionEnd, 0, len);
03539 
03540         pruneSegments();
03541         updateLineStartList();
03542 }
03543 
03545 // Returns change in number of characters in mWText
03546 
03547 S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr)
03548 {
03549         S32 len = mWText.length();
03550         S32 s_len = wstr.length();
03551         S32 new_len = len + s_len;
03552         if( new_len > mMaxTextLength )
03553         {
03554                 new_len = mMaxTextLength;
03555 
03556                 // The user's not getting everything he's hoping for
03557                 make_ui_sound("UISndBadKeystroke");
03558         }
03559 
03560         mWText.insert(pos, wstr);
03561         mTextIsUpToDate = FALSE;
03562         truncate();
03563 
03564         return new_len - len;
03565 }
03566 
03567 S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length)
03568 {
03569         mWText.erase(pos, length);
03570         mTextIsUpToDate = FALSE;
03571         return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length
03572 }
03573 
03574 S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc)
03575 {
03576         if (pos > (S32)mWText.length())
03577         {
03578                 return 0;
03579         }
03580         mWText[pos] = wc;
03581         mTextIsUpToDate = FALSE;
03582         return 1;
03583 }
03584 
03585 //----------------------------------------------------------------------------
03586 
03587 void LLTextEditor::makePristine()
03588 {
03589         mPristineCmd = mLastCmd;
03590         mBaseDocIsPristine = !mLastCmd;
03591 
03592         // Create a clean partition in the undo stack.  We don't want a single command to extend from
03593         // the "pre-pristine" state to the "post-pristine" state.
03594         if( mLastCmd )
03595         {
03596                 mLastCmd->blockExtensions();
03597         }
03598 }
03599 
03600 BOOL LLTextEditor::isPristine() const
03601 {
03602         if( mPristineCmd )
03603         {
03604                 return (mPristineCmd == mLastCmd);
03605         }
03606         else
03607         {
03608                 // No undo stack, so check if the version before and commands were done was the original version
03609                 return !mLastCmd && mBaseDocIsPristine;
03610         }
03611 }
03612 
03613 BOOL LLTextEditor::tryToRevertToPristineState()
03614 {
03615         if( !isPristine() )
03616         {
03617                 deselect();
03618                 S32 i = 0;
03619                 while( !isPristine() && canUndo() )
03620                 {
03621                         undo();
03622                         i--;
03623                 }
03624 
03625                 while( !isPristine() && canRedo() )
03626                 {
03627                         redo();
03628                         i++;
03629                 }
03630 
03631                 if( !isPristine() )
03632                 {
03633                         // failed, so go back to where we started
03634                         while( i > 0 )
03635                         {
03636                                 undo();
03637                                 i--;
03638                         }
03639                 }
03640 
03641                 updateLineStartList();
03642                 updateScrollFromCursor();
03643         }
03644 
03645         return isPristine(); // TRUE => success
03646 }
03647 
03648 // virtual    Return TRUE if changes have been made
03649 BOOL LLTextEditor::isDirty() const
03650 {
03651         return( mLastCmd != NULL || (mPristineCmd && (mPristineCmd != mLastCmd)) );
03652 }
03653 
03654 
03655 void LLTextEditor::updateTextRect()
03656 {
03657         mTextRect.setOriginAndSize( 
03658                 UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD,
03659                 UI_TEXTEDITOR_BORDER, 
03660                 mRect.getWidth() - SCROLLBAR_SIZE - 2 * (UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD),
03661                 mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER - UI_TEXTEDITOR_V_PAD_TOP );
03662 }
03663 
03664 void LLTextEditor::loadKeywords(const LLString& filename,
03665                                                                 const LLDynamicArray<const char*>& funcs,
03666                                                                 const LLDynamicArray<const char*>& tooltips,
03667                                                                 const LLColor3& color)
03668 {
03669         if(mKeywords.loadFromFile(filename))
03670         {
03671                 S32 count = funcs.count();
03672                 LLString name;
03673                 for(S32 i = 0; i < count; i++)
03674                 {
03675                         name = funcs.get(i);
03676                         name = utf8str_trim(name);
03677                         mKeywords.addToken(LLKeywordToken::WORD, name.c_str(), color, tooltips.get(i) );
03678                 }
03679 
03680                 mKeywords.findSegments( &mSegments, mWText );
03681 
03682                 llassert( mSegments.front()->getStart() == 0 );
03683                 llassert( mSegments.back()->getEnd() == getLength() );
03684         }
03685 }
03686 
03687 void LLTextEditor::updateSegments()
03688 {
03689         if (mKeywords.isLoaded())
03690         {
03691                 // HACK:  No non-ascii keywords for now
03692                 mKeywords.findSegments(&mSegments, mWText);
03693         }
03694         else if (mAllowEmbeddedItems)
03695         {
03696                 findEmbeddedItemSegments();
03697         }
03698         // Make sure we have at least one segment
03699         if (mSegments.size() == 1 && mSegments[0]->getIsDefault())
03700         {
03701                 delete mSegments[0];
03702                 mSegments.clear(); // create default segment
03703         }
03704         if (mSegments.empty())
03705         {
03706                 LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor );
03707                 LLTextSegment* default_segment = new LLTextSegment( text_color, 0, mWText.length() );
03708                 default_segment->setIsDefault(TRUE);
03709                 mSegments.push_back(default_segment);
03710         }
03711 }
03712 
03713 // Only effective if text was removed from the end of the editor
03714 void LLTextEditor::pruneSegments()
03715 {
03716         S32 len = mWText.length();
03717         // Find and update the first valid segment
03718         segment_list_t::iterator iter = mSegments.end();
03719         while(iter != mSegments.begin())
03720         {
03721                 --iter;
03722                 LLTextSegment* seg = *iter;
03723                 if (seg->getStart() < len)
03724                 {
03725                         // valid segment
03726                         if (seg->getEnd() > len)
03727                         {
03728                                 seg->setEnd(len);
03729                         }
03730                         break; // done
03731                 }                       
03732         }
03733         if (iter != mSegments.end())
03734         {
03735                 // erase invalid segments
03736                 ++iter;
03737                 std::for_each(iter, mSegments.end(), DeletePointer());
03738                 mSegments.erase(iter, mSegments.end());
03739         }
03740         else
03741         {
03742                 llwarns << "Tried to erase end of empty LLTextEditor"
03743                         << llendl;
03744         }
03745 }
03746 
03747 void LLTextEditor::findEmbeddedItemSegments()
03748 {
03749         mHoverSegment = NULL;
03750         std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
03751         mSegments.clear();
03752 
03753         BOOL found_embedded_items = FALSE;
03754         const LLWString &text = mWText;
03755         S32 idx = 0;
03756         while( text[idx] )
03757         {
03758                 if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR )
03759                 {
03760                         found_embedded_items = TRUE;
03761                         break;
03762                 }
03763                 ++idx;
03764         }
03765 
03766         if( !found_embedded_items )
03767         {
03768                 return;
03769         }
03770 
03771         S32 text_len = text.length();
03772 
03773         BOOL in_text = FALSE;
03774 
03775         LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor  );
03776 
03777         if( idx > 0 )
03778         {
03779                 mSegments.push_back( new LLTextSegment( text_color, 0, text_len ) ); // text
03780                 in_text = TRUE;
03781         }
03782 
03783         LLStyle embedded_style;
03784         embedded_style.setIsEmbeddedItem( TRUE );
03785 
03786         // Start with i just after the first embedded item
03787         while ( text[idx] )
03788         {
03789                 if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR )
03790                 {
03791                         if( in_text )
03792                         {
03793                                 mSegments.back()->setEnd( idx );
03794                         }
03795                         mSegments.push_back( new LLTextSegment( embedded_style, idx, idx + 1 ) );  // item
03796                         in_text = FALSE;
03797                 }
03798                 else
03799                 if( !in_text )
03800                 {
03801                         mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) );  // text
03802                         in_text = TRUE;
03803                 }
03804                 ++idx;
03805         }
03806 }
03807 
03808 BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask)
03809 {
03810         if ( hasMouseCapture() )
03811         {
03812                 // This mouse up was part of a click.
03813                 // Regardless of where the cursor is, see if we recently touched a link
03814                 // and launch it if we did.
03815                 if (mParseHTML && mHTML.length() > 0)
03816                 {
03817                                 //Special handling for slurls
03818                         if ( (mSecondlifeURLcallback!=NULL) && !(*mSecondlifeURLcallback)(mHTML) )
03819                         {
03820                                 if (mURLcallback!=NULL) (*mURLcallback)(mHTML.c_str());
03821                         }
03822                         mHTML="";
03823                 }
03824         }
03825 
03826         return FALSE;
03827 }
03828 
03829 llwchar LLTextEditor::pasteEmbeddedItem(llwchar ext_char)
03830 {
03831         return ext_char;
03832 }
03833 
03834 void LLTextEditor::bindEmbeddedChars(const LLFontGL* font)
03835 {
03836 }
03837 
03838 void LLTextEditor::unbindEmbeddedChars(const LLFontGL* font)
03839 {
03840 }
03841 
03842 // Finds the text segment (if any) at the give local screen position
03843 LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y )
03844 {
03845         // Find the cursor position at the requested local screen position
03846         S32 offset = getCursorPosFromLocalCoord( x, y, FALSE );
03847         S32 idx = getSegmentIdxAtOffset(offset);
03848         return idx >= 0 ? mSegments[idx] : NULL;
03849 }
03850 
03851 LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset)
03852 {
03853         S32 idx = getSegmentIdxAtOffset(offset);
03854         return idx >= 0 ? mSegments[idx] : NULL;
03855 }
03856 
03857 S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset)
03858 {
03859         if (mSegments.empty() || offset < 0 || offset >= getLength())
03860         {
03861                 return -1;
03862         }
03863         else
03864         {
03865                 S32 segidx, segoff;
03866                 getSegmentAndOffset(offset, &segidx, &segoff);
03867                 return segidx;
03868         }
03869 }
03870 
03871 void LLTextEditor::onMouseCaptureLost()
03872 {
03873         endSelection();
03874 }
03875 
03876 void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*), void* userdata)
03877 {
03878         mOnScrollEndCallback = callback;
03879         mOnScrollEndData = userdata;
03880         mScrollbar->setOnScrollEndCallback(callback, userdata);
03881 }
03882 
03884 // Hack for Notecards
03885 
03886 BOOL LLTextEditor::importBuffer(const LLString& buffer )
03887 {
03888         std::istringstream instream(buffer);
03889         
03890         // Version 1 format:
03891         //              Linden text version 1\n
03892         //              {\n
03893         //                      <EmbeddedItemList chunk>
03894         //                      Text length <bytes without \0>\n
03895         //                      <text without \0> (text may contain ext_char_values)
03896         //              }\n
03897 
03898         char tbuf[MAX_STRING];  /* Flawfinder: ignore */
03899         
03900         S32 version = 0;
03901         instream.getline(tbuf, MAX_STRING);
03902         if( 1 != sscanf(tbuf, "Linden text version %d", &version) )
03903         {
03904                 llwarns << "Invalid Linden text file header " << llendl;
03905                 return FALSE;
03906         }
03907 
03908         if( 1 != version )
03909         {
03910                 llwarns << "Invalid Linden text file version: " << version << llendl;
03911                 return FALSE;
03912         }
03913 
03914         instream.getline(tbuf, MAX_STRING);
03915         if( 0 != sscanf(tbuf, "{") )
03916         {
03917                 llwarns << "Invalid Linden text file format" << llendl;
03918                 return FALSE;
03919         }
03920 
03921         S32 text_len = 0;
03922         instream.getline(tbuf, MAX_STRING);
03923         if( 1 != sscanf(tbuf, "Text length %d", &text_len) )
03924         {
03925                 llwarns << "Invalid Linden text length field" << llendl;
03926                 return FALSE;
03927         }
03928 
03929         if( text_len > mMaxTextLength )
03930         {
03931                 llwarns << "Invalid Linden text length: " << text_len << llendl;
03932                 return FALSE;
03933         }
03934 
03935         BOOL success = TRUE;
03936 
03937         char* text = new char[ text_len + 1];
03938         if (text == NULL)
03939         {
03940                 llerrs << "Memory allocation failure." << llendl;                       
03941                 return FALSE;
03942         }
03943         instream.get(text, text_len + 1, '\0');
03944         text[text_len] = '\0';
03945         if( text_len != (S32)strlen(text) )/* Flawfinder: ignore */
03946         {
03947                 llwarns << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << llendl;/* Flawfinder: ignore */
03948                 success = FALSE;
03949         }
03950 
03951         instream.getline(tbuf, MAX_STRING);
03952         if( success && (0 != sscanf(tbuf, "}")) )
03953         {
03954                 llwarns << "Invalid Linden text file format: missing terminal }" << llendl;
03955                 success = FALSE;
03956         }
03957 
03958         if( success )
03959         {
03960                 // Actually set the text
03961                 setText( LLStringExplicit(text) );
03962         }
03963 
03964         delete[] text;
03965 
03966         setCursorPos(0);
03967         deselect();
03968 
03969         updateLineStartList();
03970         updateScrollFromCursor();
03971 
03972         return success;
03973 }
03974 
03975 BOOL LLTextEditor::exportBuffer(LLString &buffer )
03976 {
03977         std::ostringstream outstream(buffer);
03978         
03979         outstream << "Linden text version 1\n";
03980         outstream << "{\n";
03981 
03982         outstream << llformat("Text length %d\n", mWText.length() );
03983         outstream << getText();
03984         outstream << "}\n";
03985 
03986         return TRUE;
03987 }
03988 
03990 // LLTextSegment
03991 
03992 LLTextSegment::LLTextSegment(S32 start) : mStart(start)
03993 {
03994 } 
03995 LLTextSegment::LLTextSegment( const LLStyle& style, S32 start, S32 end ) :
03996         mStyle( style ),
03997         mStart( start),
03998         mEnd( end ),
03999         mToken(NULL),
04000         mIsDefault(FALSE)
04001 {
04002 }
04003 LLTextSegment::LLTextSegment(
04004         const LLColor4& color, S32 start, S32 end, BOOL is_visible) :
04005         mStyle( is_visible, color,"" ),
04006         mStart( start),
04007         mEnd( end ),
04008         mToken(NULL),
04009         mIsDefault(FALSE)
04010 {
04011 }
04012 LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end ) :
04013         mStyle( TRUE, color,"" ),
04014         mStart( start),
04015         mEnd( end ),
04016         mToken(NULL),
04017         mIsDefault(FALSE)
04018 {
04019 }
04020 LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) :
04021         mStyle( TRUE, color,"" ),
04022         mStart( start),
04023         mEnd( end ),
04024         mToken(NULL),
04025         mIsDefault(FALSE)
04026 {
04027 }
04028 
04029 BOOL LLTextSegment::getToolTip(LLString& msg)
04030 {
04031         if (mToken && !mToken->getToolTip().empty())
04032         {
04033                 const LLWString& wmsg = mToken->getToolTip();
04034                 msg = wstring_to_utf8str(wmsg);
04035                 return TRUE;
04036         }
04037         return FALSE;
04038 }
04039 
04040 
04041 
04042 void LLTextSegment::dump()
04043 {
04044         llinfos << "Segment [" << 
04045 //                      mColor.mV[VX] << ", " <<
04046 //                      mColor.mV[VY] << ", " <<
04047 //                      mColor.mV[VZ] << "]\t[" <<
04048                 mStart << ", " <<
04049                 getEnd() << "]" <<
04050                 llendl;
04051 
04052 }
04053 
04054 // virtual
04055 LLXMLNodePtr LLTextEditor::getXML(bool save_children) const
04056 {
04057         LLXMLNodePtr node = LLUICtrl::getXML();
04058 
04059         // Attributes
04060 
04061         node->createChild("max_length", TRUE)->setIntValue(getMaxLength());
04062 
04063         node->createChild("embedded_items", TRUE)->setBoolValue(mAllowEmbeddedItems);
04064 
04065         node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont));
04066 
04067         node->createChild("word_wrap", TRUE)->setBoolValue(mWordWrap);
04068 
04069         node->createChild("hide_scrollbar", TRUE)->setBoolValue(mHideScrollbarForShortDocs);
04070 
04071         addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor");
04072         addColorXML(node, mFgColor, "text_color", "TextFgColor");
04073         addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor");
04074         addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor");
04075         addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor");
04076         addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor");
04077 
04078         // Contents
04079         node->setStringValue(getText());
04080 
04081         return node;
04082 }
04083 
04084 // static
04085 LLView* LLTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
04086 {
04087         LLString name("text_editor");
04088         node->getAttributeString("name", name);
04089 
04090         LLRect rect;
04091         createRect(node, rect, parent, LLRect());
04092 
04093         U32 max_text_length = 255;
04094         node->getAttributeU32("max_length", max_text_length);
04095 
04096         BOOL allow_embedded_items;
04097         node->getAttributeBOOL("embedded_items", allow_embedded_items);
04098 
04099         LLFontGL* font = LLView::selectFont(node);
04100 
04101         LLString text = node->getTextContents().substr(0, max_text_length - 1);
04102 
04103         LLTextEditor* text_editor = new LLTextEditor(name, 
04104                                                                 rect,
04105                                                                 max_text_length,
04106                                                                 text,
04107                                                                 font,
04108                                                                 allow_embedded_items);
04109 
04110         text_editor->setTextEditorParameters(node);
04111 
04112         BOOL hide_scrollbar = FALSE;
04113         node->getAttributeBOOL("hide_scrollbar",hide_scrollbar);
04114         text_editor->setHideScrollbarForShortDocs(hide_scrollbar);
04115 
04116         text_editor->initFromXML(node, parent);
04117 
04118         return text_editor;
04119 }
04120 
04121 void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node)
04122 {
04123         BOOL word_wrap = FALSE;
04124         node->getAttributeBOOL("word_wrap", word_wrap);
04125         setWordWrap(word_wrap);
04126 
04127         LLColor4 color;
04128         if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) 
04129         {
04130                 setCursorColor(color);
04131         }
04132         if(LLUICtrlFactory::getAttributeColor(node,"text_color", color))
04133         {
04134                 setFgColor(color);
04135         }
04136         if(LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color))
04137         {
04138                 setReadOnlyFgColor(color);
04139         }
04140         if(LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color))
04141         {
04142                 setReadOnlyBgColor(color);
04143         }
04144         if(LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color))
04145         {
04146                 setWriteableBgColor(color);
04147         }
04148 }
04149 
04151 S32 LLTextEditor::findHTMLToken(const LLString &line, S32 pos, BOOL reverse)
04152 {
04153         LLString openers=" \t('\"[{<>";
04154         LLString closers=" \t)'\"]}><;";
04155 
04156         S32 m2;
04157         S32 retval;
04158         
04159         if (reverse)
04160         {
04161                 
04162                 for (retval=pos; retval>0; retval--)
04163                 {
04164                         m2 = openers.find(line.substr(retval,1));
04165                         if (m2 >= 0)
04166                         {
04167                                 retval++;
04168                                 break;
04169                         }
04170                 }
04171         } 
04172         else
04173         {
04174                 
04175                 for (retval=pos; retval<(S32)line.length(); retval++)
04176                 {
04177                         m2 = closers.find(line.substr(retval,1));
04178                         if (m2 >= 0)
04179                         {
04180                                 break;
04181                         }
04182                 } 
04183         }               
04184         
04185         return retval;
04186 }
04187 
04188 BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end)
04189 {
04190           
04191         S32 m1,m2,m3;
04192         BOOL matched = FALSE;
04193         
04194         m1=line.find("://",*end);
04195         
04196         if (m1 >= 0) //Easy match.
04197         {
04198                 *begin = findHTMLToken(line, m1, TRUE);
04199                 *end   = findHTMLToken(line, m1, FALSE);
04200                 
04201                 //Load_url only handles http and https so don't hilite ftp, smb, etc.
04202                 m2 = line.substr(*begin,(m1 - *begin)).find("http");
04203                 m3 = line.substr(*begin,(m1 - *begin)).find("secondlife");
04204         
04205                 LLString badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\";
04206         
04207                 if (m2 >= 0 || m3>=0)
04208                 {
04209                         S32 bn = badneighbors.find(line.substr(m1+3,1));
04210                         
04211                         if (bn < 0)
04212                         {
04213                                 matched = TRUE;
04214                         }
04215                 }
04216         }
04217 /*      matches things like secondlife.com (no http://) needs a whitelist to really be effective.
04218         else    //Harder match.
04219         {
04220                 m1 = line.find(".",*end);
04221                 
04222                 if (m1 >= 0)
04223                 {
04224                         *end   = findHTMLToken(line, m1, FALSE);
04225                         *begin = findHTMLToken(line, m1, TRUE);
04226                         
04227                         m1 = line.rfind(".",*end);
04228 
04229                         if ( ( *end - m1 ) > 2 && m1 > *begin)
04230                         {
04231                                 LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`";
04232                                 m2 = badneighbors.find(line.substr(m1+1,1));
04233                                 m3 = badneighbors.find(line.substr(m1-1,1));
04234                                 if (m3<0 && m2<0)
04235                                 {
04236                                         matched = TRUE;
04237                                 }
04238                         }
04239                 }
04240         }
04241         */
04242         
04243         if (matched)
04244         {
04245                 S32 strpos, strpos2;
04246 
04247                 LLString url     = line.substr(*begin,*end - *begin);
04248                 LLString slurlID = "slurl.com/secondlife/";
04249                 strpos = url.find(slurlID);
04250                 
04251                 if (strpos < 0)
04252                 {
04253                         slurlID="secondlife://";
04254                         strpos = url.find(slurlID);
04255                 }
04256         
04257                 if (strpos < 0)
04258                 {
04259                         slurlID="sl://";
04260                         strpos = url.find(slurlID);
04261                 }
04262         
04263                 if (strpos >= 0) 
04264                 {
04265                         strpos+=slurlID.length();
04266                         
04267                         while ( ( strpos2=url.find("/",strpos) ) == -1 ) 
04268                         {
04269                                 if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " )
04270                                 {
04271                                         matched=FALSE;
04272                                         break;
04273                                 }
04274                                 
04275                                 strpos = (*end + 1) - *begin;
04276                                                                 
04277                                 *end = findHTMLToken(line,(*begin + strpos),FALSE);
04278                                 url = line.substr(*begin,*end - *begin);
04279                         }
04280                 }
04281 
04282         }
04283         
04284         if (!matched)
04285         {
04286                 *begin=*end=0;
04287         }
04288         return matched;
04289 }

Generated on Thu Jul 1 06:09:17 2010 for Second Life Viewer by  doxygen 1.4.7