00001
00032
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
00066
00067 static LLRegisterWidget<LLTextEditor> r("simple_text_editor");
00068
00069 BOOL gDebugTextEditorTips = FALSE;
00070
00071
00072
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;
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
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
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,
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
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
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();
00334
00335 mParseHTML=FALSE;
00336 mHTML="";
00337 }
00338
00339
00340 LLTextEditor::~LLTextEditor()
00341 {
00342 gFocusMgr.releaseFocusIfNeeded( this );
00343
00344
00345 if( gEditMenuHandler == this )
00346 {
00347 gEditMenuHandler = NULL;
00348 }
00349
00350
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
00417 seg_idx++;
00418 seg_offset = 0;
00419 }
00420 else
00421 {
00422
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
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++;
00446 }
00447 }
00448 else
00449 {
00450
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
00472
00473
00474 BOOL LLTextEditor::truncate()
00475 {
00476 BOOL did_truncate = FALSE;
00477
00478
00479 if (mWText.size() >= (size_t) (mMaxTextByteLength / 4))
00480 {
00481
00482 S32 utf8_byte_size = wstring_utf8_length( mWText );
00483 if ( utf8_byte_size > mMaxTextByteLength )
00484 {
00485
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
00500 mUTF8Text = utf8str_removeCRLF(utf8str);
00501
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
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
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
00616 mCursorPos += search_text.size();
00617 }
00618 }
00619
00620 S32 loc = text.find(search_text,mCursorPos);
00621
00622
00623 if (wrap && (-1 == loc))
00624 {
00625 loc = text.find(search_text);
00626 }
00627
00628
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
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
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
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
00804
00805
00806
00807
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 );
00813
00814
00815 S32 line = (mTextRect.mTop - 1 - local_y) / line_height;
00816 if (line >= total_lines)
00817 {
00818 return getLength();
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
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
00878 mDesiredXPixel = -1;
00879 }
00880
00881
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
00938
00939
00940
00941 llassert(pos >= 0);
00942 llassert(pos <= getLength() );
00943
00944 S32 delta_spaces = 0;
00945
00946 if (spaces >= 0)
00947 {
00948
00949 for(S32 i=0; i < spaces; i++)
00950 {
00951 delta_spaces += addChar(pos, ' ');
00952 }
00953 }
00954 else
00955 {
00956
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
00981 while( (cur > 0) && (text[cur] != '\n') )
00982 {
00983 cur--;
00984 }
00985 left = cur;
00986 if( cur > 0 )
00987 {
00988 left++;
00989 }
00990
00991
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
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
01020
01021
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
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
01050 BOOL LLTextEditor::canSelectAll() const
01051 {
01052 return TRUE;
01053 }
01054
01055
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
01092
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
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
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
01128 if (mask & MASK_SHIFT)
01129 {
01130 S32 old_cursor_pos = mCursorPos;
01131 setCursorAtLocalPos( x, y, TRUE );
01132
01133 if (hasSelection())
01134 {
01135
01136
01137
01138
01139
01140
01141
01142
01143
01144
01145
01146
01147
01148
01149
01150
01151
01152
01153
01154
01155
01156 mSelectionEnd = mCursorPos;
01157 }
01158 else
01159 {
01160 mSelectionStart = old_cursor_pos;
01161 mSelectionEnd = mCursorPos;
01162 }
01163
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
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
01229 handled = LLView::childrenHandleHover(x, y, mask) != NULL;
01230 }
01231
01232 if( handled )
01233 {
01234
01235 resetKeystrokeTimer();
01236 }
01237
01238
01239 if( !handled && mTakesNonScrollClicks)
01240 {
01241
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
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
01293 handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL;
01294
01295 if( !handled && mTakesNonScrollClicks)
01296 {
01297 if( mIsSelecting )
01298 {
01299
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
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
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
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
01371 startSelection();
01372 mCursorPos++;
01373 mSelectionEnd = mCursorPos;
01374 }
01375
01376
01377
01378 mIsSelecting = FALSE;
01379
01380
01381 resetKeystrokeTimer();
01382
01383 handled = TRUE;
01384 }
01385 return handled;
01386 }
01387
01388
01389
01390
01391
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
01404
01405 S32 LLTextEditor::execute( LLTextCmd* cmd )
01406 {
01407 S32 delta = 0;
01408 if( cmd->execute(this, &delta) )
01409 {
01410
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
01419 mUndoStack.push_front(cmd);
01420 mLastCmd = cmd;
01421 }
01422 else
01423 {
01424
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
01459
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
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
01489
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
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
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
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
01817 BOOL LLTextEditor::canCut() const
01818 {
01819 return !mReadOnly && hasSelection();
01820 }
01821
01822
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
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
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
01875 if( hasSelection() )
01876 {
01877 deleteSelection(TRUE);
01878 }
01879
01880
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
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
01930
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
01945
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
01960
01961 deselect();
01962
01963 setCursorPos(nextWordPos(mCursorPos + 1));
01964 }
01965 break;
01966
01967
01968 case KEY_LEFT:
01969 if( mCursorPos > 0 )
01970 {
01971
01972
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
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();
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
02159
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
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
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))
02243 {
02244 return FALSE;
02245 }
02246
02247 BOOL handled = FALSE;
02248
02249 if ( gFocusMgr.getKeyboardFocus() == this )
02250 {
02251
02252 if( !mReadOnly )
02253 {
02254 if( '}' == uni_char )
02255 {
02256 unindentLineBeforeCloseBrace();
02257 }
02258
02259
02260 addChar( uni_char );
02261
02262
02263 getWindow()->hideCursorUntilMouseMove();
02264
02265 handled = TRUE;
02266 }
02267
02268 if( handled )
02269 {
02270 resetKeystrokeTimer();
02271
02272
02273 deselect();
02274
02275 updateLineStartList();
02276 updateScrollFromCursor();
02277 }
02278 }
02279
02280 return handled;
02281 }
02282
02283
02284
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
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
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
02432 void LLTextEditor::onFocusLost()
02433 {
02434 updateAllowingLanguageInput();
02435
02436
02437 if( gEditMenuHandler == this )
02438 {
02439 gEditMenuHandler = NULL;
02440 }
02441
02442 if (mCommitOnFocusLost)
02443 {
02444 onCommit();
02445 }
02446
02447
02448 getWindow()->showCursorFromMouseMove();
02449
02450 LLUICtrl::onFocusLost();
02451 }
02452
02453 void LLTextEditor::setEnabled(BOOL enabled)
02454 {
02455
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
02491 void LLTextEditor::drawSelectionBackground()
02492 {
02493
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
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
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
02563
02564 }
02565 selection_right_y = text_y;
02566 }
02567
02568
02569 if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right)
02570 {
02571
02572
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
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
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
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
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
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
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
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
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
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
02693 text_y -= line_height;
02694 line_start = next_line;
02695 cur_pos++;
02696 }
02697
02698
02699 if( cursor_visible )
02700 {
02701
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
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
02802 if (line_start >= mPreeditPositions.back())
02803 {
02804
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
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
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
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
02956 seg_start += clipped_len;
02957 }
02958 }
02959
02960
02961 text_y -= (F32)line_height;
02962
02963 line_start = next_start;
02964 cur_line++;
02965 }
02966 }
02967
02968
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
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
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
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
03063
03064 mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);
03065 }
03066
03067
03068 mScrolledToBottom = isScrolledToBottom();
03069
03070 LLView::draw();
03071 }
03072
03073
03074 void LLTextEditor::onTabInto()
03075 {
03076
03077
03078
03079
03080
03081 }
03082
03083
03084 void LLTextEditor::clear()
03085 {
03086 setText(LLString::null);
03087 }
03088
03089
03090
03091 void LLTextEditor::setFocus( BOOL new_state )
03092 {
03093 BOOL old_state = hasFocus();
03094
03095
03096 if (new_state == old_state) return;
03097
03098
03099 if (!new_state)
03100 {
03101 getWindow()->allowLanguageTextInput(this, FALSE);
03102 }
03103
03104 LLUICtrl::setFocus( new_state );
03105
03106 if( new_state )
03107 {
03108
03109 gEditMenuHandler = this;
03110
03111
03112 resetKeystrokeTimer();
03113 }
03114 else
03115 {
03116
03117 if( gEditMenuHandler == this )
03118 {
03119 gEditMenuHandler = NULL;
03120 }
03121
03122 endSelection();
03123 }
03124 }
03125
03126
03127 BOOL LLTextEditor::acceptsTextInput() const
03128 {
03129 return !mReadOnly;
03130 }
03131
03132
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
03153 S32 desired_x_pixel = mDesiredXPixel;
03154
03155
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
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
03189 S32 desired_x_pixel = mDesiredXPixel;
03190
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
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
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
03320 void LLTextEditor::updateScrollFromCursor()
03321 {
03322 mScrollbar->setDocSize( getLineCount() );
03323
03324 if (mReadOnly)
03325 {
03326
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
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
03346 new_pos = line - page_size + 1;
03347 }
03348 else
03349 {
03350
03351 new_pos = mScrollbar->getDocPosMax();
03352 }
03353 mScrollbar->setDocPos( new_pos );
03354 }
03355
03356
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
03368
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
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
03400 if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') )
03401 {
03402 space_count += SPACES_PER_TAB;
03403 }
03404
03405
03406 addChar( '\n' );
03407 for( i = 0; i < space_count; i++ )
03408 {
03409 addChar( ' ' );
03410 }
03411 }
03412
03413
03414 void LLTextEditor::insertText(const LLString &new_text)
03415 {
03416 BOOL enabled = getEnabled();
03417 setEnabled( TRUE );
03418
03419
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
03490 void LLTextEditor::appendText(const LLString &new_text, bool allow_undo, bool prepend_newline,
03491 const LLStyleSP *stylep)
03492 {
03493
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
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
03530
03531
03532 if( was_scrolled_to_bottom && !was_selecting )
03533 {
03534 if( selection_start != selection_end )
03535 {
03536
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
03581
03582 S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr)
03583 {
03584 S32 old_len = mWText.length();
03585 S32 insert_len = wstr.length();
03586
03587 mWText.insert(pos, wstr);
03588 mTextIsUpToDate = FALSE;
03589
03590 if ( truncate() )
03591 {
03592
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;
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
03626
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
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
03667 while( i > 0 )
03668 {
03669 undo();
03670 i--;
03671 }
03672 }
03673
03674 updateLineStartList();
03675 updateScrollFromCursor();
03676 }
03677
03678 return isPristine();
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
03719 mKeywords.findSegments(&mSegments, mWText, mDefaultColor);
03720 }
03721 else if (mAllowEmbeddedItems)
03722 {
03723 findEmbeddedItemSegments();
03724 }
03725
03726 if (mSegments.size() == 1 && mSegments[0]->getIsDefault())
03727 {
03728 delete mSegments[0];
03729 mSegments.clear();
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
03741 void LLTextEditor::pruneSegments()
03742 {
03743 S32 len = mWText.length();
03744
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
03753 if (seg->getEnd() > len)
03754 {
03755 seg->setEnd(len);
03756 }
03757 break;
03758 }
03759 }
03760 if (iter != mSegments.end())
03761 {
03762
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 ) );
03806 in_text = TRUE;
03807 }
03808
03809 LLStyleSP embedded_style(new LLStyle);
03810 embedded_style->setIsEmbeddedItem( TRUE );
03811
03812
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 ) );
03822 in_text = FALSE;
03823 }
03824 else
03825 if( !in_text )
03826 {
03827 mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) );
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
03839
03840
03841 if (mParseHTML && mHTML.length() > 0)
03842 {
03843
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
03857 const LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y ) const
03858 {
03859
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
03899
03900 BOOL LLTextEditor::importBuffer(const LLString& buffer )
03901 {
03902 std::istringstream instream(buffer);
03903
03904
03905
03906
03907
03908
03909
03910
03911
03912 char tbuf[MAX_STRING];
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) )
03960 {
03961 llwarns << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << llendl;
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
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
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
04059
04060
04061 mStart << ", " <<
04062 getEnd() << "]" <<
04063 llendl;
04064
04065 }
04066
04067
04068 LLXMLNodePtr LLTextEditor::getXML(bool save_children) const
04069 {
04070 LLXMLNodePtr node = LLUICtrl::getXML();
04071
04072
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
04089 node->setStringValue(getText());
04090
04091 return node;
04092 }
04093
04094
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)
04209 {
04210 *begin = findHTMLToken(line, m1, TRUE);
04211 *end = findHTMLToken(line, m1, FALSE);
04212
04213
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
04230
04231
04232
04233
04234
04235
04236
04237
04238
04239
04240
04241
04242
04243
04244
04245
04246
04247
04248
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
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
04343
04344
04345
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
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
04388
04389
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
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 }