PageRenderTime 158ms CodeModel.GetById 12ms app.highlight 131ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/llui/lltextbase.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 2394 lines | 1838 code | 347 blank | 209 comment | 286 complexity | 3afe64d160b1c17f9a4f89e24f848248 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/** 
   2 * @file lltextbase.cpp
   3 * @author Martin Reddy
   4 * @brief The base class of text box/editor, providing Url handling support
   5 *
   6 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
   7 * Second Life Viewer Source Code
   8 * Copyright (C) 2009-2010, Linden Research, Inc.
   9 * 
  10 * This library is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU Lesser General Public
  12 * License as published by the Free Software Foundation;
  13 * version 2.1 of the License only.
  14 * 
  15 * This library is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18 * Lesser General Public License for more details.
  19 * 
  20 * You should have received a copy of the GNU Lesser General Public
  21 * License along with this library; if not, write to the Free Software
  22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  23 * 
  24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  25 * $/LicenseInfo$
  26 */
  27
  28#include "linden_common.h"
  29
  30#include "lltextbase.h"
  31
  32#include "lllocalcliprect.h"
  33#include "llmenugl.h"
  34#include "llscrollcontainer.h"
  35#include "llstl.h"
  36#include "lltextparser.h"
  37#include "lltextutil.h"
  38#include "lltooltip.h"
  39#include "lluictrl.h"
  40#include "llurlaction.h"
  41#include "llurlregistry.h"
  42#include "llview.h"
  43#include "llwindow.h"
  44#include <boost/bind.hpp>
  45
  46const F32	CURSOR_FLASH_DELAY = 1.0f;  // in seconds
  47const S32	CURSOR_THICKNESS = 2;
  48
  49LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) 
  50:	mDocIndexStart(index_start), 
  51	mDocIndexEnd(index_end),
  52	mRect(rect),
  53	mLineNum(line_num)
  54{}
  55
  56bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const
  57{
  58	// sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11)
  59	if (a->getEnd() == b->getEnd())
  60	{
  61		return a->getStart() < b->getStart();
  62	}
  63	else
  64	{
  65		return a->getEnd() < b->getEnd();
  66	}
  67}
  68
  69
  70// helper functors
  71struct LLTextBase::compare_bottom
  72{
  73	bool operator()(const S32& a, const LLTextBase::line_info& b) const
  74	{
  75		return a > b.mRect.mBottom; // bottom of a is higher than bottom of b
  76	}
  77
  78	bool operator()(const LLTextBase::line_info& a, const S32& b) const
  79	{
  80		return a.mRect.mBottom > b; // bottom of a is higher than bottom of b
  81	}
  82
  83	bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
  84	{
  85		return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b
  86	}
  87
  88};
  89
  90// helper functors
  91struct LLTextBase::compare_top
  92{
  93	bool operator()(const S32& a, const LLTextBase::line_info& b) const
  94	{
  95		return a > b.mRect.mTop; // top of a is higher than top of b
  96	}
  97
  98	bool operator()(const LLTextBase::line_info& a, const S32& b) const
  99	{
 100		return a.mRect.mTop > b; // top of a is higher than top of b
 101	}
 102
 103	bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
 104	{
 105		return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b
 106	}
 107};
 108
 109struct LLTextBase::line_end_compare
 110{
 111	bool operator()(const S32& pos, const LLTextBase::line_info& info) const
 112	{
 113		return (pos < info.mDocIndexEnd);
 114	}
 115
 116	bool operator()(const LLTextBase::line_info& info, const S32& pos) const
 117	{
 118		return (info.mDocIndexEnd < pos);
 119	}
 120
 121	bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
 122	{
 123		return (a.mDocIndexEnd < b.mDocIndexEnd);
 124	}
 125
 126};
 127
 128//////////////////////////////////////////////////////////////////////////
 129//
 130// LLTextBase
 131//
 132
 133// register LLTextBase::Params under name "textbase"
 134static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase");
 135
 136LLTextBase::LineSpacingParams::LineSpacingParams()
 137:	multiple("multiple", 1.f),
 138	pixels("pixels", 0)
 139{
 140}
 141
 142
 143LLTextBase::Params::Params()
 144:	cursor_color("cursor_color"),
 145	text_color("text_color"),
 146	text_readonly_color("text_readonly_color"),
 147	bg_visible("bg_visible", false),
 148	border_visible("border_visible", false),
 149	bg_readonly_color("bg_readonly_color"),
 150	bg_writeable_color("bg_writeable_color"),
 151	bg_focus_color("bg_focus_color"),
 152	text_selected_color("text_selected_color"),
 153	bg_selected_color("bg_selected_color"),
 154	allow_scroll("allow_scroll", true),
 155	plain_text("plain_text",false),
 156	track_end("track_end", false),
 157	read_only("read_only", false),
 158	v_pad("v_pad", 0),
 159	h_pad("h_pad", 0),
 160	clip("clip", true),
 161	clip_partial("clip_partial", true),
 162	line_spacing("line_spacing"),
 163	max_text_length("max_length", 255),
 164	font_shadow("font_shadow"),
 165	wrap("wrap"),
 166	use_ellipses("use_ellipses", false),
 167	parse_urls("parse_urls", false),
 168	parse_highlights("parse_highlights", false)
 169{
 170	addSynonym(track_end, "track_bottom");
 171	addSynonym(wrap, "word_wrap");
 172	addSynonym(parse_urls, "allow_html");
 173}
 174
 175
 176LLTextBase::LLTextBase(const LLTextBase::Params &p) 
 177:	LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)),
 178	mURLClickSignal(NULL),
 179	mMaxTextByteLength( p.max_text_length ),
 180	mDefaultFont(p.font),
 181	mFontShadow(p.font_shadow),
 182	mPopupMenu(NULL),
 183	mReadOnly(p.read_only),
 184	mCursorColor(p.cursor_color),
 185	mFgColor(p.text_color),
 186	mBorderVisible( p.border_visible ),
 187	mReadOnlyFgColor(p.text_readonly_color),
 188	mWriteableBgColor(p.bg_writeable_color),
 189	mReadOnlyBgColor(p.bg_readonly_color),
 190	mFocusBgColor(p.bg_focus_color),
 191	mTextSelectedColor(p.text_selected_color),
 192	mSelectedBGColor(p.bg_selected_color),
 193	mReflowIndex(S32_MAX),
 194	mCursorPos( 0 ),
 195	mScrollNeeded(FALSE),
 196	mDesiredXPixel(-1),
 197	mHPad(p.h_pad),
 198	mVPad(p.v_pad),
 199	mHAlign(p.font_halign),
 200	mVAlign(p.font_valign),
 201	mLineSpacingMult(p.line_spacing.multiple),
 202	mLineSpacingPixels(p.line_spacing.pixels),
 203	mClip(p.clip),
 204	mClipPartial(p.clip_partial && !p.allow_scroll),
 205	mTrackEnd( p.track_end ),
 206	mScrollIndex(-1),
 207	mSelectionStart( 0 ),
 208	mSelectionEnd( 0 ),
 209	mIsSelecting( FALSE ),
 210	mPlainText ( p.plain_text ),
 211	mWordWrap(p.wrap),
 212	mUseEllipses( p.use_ellipses ),
 213	mParseHTML(p.parse_urls),
 214	mParseHighlights(p.parse_highlights),
 215	mBGVisible(p.bg_visible),
 216	mScroller(NULL),
 217	mStyleDirty(true)
 218{
 219	if(p.allow_scroll)
 220	{
 221		LLScrollContainer::Params scroll_params;
 222		scroll_params.name = "text scroller";
 223		scroll_params.rect = getLocalRect();
 224		scroll_params.follows.flags = FOLLOWS_ALL;
 225		scroll_params.is_opaque = false;
 226		scroll_params.mouse_opaque = false;
 227		scroll_params.min_auto_scroll_rate = 200;
 228		scroll_params.max_auto_scroll_rate = 800;
 229		scroll_params.border_visible = p.border_visible;
 230		mScroller = LLUICtrlFactory::create<LLScrollContainer>(scroll_params);
 231		addChild(mScroller);
 232	}
 233
 234	LLView::Params view_params;
 235	view_params.name = "text_contents";
 236	view_params.rect =  LLRect(0, 500, 500, 0);
 237	view_params.mouse_opaque = false;
 238
 239	mDocumentView = LLUICtrlFactory::create<LLView>(view_params);
 240	if (mScroller)
 241	{
 242		mScroller->addChild(mDocumentView);
 243	}
 244	else
 245	{
 246		addChild(mDocumentView);
 247	}
 248
 249	createDefaultSegment();
 250
 251	updateRects();
 252}
 253
 254LLTextBase::~LLTextBase()
 255{
 256	mSegments.clear();
 257	delete mURLClickSignal;
 258}
 259
 260void LLTextBase::initFromParams(const LLTextBase::Params& p)
 261{
 262	LLUICtrl::initFromParams(p);
 263	resetDirty();		// Update saved text state
 264	updateSegments();
 265
 266	// HACK: work around enabled == readonly design bug -- RN
 267	// setEnabled will modify our read only status, so do this after
 268	// LLTextBase::initFromParams
 269	if (p.read_only.isProvided())
 270	{
 271		mReadOnly = p.read_only;
 272	}
 273}
 274
 275bool LLTextBase::truncate()
 276{
 277	BOOL did_truncate = FALSE;
 278
 279	// First rough check - if we're less than 1/4th the size, we're OK
 280	if (getLength() >= S32(mMaxTextByteLength / 4))
 281	{	
 282		// Have to check actual byte size
 283		LLWString text(getWText());
 284		S32 utf8_byte_size = wstring_utf8_length(text);
 285		if ( utf8_byte_size > mMaxTextByteLength )
 286		{
 287			// Truncate safely in UTF-8
 288			std::string temp_utf8_text = wstring_to_utf8str(text);
 289			temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength );
 290			LLWString text = utf8str_to_wstring( temp_utf8_text );
 291			// remove extra bit of current string, to preserve formatting, etc.
 292			removeStringNoUndo(text.size(), getWText().size() - text.size());
 293			did_truncate = TRUE;
 294		}
 295	}
 296
 297	return did_truncate;
 298}
 299
 300const LLStyle::Params& LLTextBase::getDefaultStyleParams()
 301{
 302	//FIXME: convert mDefaultStyle to a flyweight http://www.boost.org/doc/libs/1_40_0/libs/flyweight/doc/index.html
 303	//and eliminate color member values
 304	if (mStyleDirty)
 305	{
 306		  mDefaultStyle
 307				  .color(LLUIColor(&mFgColor))						// pass linked color instead of copy of mFGColor
 308				  .readonly_color(LLUIColor(&mReadOnlyFgColor))
 309				  .selected_color(LLUIColor(&mTextSelectedColor))
 310				  .font(mDefaultFont)
 311				  .drop_shadow(mFontShadow);
 312		  mStyleDirty = false;
 313	}
 314	return mDefaultStyle;
 315}
 316
 317void LLTextBase::onValueChange(S32 start, S32 end)
 318{
 319}
 320
 321
 322// Draws the black box behind the selected text
 323void LLTextBase::drawSelectionBackground()
 324{
 325	// Draw selection even if we don't have keyboard focus for search/replace
 326	if( hasSelection() && !mLineInfoList.empty())
 327	{
 328		std::vector<LLRect> selection_rects;
 329
 330		S32 selection_left		= llmin( mSelectionStart, mSelectionEnd );
 331		S32 selection_right		= llmax( mSelectionStart, mSelectionEnd );
 332		LLRect selection_rect = mVisibleTextRect;
 333
 334		// Skip through the lines we aren't drawing.
 335		LLRect content_display_rect = getVisibleDocumentRect();
 336
 337		// binary search for line that starts before top of visible buffer
 338		line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom());
 339		line_list_t::const_iterator end_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top());
 340
 341		bool done = false;
 342
 343		// Find the coordinates of the selected area
 344		for (;line_iter != end_iter && !done; ++line_iter)
 345		{
 346			// is selection visible on this line?
 347			if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right)
 348			{
 349				segment_set_t::iterator segment_iter;
 350				S32 segment_offset;
 351				getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset);
 352				
 353				LLRect selection_rect;
 354				selection_rect.mLeft = line_iter->mRect.mLeft;
 355				selection_rect.mRight = line_iter->mRect.mLeft;
 356				selection_rect.mBottom = line_iter->mRect.mBottom;
 357				selection_rect.mTop = line_iter->mRect.mTop;
 358					
 359				for(;segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0)
 360				{
 361					LLTextSegmentPtr segmentp = *segment_iter;
 362
 363					S32 segment_line_start = segmentp->getStart() + segment_offset;
 364					S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd);
 365
 366					if (segment_line_start > segment_line_end) break;
 367
 368					S32 segment_width = 0;
 369					S32 segment_height = 0;
 370
 371					// if selection after beginning of segment
 372					if(selection_left >= segment_line_start)
 373					{
 374						S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start;
 375						segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
 376						selection_rect.mLeft += segment_width;
 377					}
 378
 379					// if selection_right == segment_line_end then that means we are the first character of the next segment
 380					// or first character of the next line, in either case we want to add the length of the current segment
 381					// to the selection rectangle and continue.
 382					// if selection right > segment_line_end then selection spans end of current segment...
 383					if (selection_right >= segment_line_end)
 384					{
 385						// extend selection slightly beyond end of line
 386						// to indicate selection of newline character (use "n" character to determine width)
 387						S32 num_chars = segment_line_end - segment_line_start;
 388						segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
 389						selection_rect.mRight += segment_width;
 390					}
 391					// else if selection ends on current segment...
 392					else
 393					{
 394						S32 num_chars = selection_right - segment_line_start;
 395						segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
 396						selection_rect.mRight += segment_width;
 397
 398						break;
 399					}
 400				}
 401				selection_rects.push_back(selection_rect);
 402			}
 403		}
 404		
 405		// Draw the selection box (we're using a box instead of reversing the colors on the selected text).
 406		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
 407		const LLColor4& color = mSelectedBGColor;
 408		F32 alpha = hasFocus() ? 0.7f : 0.3f;
 409		alpha *= getDrawContext().mAlpha;
 410		LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha);
 411
 412		for (std::vector<LLRect>::iterator rect_it = selection_rects.begin();
 413			rect_it != selection_rects.end();
 414			++rect_it)
 415		{
 416			LLRect selection_rect = *rect_it;
 417			selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
 418			gl_rect_2d(selection_rect, selection_color);
 419		}
 420	}
 421}
 422
 423void LLTextBase::drawCursor()
 424{
 425	F32 alpha = getDrawContext().mAlpha;
 426
 427	if( hasFocus()
 428		&& gFocusMgr.getAppHasFocus()
 429		&& !mReadOnly)
 430	{
 431		const LLWString &wtext = getWText();
 432		const llwchar* text = wtext.c_str();
 433
 434		LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
 435		cursor_rect.translate(-1, 0);
 436		segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos);
 437
 438		// take style from last segment
 439		LLTextSegmentPtr segmentp;
 440
 441		if (seg_it != mSegments.end())
 442		{
 443			segmentp = *seg_it;
 444		}
 445		else
 446		{
 447			return;
 448		}
 449
 450		// Draw the cursor
 451		// (Flash the cursor every half second starting a fixed time after the last keystroke)
 452		F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32();
 453		if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
 454		{
 455
 456			if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
 457			{
 458				S32 segment_width = 0;
 459				S32 segment_height = 0;
 460				segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height);
 461				S32 width = llmax(CURSOR_THICKNESS, segment_width);
 462				cursor_rect.mRight = cursor_rect.mLeft + width;
 463			}
 464			else
 465			{
 466				cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS;
 467			}
 468			
 469			gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
 470
 471			LLColor4 cursor_color = mCursorColor.get() % alpha;
 472			gGL.color4fv( cursor_color.mV );
 473			
 474			gl_rect_2d(cursor_rect);
 475
 476			if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n')
 477			{
 478				LLColor4 text_color;
 479				const LLFontGL* fontp;
 480				text_color = segmentp->getColor();
 481				fontp = segmentp->getStyle()->getFont();
 482				fontp->render(text, mCursorPos, cursor_rect, 
 483					LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha),
 484					LLFontGL::LEFT, mVAlign,
 485					LLFontGL::NORMAL,
 486					LLFontGL::NO_SHADOW,
 487					1);
 488			}
 489
 490			// Make sure the IME is in the right place
 491			LLRect screen_pos = calcScreenRect();
 492			LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) );
 493
 494			ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]);
 495			ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]);
 496			getWindow()->setLanguageTextInput( ime_pos );
 497		}
 498	}
 499}
 500
 501void LLTextBase::drawText()
 502{
 503	const S32 text_len = getLength();
 504	if( text_len <= 0 )
 505	{
 506		return;
 507	}
 508	S32 selection_left = -1;
 509	S32 selection_right = -1;
 510	// Draw selection even if we don't have keyboard focus for search/replace
 511	if( hasSelection())
 512	{
 513		selection_left = llmin( mSelectionStart, mSelectionEnd );
 514		selection_right = llmax( mSelectionStart, mSelectionEnd );
 515	}
 516
 517	std::pair<S32, S32> line_range = getVisibleLines(mClipPartial);
 518	S32 first_line = line_range.first;
 519	S32 last_line = line_range.second;
 520	if (first_line >= last_line)
 521	{
 522		return;
 523	}
 524	
 525	S32 line_start = getLineStart(first_line);
 526	// find first text segment that spans top of visible portion of text buffer
 527	segment_set_t::iterator seg_iter = getSegIterContaining(line_start);
 528	if (seg_iter == mSegments.end()) 
 529	{
 530		return;
 531	}
 532
 533	LLTextSegmentPtr cur_segment = *seg_iter;
 534
 535	for (S32 cur_line = first_line; cur_line < last_line; cur_line++)
 536	{
 537		S32 next_line = cur_line + 1;
 538		line_info& line = mLineInfoList[cur_line];
 539
 540		S32 next_start = -1;
 541		S32 line_end = text_len;
 542
 543		if (next_line < getLineCount())
 544		{
 545			next_start = getLineStart(next_line);
 546			line_end = next_start;
 547		}
 548
 549		LLRect text_rect(line.mRect);
 550		text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents
 551		text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position
 552
 553		// draw a single line of text
 554		S32 seg_start = line_start;
 555		while( seg_start < line_end )
 556		{
 557			while( cur_segment->getEnd() <= seg_start )
 558			{
 559				seg_iter++;
 560				if (seg_iter == mSegments.end())
 561				{
 562					llwarns << "Ran off the segmentation end!" << llendl;
 563
 564					return;
 565				}
 566				cur_segment = *seg_iter;
 567			}
 568			
 569			S32 clipped_end	=	llmin( line_end, cur_segment->getEnd() )  - cur_segment->getStart();
 570
 571			if (mUseEllipses								// using ellipses
 572				&& clipped_end == line_end					// last segment on line
 573				&& next_line == last_line					// this is the last visible line
 574				&& last_line < (S32)mLineInfoList.size())	// and there is more text to display
 575			{
 576				// more lines of text to go, but we can't fit them
 577				// so shrink text rect to force ellipses
 578				text_rect.mRight -= 2;
 579			}
 580
 581			text_rect.mLeft = (S32)(cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect));
 582
 583			seg_start = clipped_end + cur_segment->getStart();
 584		}
 585
 586		line_start = next_start;
 587	}
 588}
 589
 590///////////////////////////////////////////////////////////////////
 591// Returns change in number of characters in mWText
 592
 593S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments )
 594{
 595	LLWString text(getWText());
 596	S32 old_len = text.length();		// length() returns character length
 597	S32 insert_len = wstr.length();
 598
 599	pos = getEditableIndex(pos, true);
 600
 601	segment_set_t::iterator seg_iter = getEditableSegIterContaining(pos);
 602
 603	LLTextSegmentPtr default_segment;
 604
 605	LLTextSegmentPtr segmentp;
 606	if (seg_iter != mSegments.end())
 607	{
 608		segmentp = *seg_iter;
 609	}
 610	else
 611	{
 612		//segmentp = mSegments.back();
 613		return pos;
 614	}
 615
 616	if (segmentp->canEdit())
 617	{
 618		segmentp->setEnd(segmentp->getEnd() + insert_len);
 619		if (seg_iter != mSegments.end())
 620		{
 621			++seg_iter;
 622		}
 623	}
 624	else
 625	{
 626		// create default editable segment to hold new text
 627		LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
 628		default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this);
 629	}
 630
 631	// shift remaining segments to right
 632	for(;seg_iter != mSegments.end(); ++seg_iter)
 633	{
 634		LLTextSegmentPtr segmentp = *seg_iter;
 635		segmentp->setStart(segmentp->getStart() + insert_len);
 636		segmentp->setEnd(segmentp->getEnd() + insert_len);
 637	}
 638
 639	// insert new segments
 640	if (segments)
 641	{
 642		if (default_segment.notNull())
 643		{
 644			// potentially overwritten by segments passed in
 645			insertSegment(default_segment);
 646		}
 647		for (segment_vec_t::iterator seg_iter = segments->begin();
 648			seg_iter != segments->end();
 649			++seg_iter)
 650		{
 651			LLTextSegment* segmentp = *seg_iter;
 652			insertSegment(segmentp);
 653		}
 654	}
 655
 656	text.insert(pos, wstr);
 657	getViewModel()->setDisplay(text);
 658
 659	if ( truncate() )
 660	{
 661		insert_len = getLength() - old_len;
 662	}
 663
 664	onValueChange(pos, pos + insert_len);
 665	needsReflow(pos);
 666
 667	return insert_len;
 668}
 669
 670S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
 671{
 672	LLWString text(getWText());
 673	segment_set_t::iterator seg_iter = getSegIterContaining(pos);
 674	while(seg_iter != mSegments.end())
 675	{
 676		LLTextSegmentPtr segmentp = *seg_iter;
 677		S32 end = pos + length;
 678		if (segmentp->getStart() < pos)
 679		{
 680			// deleting from middle of segment
 681			if (segmentp->getEnd() > end)
 682			{
 683				segmentp->setEnd(segmentp->getEnd() - length);
 684			}
 685			// truncating segment
 686			else
 687			{
 688				segmentp->setEnd(pos);
 689			}
 690		}
 691		else if (segmentp->getStart() < end)
 692		{
 693			// deleting entire segment
 694			if (segmentp->getEnd() <= end)
 695			{
 696				// remove segment
 697				segmentp->unlinkFromDocument(this);
 698				segment_set_t::iterator seg_to_erase(seg_iter++);
 699				mSegments.erase(seg_to_erase);
 700				continue;
 701			}
 702			// deleting head of segment
 703			else
 704			{
 705				segmentp->setStart(pos);
 706				segmentp->setEnd(segmentp->getEnd() - length);
 707			}
 708		}
 709		else
 710		{
 711			// shifting segments backward to fill deleted portion
 712			segmentp->setStart(segmentp->getStart() - length);
 713			segmentp->setEnd(segmentp->getEnd() - length);
 714		}
 715		++seg_iter;
 716	}
 717
 718	text.erase(pos, length);
 719	getViewModel()->setDisplay(text);
 720
 721	// recreate default segment in case we erased everything
 722	createDefaultSegment();
 723
 724	onValueChange(pos, pos);
 725	needsReflow(pos);
 726
 727	return -length;	// This will be wrong if someone calls removeStringNoUndo with an excessive length
 728}
 729
 730S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc)
 731{
 732	if (pos > (S32)getLength())
 733	{
 734		return 0;
 735	}
 736	LLWString text(getWText());
 737	text[pos] = wc;
 738	getViewModel()->setDisplay(text);
 739
 740	onValueChange(pos, pos + 1);
 741	needsReflow(pos);
 742
 743	return 1;
 744}
 745
 746
 747void LLTextBase::createDefaultSegment()
 748{
 749	// ensures that there is always at least one segment
 750	if (mSegments.empty())
 751	{
 752		LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
 753		LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this);
 754		mSegments.insert(default_segment);
 755		default_segment->linkToDocument(this);
 756	}
 757}
 758
 759void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert)
 760{
 761	if (segment_to_insert.isNull()) 
 762	{
 763		return;
 764	}
 765
 766	segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart());
 767	S32 reflow_start_index = 0;
 768
 769	if (cur_seg_iter == mSegments.end())
 770	{
 771		mSegments.insert(segment_to_insert);
 772		segment_to_insert->linkToDocument(this);
 773		reflow_start_index = segment_to_insert->getStart();
 774	}
 775	else
 776	{
 777		LLTextSegmentPtr cur_segmentp = *cur_seg_iter;
 778		reflow_start_index = cur_segmentp->getStart();
 779		if (cur_segmentp->getStart() < segment_to_insert->getStart())
 780		{
 781			S32 old_segment_end = cur_segmentp->getEnd();
 782			// split old at start point for new segment
 783			cur_segmentp->setEnd(segment_to_insert->getStart());
 784			// advance to next segment
 785			// insert remainder of old segment
 786			LLStyleConstSP sp = cur_segmentp->getStyle();
 787			LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this);
 788			mSegments.insert(cur_seg_iter, remainder_segment);
 789			remainder_segment->linkToDocument(this);
 790			// insert new segment before remainder of old segment
 791			mSegments.insert(cur_seg_iter, segment_to_insert);
 792
 793			segment_to_insert->linkToDocument(this);
 794			// at this point, there will be two overlapping segments owning the text
 795			// associated with the incoming segment
 796		}
 797		else
 798		{
 799			mSegments.insert(cur_seg_iter, segment_to_insert);
 800			segment_to_insert->linkToDocument(this);
 801		}
 802
 803		// now delete/truncate remaining segments as necessary
 804		// cur_seg_iter points to segment before incoming segment
 805		while(cur_seg_iter != mSegments.end())
 806		{
 807			cur_segmentp = *cur_seg_iter;
 808			if (cur_segmentp == segment_to_insert) 
 809			{
 810				++cur_seg_iter;
 811				continue;
 812			}
 813
 814			if (cur_segmentp->getStart() >= segment_to_insert->getStart())
 815			{
 816				if(cur_segmentp->getEnd() <= segment_to_insert->getEnd())
 817				{
 818					cur_segmentp->unlinkFromDocument(this);
 819					// grab copy of iterator to erase, and bump it
 820					segment_set_t::iterator seg_to_erase(cur_seg_iter++);
 821					mSegments.erase(seg_to_erase);
 822					continue;
 823				}
 824				else
 825				{
 826					// last overlapping segment, clip to end of incoming segment
 827					// and stop traversal
 828					cur_segmentp->setStart(segment_to_insert->getEnd());
 829					break;
 830				}
 831			}
 832			++cur_seg_iter;
 833		}
 834	}
 835
 836	// layout potentially changed
 837	needsReflow(reflow_start_index);
 838}
 839
 840BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
 841{
 842	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 843	if (cur_segment && cur_segment->handleMouseDown(x, y, mask))
 844	{
 845		return TRUE;
 846	}
 847
 848	return LLUICtrl::handleMouseDown(x, y, mask);
 849}
 850
 851BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
 852{
 853	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 854	if (cur_segment && cur_segment->handleMouseUp(x, y, mask))
 855	{
 856		// Did we just click on a link?
 857		if (mURLClickSignal
 858			&& cur_segment->getStyle()
 859			&& cur_segment->getStyle()->isLink())
 860		{
 861			// *TODO: send URL here?
 862			(*mURLClickSignal)(this, LLSD() );
 863		}
 864		return TRUE;
 865	}
 866
 867	return LLUICtrl::handleMouseUp(x, y, mask);
 868}
 869
 870BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
 871{
 872	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 873	if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask))
 874	{
 875		return TRUE;
 876	}
 877
 878	return LLUICtrl::handleMiddleMouseDown(x, y, mask);
 879}
 880
 881BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask)
 882{
 883	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 884	if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask))
 885	{
 886		return TRUE;
 887	}
 888
 889	return LLUICtrl::handleMiddleMouseUp(x, y, mask);
 890}
 891
 892BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask)
 893{
 894	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 895	if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask))
 896	{
 897		return TRUE;
 898	}
 899
 900	return LLUICtrl::handleRightMouseDown(x, y, mask);
 901}
 902
 903BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask)
 904{
 905	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 906	if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask))
 907	{
 908		return TRUE;
 909	}
 910
 911	return LLUICtrl::handleRightMouseUp(x, y, mask);
 912}
 913
 914BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
 915{
 916	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 917	if (cur_segment && cur_segment->handleDoubleClick(x, y, mask))
 918	{
 919		return TRUE;
 920	}
 921
 922	return LLUICtrl::handleDoubleClick(x, y, mask);
 923}
 924
 925BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask)
 926{
 927	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 928	if (cur_segment && cur_segment->handleHover(x, y, mask))
 929	{
 930		return TRUE;
 931	}
 932
 933	return LLUICtrl::handleHover(x, y, mask);
 934}
 935
 936BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks)
 937{
 938	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 939	if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks))
 940	{
 941		return TRUE;
 942	}
 943
 944	return LLUICtrl::handleScrollWheel(x, y, clicks);
 945}
 946
 947BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask)
 948{
 949	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 950	if (cur_segment && cur_segment->handleToolTip(x, y, mask))
 951	{
 952		return TRUE;
 953	}
 954
 955	return LLUICtrl::handleToolTip(x, y, mask);
 956}
 957
 958
 959void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent)
 960{
 961	if (width != getRect().getWidth() || height != getRect().getHeight())
 962	{
 963		bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false;
 964
 965		LLUICtrl::reshape( width, height, called_from_parent );
 966
 967		if (mScroller && scrolled_to_bottom && mTrackEnd)
 968		{
 969			// keep bottom of text buffer visible
 970			// do this here as well as in reflow to handle case
 971			// where shrinking from top, which causes buffer to temporarily 
 972			// not be scrolled to the bottom, since the scroll index
 973			// specified the _top_ of the visible document region
 974			mScroller->goToBottom();
 975		}
 976
 977		// do this first after reshape, because other things depend on
 978		// up-to-date mVisibleTextRect
 979		updateRects();
 980		
 981		needsReflow();
 982	}
 983}
 984
 985void LLTextBase::draw()
 986{
 987	// reflow if needed, on demand
 988	reflow();
 989
 990	// then update scroll position, as cursor may have moved
 991	if (!mReadOnly)
 992	{
 993		updateScrollFromCursor();
 994	}
 995
 996	LLRect text_rect;
 997	if (mScroller)
 998	{
 999		mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &text_rect, this);
1000	}
1001	else
1002	{
1003		LLRect visible_lines_rect;
1004		std::pair<S32, S32> line_range = getVisibleLines(mClipPartial);
1005		for (S32 i = line_range.first; i < line_range.second; i++)
1006		{
1007			if (visible_lines_rect.isEmpty())
1008			{
1009				visible_lines_rect = mLineInfoList[i].mRect;
1010			}
1011			else
1012			{
1013				visible_lines_rect.unionWith(mLineInfoList[i].mRect);
1014			}
1015		}
1016		text_rect = visible_lines_rect;
1017		text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom);
1018	}
1019
1020	if (mBGVisible)
1021	{
1022		F32 alpha = getCurrentTransparency();
1023		// clip background rect against extents, if we support scrolling
1024		LLRect bg_rect = mVisibleTextRect;
1025		if (mScroller)
1026		{
1027			bg_rect.intersectWith(text_rect);
1028		}
1029		LLColor4 bg_color = mReadOnly 
1030							? mReadOnlyBgColor.get()
1031							: hasFocus() 
1032								? mFocusBgColor.get() 
1033								: mWriteableBgColor.get();
1034		gl_rect_2d(text_rect, bg_color % alpha, TRUE);
1035	}
1036
1037	bool should_clip = mClip || mScroller != NULL;
1038	{ LLLocalClipRect clip(text_rect, should_clip);
1039 
1040		// draw document view
1041		if (mScroller)
1042		{
1043			drawChild(mScroller);
1044		}
1045		else
1046		{
1047			drawChild(mDocumentView);
1048		}
1049 
1050		drawSelectionBackground();
1051		drawText();
1052		drawCursor();
1053	}
1054 
1055	mDocumentView->setVisible(FALSE);
1056	LLUICtrl::draw();
1057	mDocumentView->setVisible(TRUE);
1058}
1059
1060
1061//virtual
1062void LLTextBase::setColor( const LLColor4& c )
1063{
1064	mFgColor = c;
1065	mStyleDirty = true;
1066}
1067
1068//virtual 
1069void LLTextBase::setReadOnlyColor(const LLColor4 &c)
1070{
1071	mReadOnlyFgColor = c;
1072	mStyleDirty = true;
1073}
1074
1075//virtual
1076void LLTextBase::handleVisibilityChange( BOOL new_visibility )
1077{
1078	if(!new_visibility && mPopupMenu)
1079	{
1080		mPopupMenu->hide();
1081	}
1082	LLUICtrl::handleVisibilityChange(new_visibility);
1083}
1084
1085//virtual
1086void LLTextBase::setValue(const LLSD& value )
1087{
1088	setText(value.asString());
1089}
1090
1091//virtual
1092BOOL LLTextBase::canDeselect() const 
1093{ 
1094	return hasSelection(); 
1095}
1096
1097
1098//virtual
1099void LLTextBase::deselect()
1100{
1101	mSelectionStart = 0;
1102	mSelectionEnd = 0;
1103	mIsSelecting = FALSE;
1104}
1105
1106
1107// Sets the scrollbar from the cursor position
1108void LLTextBase::updateScrollFromCursor()
1109{
1110	// Update scroll position even in read-only mode (when there's no cursor displayed)
1111	// because startOfDoc()/endOfDoc() modify cursor position. See EXT-736.
1112
1113	if (!mScrollNeeded || !mScroller)
1114	{
1115		return;
1116	}
1117	mScrollNeeded = FALSE; 
1118
1119	// scroll so that the cursor is at the top of the page
1120	LLRect scroller_doc_window = getVisibleDocumentRect();
1121	LLRect cursor_rect_doc = getDocRectFromDocIndex(mCursorPos);
1122	mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5));
1123}
1124
1125S32 LLTextBase::getLeftOffset(S32 width)
1126{
1127	switch (mHAlign)
1128	{
1129	case LLFontGL::LEFT:
1130		return mHPad;
1131	case LLFontGL::HCENTER:
1132		return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2);
1133	case LLFontGL::RIGHT:
1134		return mVisibleTextRect.getWidth() - width;
1135	default:
1136		return mHPad;
1137	}
1138}
1139
1140
1141static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow");
1142void LLTextBase::reflow()
1143{
1144	LLFastTimer ft(FTM_TEXT_REFLOW);
1145
1146	updateSegments();
1147
1148	if (mReflowIndex == S32_MAX)
1149	{
1150		return;
1151	}
1152
1153	bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false;
1154
1155	LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
1156	bool follow_selection = getLocalRect().overlaps(cursor_rect); // cursor is (potentially) visible
1157
1158	// store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing
1159	cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop;
1160	cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom;
1161
1162	S32 first_line = getFirstVisibleLine();
1163
1164	// if scroll anchor not on first line, update it to first character of first line
1165	if ((first_line < mLineInfoList.size())
1166		&&	(mScrollIndex <  mLineInfoList[first_line].mDocIndexStart
1167			||	mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd))
1168	{
1169		mScrollIndex = mLineInfoList[first_line].mDocIndexStart;
1170	}
1171	LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex);
1172	// store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing
1173	first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop;
1174	first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom;
1175
1176	S32 reflow_count = 0;
1177	while(mReflowIndex < S32_MAX)
1178	{
1179		// we can get into an infinite loop if the document height does not monotonically increase
1180		// with decreasing width (embedded ui elements with alternate layouts).  In that case, 
1181		// we want to stop reflowing after 2 iterations.  We use 2, since we need to handle the case
1182		// of introducing a vertical scrollbar causing a reflow with less width.  We should also always
1183		// use an even number of iterations to avoid user visible oscillation of the layout
1184		if(++reflow_count > 2)
1185		{
1186			lldebugs << "Breaking out of reflow due to possible infinite loop in " << getName() << llendl;
1187			break;
1188		}
1189	
1190		S32 start_index = mReflowIndex;
1191		mReflowIndex = S32_MAX;
1192
1193		// shrink document to minimum size (visible portion of text widget)
1194		// to force inlined widgets with follows set to shrink
1195		mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight());
1196
1197		S32 cur_top = 0;
1198
1199		segment_set_t::iterator seg_iter = mSegments.begin();
1200		S32 seg_offset = 0;
1201		S32 line_start_index = 0;
1202		const S32 text_available_width = mVisibleTextRect.getWidth() - mHPad;  // reserve room for margin
1203		S32 remaining_pixels = text_available_width;
1204		S32 line_count = 0;
1205
1206		// find and erase line info structs starting at start_index and going to end of document
1207		if (!mLineInfoList.empty())
1208		{
1209			// find first element whose end comes after start_index
1210			line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare());
1211			line_start_index = iter->mDocIndexStart;
1212			line_count = iter->mLineNum;
1213			cur_top = iter->mRect.mTop;
1214			getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset);
1215			mLineInfoList.erase(iter, mLineInfoList.end());
1216		}
1217
1218		S32 line_height = 0;
1219
1220		while(seg_iter != mSegments.end())
1221		{
1222			LLTextSegmentPtr segment = *seg_iter;
1223
1224			// track maximum height of any segment on this line
1225			S32 cur_index = segment->getStart() + seg_offset;
1226
1227			// ask segment how many character fit in remaining space
1228			S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX,
1229														seg_offset, 
1230														cur_index - line_start_index, 
1231														S32_MAX);
1232
1233			S32 segment_width, segment_height;
1234			bool force_newline = segment->getDimensions(seg_offset, character_count, segment_width, segment_height);
1235			// grow line height as necessary based on reported height of this segment
1236			line_height = llmax(line_height, segment_height);
1237			remaining_pixels -= segment_width;
1238
1239			seg_offset += character_count;
1240
1241			S32 last_segment_char_on_line = segment->getStart() + seg_offset;
1242
1243			S32 text_actual_width = text_available_width - remaining_pixels;
1244			S32 text_left = getLeftOffset(text_actual_width);
1245			LLRect line_rect(text_left, 
1246							cur_top, 
1247							text_left + text_actual_width, 
1248							cur_top - line_height);
1249
1250			// if we didn't finish the current segment...
1251			if (last_segment_char_on_line < segment->getEnd())
1252			{
1253				// add line info and keep going
1254				mLineInfoList.push_back(line_info(
1255											line_start_index, 
1256											last_segment_char_on_line, 
1257											line_rect, 
1258											line_count));
1259
1260				line_start_index = segment->getStart() + seg_offset;
1261				cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
1262				remaining_pixels = text_available_width;
1263				line_height = 0;
1264			}
1265			// ...just consumed last segment..
1266			else if (++segment_set_t::iterator(seg_iter) == mSegments.end())
1267			{
1268				mLineInfoList.push_back(line_info(
1269											line_start_index, 
1270											last_segment_char_on_line, 
1271											line_rect, 
1272											line_count));
1273				cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
1274				break;
1275			}
1276			// ...or finished a segment and there are segments remaining on this line
1277			else
1278			{
1279				// subtract pixels used and increment segment
1280				if (force_newline)
1281				{
1282					mLineInfoList.push_back(line_info(
1283												line_start_index, 
1284												last_segment_char_on_line, 
1285												line_rect, 
1286												line_count));
1287					line_start_index = segment->getStart() + seg_offset;
1288					cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
1289					line_height = 0;
1290					remaining_pixels = text_available_width;
1291				}
1292				++seg_iter;
1293				seg_offset = 0;
1294			}
1295			if (force_newline) 
1296			{
1297				line_count++;
1298			}
1299		}
1300
1301		// calculate visible region for diplaying text
1302		updateRects();
1303
1304		for (segment_set_t::iterator segment_it = mSegments.begin();
1305			segment_it != mSegments.end();
1306			++segment_it)
1307		{
1308			LLTextSegmentPtr segmentp = *segment_it;
1309			segmentp->updateLayout(*this);
1310
1311		}
1312	}
1313
1314	// apply scroll constraints after reflowing text
1315	if (!hasMouseCapture() && mScroller)
1316	{
1317		if (scrolled_to_bottom && mTrackEnd)
1318		{
1319			// keep bottom of text buffer visible
1320			endOfDoc();
1321		}
1322		else if (hasSelection() && follow_selection)
1323		{
1324			// keep cursor in same vertical position on screen when selecting text
1325			LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos);
1326			LLRect old_cursor_rect = cursor_rect;
1327			old_cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop;
1328			old_cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom;
1329
1330			mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect);
1331		}
1332		else
1333		{
1334			// keep first line of text visible
1335			LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex);
1336
1337			// pass in desired rect in the coordinate frame of the document viewport
1338			LLRect old_first_char_rect = first_char_rect;
1339			old_first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop;
1340			old_first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom;
1341
1342			mScroller->scrollToShowRect(new_first_char_rect, old_first_char_rect);
1343		}
1344	}
1345
1346	// reset desired x cursor position
1347	updateCursorXPos();
1348}
1349
1350LLRect LLTextBase::getTextBoundingRect()
1351{
1352	reflow();
1353	return mTextBoundingRect;
1354}
1355
1356
1357void LLTextBase::clearSegments()
1358{
1359	mSegments.clear();
1360	createDefaultSegment();
1361}
1362
1363S32 LLTextBase::getLineStart( S32 line ) const
1364{
1365	S32 num_lines = getLineCount();
1366	if (num_lines == 0)
1367	{
1368		return 0;
1369	}
1370
1371	line = llclamp(line, 0, num_lines-1);
1372	return mLineInfoList[line].mDocIndexStart;
1373}
1374
1375S32 LLTextBase::getLineEnd( S32 line ) const
1376{
1377	S32 num_lines = getLineCount();
1378	if (num_lines == 0)
1379	{
1380		return 0;
1381	}
1382
1383	line = llclamp(line, 0, num_lines-1);
1384	return mLineInfoList[line].mDocIndexEnd;
1385}
1386
1387
1388
1389S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const
1390{
1391	if (mLineInfoList.empty())
1392	{
1393		return 0;
1394	}
1395	else
1396	{
1397		line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare());
1398		if (include_wordwrap)
1399		{
1400			return iter - mLineInfoList.begin();
1401		}
1402		else
1403		{
1404			if (iter == mLineInfoList.end())
1405			{
1406				return mLineInfoList.back().mLineNum;
1407			}
1408			else
1409			{
1410				return iter->mLineNum;
1411			}
1412		}
1413	}
1414}
1415
1416// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line.
1417S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const
1418{
1419	if (mLineInfoList.empty())
1420	{
1421		return startpos;
1422	}
1423	else
1424	{
1425		line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare());
1426		return startpos - iter->mDocIndexStart;
1427	}
1428}
1429
1430S32	LLTextBase::getFirstVisibleLine() const
1431{
1432	LLRect visible_region = getVisibleDocumentRect();
1433
1434	// binary search for line that starts before top of visible buffer
1435	line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
1436
1437	return iter - mLineInfoList.begin();
1438}
1439
1440std::pair<S32, S32>	LLTextBase::getVisibleLines(bool require_fully_visible) 
1441{
1442	LLRect visible_region = getVisibleDocumentRect();
1443	line_list_t::const_iterator first_iter;
1444	line_list_t::const_iterator last_iter;
1445
1446	// make sure we have an up-to-date mLineInfoList
1447	reflow();
1448
1449	if (require_fully_visible)
1450	{
1451		first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top());
1452		last_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom());
1453	}
1454	else
1455	{
1456		first_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
1457		last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top());
1458	}
1459	return std::pair<S32, S32>(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin());
1460}
1461
1462
1463
1464LLTextViewModel* LLTextBase::getViewModel() const
1465{
1466	return (LLTextViewModel*)mViewModel.get();
1467}
1468
1469void LLTextBase::addDocumentChild(LLView* view) 
1470{ 
1471	mDocumentView->addChild(view); 
1472}
1473
1474void LLTextBase::removeDocumentChild(LLView* view) 
1475{ 
1476	mDocumentView->removeChild(view); 
1477}
1478
1479
1480static LLFastTimer::DeclareTimer FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments");
1481void LLTextBase::updateSegments()
1482{
1483	LLFastTimer ft(FTM_UPDATE_TEXT_SEGMENTS);
1484	createDefaultSegment();
1485}
1486
1487void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const
1488{
1489	*seg_iter = getSegIterContaining(startpos);
1490	if (*seg_iter == mSegments.end())
1491	{
1492		*offsetp = 0;
1493	}
1494	else
1495	{
1496		*offsetp = startpos - (**seg_iter)->getStart();
1497	}
1498}
1499
1500void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp )
1501{
1502	*seg_iter = getSegIterContaining(startpos);
1503	if (*seg_iter == mSegments.end())
1504	{
1505		*offsetp = 0;
1506	}
1507	else
1508	{
1509		*offsetp = startpos - (**seg_iter)->getStart();
1510	}
1511}
1512
1513LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 index)
1514{
1515	segment_set_t::iterator it = getSegIterContaining(index);
1516	segment_set_t::iterator orig_it = it;
1517
1518	if (it == mSegments.end()) return it;
1519
1520	if (!(*it)->canEdit() 
1521		&& index == (*it)->getStart() 
1522		&& it != mSegments.begin())
1523	{
1524		it--;
1525		if ((*it)->canEdit())
1526		{
1527			return it;
1528		}
1529	}
1530	return orig_it;
1531}
1532
1533LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaining(S32 index) const
1534{
1535	segment_set_t::const_iterator it = getSegIterContaining(index);
1536	segment_set_t::const_iterator orig_it = it;
1537	if (it == mSegments.end()) return it;
1538
1539	if (!(*it)->canEdit() 
1540		&& index == (*it)->getStart() 
1541		&& it != mSegments.begin())
1542	{
1543		it--;
1544		if ((*it)->canEdit())
1545		{
1546			return it;
1547		}
1548	}
1549	return orig_it;
1550}
1551
1552LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index)
1553{
1554
1555	static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
1556
1557	if (index > getLength()) { return mSegments.end(); }
1558
1559	// when there are no segments, we return the end iterator, which must be checked by caller
1560	if (mSegments.size() <= 1) { return mSegments.begin(); }
1561
1562	//FIXME: avoid operator new somehow (without running into refcount problems)
1563	index_segment->setStart(index);
1564	index_segment->setEnd(index);
1565	segment_set_t::iterator it = mSegments.upper_bound(index_segment);
1566	return it;
1567}
1568
1569LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const
1570{
1571	static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
1572
1573	if (index > getLength()) { return mSegments.end(); }
1574
1575	// when there are no segments, we return the end iterator, which must be checked by caller
1576	if (mSegments.size() <= 1) { return mSegments.begin(); }
1577
1578	index_segment->setStart(index);
1579	index_segment->setEnd(index);
1580	LLTextBase::segment_set_t::const_iterator it =  mSegments.upper_bound(index_segment);
1581	return it;
1582}
1583
1584// Finds the text segment (if any) at the give local screen position
1585LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line)
1586{
1587	// Find the cursor position at the requested local screen position
1588	S32 offset = getDocIndexFromLocalCoord( x, y, FALSE, hit_past_end_of_line);
1589	segment_set_t::iterator seg_iter = getSegIterContaining(offset);
1590	if (seg_iter != mSegments.end())
1591	{
1592		return *seg_iter;
1593	}
1594	else
1595	{
1596		return LLTextSegmentPtr();
1597	}
1598}
1599
1600void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
1601{
1602	// work out the XUI menu file to use for this url
1603	LLUrlMatch match;
1604	std::string url = in_url;
1605	if (! LLUrlRegistry::instance().findUrl(url, match))
1606	{
1607		return;
1608	}
1609	
1610	std::string xui_file = match.getMenuName();
1611	if (xui_file.empty())
1612	{
1613		return;
1614	}
1615
1616	// set up the callbacks for all of the potential menu items, N.B. we
1617	// don't use const ref strings in callbacks in case url goes out of scope
1618	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
1619	registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url));
1620	registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url));
1621	registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url));
1622	registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url));
1623	registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url));
1624	registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url));
1625	registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url));
1626	registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url));
1627	registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url));
1628
1629	// create and return the context menu from the XUI file
1630	delete mPopupMenu;
1631	mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer,
1632																		 LLMenuHolderGL::child_registry_t::instance());	
1633	if (mPopupMenu)
1634	{
1635		mPopupMenu->show(x, y);
1636		LLMenuGL::showPopup(this, mPopupMenu, x, y);
1637	}
1638}
1639
1640void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params)
1641{
1642	// clear out the existing text and segments
1643	getViewModel()->setDisplay(LLWStringUtil::null);
1644
1645	clearSegments();
1646//	createDefaultSegment();
1647
1648	deselect();
1649
1650	// append the new text (supports Url linking)
1651	std::string text(utf8str);
1652	LLStringUtil::removeCRLF(text);
1653
1654	// appendText modifies mCursorPos...
1655	appendText(text, false, input_params);
1656	// ...so move cursor to top after appending text
1657	if (!mTrackEnd)
1658	{
1659		startOfDoc();
1660	}
1661
1662	onValueChange(0, getLength());
1663}
1664
1665//virtual
1666std::string LLTextBase::getText() const
1667{
1668	return getViewModel()->getValue().asString();
1669}
1670
1671// IDEVO - icons can be UI image names or UUID sent from
1672// server with avatar display name
1673static LLUIImagePtr image_from_icon_name(const std::string& icon_name)
1674{
1675	if (LLUUID::validate(icon_name))
1676	{
1677		return LLUI::getUIImageByID( LLUUID(icon_name) );
1678	}
1679	else
1680	{
1681		return LLUI::getUIImage(icon_name);
1682	}
1683}
1684
1685void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params)
1686{
1687	LLStyle::Params style_params(input_params);
1688	style_params.fillFrom(getDefaultStyleParams());
1689
1690	S32 part = (S32)LLTextParser::WHOLE;
1691	if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
1692	{
1693		S32 start=0,end=0;
1694		LLUrlMatch match;
1695		std::string text = new_text;
1696		while ( LLUrlRegistry::instance().findUrl(text, match,
1697				boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) )
1698		{
1699			
1700			LLTextUtil::processUrlMatch(&match,this);
1701
1702			start = match.getStart();
1703			end = match.getEnd()+1;
1704
1705			LLStyle::Params link_params(style_params);
1706			link_params.overwriteFrom(match.getStyle());
1707
1708			// output the text before the Url
1709			if (start > 0)
1710			{
1711				if (part == (S32)LLTextParser::WHOLE ||
1712					part == (S32)LLTextParser::START)
1713				{
1714					part = (S32)LLTextParser::START;
1715				}
1716				else
1717				{
1718					part = (S32)LLTextParser::MIDDLE;
1719				}
1720				std::string subtext=text.substr(0,start);
1721				appendAndHighlightText(subtext, part, style_params); 
1722			}
1723			// output the styled Url
1724			appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly());
1725			
1726			// set the tooltip for the Url label
1727			if (! match.getTooltip().empty())
1728			{
1729				segment_set_t::iterator it = getSegIterContaining(getLength()-1);
1730				if (it != mSegments.end())
1731					{
1732						LLTextSegmentPtr segment = *it;
1733						segment->setToolTip(match.getTooltip());
1734					}
1735			}
1736
1737			// move on to the rest of the text after the Url
1738			if (end < (S32)text.length()) 
1739			{
1740				text = text.substr(end,text.length() - end);
1741				end=0;
1742				part=(S32)LLTextParser::END;
1743			}
1744			else
1745			{
1746				break;
1747			}
1748		}
1749		if (part != (S32)LLTextParser::WHOLE) 
1750			part=(S32)LLTextParser::END;
1751		if (end < (S32)text.length()) 
1752			appendAndHighlightText(text, part, style_params);		
1753	}
1754	else
1755	{
1756		appendAndHighlightText(new_text, part, style_params);
1757	}
1758}
1759
1760void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params)
1761{
1762	if (new_text.empty()) 
1763		return;
1764
1765	if(prepend_newline)
1766		appendLineBreakSegment(input_params);
1767	appendTextImpl(new_text,input_params);
1768}
1769
1770void LLTextBase::needsReflow(S32 index)
1771{
1772	lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl;
1773	mReflowIndex = llmin(mReflowIndex, index);
1774}
1775
1776void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params)
1777{
1778	segment_vec_t segments;
1779	LLStyleConstSP sp(new LLStyle(style_params));
1780	segments.push_back(new LLLineBreakTextSegment(sp, getLength()));
1781
1782	insertStringNoUndo(getLength(), utf8str_to_wstring("\n"), &segments);
1783}
1784
1785void LLTextBase::appendImageSegment(const LLStyle::Params& style_params)
1786{
1787	if(getPlainText())
1788	{
1789		return;
1790	}
1791	segment_vec_t segments;
1792	LLStyleConstSP sp(new LLStyle(style_params));
1793	segments.push_back(new LLImageTextSegment(sp, getLength(),*this));
1794
1795	insertStringNoUndo(getLength(), utf8str_to_wstring(" "), &segments);
1796}
1797
1798void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo)
1799{
1800	segment_vec_t segments;
1801	LLWString widget_wide_text = utf8str_to_wstring(text);
1802	segmen

Large files files are truncated, but you can click here to view the full file