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

Generated on Fri May 16 08:32:59 2008 for SecondLife by  doxygen 1.5.5