Commits

richard_linden committed 32a3302

text boxes are now *not* mouse_opaque by default
fixed some textbox and text editor layout problems (getWidth called with wrong index)
EXT-1302 - rewrite LLExpandableTextBox to use new LLTextBase functionality (using custom LLExpanderSegment)

reviewed by James

Comments (0)

Files changed (12)

indra/llrender/llfontgl.cpp

 	case LEFT:
 		break;
 	case RIGHT:
-	  	cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX));
+	  	cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), begin_offset, length) * sScaleX));
 		break;
 	case HCENTER:
-	    cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2;
+	    cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), begin_offset, length) * sScaleX)) / 2;
 		break;
 	default:
 		break;
 	if (use_ellipses)
 	{
 		// check for too long of a string
-		if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels)
+		if (getWidthF32(wstr.c_str(), begin_offset, max_chars) * sScaleX > scaled_max_pixels)
 		{
 			// use four dots for ellipsis width to generate padding
 			const LLWString dots(utf8str_to_wstring(std::string("....")));

indra/llui/llscrolllistctrl.cpp

 			LLToolTipMgr::instance().show(LLToolTip::Params()
 										.message(hit_cell->getValue().asString())
 										.font(LLFontGL::getFontSansSerifSmall())
-										.pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 4))
+										.pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6))
 										.delay_time(0.2f)
 										.sticky_rect(sticky_rect));		
 		}

indra/llui/lltextbase.cpp

 	mWordWrap(p.wrap),
 	mUseEllipses( p.use_ellipses ),
 	mParseHTML(p.allow_html),
-	mParseHighlights(p.parse_highlights)
+	mParseHighlights(p.parse_highlights),
+	mHideScrollbar(p.hide_scrollbar)
 {
 	LLScrollContainer::Params scroll_params;
 	scroll_params.name = "text scroller";
 			// Truncate safely in UTF-8
 			std::string temp_utf8_text = wstring_to_utf8str(text);
 			temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength );
-			getViewModel()->setDisplay(utf8str_to_wstring( temp_utf8_text ));
+			LLWString text = utf8str_to_wstring( temp_utf8_text );
+			// remove extra bit of current string, to preserve formatting, etc.
+			removeStringNoUndo(text.size(), getWText().size() - text.size());
 			did_truncate = TRUE;
 		}
 	}
 			// split old at start point for new segment
 			cur_segmentp->setEnd(segment_to_insert->getStart());
 			// advance to next segment
-			++cur_seg_iter;
 			// insert remainder of old segment
 			LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( cur_segmentp->getStyle(), segment_to_insert->getStart(), old_segment_end, *this);
-			cur_seg_iter = mSegments.insert(cur_seg_iter, remainder_segment);
+			mSegments.insert(cur_seg_iter, remainder_segment);
 			remainder_segment->linkToDocument(this);
 			// insert new segment before remainder of old segment
-			cur_seg_iter = mSegments.insert(cur_seg_iter, segment_to_insert);
+			mSegments.insert(cur_seg_iter, segment_to_insert);
 
 			segment_to_insert->linkToDocument(this);
-			// move to "remanider" segment and start truncation there
-			++cur_seg_iter;
+			// at this point, there will be two overlapping segments owning the text
+			// associated with the incoming segment
 		}
 		else
 		{
-			cur_seg_iter = mSegments.insert(cur_seg_iter, segment_to_insert);
-			++cur_seg_iter;
+			mSegments.insert(cur_seg_iter, segment_to_insert);
 			segment_to_insert->linkToDocument(this);
 		}
 
 		// now delete/truncate remaining segments as necessary
+		// cur_seg_iter points to segment before incoming segment
 		while(cur_seg_iter != mSegments.end())
 		{
 			cur_segmentp = *cur_seg_iter;
-			if (cur_segmentp->getEnd() <= segment_to_insert->getEnd())
+			if (cur_segmentp == segment_to_insert) 
 			{
-				cur_segmentp->unlinkFromDocument(this);
-				segment_set_t::iterator seg_to_erase(cur_seg_iter++);
-				mSegments.erase(seg_to_erase);
+				++cur_seg_iter;
+				continue;
 			}
-			else
+
+			if (cur_segmentp->getStart() >= segment_to_insert->getStart())
 			{
-				cur_segmentp->setStart(segment_to_insert->getEnd());
-				break;
+				if(cur_segmentp->getEnd() <= segment_to_insert->getEnd())
+				{
+					cur_segmentp->unlinkFromDocument(this);
+					// grab copy of iterator to erase, and bump it
+					segment_set_t::iterator seg_to_erase(cur_seg_iter++);
+					mSegments.erase(seg_to_erase);
+					continue;
+				}
+				else
+				{
+					// last overlapping segment, clip to end of incoming segment
+					// and stop traversal
+					cur_segmentp->setStart(segment_to_insert->getEnd());
+					break;
+				}
 			}
+			++cur_seg_iter;
 		}
 	}
 }
 
 BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleMouseDown(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleMouseUp(x, y, mask))
 	{
 		// Did we just click on a link?
 
 BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleDoubleClick(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleHover(x, y, mask))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks))
 	{
 		return TRUE;
 
 BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask)
 {
-	LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+	LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
 	if (cur_segment && cur_segment->handleToolTip(x, y, mask))
 	{
 		return TRUE;
 
 		S32 cur_top = 0;
 
-		if (getLength())
+		segment_set_t::iterator seg_iter = mSegments.begin();
+		S32 seg_offset = 0;
+		S32 line_start_index = 0;
+		const S32 text_width = mTextRect.getWidth();  // optionally reserve room for margin
+		S32 remaining_pixels = text_width;
+		LLWString text(getWText());
+		S32 line_count = 0;
+
+		// find and erase line info structs starting at start_index and going to end of document
+		if (!mLineInfoList.empty())
 		{
-			segment_set_t::iterator seg_iter = mSegments.begin();
-			S32 seg_offset = 0;
-			S32 line_start_index = 0;
-			const S32 text_width = mTextRect.getWidth();  // optionally reserve room for margin
-			S32 remaining_pixels = text_width;
-			LLWString text(getWText());
-			S32 line_count = 0;
+			// find first element whose end comes after start_index
+			line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare());
+			line_start_index = iter->mDocIndexStart;
+			line_count = iter->mLineNum;
+			getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset);
+			mLineInfoList.erase(iter, mLineInfoList.end());
+		}
 
-			// find and erase line info structs starting at start_index and going to end of document
-			if (!mLineInfoList.empty())
+		S32 line_height = 0;
+
+		while(seg_iter != mSegments.end())
+		{
+			LLTextSegmentPtr segment = *seg_iter;
+
+			// track maximum height of any segment on this line
+			line_height = llmax(line_height, segment->getMaxHeight());
+			S32 cur_index = segment->getStart() + seg_offset;
+			// find run of text from this segment that we can display on one line
+			S32 end_index = cur_index;
+			while(end_index < segment->getEnd() && text[end_index] != '\n')
 			{
-				// find first element whose end comes after start_index
-				line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare());
-				line_start_index = iter->mDocIndexStart;
-				line_count = iter->mLineNum;
-				getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset);
-				mLineInfoList.erase(iter, mLineInfoList.end());
+				++end_index;
 			}
 
-			S32 line_height = 0;
+			// ask segment how many character fit in remaining space
+			S32 max_characters = end_index - cur_index;
+			S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX,
+														seg_offset, 
+														cur_index - line_start_index, 
+														max_characters);
+			
 
-			while(seg_iter != mSegments.end())
+			S32 segment_width = segment->getWidth(seg_offset, character_count);
+			remaining_pixels -= segment_width;
+			S32 text_left = getLeftOffset(text_width - remaining_pixels);
+
+			seg_offset += character_count;
+
+			S32 last_segment_char_on_line = segment->getStart() + seg_offset;
+
+			// if we didn't finish the current segment...
+			if (last_segment_char_on_line < segment->getEnd())
 			{
-				LLTextSegmentPtr segment = *seg_iter;
-
-				// track maximum height of any segment on this line
-				line_height = llmax(line_height, segment->getMaxHeight());
-				S32 cur_index = segment->getStart() + seg_offset;
-				// find run of text from this segment that we can display on one line
-				S32 end_index = cur_index;
-				while(end_index < segment->getEnd() && text[end_index] != '\n')
+				// set up index for next line
+				// ...skip newline, we don't want to draw
+				S32 next_line_count = line_count;
+				if (text[last_segment_char_on_line] == '\n')
 				{
-					++end_index;
+					seg_offset++;
+					last_segment_char_on_line++;
+					next_line_count++;
 				}
 
-				// ask segment how many character fit in remaining space
-				S32 max_characters = end_index - cur_index;
-				S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX,
-															seg_offset, 
-															cur_index - line_start_index, 
-															max_characters);
-				
+				// add line info and keep going
+				mLineInfoList.push_back(line_info(
+											line_start_index, 
+											last_segment_char_on_line, 
+											LLRect(text_left, 
+													cur_top, 
+													text_left + (text_width - remaining_pixels),
+													cur_top - line_height), 
+											line_count));
 
-				S32 segment_width = segment->getWidth(seg_offset, character_count);
-				remaining_pixels -= segment_width;
-				S32 text_left = getLeftOffset(text_width - remaining_pixels);
-
-				seg_offset += character_count;
-
-				S32 last_segment_char_on_line = segment->getStart() + seg_offset;
-
-				// if we didn't finish the current segment...
-				if (last_segment_char_on_line < segment->getEnd())
-				{
-					// set up index for next line
-					// ...skip newline, we don't want to draw
-					S32 next_line_count = line_count;
-					if (text[last_segment_char_on_line] == '\n')
-					{
-						seg_offset++;
-						last_segment_char_on_line++;
-						next_line_count++;
-					}
-
-					// add line info and keep going
-					mLineInfoList.push_back(line_info(
-												line_start_index, 
-												last_segment_char_on_line, 
-												LLRect(text_left, 
-														cur_top, 
-														text_left + (text_width - remaining_pixels),
-														cur_top - line_height), 
-												line_count));
-
-					line_start_index = segment->getStart() + seg_offset;
-					cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
-					remaining_pixels = text_width;
-					line_height = 0;
-					line_count = next_line_count;
-				}
-				// ...just consumed last segment..
-				else if (++segment_set_t::iterator(seg_iter) == mSegments.end())
-				{
-					mLineInfoList.push_back(line_info(
-												line_start_index, 
-												last_segment_char_on_line, 
-												LLRect(text_left, 
-														cur_top, 
-														text_left + (text_width - remaining_pixels),
-														cur_top - line_height), 
-												line_count));
-					cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
-					break;
-				}
-				// finished a segment and there are segments remaining on this line
-				else
-				{
-					// subtract pixels used and increment segment
-					++seg_iter;
-					seg_offset = 0;
-				}
+				line_start_index = segment->getStart() + seg_offset;
+				cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
+				remaining_pixels = text_width;
+				line_height = 0;
+				line_count = next_line_count;
+			}
+			// ...just consumed last segment..
+			else if (++segment_set_t::iterator(seg_iter) == mSegments.end())
+			{
+				mLineInfoList.push_back(line_info(
+											line_start_index, 
+											last_segment_char_on_line, 
+											LLRect(text_left, 
+													cur_top, 
+													text_left + (text_width - remaining_pixels),
+													cur_top - line_height), 
+											line_count));
+				cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
+				break;
+			}
+			// finished a segment and there are segments remaining on this line
+			else
+			{
+				// subtract pixels used and increment segment
+				++seg_iter;
+				seg_offset = 0;
 			}
 		}
 
 	return mLineInfoList[line].mDocIndexStart;
 }
 
+S32 LLTextBase::getLineEnd( S32 line ) const
+{
+	S32 num_lines = getLineCount();
+	if (num_lines == 0)
+    {
+		return 0;
+    }
+
+	line = llclamp(line, 0, num_lines-1);
+	return mLineInfoList[line].mDocIndexEnd;
+}
+
+
 
 S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const
 {
 	return iter - mLineInfoList.begin();
 }
 
-std::pair<S32, S32>	LLTextBase::getVisibleLines() const
+std::pair<S32, S32>	LLTextBase::getVisibleLines(bool fully_visible) 
 {
 	LLRect visible_region = mScroller->getVisibleContentRect();
+	line_list_t::const_iterator first_iter;
+	line_list_t::const_iterator last_iter;
 
-	// binary search for line that starts before top of visible buffer and starts before end of visible buffer
-	line_list_t::const_iterator first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
-	line_list_t::const_iterator last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top());
+	// make sure we have an up-to-date mLineInfoList
+	reflow();
 
+	if (fully_visible)
+	{
+		// binary search for line that starts before top of visible buffer and starts before end of visible buffer
+		first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top());
+		last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom());
+	}
+	else
+	{
+		// binary search for line that starts before top of visible buffer and starts before end of visible buffer
+		first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
+		last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top());
+	}
 	return std::pair<S32, S32>(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin());
 }
 
 		S32 start = seg_start;
 		S32 end = llmin( selection_start, seg_end );
 		S32 length =  end - start;
-		font->render(text, start, rect.mLeft, rect.mTop, color, LLFontGL::LEFT, LLFontGL::TOP, 0, mStyle->getShadowType(), length, rect.getWidth(), &right_x, mEditor.getUseEllipses());
+		font->render(text, start, 
+					rect.mLeft, rect.mTop, 
+					color, 
+					LLFontGL::LEFT, LLFontGL::TOP, 
+					0, 
+					mStyle->getShadowType(), 
+					length, rect.getWidth(), 
+					&right_x, 
+					mEditor.getUseEllipses());
 	}
 	rect.mLeft = (S32)ceil(right_x);
 	
 		S32 end = llmin( selection_end, seg_end );
 		S32 length = end - start;
 
-		font->render(text, start, rect.mLeft, rect.mTop,
-					 LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
-					 LLFontGL::LEFT, LLFontGL::TOP, 0, LLFontGL::NO_SHADOW, length, rect.mRight, &right_x, mEditor.getUseEllipses());
+		font->render(text, start, 
+					rect.mLeft, rect.mTop,
+					LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
+					LLFontGL::LEFT, LLFontGL::TOP, 
+					0, 
+					LLFontGL::NO_SHADOW, 
+					length, rect.mRight, 
+					&right_x, 
+					mEditor.getUseEllipses());
 	}
 	rect.mLeft = (S32)ceil(right_x);
 	if( selection_end < seg_end )
 		S32 start = llmax( selection_end, seg_start );
 		S32 end = seg_end;
 		S32 length = end - start;
-		font->render(text, start, rect.mLeft, rect.mTop, color, LLFontGL::LEFT, LLFontGL::TOP, 0, mStyle->getShadowType(), length, rect.mRight, &right_x, mEditor.getUseEllipses());
+		font->render(text, start, 
+					rect.mLeft, rect.mTop, 
+					color, 
+					LLFontGL::LEFT, LLFontGL::TOP, 
+					0, 
+					mStyle->getShadowType(), 
+					length, rect.mRight, 
+					&right_x, 
+					mEditor.getUseEllipses());
 	}
 	return right_x;
 }
 	S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, 
 												(F32)num_pixels,
 												max_chars, 
-												mEditor.getWordWrap());
+												TRUE);
 
 	if (num_chars == 0 
 		&& line_offset == 0 

indra/llui/lltextbase.h

 	
 	//  manage lines
 	S32								getLineStart( S32 line ) const;
+	S32								getLineEnd( S32 line ) const;
 	S32								getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap = true) const;
 	S32								getLineOffsetFromDocIndex( S32 doc_index, bool include_wordwrap = true) const;
 	S32								getFirstVisibleLine() const;
-	std::pair<S32, S32>				getVisibleLines() const;
+	std::pair<S32, S32>				getVisibleLines(bool fully_visible = false);
 	S32								getLeftOffset(S32 width);
 	void							reflow(S32 start_index = 0);
 
 	bool						mTrackEnd;			// if true, keeps scroll position at end of document during resize
 	bool						mReadOnly;
 	bool						mClip;
+	bool						mHideScrollbar;
 	S32							mMaxTextByteLength;	// Maximum length mText is allowed to be in bytes
 
 	// support widgets

indra/llui/lltextbox.cpp

 
 BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask)
 {
-	BOOL	handled = FALSE;
+	BOOL	handled = LLTextBase::handleMouseDown(x, y, mask);
 
-	// HACK: Only do this if there actually is something to click, so that
-	// overly large text boxes in the older UI won't start eating clicks.
-	if (isClickable())
+	if (getSoundFlags() & MOUSE_DOWN)
 	{
-		handled = TRUE;
+		make_ui_sound("UISndClick");
+	}
 
+	if (!handled && mClickedCallback)
+	{
 		// Route future Mouse messages here preemptively.  (Release on mouse up.)
 		gFocusMgr.setMouseCapture( this );
-		
-		if (getSoundFlags() & MOUSE_DOWN)
-		{
-			make_ui_sound("UISndClick");
-		}
-	}
 
-	if (!handled)
-	{
-		handled = LLTextBase::handleMouseDown(x, y, mask);
+		handled = TRUE;
 	}
 
 	return handled;
 {
 	BOOL	handled = FALSE;
 
+	if (getSoundFlags() & MOUSE_UP)
+	{
+		make_ui_sound("UISndClickRelease");
+	}
+
 	// We only handle the click if the click both started and ended within us
-
-	// HACK: Only do this if there actually is something to click, so that
-	// overly large text boxes in the older UI won't start eating clicks.
-	if (isClickable() && hasMouseCapture())
+	if (hasMouseCapture())
 	{
-		handled = TRUE;
-
 		// Release the mouse
 		gFocusMgr.setMouseCapture( NULL );
 
-		if (getSoundFlags() & MOUSE_UP)
-		{
-			make_ui_sound("UISndClickRelease");
-		}
-
-		// handle clicks on Urls in the textbox first
-		handled = LLTextBase::handleMouseUp(x, y, mask);
-
-		// DO THIS AT THE VERY END to allow the button to be destroyed
+		// DO THIS AT THE VERY END to allow the button  to be destroyed
 		// as a result of being clicked.  If mouseup in the widget,
 		// it's been clicked
 		if (mClickedCallback && !handled)
 		{
 			mClickedCallback();
+			handled = TRUE;
 		}
 	}
+	else
+	{
+		handled = LLTextBase::handleMouseUp(x, y, mask);
+	}
 
 	return handled;
 }
 	needsReflow();
 }
 
-bool LLTextBox::isClickable() const
-{
-	// return true if we have been given a click callback
-	if (mClickedCallback)
-	{
-		return true;
-	}
-
-	// also return true if we have a clickable Url in the text
-	segment_set_t::const_iterator it;
-	for (it = mSegments.begin(); it != mSegments.end(); ++it)
-	{
-		LLTextSegmentPtr segmentp = *it;
-		if (segmentp)
-		{
-			const LLStyleSP style = segmentp->getStyle();
-			if (style && style->isLink())
-			{
-				return true;
-			}
-		}
-	}
-
-	// otherwise there is nothing clickable here
-	return false;
-}
-

indra/llui/lltextbox.h

 
 protected:
 	void            onUrlLabelUpdated(const std::string &url, const std::string &label);
-	bool            isClickable() const;
 
 	LLUIString			mText;
 	callback_t			mClickedCallback;

indra/newview/llexpandabletextbox.cpp

 #include "llexpandabletextbox.h"
 
 #include "llscrollcontainer.h"
+#include "llwindow.h"
 
 static LLDefaultChildRegistry::Register<LLExpandableTextBox> t1("expandable_text");
 
-LLExpandableTextBox::LLTextBoxEx::Params::Params()
-: expand_textbox("expand_textbox")
+class LLExpanderSegment : public LLTextSegment
 {
-}
+public:
+	LLExpanderSegment(const LLStyleSP& style, S32 start, S32 end, const std::string& more_text, LLTextBase& editor )
+	:	LLTextSegment(start, end),
+		mEditor(editor),
+		mStyle(style),
+		mMoreText(more_text)
+	{}
+
+	/*virtual*/ S32		getWidth(S32 first_char, S32 num_chars) const 
+	{
+		// more label always spans width of text box
+		return mEditor.getTextRect().getWidth(); 
+	}
+	/*virtual*/ S32		getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const 
+	{ 
+		return start_offset;
+	}
+	/*virtual*/ S32		getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return getEnd() - getStart(); }
+	/*virtual*/ F32		draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
+	{
+		F32 right_x;
+		mStyle->getFont()->renderUTF8(mMoreText, start, draw_rect.mRight, draw_rect.mTop, mStyle->getColor(), LLFontGL::RIGHT, LLFontGL::TOP, 0, mStyle->getShadowType(), end - start, draw_rect.getWidth(), &right_x, mEditor.getUseEllipses());
+		return right_x;
+	}
+	/*virtual*/ S32		getMaxHeight() const { return llceil(mStyle->getFont()->getLineHeight()); }
+	/*virtual*/ bool	canEdit() const { return false; }
+	/*virtual*/ BOOL	handleMouseUp(S32 x, S32 y, MASK mask) { mEditor.onCommit(); return TRUE; }
+	/*virtual*/ BOOL	handleHover(S32 x, S32 y, MASK mask) 
+	{
+		LLUI::getWindow()->setCursor(UI_CURSOR_HAND);
+		return TRUE; 
+	}
+private:
+	LLTextBase& mEditor;
+	LLStyleSP	mStyle;
+	std::string	mMoreText;
+};
+
+
 
 LLExpandableTextBox::LLTextBoxEx::LLTextBoxEx(const Params& p)
-: LLTextBox(p)
+:	LLTextBox(p),
+	mExpanded(false)
 {
 	setIsChrome(TRUE);
 
-	LLTextBox::Params params = p.expand_textbox;
-	mExpandTextBox = LLUICtrlFactory::create<LLTextBox>(params);
-	addChild(mExpandTextBox);
-
-	LLRect rc = getLocalRect();
-	rc.mRight -= getHPad();
-	rc.mLeft = rc.mRight - mExpandTextBox->getTextPixelWidth();
-	rc.mTop = mExpandTextBox->getTextPixelHeight();
-	mExpandTextBox->setRect(rc);
 }
 
-BOOL LLExpandableTextBox::LLTextBoxEx::handleMouseUp(S32 x, S32 y, MASK mask)
+void LLExpandableTextBox::LLTextBoxEx::reshape(S32 width, S32 height, BOOL called_from_parent)
 {
-	BOOL ret = LLTextBox::handleMouseUp(x, y, mask);
+	LLTextBox::reshape(width, height, called_from_parent);
 
-	if(mExpandTextBox->getRect().pointInRect(x, y))
+	if (getTextPixelHeight() > getRect().getHeight())
 	{
-		onCommit();
-	}
-
-	return ret;
-}
-
-void LLExpandableTextBox::LLTextBoxEx::draw()
-{
-	// draw text box
-	LLTextBox::draw();
-	// force text box to draw children
-	LLUICtrl::draw();
-}
-
-/* LLTextBox has been rewritten, the variables referenced in this code
-no longer exist.
-
-void LLExpandableTextBox::LLTextBoxEx::drawText( S32 x, S32 y, const LLWString &text, const LLColor4& color )
-{
-	// *NOTE:dzaporozhan:
-	// Copy/paste from LLTextBox::drawText in order to modify last 
-	// line width if needed and who "More" link
-	F32 alpha = getDrawContext().mAlpha;
-	if (mSegments.size() > 1)
-	{
-		// we have Urls (or other multi-styled segments)
-		drawTextSegments(x, y, text);
-	}
-	else if( mLineLengthList.empty() )
-	{
-		// simple case of 1 line of text in one style
-		mDefaultFont->render(text, 0, (F32)x, (F32)y, color % alpha,
-			mHAlign, mVAlign, 
-			0,
-			mShadowType,
-			S32_MAX, getRect().getWidth(), NULL, mUseEllipses);
-
-		mExpandTextBox->setVisible(FALSE);
+		showExpandText();
 	}
 	else
 	{
-		// simple case of multiple lines of text, all in the same style
-		S32 cur_pos = 0;
-		for (std::vector<S32>::iterator iter = mLineLengthList.begin();
-			iter != mLineLengthList.end(); ++iter)
-		{
-			S32 line_length = *iter;
-			S32 line_height = llfloor(mDefaultFont->getLineHeight()) + mLineSpacing;
-			S32 max_pixels = getRect().getWidth();
-
-			if(iter + 1 != mLineLengthList.end() 
-				&& y - line_height < line_height)
-			{
-				max_pixels = getCropTextWidth();
-			}
-
-			mDefaultFont->render(text, cur_pos, (F32)x, (F32)y, color % alpha,
-				mHAlign, mVAlign,
-				0,
-				mShadowType,
-				line_length, max_pixels, NULL, mUseEllipses );
-
-			cur_pos += line_length + 1;
-
-			y -= line_height;
-			if(y < line_height)
-			{
-				if( mLineLengthList.end() != iter + 1 )
-				{
-					showExpandText(y);
-				}
-				else
-				{
-					hideExpandText();
-				}
-				break;
-			}
-		}
+		hideExpandText();
 	}
 }
-*/
 
-void LLExpandableTextBox::LLTextBoxEx::showExpandText(S32 y)
+void LLExpandableTextBox::LLTextBoxEx::setValue(const LLSD& value)
 {
-	LLRect rc = mExpandTextBox->getRect();
-	rc.mTop = y + mExpandTextBox->getTextPixelHeight();
-	rc.mBottom = y;
-	mExpandTextBox->setRect(rc);
-	mExpandTextBox->setVisible(TRUE);
+	LLTextBox::setValue(value);
+
+	if (getTextPixelHeight() > getRect().getHeight())
+	{
+		showExpandText();
+	}
+	else
+	{
+		hideExpandText();
+	}
 }
 
+
+void LLExpandableTextBox::LLTextBoxEx::showExpandText()
+{
+	if (!mExpanded)
+	{
+		// get fully visible lines
+		std::pair<S32, S32> visible_lines = getVisibleLines(true);
+		S32 last_line = visible_lines.second - 1;
+
+		LLStyle::Params expander_style = getDefaultStyle();
+		expander_style.font.name.setIfNotProvided(LLFontGL::nameFromFont(expander_style.font));
+		expander_style.font.style = "UNDERLINE";
+		expander_style.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
+		LLExpanderSegment* expanderp = new LLExpanderSegment(new LLStyle(expander_style), getLineStart(last_line), getLength() + 1, "More", *this);
+		insertSegment(expanderp);
+		mExpanded = true;
+	}
+
+}
+
+//NOTE: obliterates existing styles (including hyperlinks)
 void LLExpandableTextBox::LLTextBoxEx::hideExpandText() 
 { 
-	mExpandTextBox->setVisible(FALSE); 
-}
-
-S32 LLExpandableTextBox::LLTextBoxEx::getCropTextWidth() 
-{ 
-	return mExpandTextBox->getRect().mLeft - getHPad() * 2; 
-}
-
-/*
-// *NOTE:James:
-// LLTextBox::drawText() has been completely rewritten, as it now handles
-// arbitrarily styled segments of text.  This needs to be rebuilt.
-
-void LLExpandableTextBox::LLTextBoxEx::drawTextSegments(S32 init_x, S32 init_y, const LLWString &text)
-{
-
-	// *NOTE:dzaporozhan:
-	// Copy/paste from LLTextBox::drawTextSegments in order to modify last 
-	// line width if needed and who "More" link
-	F32 alpha = getDrawContext().mAlpha;
-
-	const S32 text_len = text.length();
-	if (text_len <= 0)
+	if (mExpanded)
 	{
-		return;
-	}
-
-	S32 cur_line = 0;
-	S32 num_lines = getLineCount();
-	S32 line_start = getLineStart(cur_line);
-	S32 line_height = llround( mDefaultFont->getLineHeight() ) + mLineSpacing;
-	F32 text_y = (F32) init_y;
-	segment_set_t::iterator cur_seg = mSegments.begin();
-
-	// render a line of text at a time
-	const LLRect textRect = getLocalRect();
-	while((textRect.mBottom <= text_y) && (cur_line < num_lines))
-	{
-		S32 next_start = -1;
-		S32 line_end = text_len;
-
-		if ((cur_line + 1) < num_lines)
-		{
-			next_start = getLineStart(cur_line + 1);
-			line_end = next_start;
-		}
-		if ( text[line_end-1] == '\n' )
-		{
-			--line_end;
-		}
-
-		// render all segments on this line
-		F32 text_x = init_x;
-		S32 seg_start = line_start;
-		while (seg_start < line_end && cur_seg != mSegments.end())
-		{
-			// move to the next segment (or continue the previous one)
-			LLTextSegment *cur_segment = *cur_seg;
-			while (cur_segment->getEnd() <= seg_start)
-			{
-				if (++cur_seg == mSegments.end())
-				{
-					return;
-				}
-				cur_segment = *cur_seg;
-			}
-
-			// Draw a segment within the line
-			S32 clipped_end	= llmin( line_end, cur_segment->getEnd() );
-			S32 clipped_len = clipped_end - seg_start;
-			if( clipped_len > 0 )
-			{
-				LLStyleSP style = cur_segment->getStyle();
-				if (style && style->isVisible())
-				{
-					// work out the color for the segment
-					LLColor4 color ;
-					if (getEnabled())
-					{
-						color = style->isLink() ? mLinkColor.get() : mTextColor.get();
-					}
-					else
-					{
-						color = mDisabledColor.get();
-					}
-					color = color % alpha;
-
-					S32 max_pixels = textRect.getWidth();
-
-					if(cur_line + 1 < num_lines
-						&& text_y - line_height < line_height)
-					{
-						max_pixels = getCropTextWidth();
-					}
-
-					// render a single line worth for this segment
-					mDefaultFont->render(text, seg_start, text_x, text_y, color,
-						mHAlign, mVAlign, 0, mShadowType, clipped_len,
-						max_pixels, &text_x, mUseEllipses);
-				}
-
-				seg_start += clipped_len;
-			}
-		}
-
-		// move down one line
-		text_y -= (F32)line_height;
-		line_start = next_start;
-		cur_line++;
-		if(text_y < line_height)
-		{
-			if( cur_line < num_lines )
-			{
-				showExpandText((S32)text_y);
-			}
-			else
-			{
-				hideExpandText();
-			}
-			break;
-		}
+		// this will overwrite the expander segment and all text styling with a single style
+		LLNormalTextSegment* segmentp = new LLNormalTextSegment(
+											new LLStyle(getDefaultStyle()), 0, getLength() + 1, *this);
+		insertSegment(segmentp);
+		
+		mExpanded = false;
 	}
 }
-*/
 
 S32 LLExpandableTextBox::LLTextBoxEx::getVerticalTextDelta()
 {
 //////////////////////////////////////////////////////////////////////////
 
 LLExpandableTextBox::Params::Params()
-: textbox("textbox")
-, scroll("scroll")
-, max_height("max_height", 0)
-, bg_visible("bg_visible", false)
-, expanded_bg_visible("expanded_bg_visible", true)
-, bg_color("bg_color", LLColor4::black)
-, expanded_bg_color("expanded_bg_color", LLColor4::black)
+:	textbox("textbox"),
+	scroll("scroll"),
+	max_height("max_height", 0),
+	bg_visible("bg_visible", false),
+	expanded_bg_visible("expanded_bg_visible", true),
+	bg_color("bg_color", LLColor4::black),
+	expanded_bg_color("expanded_bg_color", LLColor4::black)
 {
 }
 
 LLExpandableTextBox::LLExpandableTextBox(const Params& p)
-: LLUICtrl(p)
-, mMaxHeight(p.max_height)
-, mBGVisible(p.bg_visible)
-, mExpandedBGVisible(p.expanded_bg_visible)
-, mBGColor(p.bg_color)
-, mExpandedBGColor(p.expanded_bg_color)
-, mExpanded(false)
+:	LLUICtrl(p),
+	mMaxHeight(p.max_height),
+	mBGVisible(p.bg_visible),
+	mExpandedBGVisible(p.expanded_bg_visible),
+	mBGColor(p.bg_color),
+	mExpandedBGColor(p.expanded_bg_color),
+	mExpanded(false)
 {
 	LLRect rc = getLocalRect();
 
 
 	updateTextBoxRect();
 
-	// Should be handled automatically in reshape above. JC
-	//mTextBox->setWrappedText(mText);
 	if(gFocusMgr.getTopCtrl() == this)
 	{
 		gFocusMgr.setTopCtrl(NULL);

indra/newview/llexpandabletextbox.h

 	public:
 		struct Params :	public LLInitParam::Block<Params, LLTextBox::Params>
 		{
-			Optional<LLTextBox::Params> expand_textbox;
-
-			Params();
 		};
 
-		/**
-		 * Draw text box and "More" link
-		 */
-		/*virtual*/ void draw();
-
-//		/**
-//		 * Draws simple text(no urls) line by line, will show or hide "More" link
-//		 * if needed.
-//		 */
-//		/*virtual*/ void drawText( S32 x, S32 y, const LLWString &text, const LLColor4& color );
-//
-//		/**
-//		 * Draws segmented text(with urls) line by line. Will show or hide "More" link 
-//		 * if needed
-//		 */
-//		void drawTextSegments(S32 x, S32 y, const LLWString &text);
+		// adds or removes "More" link as needed
+		/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
+		/*virtual*/ void setValue(const LLSD& value);
 
 		/**
 		 * Returns difference between text box height and text height.
 		 */
 		virtual S32 getHPad() { return mHPad; }
 
-		/**
-		 * Broadcasts "commit" signal if user clicked "More" link
-		 */
-		/*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask);
-
 	protected:
 
 		LLTextBoxEx(const Params& p);
 		/**
 		 * Shows "More" link
 		 */
-		void showExpandText(S32 y);
+		void showExpandText();
 
 		/**
 		 * Hides "More" link
 		 */
 		void hideExpandText();
 
-		/**
-		 * Returns cropped line width
-		 */
-		S32 getCropTextWidth();
-
 	private:
 
-		LLTextBox* mExpandTextBox;
+		bool mExpanded;
 	};
 
 public:

indra/newview/skins/default/xui/en/floater_test_widgets.xml

    height="40"
    follows="top|left|bottom"
    layout="topleft"
-   name="test_text_editor"
+   name="test_text_box"
    tool_tip="text box"
    top_pad="5"
    width="200">
       Text box
 with
 multiple lines
+and too
+many
+line to actually fit
   </text>
   <!-- And a third column -->
   

indra/newview/skins/default/xui/en/panel_profile.xml

 	    layout="topleft"
 	    top="0"
 	    left="0"
-	    width="284"
+	    width="300"
 	    height="700">
 	 <panel
 	    follows="left|top"
 	    left="10"
 	    name="second_life_image_panel"
 	    top="10"
-	    width="280">
+	    width="285">
             <texture_picker
              allow_no_texture="true"
              default_image_name="None"
 	 top_pad="10"
          left="10"
          name="first_life_image_panel"
-         width="280">
+         width="285">
             <texture_picker
              allow_no_texture="true"
              default_image_name="None"
              height="90"
              layout="topleft"
              name="fl_description_edit"
-             width="180"
+             width="170"
              expanded_bg_visible="true"
              expanded_bg_color="black">
                 Lorem ipsum dolor sit amet, consectetur adlkjpiscing elit moose moose. Aenean viverra orci et justo sagittis aliquet. Nullam malesuada mauris sit amet. adipiscing elit. Aenean rigviverra orci et justo sagittis aliquet. Nullam malesuada mauris sit amet sorbet ipsum. adipiscing elit. Aenean viverra orci et justo sagittis aliquet. Nullam malesuada mauris sit amet ipsum.

indra/newview/skins/default/xui/en/widgets/expandable_text.xml

   tab_stop="true"
   v_pad="2"
   h_pad="3" >
-  <expand_textbox 
-   name="expand"
-   follows="bottom|right"
-   visible="false"
-   bg_visible="false"
-   tab_stop="false"
-   v_pad="0"
-   h_pad="0"
-   text="[http://www.DUMMY.com More]" 
-   tool_tip="Click to expand text"/>
  </textbox>
  <scroll
   name="scroll"

indra/newview/skins/default/xui/en/widgets/text.xml

 <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
 <text allow_html="true"
       clip_to_rect="false"
+      mouse_opaque="false" 
       name="text_box" 
       font="SansSerifSmall"
       font_shadow="soft"