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