aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llui/lltexteditor.cpp
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:44:46 -0500
committerJacek Antonelli2008-08-15 23:44:46 -0500
commit38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4 (patch)
treeadca584755d22ca041a2dbfc35d4eca01f70b32c /linden/indra/llui/lltexteditor.cpp
parentREADME.txt (diff)
downloadmeta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.zip
meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.gz
meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.bz2
meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.xz
Second Life viewer sources 1.13.2.12
Diffstat (limited to '')
-rw-r--r--linden/indra/llui/lltexteditor.cpp4163
1 files changed, 4163 insertions, 0 deletions
diff --git a/linden/indra/llui/lltexteditor.cpp b/linden/indra/llui/lltexteditor.cpp
new file mode 100644
index 0000000..b0f8b5a
--- /dev/null
+++ b/linden/indra/llui/lltexteditor.cpp
@@ -0,0 +1,4163 @@
1/**
2 * @file lltexteditor.cpp
3 * @brief LLTextEditor base class
4 *
5 * Copyright (c) 2001-2007, Linden Research, Inc.
6 *
7 * The source code in this file ("Source Code") is provided by Linden Lab
8 * to you under the terms of the GNU General Public License, version 2.0
9 * ("GPL"), unless you have obtained a separate licensing agreement
10 * ("Other License"), formally executed by you and Linden Lab. Terms of
11 * the GPL can be found in doc/GPL-license.txt in this distribution, or
12 * online at http://secondlife.com/developers/opensource/gplv2
13 *
14 * There are special exceptions to the terms and conditions of the GPL as
15 * it is applied to this Source Code. View the full text of the exception
16 * in the file doc/FLOSS-exception.txt in this software distribution, or
17 * online at http://secondlife.com/developers/opensource/flossexception
18 *
19 * By copying, modifying or distributing this software, you acknowledge
20 * that you have read and understood your obligations described above,
21 * and agree to abide by those obligations.
22 *
23 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25 * COMPLETENESS OR PERFORMANCE.
26 */
27
28// Text editor widget to let users enter a a multi-line ASCII document.
29
30#include "linden_common.h"
31
32#include "lltexteditor.h"
33
34#include "llfontgl.h"
35#include "llgl.h"
36#include "llui.h"
37#include "lluictrlfactory.h"
38#include "llrect.h"
39#include "llfocusmgr.h"
40#include "sound_ids.h"
41#include "lltimer.h"
42#include "llmath.h"
43
44#include "audioengine.h"
45#include "llclipboard.h"
46#include "llscrollbar.h"
47#include "llstl.h"
48#include "llkeyboard.h"
49#include "llkeywords.h"
50#include "llundo.h"
51#include "llviewborder.h"
52#include "llcontrol.h"
53#include "llimagegl.h"
54#include "llwindow.h"
55#include "llglheaders.h"
56#include <queue>
57
58//
59// Globals
60//
61
62BOOL gDebugTextEditorTips = FALSE;
63
64//
65// Constants
66//
67
68const S32 UI_TEXTEDITOR_BUFFER_BLOCK_SIZE = 512;
69
70const S32 UI_TEXTEDITOR_BORDER = 1;
71const S32 UI_TEXTEDITOR_H_PAD = 4;
72const S32 UI_TEXTEDITOR_V_PAD_TOP = 4;
73const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds
74const S32 CURSOR_THICKNESS = 2;
75const S32 SPACES_PER_TAB = 4;
76
77LLColor4 LLTextEditor::mLinkColor = LLColor4::blue;
78void (* LLTextEditor::mURLcallback)(const char*) = NULL;
79BOOL (* LLTextEditor::mSecondlifeURLcallback)(LLString) = NULL;
80
81///////////////////////////////////////////////////////////////////
82//virtuals
83BOOL LLTextCmd::canExtend(S32 pos)
84{
85 return FALSE;
86}
87
88void LLTextCmd::blockExtensions()
89{
90}
91
92BOOL LLTextCmd::extendAndExecute( LLTextEditor* editor, S32 pos, llwchar c, S32* delta )
93{
94 llassert(0);
95 return 0;
96}
97
98BOOL LLTextCmd::hasExtCharValue( llwchar value )
99{
100 return FALSE;
101}
102
103// Utility funcs
104S32 LLTextCmd::insert(LLTextEditor* editor, S32 pos, const LLWString &utf8str)
105{
106 return editor->insertStringNoUndo( pos, utf8str );
107}
108S32 LLTextCmd::remove(LLTextEditor* editor, S32 pos, S32 length)
109{
110 return editor->removeStringNoUndo( pos, length );
111}
112S32 LLTextCmd::overwrite(LLTextEditor* editor, S32 pos, llwchar wc)
113{
114 return editor->overwriteCharNoUndo(pos, wc);
115}
116
117///////////////////////////////////////////////////////////////////
118
119class LLTextCmdInsert : public LLTextCmd
120{
121public:
122 LLTextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws)
123 : LLTextCmd(pos, group_with_next), mString(ws)
124 {
125 }
126 virtual BOOL execute( LLTextEditor* editor, S32* delta )
127 {
128 *delta = insert(editor, mPos, mString );
129 LLWString::truncate(mString, *delta);
130 //mString = wstring_truncate(mString, *delta);
131 return (*delta != 0);
132 }
133 virtual S32 undo( LLTextEditor* editor )
134 {
135 remove(editor, mPos, mString.length() );
136 return mPos;
137 }
138 virtual S32 redo( LLTextEditor* editor )
139 {
140 insert(editor, mPos, mString );
141 return mPos + mString.length();
142 }
143
144private:
145 LLWString mString;
146};
147
148///////////////////////////////////////////////////////////////////
149
150class LLTextCmdAddChar : public LLTextCmd
151{
152public:
153 LLTextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc)
154 : LLTextCmd(pos, group_with_next), mString(1, wc), mBlockExtensions(FALSE)
155 {
156 }
157 virtual void blockExtensions()
158 {
159 mBlockExtensions = TRUE;
160 }
161 virtual BOOL canExtend(S32 pos)
162 {
163 return !mBlockExtensions && (pos == mPos + (S32)mString.length());
164 }
165 virtual BOOL execute( LLTextEditor* editor, S32* delta )
166 {
167 *delta = insert(editor, mPos, mString);
168 LLWString::truncate(mString, *delta);
169 //mString = wstring_truncate(mString, *delta);
170 return (*delta != 0);
171 }
172 virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar wc, S32* delta )
173 {
174 LLWString ws;
175 ws += wc;
176
177 *delta = insert(editor, pos, ws);
178 if( *delta > 0 )
179 {
180 mString += wc;
181 }
182 return (*delta != 0);
183 }
184 virtual S32 undo( LLTextEditor* editor )
185 {
186 remove(editor, mPos, mString.length() );
187 return mPos;
188 }
189 virtual S32 redo( LLTextEditor* editor )
190 {
191 insert(editor, mPos, mString );
192 return mPos + mString.length();
193 }
194
195private:
196 LLWString mString;
197 BOOL mBlockExtensions;
198
199};
200
201///////////////////////////////////////////////////////////////////
202
203class LLTextCmdOverwriteChar : public LLTextCmd
204{
205public:
206 LLTextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc)
207 : LLTextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {}
208
209 virtual BOOL execute( LLTextEditor* editor, S32* delta )
210 {
211 mOldChar = editor->getWChar(mPos);
212 overwrite(editor, mPos, mChar);
213 *delta = 0;
214 return TRUE;
215 }
216 virtual S32 undo( LLTextEditor* editor )
217 {
218 overwrite(editor, mPos, mOldChar);
219 return mPos;
220 }
221 virtual S32 redo( LLTextEditor* editor )
222 {
223 overwrite(editor, mPos, mChar);
224 return mPos+1;
225 }
226
227private:
228 llwchar mChar;
229 llwchar mOldChar;
230};
231
232///////////////////////////////////////////////////////////////////
233
234class LLTextCmdRemove : public LLTextCmd
235{
236public:
237 LLTextCmdRemove( S32 pos, BOOL group_with_next, S32 len ) :
238 LLTextCmd(pos, group_with_next), mLen(len)
239 {
240 }
241 virtual BOOL execute( LLTextEditor* editor, S32* delta )
242 {
243 mString = editor->getWSubString(mPos, mLen);
244 *delta = remove(editor, mPos, mLen );
245 return (*delta != 0);
246 }
247 virtual S32 undo( LLTextEditor* editor )
248 {
249 insert(editor, mPos, mString );
250 return mPos + mString.length();
251 }
252 virtual S32 redo( LLTextEditor* editor )
253 {
254 remove(editor, mPos, mLen );
255 return mPos;
256 }
257private:
258 LLWString mString;
259 S32 mLen;
260};
261
262///////////////////////////////////////////////////////////////////
263
264//
265// Member functions
266//
267
268LLTextEditor::LLTextEditor(
269 const LLString& name,
270 const LLRect& rect,
271 S32 max_length,
272 const LLString &default_text,
273 const LLFontGL* font,
274 BOOL allow_embedded_items)
275 :
276 LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ),
277 mTextIsUpToDate(TRUE),
278 mMaxTextLength( max_length ),
279 mBaseDocIsPristine(TRUE),
280 mPristineCmd( NULL ),
281 mLastCmd( NULL ),
282 mCursorPos( 0 ),
283 mIsSelecting( FALSE ),
284 mSelectionStart( 0 ),
285 mSelectionEnd( 0 ),
286 mOnScrollEndCallback( NULL ),
287 mOnScrollEndData( NULL ),
288 mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ),
289 mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ),
290 mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ),
291 mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ),
292 mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ),
293 mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ),
294 mReadOnly(FALSE),
295 mWordWrap( FALSE ),
296 mTabToNextField( TRUE ),
297 mCommitOnFocusLost( FALSE ),
298 mTakesFocus( TRUE ),
299 mHideScrollbarForShortDocs( FALSE ),
300 mTakesNonScrollClicks( TRUE ),
301 mAllowEmbeddedItems( allow_embedded_items ),
302 mAcceptCallingCardNames(FALSE),
303 mHandleEditKeysDirectly( FALSE ),
304 mMouseDownX(0),
305 mMouseDownY(0),
306 mLastSelectionX(-1),
307 mLastSelectionY(-1)
308{
309 mSourceID.generate();
310
311 if (font)
312 {
313 mGLFont = font;
314 }
315 else
316 {
317 mGLFont = LLFontGL::sSansSerif;
318 }
319
320 updateTextRect();
321
322 S32 line_height = llround( mGLFont->getLineHeight() );
323 S32 page_size = mTextRect.getHeight() / line_height;
324
325 // Init the scrollbar
326 LLRect scroll_rect;
327 scroll_rect.setOriginAndSize(
328 mRect.getWidth() - UI_TEXTEDITOR_BORDER - SCROLLBAR_SIZE,
329 UI_TEXTEDITOR_BORDER,
330 SCROLLBAR_SIZE,
331 mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER );
332 S32 lines_in_doc = getLineCount();
333 mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect,
334 LLScrollbar::VERTICAL,
335 lines_in_doc,
336 0,
337 page_size,
338 NULL, this );
339 mScrollbar->setFollowsRight();
340 mScrollbar->setFollowsTop();
341 mScrollbar->setFollowsBottom();
342 mScrollbar->setEnabled( TRUE );
343 mScrollbar->setVisible( TRUE );
344 mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData);
345 addChild(mScrollbar);
346
347 mBorder = new LLViewBorder( "text ed border", LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER );
348 addChild( mBorder );
349
350 setText(default_text);
351
352 mParseHTML=FALSE;
353 mHTML="";
354}
355
356
357LLTextEditor::~LLTextEditor()
358{
359 gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
360
361 // Route menu back to the default
362 if( gEditMenuHandler == this )
363 {
364 gEditMenuHandler = NULL;
365 }
366
367 // Scrollbar is deleted by LLView
368 mHoverSegment = NULL;
369 std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
370
371 std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
372}
373
374//virtual
375LLString LLTextEditor::getWidgetTag() const
376{
377 return LL_TEXT_EDITOR_TAG;
378}
379
380void LLTextEditor::setTrackColor( const LLColor4& color )
381{
382 mScrollbar->setTrackColor(color);
383}
384
385void LLTextEditor::setThumbColor( const LLColor4& color )
386{
387 mScrollbar->setThumbColor(color);
388}
389
390void LLTextEditor::setHighlightColor( const LLColor4& color )
391{
392 mScrollbar->setHighlightColor(color);
393}
394
395void LLTextEditor::setShadowColor( const LLColor4& color )
396{
397 mScrollbar->setShadowColor(color);
398}
399
400void LLTextEditor::updateLineStartList(S32 startpos)
401{
402 updateSegments();
403
404 bindEmbeddedChars( mGLFont );
405
406 S32 seg_num = mSegments.size();
407 S32 seg_idx = 0;
408 S32 seg_offset = 0;
409
410 if (!mLineStartList.empty())
411 {
412 getSegmentAndOffset(startpos, &seg_idx, &seg_offset);
413 line_info t(seg_idx, seg_offset);
414 line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), t, line_info_compare());
415 if (iter != mLineStartList.begin()) --iter;
416 seg_idx = iter->mSegment;
417 seg_offset = iter->mOffset;
418 mLineStartList.erase(iter, mLineStartList.end());
419 }
420
421 while( seg_idx < seg_num )
422 {
423 mLineStartList.push_back(line_info(seg_idx,seg_offset));
424 BOOL line_ended = FALSE;
425 S32 line_width = 0;
426 while(!line_ended && seg_idx < seg_num)
427 {
428 LLTextSegment* segment = mSegments[seg_idx];
429 S32 start_idx = segment->getStart() + seg_offset;
430 S32 end_idx = start_idx;
431 while (end_idx < segment->getEnd() && mWText[end_idx] != '\n')
432 {
433 end_idx++;
434 }
435 if (start_idx == end_idx)
436 {
437 if (end_idx >= segment->getEnd())
438 {
439 // empty segment
440 seg_idx++;
441 seg_offset = 0;
442 }
443 else
444 {
445 // empty line
446 line_ended = TRUE;
447 seg_offset++;
448 }
449 }
450 else
451 {
452 const llwchar* str = mWText.c_str() + start_idx;
453 S32 drawn = mGLFont->maxDrawableChars(str, (F32)mTextRect.getWidth() - line_width,
454 end_idx - start_idx, mWordWrap, mAllowEmbeddedItems );
455 if( 0 == drawn && line_width == 0)
456 {
457 // If at the beginning of a line, draw at least one character, even if it doesn't all fit.
458 drawn = 1;
459 }
460 seg_offset += drawn;
461 line_width += mGLFont->getWidth(str, 0, drawn, mAllowEmbeddedItems);
462 end_idx = segment->getStart() + seg_offset;
463 if (end_idx < segment->getEnd())
464 {
465 line_ended = TRUE;
466 if (mWText[end_idx] == '\n')
467 {
468 seg_offset++; // skip newline
469 }
470 }
471 else
472 {
473 // finished with segment
474 seg_idx++;
475 seg_offset = 0;
476 }
477 }
478 }
479 }
480
481 unbindEmbeddedChars(mGLFont);
482
483 mScrollbar->setDocSize( getLineCount() );
484
485 if (mHideScrollbarForShortDocs)
486 {
487 BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize());
488 mScrollbar->setVisible(!short_doc);
489 }
490
491}
492
493////////////////////////////////////////////////////////////
494// LLTextEditor
495// Public methods
496
497//static
498BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); }
499
500
501
502void LLTextEditor::truncate()
503{
504 if (mWText.size() > (size_t)mMaxTextLength)
505 {
506 LLWString::truncate(mWText, mMaxTextLength);
507 mTextIsUpToDate = FALSE;
508 }
509}
510
511void LLTextEditor::setText(const LLString &utf8str)
512{
513 mUTF8Text = utf8str;
514 mWText = utf8str_to_wstring(utf8str);
515 mTextIsUpToDate = TRUE;
516
517 truncate();
518 blockUndo();
519
520 setCursorPos(0);
521 deselect();
522
523 updateLineStartList();
524 updateScrollFromCursor();
525}
526
527void LLTextEditor::setWText(const LLWString &wtext)
528{
529 mWText = wtext;
530 mUTF8Text.clear();
531 mTextIsUpToDate = FALSE;
532
533 truncate();
534 blockUndo();
535
536 setCursorPos(0);
537 deselect();
538
539 updateLineStartList();
540 updateScrollFromCursor();
541}
542
543void LLTextEditor::setValue(const LLSD& value)
544{
545 setText(value.asString());
546}
547
548const LLString& LLTextEditor::getText() const
549{
550 if (!mTextIsUpToDate)
551 {
552 if (mAllowEmbeddedItems)
553 {
554 llwarns << "getText() called on text with embedded items (not supported)" << llendl;
555 }
556 mUTF8Text = wstring_to_utf8str(mWText);
557 mTextIsUpToDate = TRUE;
558 }
559 return mUTF8Text;
560}
561
562LLSD LLTextEditor::getValue() const
563{
564 return LLSD(getText());
565}
566
567void LLTextEditor::setWordWrap(BOOL b)
568{
569 mWordWrap = b;
570
571 setCursorPos(0);
572 deselect();
573
574 updateLineStartList();
575 updateScrollFromCursor();
576}
577
578
579void LLTextEditor::setBorderVisible(BOOL b)
580{
581 mBorder->setVisible(b);
582}
583
584
585
586void LLTextEditor::setHideScrollbarForShortDocs(BOOL b)
587{
588 mHideScrollbarForShortDocs = b;
589
590 if (mHideScrollbarForShortDocs)
591 {
592 BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize());
593 mScrollbar->setVisible(!short_doc);
594 }
595}
596
597void LLTextEditor::selectNext(const LLString& search_text_in, BOOL case_insensitive, BOOL wrap)
598{
599 if (search_text_in.empty())
600 {
601 return;
602 }
603
604 LLWString text = getWText();
605 LLWString search_text = utf8str_to_wstring(search_text_in);
606 if (case_insensitive)
607 {
608 LLWString::toLower(text);
609 LLWString::toLower(search_text);
610 }
611
612 if (mIsSelecting)
613 {
614 LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd);
615
616 if (selected_text == search_text)
617 {
618 // We already have this word selected, we are searching for the next.
619 mCursorPos += search_text.size();
620 }
621 }
622
623 S32 loc = text.find(search_text,mCursorPos);
624
625 // If Maybe we wrapped, search again
626 if (wrap && (-1 == loc))
627 {
628 loc = text.find(search_text);
629 }
630
631 // If still -1, then search_text just isn't found.
632 if (-1 == loc)
633 {
634 mIsSelecting = FALSE;
635 mSelectionEnd = 0;
636 mSelectionStart = 0;
637 return;
638 }
639
640 setCursorPos(loc);
641
642 mIsSelecting = TRUE;
643 mSelectionEnd = mCursorPos;
644 mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size()));
645}
646
647BOOL LLTextEditor::replaceText(const LLString& search_text_in, const LLString& replace_text,
648 BOOL case_insensitive, BOOL wrap)
649{
650 BOOL replaced = FALSE;
651
652 if (search_text_in.empty())
653 {
654 return replaced;
655 }
656
657 LLWString search_text = utf8str_to_wstring(search_text_in);
658 if (mIsSelecting)
659 {
660 LLWString text = getWText();
661 LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd);
662
663 if (case_insensitive)
664 {
665 LLWString::toLower(selected_text);
666 LLWString::toLower(search_text);
667 }
668
669 if (selected_text == search_text)
670 {
671 insertText(replace_text);
672 replaced = TRUE;
673 }
674 }
675
676 selectNext(search_text_in, case_insensitive, wrap);
677 return replaced;
678}
679
680void LLTextEditor::replaceTextAll(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive)
681{
682 S32 cur_pos = mScrollbar->getDocPos();
683
684 setCursorPos(0);
685 selectNext(search_text, case_insensitive, FALSE);
686
687 BOOL replaced = TRUE;
688 while ( replaced )
689 {
690 replaced = replaceText(search_text,replace_text, case_insensitive, FALSE);
691 }
692
693 mScrollbar->setDocPos(cur_pos);
694}
695
696void LLTextEditor::setTakesNonScrollClicks(BOOL b)
697{
698 mTakesNonScrollClicks = b;
699}
700
701
702// Picks a new cursor position based on the screen size of text being drawn.
703void LLTextEditor::setCursorAtLocalPos( S32 local_x, S32 local_y, BOOL round )
704{
705 setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round));
706}
707
708S32 LLTextEditor::prevWordPos(S32 cursorPos) const
709{
710 const LLWString& wtext = mWText;
711 while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') )
712 {
713 cursorPos--;
714 }
715 while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) )
716 {
717 cursorPos--;
718 }
719 return cursorPos;
720}
721
722S32 LLTextEditor::nextWordPos(S32 cursorPos) const
723{
724 const LLWString& wtext = mWText;
725 while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) )
726 {
727 cursorPos++;
728 }
729 while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') )
730 {
731 cursorPos++;
732 }
733 return cursorPos;
734}
735
736S32 LLTextEditor::getLineCount()
737{
738 return mLineStartList.size();
739}
740
741S32 LLTextEditor::getLineStart( S32 line )
742{
743 S32 num_lines = getLineCount();
744 if (num_lines == 0)
745 {
746 return 0;
747 }
748 line = llclamp(line, 0, num_lines-1);
749 S32 segidx = mLineStartList[line].mSegment;
750 S32 segoffset = mLineStartList[line].mOffset;
751 LLTextSegment* seg = mSegments[segidx];
752 S32 res = seg->getStart() + segoffset;
753 if (res > seg->getEnd()) llerrs << "wtf" << llendl;
754 return res;
755}
756
757// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line.
758void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp )
759{
760 if (mLineStartList.empty())
761 {
762 *linep = 0;
763 *offsetp = startpos;
764 }
765 else
766 {
767 S32 seg_idx, seg_offset;
768 getSegmentAndOffset( startpos, &seg_idx, &seg_offset );
769
770 line_info tline(seg_idx, seg_offset);
771 line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), tline, line_info_compare());
772 if (iter != mLineStartList.begin()) --iter;
773 *linep = iter - mLineStartList.begin();
774 S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset;
775 *offsetp = startpos - line_start;
776 }
777}
778
779void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp )
780{
781 if (mSegments.empty())
782 {
783 *segidxp = -1;
784 *offsetp = startpos;
785 }
786
787 LLTextSegment tseg(startpos);
788 segment_list_t::iterator seg_iter;
789 seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg, LLTextSegment::compare());
790 if (seg_iter != mSegments.begin()) --seg_iter;
791 *segidxp = seg_iter - mSegments.begin();
792 *offsetp = startpos - (*seg_iter)->getStart();
793}
794
795const LLWString& LLTextEditor::getWText() const
796{
797 return mWText;
798}
799
800S32 LLTextEditor::getLength() const
801{
802 return mWText.length();
803}
804
805llwchar LLTextEditor::getWChar(S32 pos)
806{
807 return mWText[pos];
808}
809
810LLWString LLTextEditor::getWSubString(S32 pos, S32 len)
811{
812 return mWText.substr(pos, len);
813}
814
815S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round )
816{
817 // If round is true, if the position is on the right half of a character, the cursor
818 // will be put to its right. If round is false, the cursor will always be put to the
819 // character's left.
820
821 // Figure out which line we're nearest to.
822 S32 total_lines = getLineCount();
823 S32 line_height = llround( mGLFont->getLineHeight() );
824 S32 max_visible_lines = mTextRect.getHeight() / line_height;
825 S32 scroll_lines = mScrollbar->getDocPos();
826 S32 visible_lines = llmin( total_lines - scroll_lines, max_visible_lines ); // Lines currently visible
827
828 //S32 line = S32( 0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()) );
829 S32 line = (mTextRect.mTop - 1 - local_y) / line_height;
830 if (line >= total_lines)
831 {
832 return getLength(); // past the end
833 }
834
835 line = llclamp( line, 0, visible_lines ) + scroll_lines;
836
837 S32 line_start = getLineStart(line);
838 S32 next_start = getLineStart(line+1);
839 S32 line_end = (next_start != line_start) ? next_start - 1 : getLength();
840
841 if(line_start == -1)
842 {
843 return 0;
844 }
845 else
846 {
847 S32 line_len = line_end - line_start;
848 S32 pos;
849
850 if (mAllowEmbeddedItems)
851 {
852 // Figure out which character we're nearest to.
853 bindEmbeddedChars(mGLFont);
854 pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start,
855 (F32)(local_x - mTextRect.mLeft),
856 (F32)(mTextRect.getWidth()),
857 line_len,
858 round, TRUE);
859 unbindEmbeddedChars(mGLFont);
860 }
861 else
862 {
863 pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start,
864 (F32)(local_x - mTextRect.mLeft),
865 (F32)mTextRect.getWidth(),
866 line_len,
867 round);
868 }
869
870 return line_start + pos;
871 }
872}
873
874void LLTextEditor::setCursor(S32 row, S32 column)
875{
876 const llwchar* doc = mWText.c_str();
877 const char CR = 10;
878 while(row--)
879 {
880 while (CR != *doc++);
881 }
882 doc += column;
883 setCursorPos(doc - mWText.c_str());
884 updateScrollFromCursor();
885}
886
887void LLTextEditor::setCursorPos(S32 offset)
888{
889 mCursorPos = llclamp(offset, 0, (S32)getLength());
890 updateScrollFromCursor();
891}
892
893
894BOOL LLTextEditor::canDeselect()
895{
896 return hasSelection();
897}
898
899
900void LLTextEditor::deselect()
901{
902 mSelectionStart = 0;
903 mSelectionEnd = 0;
904 mIsSelecting = FALSE;
905}
906
907
908void LLTextEditor::startSelection()
909{
910 if( !mIsSelecting )
911 {
912 mIsSelecting = TRUE;
913 mSelectionStart = mCursorPos;
914 mSelectionEnd = mCursorPos;
915 }
916}
917
918void LLTextEditor::endSelection()
919{
920 if( mIsSelecting )
921 {
922 mIsSelecting = FALSE;
923 mSelectionEnd = mCursorPos;
924 }
925 if (mParseHTML && mHTML.length() > 0)
926 {
927 //Special handling for slurls
928 if ( (mSecondlifeURLcallback!=NULL) && !(*mSecondlifeURLcallback)(mHTML) )
929 {
930 if (mURLcallback!=NULL) (*mURLcallback)(mHTML.c_str());
931
932 //load_url(url.c_str());
933 }
934 mHTML="";
935 }
936}
937
938BOOL LLTextEditor::selectionContainsLineBreaks()
939{
940 if (hasSelection())
941 {
942 S32 left = llmin(mSelectionStart, mSelectionEnd);
943 S32 right = left + abs(mSelectionStart - mSelectionEnd);
944
945 const LLWString &wtext = mWText;
946 for( S32 i = left; i < right; i++ )
947 {
948 if (wtext[i] == '\n')
949 {
950 return TRUE;
951 }
952 }
953 }
954 return FALSE;
955}
956
957
958S32 LLTextEditor::indentLine( S32 pos, S32 spaces )
959{
960 // Assumes that pos is at the start of the line
961 // spaces may be positive (indent) or negative (unindent).
962 // Returns the actual number of characters added or removed.
963
964 llassert(pos >= 0);
965 llassert(pos <= getLength() );
966
967 S32 delta_spaces = 0;
968
969 if (spaces >= 0)
970 {
971 // Indent
972 for(S32 i=0; i < spaces; i++)
973 {
974 delta_spaces += addChar(pos, ' ');
975 }
976 }
977 else
978 {
979 // Unindent
980 for(S32 i=0; i < -spaces; i++)
981 {
982 const LLWString &wtext = mWText;
983 if (wtext[pos] == ' ')
984 {
985 delta_spaces += remove( pos, 1, FALSE );
986 }
987 }
988 }
989
990 return delta_spaces;
991}
992
993void LLTextEditor::indentSelectedLines( S32 spaces )
994{
995 if( hasSelection() )
996 {
997 const LLWString &text = mWText;
998 S32 left = llmin( mSelectionStart, mSelectionEnd );
999 S32 right = left + abs( mSelectionStart - mSelectionEnd );
1000 BOOL cursor_on_right = (mSelectionEnd > mSelectionStart);
1001 S32 cur = left;
1002
1003 // Expand left to start of line
1004 while( (cur > 0) && (text[cur] != '\n') )
1005 {
1006 cur--;
1007 }
1008 left = cur;
1009 if( cur > 0 )
1010 {
1011 left++;
1012 }
1013
1014 // Expand right to end of line
1015 if( text[right - 1] == '\n' )
1016 {
1017 right--;
1018 }
1019 else
1020 {
1021 while( (text[right] != '\n') && (right <= getLength() ) )
1022 {
1023 right++;
1024 }
1025 }
1026
1027 // Find each start-of-line and indent it
1028 do
1029 {
1030 if( text[cur] == '\n' )
1031 {
1032 cur++;
1033 }
1034
1035 S32 delta_spaces = indentLine( cur, spaces );
1036 if( delta_spaces > 0 )
1037 {
1038 cur += delta_spaces;
1039 }
1040 right += delta_spaces;
1041
1042 //text = mWText;
1043
1044 // Find the next new line
1045 while( (cur < right) && (text[cur] != '\n') )
1046 {
1047 cur++;
1048 }
1049 }
1050 while( cur < right );
1051
1052 if( (right < getLength()) && (text[right] == '\n') )
1053 {
1054 right++;
1055 }
1056
1057 // Set the selection and cursor
1058 if( cursor_on_right )
1059 {
1060 mSelectionStart = left;
1061 mSelectionEnd = right;
1062 }
1063 else
1064 {
1065 mSelectionStart = right;
1066 mSelectionEnd = left;
1067 }
1068 mCursorPos = mSelectionEnd;
1069 }
1070}
1071
1072
1073BOOL LLTextEditor::canSelectAll()
1074{
1075 return TRUE;
1076}
1077
1078void LLTextEditor::selectAll()
1079{
1080 mSelectionStart = getLength();
1081 mSelectionEnd = 0;
1082 mCursorPos = mSelectionEnd;
1083}
1084
1085
1086BOOL LLTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
1087{
1088 if (pointInView(x, y) && getVisible())
1089 {
1090 for ( child_list_const_iter_t child_it = getChildList()->begin();
1091 child_it != getChildList()->end(); ++child_it)
1092 {
1093 LLView* viewp = *child_it;
1094 S32 local_x = x - viewp->getRect().mLeft;
1095 S32 local_y = y - viewp->getRect().mBottom;
1096 if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) )
1097 {
1098 return TRUE;
1099 }
1100 }
1101
1102 if( mSegments.empty() )
1103 {
1104 return TRUE;
1105 }
1106
1107 LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
1108 if( cur_segment )
1109 {
1110 BOOL has_tool_tip = FALSE;
1111 has_tool_tip = cur_segment->getToolTip( msg );
1112
1113 if( has_tool_tip )
1114 {
1115 // Just use a slop area around the cursor
1116 // Convert rect local to screen coordinates
1117 S32 SLOP = 8;
1118 localPointToScreen(
1119 x - SLOP, y - SLOP,
1120 &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
1121 sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP;
1122 sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP;
1123 }
1124 }
1125 return TRUE;
1126 }
1127 return FALSE;
1128}
1129
1130BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks)
1131{
1132 // Pretend the mouse is over the scrollbar
1133 if (getVisible())
1134 {
1135 return mScrollbar->handleScrollWheel( 0, 0, clicks );
1136 }
1137 else
1138 {
1139 return FALSE;
1140 }
1141}
1142
1143BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
1144{
1145 BOOL handled = FALSE;
1146
1147 // Let scrollbar have first dibs
1148 handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
1149
1150 if( !handled && mTakesNonScrollClicks)
1151 {
1152 if (!(mask & MASK_SHIFT))
1153 {
1154 deselect();
1155 }
1156
1157 BOOL start_select = TRUE;
1158 if( start_select )
1159 {
1160 // If we're not scrolling (handled by child), then we're selecting
1161 if (mask & MASK_SHIFT)
1162 {
1163 S32 old_cursor_pos = mCursorPos;
1164 setCursorAtLocalPos( x, y, TRUE );
1165
1166 if (hasSelection())
1167 {
1168 /* Mac-like behavior - extend selection towards the cursor
1169 if (mCursorPos < mSelectionStart
1170 && mCursorPos < mSelectionEnd)
1171 {
1172 // ...left of selection
1173 mSelectionStart = llmax(mSelectionStart, mSelectionEnd);
1174 mSelectionEnd = mCursorPos;
1175 }
1176 else if (mCursorPos > mSelectionStart
1177 && mCursorPos > mSelectionEnd)
1178 {
1179 // ...right of selection
1180 mSelectionStart = llmin(mSelectionStart, mSelectionEnd);
1181 mSelectionEnd = mCursorPos;
1182 }
1183 else
1184 {
1185 mSelectionEnd = mCursorPos;
1186 }
1187 */
1188 // Windows behavior
1189 mSelectionEnd = mCursorPos;
1190 }
1191 else
1192 {
1193 mSelectionStart = old_cursor_pos;
1194 mSelectionEnd = mCursorPos;
1195 }
1196 // assume we're starting a drag select
1197 mIsSelecting = TRUE;
1198 }
1199 else
1200 {
1201 setCursorAtLocalPos( x, y, TRUE );
1202 startSelection();
1203 }
1204 gFocusMgr.setMouseCapture( this, &LLTextEditor::onMouseCaptureLost );
1205 }
1206
1207 handled = TRUE;
1208 }
1209
1210 if (mTakesFocus)
1211 {
1212 setFocus( TRUE );
1213 handled = TRUE;
1214 }
1215
1216 // Delay cursor flashing
1217 mKeystrokeTimer.reset();
1218
1219 return handled;
1220}
1221
1222
1223BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask)
1224{
1225 BOOL handled = FALSE;
1226
1227 mHoverSegment = NULL;
1228 if( getVisible() )
1229 {
1230 if(gFocusMgr.getMouseCapture() == this )
1231 {
1232 if( mIsSelecting )
1233 {
1234 if (x != mLastSelectionX || y != mLastSelectionY)
1235 {
1236 mLastSelectionX = x;
1237 mLastSelectionY = y;
1238 }
1239
1240 if( y > mTextRect.mTop )
1241 {
1242 mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 );
1243 }
1244 else
1245 if( y < mTextRect.mBottom )
1246 {
1247 mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 );
1248 }
1249
1250 setCursorAtLocalPos( x, y, TRUE );
1251 mSelectionEnd = mCursorPos;
1252
1253 updateScrollFromCursor();
1254 }
1255
1256 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;
1257 getWindow()->setCursor(UI_CURSOR_IBEAM);
1258 handled = TRUE;
1259 }
1260
1261 if( !handled )
1262 {
1263 // Pass to children
1264 handled = LLView::childrenHandleHover(x, y, mask) != NULL;
1265 }
1266
1267 if( handled )
1268 {
1269 // Delay cursor flashing
1270 mKeystrokeTimer.reset();
1271 }
1272
1273 // Opaque
1274 if( !handled && mTakesNonScrollClicks)
1275 {
1276 // Check to see if we're over an HTML-style link
1277 if( !mSegments.empty() )
1278 {
1279 LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
1280 if( cur_segment )
1281 {
1282 if(cur_segment->getStyle().isLink())
1283 {
1284 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl;
1285 getWindow()->setCursor(UI_CURSOR_HAND);
1286 handled = TRUE;
1287 }
1288 else
1289 if(cur_segment->getStyle().getIsEmbeddedItem())
1290 {
1291 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl;
1292 getWindow()->setCursor(UI_CURSOR_HAND);
1293 //getWindow()->setCursor(UI_CURSOR_ARROW);
1294 handled = TRUE;
1295 }
1296 mHoverSegment = cur_segment;
1297 }
1298 }
1299
1300 if( !handled )
1301 {
1302 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;
1303 if (!mScrollbar->getVisible() || x < mRect.getWidth() - SCROLLBAR_SIZE)
1304 {
1305 getWindow()->setCursor(UI_CURSOR_IBEAM);
1306 }
1307 else
1308 {
1309 getWindow()->setCursor(UI_CURSOR_ARROW);
1310 }
1311 handled = TRUE;
1312 }
1313 }
1314 }
1315
1316 if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
1317 {
1318 mOnScrollEndCallback(mOnScrollEndData);
1319 }
1320 return handled;
1321}
1322
1323
1324BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
1325{
1326 BOOL handled = FALSE;
1327
1328 // let scrollbar have first dibs
1329 handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL;
1330
1331 if( !handled && mTakesNonScrollClicks)
1332 {
1333 if( mIsSelecting )
1334 {
1335 // Finish selection
1336 if( y > mTextRect.mTop )
1337 {
1338 mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 );
1339 }
1340 else
1341 if( y < mTextRect.mBottom )
1342 {
1343 mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 );
1344 }
1345
1346 setCursorAtLocalPos( x, y, TRUE );
1347 endSelection();
1348
1349 updateScrollFromCursor();
1350 }
1351
1352 if( !hasSelection() )
1353 {
1354 handleMouseUpOverSegment( x, y, mask );
1355 }
1356
1357 handled = TRUE;
1358 }
1359
1360 // Delay cursor flashing
1361 mKeystrokeTimer.reset();
1362
1363 if( gFocusMgr.getMouseCapture() == this )
1364 {
1365 gFocusMgr.setMouseCapture( NULL, NULL );
1366 handled = TRUE;
1367 }
1368
1369 return handled;
1370}
1371
1372
1373BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
1374{
1375 BOOL handled = FALSE;
1376
1377 // let scrollbar have first dibs
1378 handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL;
1379
1380 if( !handled && mTakesNonScrollClicks)
1381 {
1382 if (mTakesFocus)
1383 {
1384 setFocus( TRUE );
1385 }
1386
1387 setCursorAtLocalPos( x, y, FALSE );
1388 deselect();
1389
1390 const LLWString &text = mWText;
1391
1392 if( isPartOfWord( text[mCursorPos] ) )
1393 {
1394 // Select word the cursor is over
1395 while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1]))
1396 {
1397 mCursorPos--;
1398 }
1399 startSelection();
1400
1401 while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) )
1402 {
1403 mCursorPos++;
1404 }
1405
1406 mSelectionEnd = mCursorPos;
1407 }
1408 else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) )
1409 {
1410 // Select the character the cursor is over
1411 startSelection();
1412 mCursorPos++;
1413 mSelectionEnd = mCursorPos;
1414 }
1415
1416 // We don't want handleMouseUp() to "finish" the selection (and thereby
1417 // set mSelectionEnd to where the mouse is), so we finish the selection here.
1418 mIsSelecting = FALSE;
1419
1420 // delay cursor flashing
1421 mKeystrokeTimer.reset();
1422
1423 handled = TRUE;
1424 }
1425 return handled;
1426}
1427
1428
1429// Allow calling cards to be dropped onto text fields. Append the name and
1430// a carriage return.
1431// virtual
1432BOOL LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask,
1433 BOOL drop, EDragAndDropType cargo_type, void *cargo_data,
1434 EAcceptance *accept,
1435 LLString& tooltip_msg)
1436{
1437 *accept = ACCEPT_NO;
1438
1439 return TRUE;
1440}
1441
1442//----------------------------------------------------------------------------
1443// Returns change in number of characters in mText
1444
1445S32 LLTextEditor::execute( LLTextCmd* cmd )
1446{
1447 S32 delta = 0;
1448 if( cmd->execute(this, &delta) )
1449 {
1450 // Delete top of undo stack
1451 undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
1452 if (enditer != mUndoStack.begin())
1453 {
1454 --enditer;
1455 std::for_each(mUndoStack.begin(), enditer, DeletePointer());
1456 mUndoStack.erase(mUndoStack.begin(), enditer);
1457 }
1458 // Push the new command is now on the top (front) of the undo stack.
1459 mUndoStack.push_front(cmd);
1460 mLastCmd = cmd;
1461 }
1462 else
1463 {
1464 // Operation failed, so don't put it on the undo stack.
1465 delete cmd;
1466 }
1467
1468 return delta;
1469}
1470
1471S32 LLTextEditor::insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op)
1472{
1473 return execute( new LLTextCmdInsert( pos, group_with_next_op, wstr ) );
1474}
1475
1476S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_next_op)
1477{
1478 return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) );
1479}
1480
1481S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op)
1482{
1483 return insert(mWText.length(), wstr, group_with_next_op);
1484}
1485
1486S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc)
1487{
1488 if ((S32)mWText.length() == pos)
1489 {
1490 return addChar(pos, wc);
1491 }
1492 else
1493 {
1494 return execute(new LLTextCmdOverwriteChar(pos, FALSE, wc));
1495 }
1496}
1497
1498// Remove a single character from the text. Tries to remove
1499// a pseudo-tab (up to for spaces in a row)
1500void LLTextEditor::removeCharOrTab()
1501{
1502 if( getEnabled() )
1503 {
1504 if( mCursorPos > 0 )
1505 {
1506 S32 chars_to_remove = 1;
1507
1508 const LLWString &text = mWText;
1509 if (text[mCursorPos - 1] == ' ')
1510 {
1511 // Try to remove a "tab"
1512 S32 line, offset;
1513 getLineAndOffset(mCursorPos, &line, &offset);
1514 if (offset > 0)
1515 {
1516 chars_to_remove = offset % SPACES_PER_TAB;
1517 if( chars_to_remove == 0 )
1518 {
1519 chars_to_remove = SPACES_PER_TAB;
1520 }
1521
1522 for( S32 i = 0; i < chars_to_remove; i++ )
1523 {
1524 if (text[ mCursorPos - i - 1] != ' ')
1525 {
1526 // Fewer than a full tab's worth of spaces, so
1527 // just delete a single character.
1528 chars_to_remove = 1;
1529 break;
1530 }
1531 }
1532 }
1533 }
1534
1535 for (S32 i = 0; i < chars_to_remove; i++)
1536 {
1537 setCursorPos(mCursorPos - 1);
1538 remove( mCursorPos, 1, FALSE );
1539 }
1540 }
1541 else
1542 {
1543 reportBadKeystroke();
1544 }
1545 }
1546}
1547
1548// Remove a single character from the text
1549S32 LLTextEditor::removeChar(S32 pos)
1550{
1551 return remove( pos, 1, FALSE );
1552}
1553
1554void LLTextEditor::removeChar()
1555{
1556 if (getEnabled())
1557 {
1558 if (mCursorPos > 0)
1559 {
1560 setCursorPos(mCursorPos - 1);
1561 removeChar(mCursorPos);
1562 }
1563 else
1564 {
1565 reportBadKeystroke();
1566 }
1567 }
1568}
1569
1570// Add a single character to the text
1571S32 LLTextEditor::addChar(S32 pos, llwchar wc)
1572{
1573 if ((S32)mWText.length() == mMaxTextLength)
1574 {
1575 make_ui_sound("UISndBadKeystroke");
1576 return 0;
1577 }
1578
1579 if (mLastCmd && mLastCmd->canExtend(pos))
1580 {
1581 S32 delta = 0;
1582 mLastCmd->extendAndExecute(this, pos, wc, &delta);
1583 return delta;
1584 }
1585 else
1586 {
1587 return execute(new LLTextCmdAddChar(pos, FALSE, wc));
1588 }
1589}
1590
1591void LLTextEditor::addChar(llwchar wc)
1592{
1593 if( getEnabled() )
1594 {
1595 if( hasSelection() )
1596 {
1597 deleteSelection(TRUE);
1598 }
1599 else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
1600 {
1601 removeChar(mCursorPos);
1602 }
1603
1604 setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
1605 }
1606}
1607
1608
1609BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask)
1610{
1611 BOOL handled = FALSE;
1612
1613 if( mask & MASK_SHIFT )
1614 {
1615 handled = TRUE;
1616
1617 switch( key )
1618 {
1619 case KEY_LEFT:
1620 if( 0 < mCursorPos )
1621 {
1622 startSelection();
1623 mCursorPos--;
1624 if( mask & MASK_CONTROL )
1625 {
1626 mCursorPos = prevWordPos(mCursorPos);
1627 }
1628 mSelectionEnd = mCursorPos;
1629 }
1630 break;
1631
1632 case KEY_RIGHT:
1633 if( mCursorPos < getLength() )
1634 {
1635 startSelection();
1636 mCursorPos++;
1637 if( mask & MASK_CONTROL )
1638 {
1639 mCursorPos = nextWordPos(mCursorPos);
1640 }
1641 mSelectionEnd = mCursorPos;
1642 }
1643 break;
1644
1645 case KEY_UP:
1646 startSelection();
1647 changeLine( -1 );
1648 mSelectionEnd = mCursorPos;
1649 break;
1650
1651 case KEY_PAGE_UP:
1652 startSelection();
1653 changePage( -1 );
1654 mSelectionEnd = mCursorPos;
1655 break;
1656
1657 case KEY_HOME:
1658 startSelection();
1659 if( mask & MASK_CONTROL )
1660 {
1661 mCursorPos = 0;
1662 }
1663 else
1664 {
1665 startOfLine();
1666 }
1667 mSelectionEnd = mCursorPos;
1668 break;
1669
1670 case KEY_DOWN:
1671 startSelection();
1672 changeLine( 1 );
1673 mSelectionEnd = mCursorPos;
1674 break;
1675
1676 case KEY_PAGE_DOWN:
1677 startSelection();
1678 changePage( 1 );
1679 mSelectionEnd = mCursorPos;
1680 break;
1681
1682 case KEY_END:
1683 startSelection();
1684 if( mask & MASK_CONTROL )
1685 {
1686 mCursorPos = getLength();
1687 }
1688 else
1689 {
1690 endOfLine();
1691 }
1692 mSelectionEnd = mCursorPos;
1693 break;
1694
1695 default:
1696 handled = FALSE;
1697 break;
1698 }
1699 }
1700
1701
1702 if( !handled && mHandleEditKeysDirectly )
1703 {
1704 if( (MASK_CONTROL & mask) && ('A' == key) )
1705 {
1706 if( canSelectAll() )
1707 {
1708 selectAll();
1709 }
1710 else
1711 {
1712 reportBadKeystroke();
1713 }
1714 handled = TRUE;
1715 }
1716 }
1717
1718 return handled;
1719}
1720
1721BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask)
1722{
1723 BOOL handled = FALSE;
1724
1725 // Ignore capslock key
1726 if( MASK_NONE == mask )
1727 {
1728 handled = TRUE;
1729 switch( key )
1730 {
1731 case KEY_UP:
1732 if (mReadOnly)
1733 {
1734 mScrollbar->setDocPos(mScrollbar->getDocPos() - 1);
1735 }
1736 else
1737 {
1738 changeLine( -1 );
1739 }
1740 break;
1741
1742 case KEY_PAGE_UP:
1743 changePage( -1 );
1744 break;
1745
1746 case KEY_HOME:
1747 if (mReadOnly)
1748 {
1749 mScrollbar->setDocPos(0);
1750 }
1751 else
1752 {
1753 startOfLine();
1754 }
1755 break;
1756
1757 case KEY_DOWN:
1758 if (mReadOnly)
1759 {
1760 mScrollbar->setDocPos(mScrollbar->getDocPos() + 1);
1761 }
1762 else
1763 {
1764 changeLine( 1 );
1765 }
1766 break;
1767
1768 case KEY_PAGE_DOWN:
1769 changePage( 1 );
1770 break;
1771
1772 case KEY_END:
1773 if (mReadOnly)
1774 {
1775 mScrollbar->setDocPos(mScrollbar->getDocPosMax());
1776 }
1777 else
1778 {
1779 endOfLine();
1780 }
1781 break;
1782
1783 case KEY_LEFT:
1784 if (mReadOnly)
1785 {
1786 break;
1787 }
1788 if( hasSelection() )
1789 {
1790 setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd ));
1791 }
1792 else
1793 {
1794 if( 0 < mCursorPos )
1795 {
1796 setCursorPos(mCursorPos - 1);
1797 }
1798 else
1799 {
1800 reportBadKeystroke();
1801 }
1802 }
1803 break;
1804
1805 case KEY_RIGHT:
1806 if (mReadOnly)
1807 {
1808 break;
1809 }
1810 if( hasSelection() )
1811 {
1812 setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd ));
1813 }
1814 else
1815 {
1816 if( mCursorPos < getLength() )
1817 {
1818 setCursorPos(mCursorPos + 1);
1819 }
1820 else
1821 {
1822 reportBadKeystroke();
1823 }
1824 }
1825 break;
1826
1827 default:
1828 handled = FALSE;
1829 break;
1830 }
1831 }
1832
1833 if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
1834 {
1835 mOnScrollEndCallback(mOnScrollEndData);
1836 }
1837 return handled;
1838}
1839
1840void LLTextEditor::deleteSelection(BOOL group_with_next_op )
1841{
1842 if( getEnabled() && hasSelection() )
1843 {
1844 S32 pos = llmin( mSelectionStart, mSelectionEnd );
1845 S32 length = abs( mSelectionStart - mSelectionEnd );
1846
1847 remove( pos, length, group_with_next_op );
1848
1849 deselect();
1850 setCursorPos(pos);
1851 }
1852}
1853
1854BOOL LLTextEditor::canCut()
1855{
1856 return !mReadOnly && hasSelection();
1857}
1858
1859// cut selection to clipboard
1860void LLTextEditor::cut()
1861{
1862 if( canCut() )
1863 {
1864 S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
1865 S32 length = abs( mSelectionStart - mSelectionEnd );
1866 gClipboard.copyFromSubstring( mWText, left_pos, length, mSourceID );
1867 deleteSelection( FALSE );
1868
1869 updateLineStartList();
1870 updateScrollFromCursor();
1871 }
1872}
1873
1874BOOL LLTextEditor::canCopy()
1875{
1876 return hasSelection();
1877}
1878
1879
1880// copy selection to clipboard
1881void LLTextEditor::copy()
1882{
1883 if( canCopy() )
1884 {
1885 S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
1886 S32 length = abs( mSelectionStart - mSelectionEnd );
1887 gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID);
1888 }
1889}
1890
1891BOOL LLTextEditor::canPaste()
1892{
1893 return !mReadOnly && gClipboard.canPasteString();
1894}
1895
1896
1897// paste from clipboard
1898void LLTextEditor::paste()
1899{
1900 if (canPaste())
1901 {
1902 LLUUID source_id;
1903 LLWString paste = gClipboard.getPasteWString(&source_id);
1904 if (!paste.empty())
1905 {
1906 // Delete any selected characters (the paste replaces them)
1907 if( hasSelection() )
1908 {
1909 deleteSelection(TRUE);
1910 }
1911
1912 // Clean up string (replace tabs and remove characters that our fonts don't support).
1913 LLWString clean_string(paste);
1914 LLWString::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB);
1915 if( mAllowEmbeddedItems )
1916 {
1917 const llwchar LF = 10;
1918 S32 len = clean_string.length();
1919 for( S32 i = 0; i < len; i++ )
1920 {
1921 llwchar wc = clean_string[i];
1922 if( (wc < LLFont::FIRST_CHAR) && (wc != LF) )
1923 {
1924 clean_string[i] = LL_UNKNOWN_CHAR;
1925 }
1926 else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR)
1927 {
1928 clean_string[i] = pasteEmbeddedItem(wc);
1929 }
1930 }
1931 }
1932
1933 // Insert the new text into the existing text.
1934 setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE));
1935 deselect();
1936
1937 updateLineStartList();
1938 updateScrollFromCursor();
1939 }
1940 }
1941}
1942
1943
1944BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask)
1945{
1946 BOOL handled = FALSE;
1947
1948 if( mask & MASK_CONTROL )
1949 {
1950 handled = TRUE;
1951
1952 switch( key )
1953 {
1954 case KEY_HOME:
1955 if( mask & MASK_SHIFT )
1956 {
1957 startSelection();
1958 mCursorPos = 0;
1959 mSelectionEnd = mCursorPos;
1960 }
1961 else
1962 {
1963 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1964 // all move the cursor as if clicking, so should deselect.
1965 deselect();
1966 setCursorPos(0);
1967 }
1968 break;
1969
1970 case KEY_END:
1971 {
1972 if( mask & MASK_SHIFT )
1973 {
1974 startSelection();
1975 }
1976 else
1977 {
1978 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1979 // all move the cursor as if clicking, so should deselect.
1980 deselect();
1981 }
1982 endOfDoc();
1983 if( mask & MASK_SHIFT )
1984 {
1985 mSelectionEnd = mCursorPos;
1986 }
1987 break;
1988 }
1989
1990 case KEY_RIGHT:
1991 if( mCursorPos < getLength() )
1992 {
1993 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1994 // all move the cursor as if clicking, so should deselect.
1995 deselect();
1996
1997 setCursorPos(nextWordPos(mCursorPos + 1));
1998 }
1999 break;
2000
2001
2002 case KEY_LEFT:
2003 if( mCursorPos > 0 )
2004 {
2005 // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
2006 // all move the cursor as if clicking, so should deselect.
2007 deselect();
2008
2009 setCursorPos(prevWordPos(mCursorPos - 1));
2010 }
2011 break;
2012
2013 default:
2014 handled = FALSE;
2015 break;
2016 }
2017 }
2018
2019 return handled;
2020}
2021
2022BOOL LLTextEditor::handleEditKey(const KEY key, const MASK mask)
2023{
2024 BOOL handled = FALSE;
2025
2026 // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system.
2027 if( KEY_DELETE == key )
2028 {
2029 if( canDoDelete() )
2030 {
2031 doDelete();
2032 }
2033 else
2034 {
2035 reportBadKeystroke();
2036 }
2037 handled = TRUE;
2038 }
2039 else
2040 if( MASK_CONTROL & mask )
2041 {
2042 if( 'C' == key )
2043 {
2044 if( canCopy() )
2045 {
2046 copy();
2047 }
2048 else
2049 {
2050 reportBadKeystroke();
2051 }
2052 handled = TRUE;
2053 }
2054 else
2055 if( 'V' == key )
2056 {
2057 if( canPaste() )
2058 {
2059 paste();
2060 }
2061 else
2062 {
2063 reportBadKeystroke();
2064 }
2065 handled = TRUE;
2066 }
2067 else
2068 if( 'X' == key )
2069 {
2070 if( canCut() )
2071 {
2072 cut();
2073 }
2074 else
2075 {
2076 reportBadKeystroke();
2077 }
2078 handled = TRUE;
2079 }
2080 }
2081
2082 return handled;
2083}
2084
2085
2086BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit)
2087{
2088 *return_key_hit = FALSE;
2089 BOOL handled = TRUE;
2090
2091 switch( key )
2092 {
2093 case KEY_INSERT:
2094 if (mask == MASK_NONE)
2095 {
2096 gKeyboard->toggleInsertMode();
2097 }
2098 break;
2099
2100 case KEY_BACKSPACE:
2101 if( hasSelection() )
2102 {
2103 deleteSelection(FALSE);
2104 }
2105 else
2106 if( 0 < mCursorPos )
2107 {
2108 removeCharOrTab();
2109 }
2110 else
2111 {
2112 reportBadKeystroke();
2113 }
2114 break;
2115
2116
2117 case KEY_RETURN:
2118 if (mask == MASK_NONE)
2119 {
2120 if( hasSelection() )
2121 {
2122 deleteSelection(FALSE);
2123 }
2124 autoIndent(); // TODO: make this optional
2125 }
2126 else
2127 {
2128 handled = FALSE;
2129 break;
2130 }
2131 break;
2132
2133 case KEY_TAB:
2134 if (mask & MASK_CONTROL)
2135 {
2136 handled = FALSE;
2137 break;
2138 }
2139 if( hasSelection() && selectionContainsLineBreaks() )
2140 {
2141 indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB );
2142 }
2143 else
2144 {
2145 if( hasSelection() )
2146 {
2147 deleteSelection(FALSE);
2148 }
2149
2150 S32 line, offset;
2151 getLineAndOffset( mCursorPos, &line, &offset );
2152
2153 S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
2154 for( S32 i=0; i < spaces_needed; i++ )
2155 {
2156 addChar( ' ' );
2157 }
2158 }
2159 break;
2160
2161 default:
2162 handled = FALSE;
2163 break;
2164 }
2165
2166 return handled;
2167}
2168
2169
2170void LLTextEditor::unindentLineBeforeCloseBrace()
2171{
2172 if( mCursorPos >= 1 )
2173 {
2174 const LLWString &text = mWText;
2175 if( ' ' == text[ mCursorPos - 1 ] )
2176 {
2177 removeCharOrTab();
2178 }
2179 }
2180}
2181
2182
2183BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent )
2184{
2185 BOOL handled = FALSE;
2186 BOOL selection_modified = FALSE;
2187 BOOL return_key_hit = FALSE;
2188 BOOL text_may_have_changed = TRUE;
2189
2190 if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible())
2191 {
2192 // Special case for TAB. If want to move to next field, report
2193 // not handled and let the parent take care of field movement.
2194 if (KEY_TAB == key && mTabToNextField)
2195 {
2196 return FALSE;
2197 }
2198
2199 handled = handleNavigationKey( key, mask );
2200 if( handled )
2201 {
2202 text_may_have_changed = FALSE;
2203 }
2204
2205 if( !handled )
2206 {
2207 handled = handleSelectionKey( key, mask );
2208 if( handled )
2209 {
2210 selection_modified = TRUE;
2211 }
2212 }
2213
2214 if( !handled )
2215 {
2216 handled = handleControlKey( key, mask );
2217 if( handled )
2218 {
2219 selection_modified = TRUE;
2220 }
2221 }
2222
2223 if( !handled && mHandleEditKeysDirectly )
2224 {
2225 handled = handleEditKey( key, mask );
2226 if( handled )
2227 {
2228 selection_modified = TRUE;
2229 text_may_have_changed = TRUE;
2230 }
2231 }
2232
2233 // Handle most keys only if the text editor is writeable.
2234 if( !mReadOnly )
2235 {
2236 if( !handled )
2237 {
2238 handled = handleSpecialKey( key, mask, &return_key_hit );
2239 if( handled )
2240 {
2241 selection_modified = TRUE;
2242 text_may_have_changed = TRUE;
2243 }
2244 }
2245
2246 }
2247
2248 if( handled )
2249 {
2250 mKeystrokeTimer.reset();
2251
2252 // Most keystrokes will make the selection box go away, but not all will.
2253 if( !selection_modified &&
2254 KEY_SHIFT != key &&
2255 KEY_CONTROL != key &&
2256 KEY_ALT != key &&
2257 KEY_CAPSLOCK )
2258 {
2259 deselect();
2260 }
2261
2262 if(text_may_have_changed)
2263 {
2264 updateLineStartList();
2265 }
2266 updateScrollFromCursor();
2267 }
2268 }
2269
2270 return handled;
2271}
2272
2273
2274BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
2275{
2276 if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
2277 {
2278 return FALSE;
2279 }
2280
2281 BOOL handled = FALSE;
2282
2283 if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible())
2284 {
2285 // Handle most keys only if the text editor is writeable.
2286 if( !mReadOnly )
2287 {
2288 if( '}' == uni_char )
2289 {
2290 unindentLineBeforeCloseBrace();
2291 }
2292
2293 // TODO: KLW Add auto show of tool tip on (
2294 addChar( uni_char );
2295
2296 // Keys that add characters temporarily hide the cursor
2297 getWindow()->hideCursorUntilMouseMove();
2298
2299 handled = TRUE;
2300 }
2301
2302 if( handled )
2303 {
2304 mKeystrokeTimer.reset();
2305
2306 // Most keystrokes will make the selection box go away, but not all will.
2307 deselect();
2308
2309 updateLineStartList();
2310 updateScrollFromCursor();
2311 }
2312 }
2313
2314 return handled;
2315}
2316
2317
2318
2319BOOL LLTextEditor::canDoDelete()
2320{
2321 return !mReadOnly && ( hasSelection() || (mCursorPos < getLength()) );
2322}
2323
2324void LLTextEditor::doDelete()
2325{
2326 if( canDoDelete() )
2327 {
2328 if( hasSelection() )
2329 {
2330 deleteSelection(FALSE);
2331 }
2332 else
2333 if( mCursorPos < getLength() )
2334 {
2335 S32 i;
2336 S32 chars_to_remove = 1;
2337 const LLWString &text = mWText;
2338 if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) )
2339 {
2340 // Try to remove a full tab's worth of spaces
2341 S32 line, offset;
2342 getLineAndOffset( mCursorPos, &line, &offset );
2343 chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
2344 if( chars_to_remove == 0 )
2345 {
2346 chars_to_remove = SPACES_PER_TAB;
2347 }
2348
2349 for( i = 0; i < chars_to_remove; i++ )
2350 {
2351 if( text[mCursorPos + i] != ' ' )
2352 {
2353 chars_to_remove = 1;
2354 break;
2355 }
2356 }
2357 }
2358
2359
2360 for( i = 0; i < chars_to_remove; i++ )
2361 {
2362 setCursorPos(mCursorPos + 1);
2363 removeChar();
2364 }
2365 }
2366
2367 updateLineStartList();
2368 updateScrollFromCursor();
2369 }
2370}
2371
2372//----------------------------------------------------------------------------
2373
2374
2375void LLTextEditor::blockUndo()
2376{
2377 mBaseDocIsPristine = FALSE;
2378 mLastCmd = NULL;
2379 std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
2380 mUndoStack.clear();
2381}
2382
2383
2384BOOL LLTextEditor::canUndo()
2385{
2386 return !mReadOnly && mLastCmd != NULL;
2387}
2388
2389void LLTextEditor::undo()
2390{
2391 if( canUndo() )
2392 {
2393 deselect();
2394
2395 S32 pos = 0;
2396 do
2397 {
2398 pos = mLastCmd->undo(this);
2399 undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
2400 if (iter != mUndoStack.end())
2401 ++iter;
2402 if (iter != mUndoStack.end())
2403 mLastCmd = *iter;
2404 else
2405 mLastCmd = NULL;
2406
2407 } while( mLastCmd && mLastCmd->groupWithNext() );
2408
2409 setCursorPos(pos);
2410
2411 updateLineStartList();
2412 updateScrollFromCursor();
2413 }
2414}
2415
2416BOOL LLTextEditor::canRedo()
2417{
2418 return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front());
2419}
2420
2421void LLTextEditor::redo()
2422{
2423 if( canRedo() )
2424 {
2425 deselect();
2426
2427 S32 pos = 0;
2428 do
2429 {
2430 if( !mLastCmd )
2431 {
2432 mLastCmd = mUndoStack.back();
2433 }
2434 else
2435 {
2436 undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
2437 if (iter != mUndoStack.begin())
2438 mLastCmd = *(--iter);
2439 else
2440 mLastCmd = NULL;
2441 }
2442
2443 if( mLastCmd )
2444 {
2445 pos = mLastCmd->redo(this);
2446 }
2447 } while(
2448 mLastCmd &&
2449 mLastCmd->groupWithNext() &&
2450 (mLastCmd != mUndoStack.front()) );
2451
2452 setCursorPos(pos);
2453
2454 updateLineStartList();
2455 updateScrollFromCursor();
2456 }
2457}
2458
2459
2460// virtual, from LLView
2461void LLTextEditor::onFocusLost()
2462{
2463 // Route menu back to the default
2464 if( gEditMenuHandler == this )
2465 {
2466 gEditMenuHandler = NULL;
2467 }
2468
2469 if (mCommitOnFocusLost)
2470 {
2471 onCommit();
2472 }
2473
2474 // Make sure cursor is shown again
2475 getWindow()->showCursorFromMouseMove();
2476}
2477
2478void LLTextEditor::setEnabled(BOOL enabled)
2479{
2480 // just treat enabled as read-only flag
2481 BOOL read_only = !enabled;
2482 if (read_only != mReadOnly)
2483 {
2484 mReadOnly = read_only;
2485 updateSegments();
2486 }
2487}
2488
2489void LLTextEditor::drawBackground()
2490{
2491 S32 left = 0;
2492 S32 top = mRect.getHeight();
2493 S32 right = mRect.getWidth();
2494 S32 bottom = 0;
2495
2496 LLColor4 bg_color = mReadOnlyBgColor;
2497
2498 if( !mReadOnly )
2499 {
2500 if (gFocusMgr.getKeyboardFocus() == this)
2501 {
2502 bg_color = mFocusBgColor;
2503 }
2504 else
2505 {
2506 bg_color = mWriteableBgColor;
2507 }
2508 }
2509 gl_rect_2d(left, top, right, bottom, bg_color);
2510
2511 LLView::draw();
2512}
2513
2514// Draws the black box behind the selected text
2515void LLTextEditor::drawSelectionBackground()
2516{
2517 // Draw selection even if we don't have keyboard focus for search/replace
2518 if( hasSelection() )
2519 {
2520 const LLWString &text = mWText;
2521 const S32 text_len = getLength();
2522 std::queue<S32> line_endings;
2523
2524 S32 line_height = llround( mGLFont->getLineHeight() );
2525
2526 S32 selection_left = llmin( mSelectionStart, mSelectionEnd );
2527 S32 selection_right = llmax( mSelectionStart, mSelectionEnd );
2528 S32 selection_left_x = mTextRect.mLeft;
2529 S32 selection_left_y = mTextRect.mTop - line_height;
2530 S32 selection_right_x = mTextRect.mRight;
2531 S32 selection_right_y = mTextRect.mBottom;
2532
2533 BOOL selection_left_visible = FALSE;
2534 BOOL selection_right_visible = FALSE;
2535
2536 // Skip through the lines we aren't drawing.
2537 S32 cur_line = mScrollbar->getDocPos();
2538
2539 S32 left_line_num = cur_line;
2540 S32 num_lines = getLineCount();
2541 S32 right_line_num = num_lines - 1;
2542
2543 S32 line_start = -1;
2544 if (cur_line >= num_lines)
2545 {
2546 return;
2547 }
2548
2549 line_start = getLineStart(cur_line);
2550
2551 S32 left_visible_pos = line_start;
2552 S32 right_visible_pos = line_start;
2553
2554 S32 text_y = mTextRect.mTop - line_height;
2555
2556 // Find the coordinates of the selected area
2557 while((cur_line < num_lines))
2558 {
2559 S32 next_line = -1;
2560 S32 line_end = text_len;
2561
2562 if ((cur_line + 1) < num_lines)
2563 {
2564 next_line = getLineStart(cur_line + 1);
2565 line_end = next_line;
2566
2567 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;
2568 }
2569
2570 const llwchar* line = text.c_str() + line_start;
2571
2572 if( line_start <= selection_left && selection_left <= line_end )
2573 {
2574 left_line_num = cur_line;
2575 selection_left_visible = TRUE;
2576 selection_left_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_left - line_start, mAllowEmbeddedItems);
2577 selection_left_y = text_y;
2578 }
2579 if( line_start <= selection_right && selection_right <= line_end )
2580 {
2581 right_line_num = cur_line;
2582 selection_right_visible = TRUE;
2583 selection_right_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_right - line_start, mAllowEmbeddedItems);
2584 if (selection_right == line_end)
2585 {
2586 // add empty space for "newline"
2587 //selection_right_x += mGLFont->getWidth("n");
2588 }
2589 selection_right_y = text_y;
2590 }
2591
2592 // if selection spans end of current line...
2593 if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right)
2594 {
2595 // extend selection slightly beyond end of line
2596 // to indicate selection of newline character (use "n" character to determine width)
2597 const LLWString nstr(utf8str_to_wstring(LLString("n")));
2598 line_endings.push(mTextRect.mLeft + mGLFont->getWidth(line, 0, line_end - line_start, mAllowEmbeddedItems) + mGLFont->getWidth(nstr.c_str()));
2599 }
2600
2601 // move down one line
2602 text_y -= line_height;
2603
2604 right_visible_pos = line_end;
2605 line_start = next_line;
2606 cur_line++;
2607
2608 if (selection_right_visible)
2609 {
2610 break;
2611 }
2612 }
2613
2614 // Draw the selection box (we're using a box instead of reversing the colors on the selected text).
2615 BOOL selection_visible = (left_visible_pos <= selection_right) && (selection_left <= right_visible_pos);
2616 if( selection_visible )
2617 {
2618 LLGLSNoTexture no_texture;
2619 const LLColor4& color = mReadOnly ? mReadOnlyBgColor : mWriteableBgColor;
2620 glColor3f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2] );
2621
2622 if( selection_left_y == selection_right_y )
2623 {
2624 // Draw from selection start to selection end
2625 gl_rect_2d( selection_left_x, selection_left_y + line_height + 1,
2626 selection_right_x, selection_right_y);
2627 }
2628 else
2629 {
2630 // Draw from selection start to the end of the first line
2631 if( mTextRect.mRight == selection_left_x )
2632 {
2633 selection_left_x -= CURSOR_THICKNESS;
2634 }
2635
2636 S32 line_end = line_endings.front();
2637 line_endings.pop();
2638 gl_rect_2d( selection_left_x, selection_left_y + line_height + 1,
2639 line_end, selection_left_y );
2640
2641 S32 line_num = left_line_num + 1;
2642 while(line_endings.size())
2643 {
2644 S32 vert_offset = -(line_num - left_line_num) * line_height;
2645 // Draw the block between the two lines
2646 gl_rect_2d( mTextRect.mLeft, selection_left_y + vert_offset + line_height + 1,
2647 line_endings.front(), selection_left_y + vert_offset);
2648 line_endings.pop();
2649 line_num++;
2650 }
2651
2652 // Draw from the start of the last line to selection end
2653 if( mTextRect.mLeft == selection_right_x )
2654 {
2655 selection_right_x += CURSOR_THICKNESS;
2656 }
2657 gl_rect_2d( mTextRect.mLeft, selection_right_y + line_height + 1,
2658 selection_right_x, selection_right_y );
2659 }
2660 }
2661 }
2662}
2663
2664void LLTextEditor::drawCursor()
2665{
2666 if( gFocusMgr.getKeyboardFocus() == this
2667 && gShowTextEditCursor && !mReadOnly)
2668 {
2669 const LLWString &text = mWText;
2670 const S32 text_len = getLength();
2671
2672 // Skip through the lines we aren't drawing.
2673 S32 cur_pos = mScrollbar->getDocPos();
2674
2675 S32 num_lines = getLineCount();
2676 if (cur_pos >= num_lines)
2677 {
2678 return;
2679 }
2680 S32 line_start = getLineStart(cur_pos);
2681
2682 F32 line_height = mGLFont->getLineHeight();
2683 F32 text_y = (F32)(mTextRect.mTop) - line_height;
2684
2685 F32 cursor_left = 0.f;
2686 F32 next_char_left = 0.f;
2687 F32 cursor_bottom = 0.f;
2688 BOOL cursor_visible = FALSE;
2689
2690 S32 line_end = 0;
2691 // Determine if the cursor is visible and if so what its coordinates are.
2692 while( (mTextRect.mBottom <= llround(text_y)) && (cur_pos < num_lines))
2693 {
2694 line_end = text_len + 1;
2695 S32 next_line = -1;
2696
2697 if ((cur_pos + 1) < num_lines)
2698 {
2699 next_line = getLineStart(cur_pos + 1);
2700 line_end = next_line - 1;
2701 }
2702
2703 const llwchar* line = text.c_str() + line_start;
2704
2705 // Find the cursor and selection bounds
2706 if( line_start <= mCursorPos && mCursorPos <= line_end )
2707 {
2708 cursor_visible = TRUE;
2709 next_char_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, mCursorPos - line_start, mAllowEmbeddedItems );
2710 cursor_left = next_char_left - 1.f;
2711 cursor_bottom = text_y;
2712 break;
2713 }
2714
2715 // move down one line
2716 text_y -= line_height;
2717 line_start = next_line;
2718 cur_pos++;
2719 }
2720
2721 // Draw the cursor
2722 if( cursor_visible )
2723 {
2724 // (Flash the cursor every half second starting a fixed time after the last keystroke)
2725 F32 elapsed = mKeystrokeTimer.getElapsedTimeF32();
2726 if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
2727 {
2728 F32 cursor_top = cursor_bottom + line_height + 1.f;
2729 F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS;
2730 if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
2731 {
2732 cursor_left += CURSOR_THICKNESS;
2733 const LLWString space(utf8str_to_wstring(LLString(" ")));
2734 F32 spacew = mGLFont->getWidthF32(space.c_str());
2735 if (mCursorPos == line_end)
2736 {
2737 cursor_right = cursor_left + spacew;
2738 }
2739 else
2740 {
2741 F32 width = mGLFont->getWidthF32(text.c_str(), mCursorPos, 1, mAllowEmbeddedItems);
2742 cursor_right = cursor_left + llmax(spacew, width);
2743 }
2744 }
2745
2746 LLGLSNoTexture no_texture;
2747
2748 glColor4fv( mCursorColor.mV );
2749
2750 gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top),
2751 llfloor(cursor_right), llfloor(cursor_bottom));
2752
2753 if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n')
2754 {
2755 LLTextSegment* segmentp = getSegmentAtOffset(mCursorPos);
2756 LLColor4 text_color;
2757 if (segmentp)
2758 {
2759 text_color = segmentp->getColor();
2760 }
2761 else if (mReadOnly)
2762 {
2763 text_color = mReadOnlyFgColor;
2764 }
2765 else
2766 {
2767 text_color = mFgColor;
2768 }
2769 LLGLSTexture texture;
2770 mGLFont->render(text, mCursorPos, next_char_left, cursor_bottom + line_height,
2771 LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], 1.f),
2772 LLFontGL::LEFT, LLFontGL::TOP,
2773 LLFontGL::NORMAL,
2774 1);
2775 }
2776
2777
2778 }
2779 }
2780 }
2781}
2782
2783
2784void LLTextEditor::drawText()
2785{
2786 const LLWString &text = mWText;
2787 const S32 text_len = getLength();
2788
2789 if( text_len > 0 )
2790 {
2791 S32 selection_left = -1;
2792 S32 selection_right = -1;
2793 // Draw selection even if we don't have keyboard focus for search/replace
2794 if( hasSelection())
2795 {
2796 selection_left = llmin( mSelectionStart, mSelectionEnd );
2797 selection_right = llmax( mSelectionStart, mSelectionEnd );
2798 }
2799
2800 LLGLSUIDefault gls_ui;
2801
2802 S32 cur_line = mScrollbar->getDocPos();
2803 S32 num_lines = getLineCount();
2804 if (cur_line >= num_lines)
2805 {
2806 return;
2807 }
2808
2809 S32 line_start = getLineStart(cur_line);
2810 LLTextSegment t(line_start);
2811 segment_list_t::iterator seg_iter;
2812 seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t, LLTextSegment::compare());
2813 if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start) --seg_iter;
2814 LLTextSegment* cur_segment = *seg_iter;
2815
2816 S32 line_height = llround( mGLFont->getLineHeight() );
2817 F32 text_y = (F32)(mTextRect.mTop - line_height);
2818 while((mTextRect.mBottom <= text_y) && (cur_line < num_lines))
2819 {
2820 S32 next_start = -1;
2821 S32 line_end = text_len;
2822
2823 if ((cur_line + 1) < num_lines)
2824 {
2825 next_start = getLineStart(cur_line + 1);
2826 line_end = next_start;
2827 }
2828 if ( text[line_end-1] == '\n' )
2829 {
2830 --line_end;
2831 }
2832
2833 F32 text_x = (F32)mTextRect.mLeft;
2834
2835 S32 seg_start = line_start;
2836 while( seg_start < line_end )
2837 {
2838 while( cur_segment->getEnd() <= seg_start )
2839 {
2840 seg_iter++;
2841 if (seg_iter == mSegments.end())
2842 {
2843 llwarns << "Ran off the segmentation end!" << llendl;
2844 return;
2845 }
2846 cur_segment = *seg_iter;
2847 }
2848
2849 // Draw a segment within the line
2850 S32 clipped_end = llmin( line_end, cur_segment->getEnd() );
2851 S32 clipped_len = clipped_end - seg_start;
2852 if( clipped_len > 0 )
2853 {
2854 LLStyle style = cur_segment->getStyle();
2855 if ( style.isImage() && (cur_segment->getStart() >= seg_start) && (cur_segment->getStart() <= clipped_end))
2856 {
2857 LLImageGL *image = style.getImage();
2858
2859 gl_draw_scaled_image( llround(text_x), llround(text_y)+line_height-style.mImageHeight, style.mImageWidth, style.mImageHeight, image, LLColor4::white );
2860
2861 }
2862
2863 if (cur_segment == mHoverSegment && style.getIsEmbeddedItem())
2864 {
2865 style.mUnderline = TRUE;
2866 }
2867
2868 S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
2869
2870 if ( (mParseHTML) && (left_pos > seg_start) && (left_pos < clipped_end) && mIsSelecting && (mSelectionStart == mSelectionEnd) )
2871 {
2872 mHTML = style.getLinkHREF();
2873 }
2874
2875 drawClippedSegment( text, seg_start, clipped_end, text_x, text_y, selection_left, selection_right, style, &text_x );
2876
2877 // Note: text_x is incremented by drawClippedSegment()
2878 seg_start += clipped_len;
2879 }
2880 }
2881
2882 // move down one line
2883 text_y -= (F32)line_height;
2884
2885 line_start = next_start;
2886 cur_line++;
2887 }
2888 }
2889}
2890
2891// Draws a single text segment, reversing the color for selection if needed.
2892void 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 )
2893{
2894 const LLFontGL* font = mGLFont;
2895
2896 LLColor4 color;
2897
2898 if (!style.isVisible())
2899 {
2900 return;
2901 }
2902
2903 color = style.getColor();
2904
2905 if ( style.getFontString()[0] )
2906 {
2907 font = gResMgr->getRes(style.getFontID());
2908 }
2909
2910 U8 font_flags = LLFontGL::NORMAL;
2911
2912 if (style.mBold)
2913 {
2914 font_flags |= LLFontGL::BOLD;
2915 }
2916 if (style.mItalic)
2917 {
2918 font_flags |= LLFontGL::ITALIC;
2919 }
2920 if (style.mUnderline)
2921 {
2922 font_flags |= LLFontGL::UNDERLINE;
2923 }
2924
2925 if (style.getIsEmbeddedItem())
2926 {
2927 if (mReadOnly)
2928 {
2929 color = LLUI::sColorsGroup->getColor("TextEmbeddedItemReadOnlyColor");
2930 }
2931 else
2932 {
2933 color = LLUI::sColorsGroup->getColor("TextEmbeddedItemColor");
2934 }
2935 }
2936
2937 F32 y_top = y + (F32)llround(font->getLineHeight());
2938
2939 if( selection_left > seg_start )
2940 {
2941 // Draw normally
2942 S32 start = seg_start;
2943 S32 end = llmin( selection_left, seg_end );
2944 S32 length = end - start;
2945 font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems);
2946 }
2947 x = *right_x;
2948
2949 if( (selection_left < seg_end) && (selection_right > seg_start) )
2950 {
2951 // Draw reversed
2952 S32 start = llmax( selection_left, seg_start );
2953 S32 end = llmin( selection_right, seg_end );
2954 S32 length = end - start;
2955
2956 font->render(text, start, x, y_top,
2957 LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
2958 LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems);
2959 }
2960 x = *right_x;
2961 if( selection_right < seg_end )
2962 {
2963 // Draw normally
2964 S32 start = llmax( selection_right, seg_start );
2965 S32 end = seg_end;
2966 S32 length = end - start;
2967 font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems);
2968 }
2969 }
2970
2971
2972void LLTextEditor::draw()
2973{
2974 if( getVisible() )
2975 {
2976 {
2977 LLGLEnable scissor_test(GL_SCISSOR_TEST);
2978 LLUI::setScissorRegionLocal(LLRect(0, mRect.getHeight(), mRect.getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0));
2979
2980 bindEmbeddedChars( mGLFont );
2981
2982 drawBackground();
2983 drawSelectionBackground();
2984 drawText();
2985 drawCursor();
2986
2987 unbindEmbeddedChars( mGLFont );
2988
2989 //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret
2990 // when in readonly mode
2991 mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly);
2992 }
2993 LLView::draw(); // Draw children (scrollbar and border)
2994 }
2995}
2996
2997void LLTextEditor::reportBadKeystroke()
2998{
2999 make_ui_sound("UISndBadKeystroke");
3000}
3001
3002
3003void LLTextEditor::onTabInto()
3004{
3005 // selecting all on tabInto causes users to hit tab twice and replace their text with a tab character
3006 // theoretically, one could selectAll if mTabToNextField is true, but we couldn't think of a use case
3007 // where you'd want to select all anyway
3008 // preserve insertion point when returning to the editor
3009 //selectAll();
3010}
3011
3012void LLTextEditor::clear()
3013{
3014 setText("");
3015}
3016
3017// Start or stop the editor from accepting text-editing keystrokes
3018// see also LLLineEditor
3019void LLTextEditor::setFocus( BOOL new_state )
3020{
3021 BOOL old_state = hasFocus();
3022
3023 // Don't change anything if the focus state didn't change
3024 if (new_state == old_state) return;
3025
3026 LLUICtrl::setFocus( new_state );
3027
3028 if( new_state )
3029 {
3030 // Route menu to this class
3031 gEditMenuHandler = this;
3032
3033 // Don't start the cursor flashing right away
3034 mKeystrokeTimer.reset();
3035 }
3036 else
3037 {
3038 // Route menu back to the default
3039 if( gEditMenuHandler == this )
3040 {
3041 gEditMenuHandler = NULL;
3042 }
3043
3044 endSelection();
3045 }
3046}
3047
3048BOOL LLTextEditor::acceptsTextInput() const
3049{
3050 return !mReadOnly;
3051}
3052
3053// Given a line (from the start of the doc) and an offset into the line, find the offset (pos) into text.
3054S32 LLTextEditor::getPos( S32 line, S32 offset )
3055{
3056 S32 line_start = getLineStart(line);
3057 S32 next_start = getLineStart(line+1);
3058 if (next_start == line_start)
3059 {
3060 next_start = getLength() + 1;
3061 }
3062 S32 line_length = next_start - line_start - 1;
3063 line_length = llmax(line_length, 0);
3064 return line_start + llmin( offset, line_length );
3065}
3066
3067
3068void LLTextEditor::changePage( S32 delta )
3069{
3070 S32 line, offset;
3071 getLineAndOffset( mCursorPos, &line, &offset );
3072
3073 // allow one line overlap
3074 S32 page_size = mScrollbar->getPageSize() - 1;
3075 if( delta == -1 )
3076 {
3077 line = llmax( line - page_size, 0);
3078 setCursorPos(getPos( line, offset ));
3079 mScrollbar->setDocPos( mScrollbar->getDocPos() - page_size );
3080 }
3081 else
3082 if( delta == 1 )
3083 {
3084 setCursorPos(getPos( line + page_size, offset ));
3085 mScrollbar->setDocPos( mScrollbar->getDocPos() + page_size );
3086 }
3087 if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
3088 {
3089 mOnScrollEndCallback(mOnScrollEndData);
3090 }
3091}
3092
3093void LLTextEditor::changeLine( S32 delta )
3094{
3095 bindEmbeddedChars( mGLFont );
3096
3097 S32 line, offset;
3098 getLineAndOffset( mCursorPos, &line, &offset );
3099
3100 S32 line_start = getLineStart(line);
3101
3102 S32 desired_x_pixel;
3103
3104 desired_x_pixel = mGLFont->getWidth(mWText.c_str(), line_start, offset, mAllowEmbeddedItems );
3105
3106 S32 new_line = 0;
3107 if( (delta < 0) && (line > 0 ) )
3108 {
3109 new_line = line - 1;
3110 }
3111 else
3112 if( (delta > 0) && (line < (getLineCount() - 1)) )
3113 {
3114 new_line = line + 1;
3115 }
3116 else
3117 {
3118 unbindEmbeddedChars( mGLFont );
3119 return;
3120 }
3121
3122 S32 num_lines = getLineCount();
3123 S32 new_line_start = getLineStart(new_line);
3124 S32 new_line_end = getLength();
3125 if (new_line + 1 < num_lines)
3126 {
3127 new_line_end = getLineStart(new_line + 1) - 1;
3128 }
3129
3130 S32 new_line_len = new_line_end - new_line_start;
3131
3132 S32 new_offset;
3133 new_offset = mGLFont->charFromPixelOffset(mWText.c_str(), new_line_start,
3134 (F32)desired_x_pixel,
3135 (F32)mTextRect.getWidth(),
3136 new_line_len,
3137 mAllowEmbeddedItems);
3138
3139 setCursorPos (getPos( new_line, new_offset ));
3140 unbindEmbeddedChars( mGLFont );
3141}
3142
3143void LLTextEditor::startOfLine()
3144{
3145 S32 line, offset;
3146 getLineAndOffset( mCursorPos, &line, &offset );
3147 setCursorPos(mCursorPos - offset);
3148}
3149
3150
3151// public
3152void LLTextEditor::setCursorAndScrollToEnd()
3153{
3154 deselect();
3155 endOfDoc();
3156 updateScrollFromCursor();
3157}
3158
3159
3160void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap )
3161{
3162 if( include_wordwrap )
3163 {
3164 getLineAndOffset( mCursorPos, line, col );
3165 }
3166 else
3167 {
3168 const LLWString &text = mWText;
3169 S32 line_count = 0;
3170 S32 line_start = 0;
3171 S32 i;
3172 for( i = 0; text[i] && (i < mCursorPos); i++ )
3173 {
3174 if( '\n' == text[i] )
3175 {
3176 line_start = i + 1;
3177 line_count++;
3178 }
3179 }
3180 *line = line_count;
3181 *col = i - line_start;
3182 }
3183}
3184
3185
3186void LLTextEditor::endOfLine()
3187{
3188 S32 line, offset;
3189 getLineAndOffset( mCursorPos, &line, &offset );
3190 S32 num_lines = getLineCount();
3191 if (line + 1 >= num_lines)
3192 {
3193 setCursorPos(getLength());
3194 }
3195 else
3196 {
3197 setCursorPos( getLineStart(line + 1) - 1 );
3198 }
3199}
3200
3201void LLTextEditor::endOfDoc()
3202{
3203 mScrollbar->setDocPos( mScrollbar->getDocPosMax() );
3204 S32 len = getLength();
3205 if( len )
3206 {
3207 setCursorPos(len);
3208 }
3209 if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
3210 {
3211 mOnScrollEndCallback(mOnScrollEndData);
3212 }
3213}
3214
3215// Sets the scrollbar from the cursor position
3216void LLTextEditor::updateScrollFromCursor()
3217{
3218 mScrollbar->setDocSize( getLineCount() );
3219
3220 if (mReadOnly)
3221 {
3222 // no cursor in read only mode
3223 return;
3224 }
3225
3226 S32 line, offset;
3227 getLineAndOffset( mCursorPos, &line, &offset );
3228
3229 S32 page_size = mScrollbar->getPageSize();
3230
3231 if( line < mScrollbar->getDocPos() )
3232 {
3233 // scroll so that the cursor is at the top of the page
3234 mScrollbar->setDocPos( line );
3235 }
3236 else if( line >= mScrollbar->getDocPos() + page_size - 1 )
3237 {
3238 S32 new_pos = 0;
3239 if( line < mScrollbar->getDocSize() - 1 )
3240 {
3241 // scroll so that the cursor is one line above the bottom of the page,
3242 new_pos = line - page_size + 1;
3243 }
3244 else
3245 {
3246 // if there is less than a page of text remaining, scroll so that the cursor is at the bottom
3247 new_pos = mScrollbar->getDocPosMax();
3248 }
3249 mScrollbar->setDocPos( new_pos );
3250 }
3251
3252 // Check if we've scrolled to bottom for callback if asked for callback
3253 if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()))
3254 {
3255 mOnScrollEndCallback(mOnScrollEndData);
3256 }
3257}
3258
3259void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent)
3260{
3261 LLView::reshape( width, height, called_from_parent );
3262
3263 updateTextRect();
3264
3265 S32 line_height = llround( mGLFont->getLineHeight() );
3266 S32 page_lines = mTextRect.getHeight() / line_height;
3267 mScrollbar->setPageSize( page_lines );
3268
3269 updateLineStartList();
3270}
3271
3272void LLTextEditor::autoIndent()
3273{
3274 // Count the number of spaces in the current line
3275 S32 line, offset;
3276 getLineAndOffset( mCursorPos, &line, &offset );
3277 S32 line_start = getLineStart(line);
3278 S32 space_count = 0;
3279 S32 i;
3280
3281 const LLWString &text = mWText;
3282 while( ' ' == text[line_start] )
3283 {
3284 space_count++;
3285 line_start++;
3286 }
3287
3288 // If we're starting a braced section, indent one level.
3289 if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') )
3290 {
3291 space_count += SPACES_PER_TAB;
3292 }
3293
3294 // Insert that number of spaces on the new line
3295 addChar( '\n' );
3296 for( i = 0; i < space_count; i++ )
3297 {
3298 addChar( ' ' );
3299 }
3300}
3301
3302// Inserts new text at the cursor position
3303void LLTextEditor::insertText(const LLString &new_text)
3304{
3305 BOOL enabled = getEnabled();
3306 setEnabled( TRUE );
3307
3308 // Delete any selected characters (the insertion replaces them)
3309 if( hasSelection() )
3310 {
3311 deleteSelection(TRUE);
3312 }
3313
3314 setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE ));
3315
3316 updateLineStartList();
3317 updateScrollFromCursor();
3318
3319 setEnabled( enabled );
3320}
3321
3322
3323void LLTextEditor::appendColoredText(const LLString &new_text,
3324 bool allow_undo,
3325 bool prepend_newline,
3326 const LLColor4 &color,
3327 const LLString& font_name)
3328{
3329 LLStyle style;
3330 style.setVisible(true);
3331 style.setColor(color);
3332 style.setFontName(font_name);
3333 if(mParseHTML)
3334 {
3335
3336 S32 start=0,end=0;
3337 LLString text = new_text;
3338 while ( findHTML(text, &start, &end) )
3339 {
3340 LLStyle html;
3341 html.setVisible(true);
3342 html.setColor(mLinkColor);
3343 html.setFontName(font_name);
3344 html.mUnderline = TRUE;
3345
3346 if (start > 0) appendText(text.substr(0,start),allow_undo, prepend_newline, &style);
3347 html.setLinkHREF(text.substr(start,end-start));
3348 appendText(text.substr(start, end-start),allow_undo, prepend_newline, &html);
3349 if (end < (S32)text.length())
3350 {
3351 text = text.substr(end,text.length() - end);
3352 end=0;
3353 }
3354 else
3355 {
3356 break;
3357 }
3358 }
3359 if (end < (S32)text.length()) appendText(text,allow_undo, prepend_newline, &style);
3360 }
3361 else
3362 {
3363 appendText(new_text, allow_undo, prepend_newline, &style);
3364 }
3365}
3366
3367void LLTextEditor::appendStyledText(const LLString &new_text,
3368 bool allow_undo,
3369 bool prepend_newline,
3370 const LLStyle &style)
3371{
3372 appendText(new_text, allow_undo, prepend_newline, &style);
3373}
3374
3375// Appends new text to end of document
3376void LLTextEditor::appendText(const LLString &new_text, bool allow_undo, bool prepend_newline,
3377 const LLStyle* segment_style)
3378{
3379 // Save old state
3380 BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax());
3381 S32 selection_start = mSelectionStart;
3382 S32 selection_end = mSelectionEnd;
3383 S32 cursor_pos = mCursorPos;
3384 S32 old_length = getLength();
3385 BOOL cursor_was_at_end = (mCursorPos == old_length);
3386
3387 deselect();
3388
3389 setCursorPos(old_length);
3390
3391 // Add carriage return if not first line
3392 if (getLength() != 0
3393 && prepend_newline)
3394 {
3395 LLString final_text = "\n";
3396 final_text += new_text;
3397 append(utf8str_to_wstring(final_text), TRUE);
3398 }
3399 else
3400 {
3401 append(utf8str_to_wstring(new_text), TRUE );
3402 }
3403
3404 if (segment_style)
3405 {
3406 S32 segment_start = old_length;
3407 S32 segment_end = getLength();
3408 LLTextSegment* segment = new LLTextSegment(*segment_style, segment_start, segment_end );
3409 mSegments.push_back(segment);
3410 }
3411
3412 updateLineStartList(old_length);
3413
3414 // Set the cursor and scroll position
3415 // Maintain the scroll position unless the scroll was at the end of the doc
3416 // (in which case, move it to the new end of the doc)
3417 if( was_scrolled_to_bottom )
3418 {
3419 endOfDoc();
3420 }
3421 else if( selection_start != selection_end )
3422 {
3423 mSelectionStart = selection_start;
3424
3425 mSelectionEnd = selection_end;
3426 setCursorPos(cursor_pos);
3427 }
3428 else if( cursor_was_at_end )
3429 {
3430 setCursorPos(getLength());
3431 }
3432 else
3433 {
3434 setCursorPos(cursor_pos);
3435 }
3436
3437 if( !allow_undo )
3438 {
3439 blockUndo();
3440 }
3441}
3442
3443void LLTextEditor::removeTextFromEnd(S32 num_chars)
3444{
3445 if (num_chars <= 0) return;
3446
3447 remove(getLength() - num_chars, num_chars, FALSE);
3448
3449 S32 len = getLength();
3450 mCursorPos = llclamp(mCursorPos, 0, len);
3451 mSelectionStart = llclamp(mSelectionStart, 0, len);
3452 mSelectionEnd = llclamp(mSelectionEnd, 0, len);
3453
3454 pruneSegments();
3455 updateLineStartList();
3456}
3457
3458///////////////////////////////////////////////////////////////////
3459// Returns change in number of characters in mWText
3460
3461S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr)
3462{
3463 S32 len = mWText.length();
3464 S32 s_len = wstr.length();
3465 S32 new_len = len + s_len;
3466 if( new_len > mMaxTextLength )
3467 {
3468 new_len = mMaxTextLength;
3469
3470 // The user's not getting everything he's hoping for
3471 make_ui_sound("UISndBadKeystroke");
3472 }
3473
3474 mWText.insert(pos, wstr);
3475 mTextIsUpToDate = FALSE;
3476 truncate();
3477
3478 return new_len - len;
3479}
3480
3481S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length)
3482{
3483 mWText.erase(pos, length);
3484 mTextIsUpToDate = FALSE;
3485 return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length
3486}
3487
3488S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc)
3489{
3490 if (pos > (S32)mWText.length())
3491 {
3492 return 0;
3493 }
3494 mWText[pos] = wc;
3495 mTextIsUpToDate = FALSE;
3496 return 1;
3497}
3498
3499//----------------------------------------------------------------------------
3500
3501void LLTextEditor::makePristine()
3502{
3503 mPristineCmd = mLastCmd;
3504 mBaseDocIsPristine = !mLastCmd;
3505
3506 // Create a clean partition in the undo stack. We don't want a single command to extend from
3507 // the "pre-pristine" state to the "post-pristine" state.
3508 if( mLastCmd )
3509 {
3510 mLastCmd->blockExtensions();
3511 }
3512}
3513
3514BOOL LLTextEditor::isPristine() const
3515{
3516 if( mPristineCmd )
3517 {
3518 return (mPristineCmd == mLastCmd);
3519 }
3520 else
3521 {
3522 // No undo stack, so check if the version before and commands were done was the original version
3523 return !mLastCmd && mBaseDocIsPristine;
3524 }
3525}
3526
3527BOOL LLTextEditor::tryToRevertToPristineState()
3528{
3529 if( !isPristine() )
3530 {
3531 deselect();
3532 S32 i = 0;
3533 while( !isPristine() && canUndo() )
3534 {
3535 undo();
3536 i--;
3537 }
3538
3539 while( !isPristine() && canRedo() )
3540 {
3541 redo();
3542 i++;
3543 }
3544
3545 if( !isPristine() )
3546 {
3547 // failed, so go back to where we started
3548 while( i > 0 )
3549 {
3550 undo();
3551 i--;
3552 }
3553 }
3554
3555 updateLineStartList();
3556 updateScrollFromCursor();
3557 }
3558
3559 return isPristine(); // TRUE => success
3560}
3561
3562
3563
3564void LLTextEditor::updateTextRect()
3565{
3566 mTextRect.setOriginAndSize(
3567 UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD,
3568 UI_TEXTEDITOR_BORDER,
3569 mRect.getWidth() - SCROLLBAR_SIZE - 2 * (UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD),
3570 mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER - UI_TEXTEDITOR_V_PAD_TOP );
3571}
3572
3573void LLTextEditor::loadKeywords(const LLString& filename,
3574 const LLDynamicArray<const char*>& funcs,
3575 const LLDynamicArray<const char*>& tooltips,
3576 const LLColor3& color)
3577{
3578 if(mKeywords.loadFromFile(filename))
3579 {
3580 S32 count = funcs.count();
3581 LLString name;
3582 for(S32 i = 0; i < count; i++)
3583 {
3584 name = funcs.get(i);
3585 name = utf8str_trim(name);
3586 mKeywords.addToken(LLKeywordToken::WORD, name.c_str(), color, tooltips.get(i) );
3587 }
3588
3589 mKeywords.findSegments( &mSegments, mWText );
3590
3591 llassert( mSegments.front()->getStart() == 0 );
3592 llassert( mSegments.back()->getEnd() == getLength() );
3593 }
3594}
3595
3596void LLTextEditor::updateSegments()
3597{
3598 if (mKeywords.isLoaded())
3599 {
3600 // HACK: No non-ascii keywords for now
3601 mKeywords.findSegments(&mSegments, mWText);
3602 }
3603 else if (mAllowEmbeddedItems)
3604 {
3605 findEmbeddedItemSegments();
3606 }
3607 // Make sure we have at least one segment
3608 if (mSegments.size() == 1 && mSegments[0]->getIsDefault())
3609 {
3610 delete mSegments[0];
3611 mSegments.clear(); // create default segment
3612 }
3613 if (mSegments.empty())
3614 {
3615 LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor );
3616 LLTextSegment* default_segment = new LLTextSegment( text_color, 0, mWText.length() );
3617 default_segment->setIsDefault(TRUE);
3618 mSegments.push_back(default_segment);
3619 }
3620}
3621
3622// Only effective if text was removed from the end of the editor
3623void LLTextEditor::pruneSegments()
3624{
3625 S32 len = mWText.length();
3626 // Find and update the first valid segment
3627 segment_list_t::iterator iter = mSegments.end();
3628 while(iter != mSegments.begin())
3629 {
3630 --iter;
3631 LLTextSegment* seg = *iter;
3632 if (seg->getStart() < len)
3633 {
3634 // valid segment
3635 if (seg->getEnd() > len)
3636 {
3637 seg->setEnd(len);
3638 }
3639 break; // done
3640 }
3641 }
3642 // erase invalid segments
3643 ++iter;
3644 std::for_each(iter, mSegments.end(), DeletePointer());
3645 mSegments.erase(iter, mSegments.end());
3646}
3647
3648void LLTextEditor::findEmbeddedItemSegments()
3649{
3650 mHoverSegment = NULL;
3651 std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
3652 mSegments.clear();
3653
3654 BOOL found_embedded_items = FALSE;
3655 const LLWString &text = mWText;
3656 S32 idx = 0;
3657 while( text[idx] )
3658 {
3659 if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR )
3660 {
3661 found_embedded_items = TRUE;
3662 break;
3663 }
3664 ++idx;
3665 }
3666
3667 if( !found_embedded_items )
3668 {
3669 return;
3670 }
3671
3672 S32 text_len = text.length();
3673
3674 BOOL in_text = FALSE;
3675
3676 LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor );
3677
3678 if( idx > 0 )
3679 {
3680 mSegments.push_back( new LLTextSegment( text_color, 0, text_len ) ); // text
3681 in_text = TRUE;
3682 }
3683
3684 LLStyle embedded_style;
3685 embedded_style.setIsEmbeddedItem( TRUE );
3686
3687 // Start with i just after the first embedded item
3688 while ( text[idx] )
3689 {
3690 if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR )
3691 {
3692 if( in_text )
3693 {
3694 mSegments.back()->setEnd( idx );
3695 }
3696 mSegments.push_back( new LLTextSegment( embedded_style, idx, idx + 1 ) ); // item
3697 in_text = FALSE;
3698 }
3699 else
3700 if( !in_text )
3701 {
3702 mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) ); // text
3703 in_text = TRUE;
3704 }
3705 ++idx;
3706 }
3707}
3708
3709BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask)
3710{
3711 return FALSE;
3712}
3713
3714llwchar LLTextEditor::pasteEmbeddedItem(llwchar ext_char)
3715{
3716 return ext_char;
3717}
3718
3719void LLTextEditor::bindEmbeddedChars(const LLFontGL* font)
3720{
3721}
3722
3723void LLTextEditor::unbindEmbeddedChars(const LLFontGL* font)
3724{
3725}
3726
3727// Finds the text segment (if any) at the give local screen position
3728LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y )
3729{
3730 // Find the cursor position at the requested local screen position
3731 S32 offset = getCursorPosFromLocalCoord( x, y, FALSE );
3732 S32 idx = getSegmentIdxAtOffset(offset);
3733 return idx >= 0 ? mSegments[idx] : NULL;
3734}
3735
3736LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset)
3737{
3738 S32 idx = getSegmentIdxAtOffset(offset);
3739 return idx >= 0 ? mSegments[idx] : NULL;
3740}
3741
3742S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset)
3743{
3744 if (mSegments.empty() || offset < 0 || offset >= getLength())
3745 {
3746 return -1;
3747 }
3748 else
3749 {
3750 S32 segidx, segoff;
3751 getSegmentAndOffset(offset, &segidx, &segoff);
3752 return segidx;
3753 }
3754}
3755
3756//static
3757void LLTextEditor::onMouseCaptureLost( LLMouseHandler* old_captor )
3758{
3759 LLTextEditor* self = (LLTextEditor*) old_captor;
3760 self->endSelection();
3761}
3762
3763void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*), void* userdata)
3764{
3765 mOnScrollEndCallback = callback;
3766 mOnScrollEndData = userdata;
3767 mScrollbar->setOnScrollEndCallback(callback, userdata);
3768}
3769
3770///////////////////////////////////////////////////////////////////
3771// Hack for Notecards
3772
3773BOOL LLTextEditor::importBuffer(const LLString& buffer )
3774{
3775 std::istringstream instream(buffer);
3776
3777 // Version 1 format:
3778 // Linden text version 1\n
3779 // {\n
3780 // <EmbeddedItemList chunk>
3781 // Text length <bytes without \0>\n
3782 // <text without \0> (text may contain ext_char_values)
3783 // }\n
3784
3785 char tbuf[MAX_STRING];
3786
3787 S32 version = 0;
3788 instream.getline(tbuf, MAX_STRING);
3789 if( 1 != sscanf(tbuf, "Linden text version %d", &version) )
3790 {
3791 llwarns << "Invalid Linden text file header " << llendl;
3792 return FALSE;
3793 }
3794
3795 if( 1 != version )
3796 {
3797 llwarns << "Invalid Linden text file version: " << version << llendl;
3798 return FALSE;
3799 }
3800
3801 instream.getline(tbuf, MAX_STRING);
3802 if( 0 != sscanf(tbuf, "{") )
3803 {
3804 llwarns << "Invalid Linden text file format" << llendl;
3805 return FALSE;
3806 }
3807
3808 S32 text_len = 0;
3809 instream.getline(tbuf, MAX_STRING);
3810 if( 1 != sscanf(tbuf, "Text length %d", &text_len) )
3811 {
3812 llwarns << "Invalid Linden text length field" << llendl;
3813 return FALSE;
3814 }
3815
3816 if( text_len > mMaxTextLength )
3817 {
3818 llwarns << "Invalid Linden text length: " << text_len << llendl;
3819 return FALSE;
3820 }
3821
3822 BOOL success = TRUE;
3823
3824 char* text = new char[ text_len + 1];
3825 instream.get(text, text_len + 1, '\0');
3826 text[text_len] = '\0';
3827 if( text_len != (S32)strlen(text) )
3828 {
3829 llwarns << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << llendl;
3830 success = FALSE;
3831 }
3832
3833 instream.getline(tbuf, MAX_STRING);
3834 if( success && (0 != sscanf(tbuf, "}")) )
3835 {
3836 llwarns << "Invalid Linden text file format: missing terminal }" << llendl;
3837 success = FALSE;
3838 }
3839
3840 if( success )
3841 {
3842 // Actually set the text
3843 setText( text );
3844 }
3845
3846 delete[] text;
3847
3848 setCursorPos(0);
3849 deselect();
3850
3851 updateLineStartList();
3852 updateScrollFromCursor();
3853
3854 return success;
3855}
3856
3857BOOL LLTextEditor::exportBuffer(LLString &buffer )
3858{
3859 std::ostringstream outstream(buffer);
3860
3861 outstream << "Linden text version 1\n";
3862 outstream << "{\n";
3863
3864 outstream << llformat("Text length %d\n", mWText.length() );
3865 outstream << getText();
3866 outstream << "}\n";
3867
3868 return TRUE;
3869}
3870
3871//////////////////////////////////////////////////////////////////////////
3872// LLTextSegment
3873
3874LLTextSegment::LLTextSegment(S32 start) : mStart(start)
3875{
3876}
3877LLTextSegment::LLTextSegment( const LLStyle& style, S32 start, S32 end ) :
3878 mStyle( style ),
3879 mStart( start),
3880 mEnd( end ),
3881 mToken(NULL),
3882 mIsDefault(FALSE)
3883{
3884}
3885LLTextSegment::LLTextSegment(
3886 const LLColor4& color, S32 start, S32 end, BOOL is_visible) :
3887 mStyle( is_visible, color,"" ),
3888 mStart( start),
3889 mEnd( end ),
3890 mToken(NULL),
3891 mIsDefault(FALSE)
3892{
3893}
3894LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end ) :
3895 mStyle( TRUE, color,"" ),
3896 mStart( start),
3897 mEnd( end ),
3898 mToken(NULL),
3899 mIsDefault(FALSE)
3900{
3901}
3902LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) :
3903 mStyle( TRUE, color,"" ),
3904 mStart( start),
3905 mEnd( end ),
3906 mToken(NULL),
3907 mIsDefault(FALSE)
3908{
3909}
3910
3911BOOL LLTextSegment::getToolTip(LLString& msg)
3912{
3913 if (mToken && !mToken->getToolTip().empty())
3914 {
3915 const LLWString& wmsg = mToken->getToolTip();
3916 msg = wstring_to_utf8str(wmsg);
3917 return TRUE;
3918 }
3919 return FALSE;
3920}
3921
3922
3923
3924void LLTextSegment::dump()
3925{
3926 llinfos << "Segment [" <<
3927// mColor.mV[VX] << ", " <<
3928// mColor.mV[VY] << ", " <<
3929// mColor.mV[VZ] << "]\t[" <<
3930 mStart << ", " <<
3931 getEnd() << "]" <<
3932 llendl;
3933
3934}
3935
3936// virtual
3937LLXMLNodePtr LLTextEditor::getXML(bool save_children) const
3938{
3939 LLXMLNodePtr node = LLUICtrl::getXML();
3940
3941 // Attributes
3942
3943 node->createChild("max_length", TRUE)->setIntValue(getMaxLength());
3944
3945 node->createChild("embedded_items", TRUE)->setBoolValue(mAllowEmbeddedItems);
3946
3947 node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont));
3948
3949 node->createChild("word_wrap", TRUE)->setBoolValue(mWordWrap);
3950
3951 addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor");
3952 addColorXML(node, mFgColor, "text_color", "TextFgColor");
3953 addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor");
3954 addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor");
3955 addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor");
3956 addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor");
3957
3958 // Contents
3959 node->setStringValue(getText());
3960
3961 return node;
3962}
3963
3964// static
3965LLView* LLTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
3966{
3967 LLString name("text_editor");
3968 node->getAttributeString("name", name);
3969
3970 LLRect rect;
3971 createRect(node, rect, parent, LLRect());
3972
3973 U32 max_text_length = 255;
3974 node->getAttributeU32("max_length", max_text_length);
3975
3976 BOOL allow_embedded_items;
3977 node->getAttributeBOOL("embedded_items", allow_embedded_items);
3978
3979 LLFontGL* font = LLView::selectFont(node);
3980
3981 LLString text = node->getTextContents().substr(0, max_text_length - 1);
3982
3983 LLTextEditor* text_editor = new LLTextEditor(name,
3984 rect,
3985 max_text_length,
3986 text,
3987 font,
3988 allow_embedded_items);
3989
3990 text_editor->setTextEditorParameters(node);
3991
3992 BOOL hide_scrollbar = FALSE;
3993 node->getAttributeBOOL("hide_scrollbar",hide_scrollbar);
3994 text_editor->setHideScrollbarForShortDocs(hide_scrollbar);
3995
3996 text_editor->initFromXML(node, parent);
3997
3998 return text_editor;
3999}
4000
4001void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node)
4002{
4003 BOOL word_wrap = FALSE;
4004 node->getAttributeBOOL("word_wrap", word_wrap);
4005 setWordWrap(word_wrap);
4006
4007 LLColor4 color;
4008 if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color))
4009 {
4010 setCursorColor(color);
4011 }
4012 if(LLUICtrlFactory::getAttributeColor(node,"text_color", color))
4013 {
4014 setFgColor(color);
4015 }
4016 if(LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color))
4017 {
4018 setReadOnlyFgColor(color);
4019 }
4020 if(LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color))
4021 {
4022 setReadOnlyBgColor(color);
4023 }
4024 if(LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color))
4025 {
4026 setWriteableBgColor(color);
4027 }
4028}
4029
4030///////////////////////////////////////////////////////////////////
4031S32 LLTextEditor::findHTMLToken(const LLString &line, S32 pos, BOOL reverse)
4032{
4033 LLString openers=" \t('\"[{<>";
4034 LLString closers=" \t)'\"]}><;";
4035
4036 S32 m2;
4037 S32 retval;
4038
4039 if (reverse)
4040 {
4041
4042 for (retval=pos; retval>0; retval--)
4043 {
4044 m2 = openers.find(line.substr(retval,1));
4045 if (m2 >= 0)
4046 {
4047 retval++;
4048 break;
4049 }
4050 }
4051 }
4052 else
4053 {
4054
4055 for (retval=pos; retval<(S32)line.length(); retval++)
4056 {
4057 m2 = closers.find(line.substr(retval,1));
4058 if (m2 >= 0)
4059 {
4060 break;
4061 }
4062 }
4063 }
4064
4065 return retval;
4066}
4067
4068BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end)
4069{
4070
4071 S32 m1,m2,m3;
4072 BOOL matched = FALSE;
4073
4074 m1=line.find("://",*end);
4075
4076 if (m1 >= 0) //Easy match.
4077 {
4078 *begin = findHTMLToken(line, m1, TRUE);
4079 *end = findHTMLToken(line, m1, FALSE);
4080
4081 //Load_url only handles http and https so don't hilite ftp, smb, etc.
4082 m2 = line.substr(*begin,(m1 - *begin)).find("http");
4083 m3 = line.substr(*begin,(m1 - *begin)).find("secondlife");
4084
4085 LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\";
4086
4087 if (m2 >= 0 || m3>=0)
4088 {
4089 S32 bn = badneighbors.find(line.substr(m1+3,1));
4090
4091 if (bn < 0)
4092 {
4093 matched = TRUE;
4094 }
4095 }
4096 }
4097/* matches things like secondlife.com (no http://) needs a whitelist to really be effective.
4098 else //Harder match.
4099 {
4100 m1 = line.find(".",*end);
4101
4102 if (m1 >= 0)
4103 {
4104 *end = findHTMLToken(line, m1, FALSE);
4105 *begin = findHTMLToken(line, m1, TRUE);
4106
4107 m1 = line.rfind(".",*end);
4108
4109 if ( ( *end - m1 ) > 2 && m1 > *begin)
4110 {
4111 LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`";
4112 m2 = badneighbors.find(line.substr(m1+1,1));
4113 m3 = badneighbors.find(line.substr(m1-1,1));
4114 if (m3<0 && m2<0)
4115 {
4116 matched = TRUE;
4117 }
4118 }
4119 }
4120 }
4121 */
4122
4123 if (matched)
4124 {
4125 S32 strpos, strpos2;
4126
4127 LLString url = line.substr(*begin,*end - *begin);
4128 LLString slurlID = "slurl.com/secondlife/";
4129 strpos = url.find(slurlID);
4130
4131 if (strpos < 0)
4132 {
4133 slurlID="secondlife://";
4134 strpos = url.find(slurlID);
4135 }
4136
4137 if (strpos >= 0)
4138 {
4139 strpos+=slurlID.length();
4140
4141 while ( ( strpos2=url.find("/",strpos) ) == -1 )
4142 {
4143 if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " )
4144 {
4145 matched=FALSE;
4146 break;
4147 }
4148
4149 strpos = (*end + 1) - *begin;
4150
4151 *end = findHTMLToken(line,(*begin + strpos),FALSE);
4152 url = line.substr(*begin,*end - *begin);
4153 }
4154 }
4155
4156 }
4157
4158 if (!matched)
4159 {
4160 *begin=*end=0;
4161 }
4162 return matched;
4163}