diff options
Diffstat (limited to 'indra/llui/lltextbase.cpp')
-rw-r--r-- | indra/llui/lltextbase.cpp | 566 |
1 files changed, 371 insertions, 195 deletions
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 2b1d677ffb..851fb966ec 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -60,6 +60,11 @@ LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S3 bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const { + // sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11) + if (a->getEnd() == b->getEnd()) + { + return a->getStart() < b->getStart(); + } return a->getEnd() < b->getEnd(); } @@ -151,6 +156,7 @@ LLTextBase::Params::Params() read_only("read_only", false), v_pad("v_pad", 0), h_pad("h_pad", 0), + clip_partial("clip_partial", true), line_spacing("line_spacing"), max_text_length("max_length", 255), font_shadow("font_shadow"), @@ -179,7 +185,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mWriteableBgColor(p.bg_writeable_color), mReadOnlyBgColor(p.bg_readonly_color), mFocusBgColor(p.bg_focus_color), - mReflowNeeded(FALSE), + mReflowIndex(S32_MAX), mCursorPos( 0 ), mScrollNeeded(FALSE), mDesiredXPixel(-1), @@ -188,6 +194,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mHAlign(p.font_halign), mLineSpacingMult(p.line_spacing.multiple), mLineSpacingPixels(p.line_spacing.pixels), + mClipPartial(p.clip_partial && !p.allow_scroll), mTrackEnd( p.track_end ), mScrollIndex(-1), mSelectionStart( 0 ), @@ -237,7 +244,8 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) LLTextBase::~LLTextBase() { - delete mPopupMenu; + // Menu, like any other LLUICtrl, is deleted by its parent - gMenuHolder + clearSegments(); } @@ -284,10 +292,13 @@ bool LLTextBase::truncate() return did_truncate; } -LLStyle::Params LLTextBase::getDefaultStyle() +LLStyle::Params LLTextBase::getDefaultStyleParams() { - LLColor4 text_color = ( mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get() ); - return LLStyle::Params().color(text_color).font(mDefaultFont).drop_shadow(mFontShadow); + return LLStyle::Params() + .color(LLUIColor(&mFgColor)) + .readonly_color(LLUIColor(&mReadOnlyFgColor)) + .font(mDefaultFont) + .drop_shadow(mFontShadow); } void LLTextBase::onValueChange(S32 start, S32 end) @@ -301,12 +312,11 @@ void LLTextBase::drawSelectionBackground() // Draw selection even if we don't have keyboard focus for search/replace if( hasSelection() && !mLineInfoList.empty()) { - LLWString text = getWText(); std::vector<LLRect> selection_rects; S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); - LLRect selection_rect = mTextRect; + LLRect selection_rect = mVisibleTextRect; // Skip through the lines we aren't drawing. LLRect content_display_rect = getVisibleDocumentRect(); @@ -340,7 +350,8 @@ void LLTextBase::drawSelectionBackground() S32 segment_line_start = segmentp->getStart() + segment_offset; S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); - S32 segment_width, segment_height; + S32 segment_width = 0; + S32 segment_height = 0; // if selection after beginning of segment if(selection_left >= segment_line_start) @@ -375,18 +386,18 @@ void LLTextBase::drawSelectionBackground() // Draw the selection box (we're using a box instead of reversing the colors on the selected text). gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - const LLColor4& color = mReadOnly ? mReadOnlyBgColor.get() : mWriteableBgColor.get(); + const LLColor4& color = mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get(); F32 alpha = hasFocus() ? 0.7f : 0.3f; alpha *= getDrawContext().mAlpha; - gGL.color4f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], alpha ); + LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); for (std::vector<LLRect>::iterator rect_it = selection_rects.begin(); rect_it != selection_rects.end(); ++rect_it) { LLRect selection_rect = *rect_it; - selection_rect.translate(mTextRect.mLeft - content_display_rect.mLeft, mTextRect.mBottom - content_display_rect.mBottom); - gl_rect_2d(selection_rect); + selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); + gl_rect_2d(selection_rect, selection_color); } } } @@ -399,7 +410,7 @@ void LLTextBase::drawCursor() && gFocusMgr.getAppHasFocus() && !mReadOnly) { - LLWString wtext = getWText(); + const LLWString &wtext = getWText(); const llwchar* text = wtext.c_str(); LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); @@ -427,7 +438,8 @@ void LLTextBase::drawCursor() if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) { - S32 segment_width, segment_height; + S32 segment_width = 0; + S32 segment_height = 0; segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height); S32 width = llmax(CURSOR_THICKNESS, segment_width); cursor_rect.mRight = cursor_rect.mLeft + width; @@ -484,7 +496,6 @@ void LLTextBase::drawCursor() void LLTextBase::drawText() { - LLWString text = getWText(); const S32 text_len = getLength(); if( text_len <= 0 ) { @@ -500,7 +511,7 @@ void LLTextBase::drawText() } LLRect scrolled_view_rect = getVisibleDocumentRect(); - std::pair<S32, S32> line_range = getVisibleLines(); + std::pair<S32, S32> line_range = getVisibleLines(mClipPartial); S32 first_line = line_range.first; S32 last_line = line_range.second; if (first_line >= last_line) @@ -520,26 +531,22 @@ void LLTextBase::drawText() for (S32 cur_line = first_line; cur_line < last_line; cur_line++) { + S32 next_line = cur_line + 1; line_info& line = mLineInfoList[cur_line]; - if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mTextRect.mBottom) - { - break; - } - S32 next_start = -1; S32 line_end = text_len; - if ((cur_line + 1) < getLineCount()) + if (next_line < getLineCount()) { - next_start = getLineStart(cur_line + 1); + next_start = getLineStart(next_line); line_end = next_start; } - LLRect text_rect(line.mRect.mLeft + mTextRect.mLeft - scrolled_view_rect.mLeft, - line.mRect.mTop - scrolled_view_rect.mBottom + mTextRect.mBottom, - mDocumentView->getRect().getWidth() - scrolled_view_rect.mLeft, - line.mRect.mBottom - scrolled_view_rect.mBottom + mTextRect.mBottom); + LLRect text_rect(line.mRect.mLeft + mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, + line.mRect.mTop - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom, + llmin(mDocumentView->getRect().getWidth(), line.mRect.mRight) - scrolled_view_rect.mLeft, + line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom); // draw a single line of text S32 seg_start = line_start; @@ -558,6 +565,17 @@ void LLTextBase::drawText() } S32 clipped_end = llmin( line_end, cur_segment->getEnd() ) - cur_segment->getStart(); + + if (mUseEllipses // using ellipses + && clipped_end == line_end // last segment on line + && next_line == last_line // this is the last visible line + && last_line < (S32)mLineInfoList.size()) // and there is more text to display + { + // more lines of text to go, but we can't fit them + // so shrink text rect to force ellipses + text_rect.mRight -= 2; + } + text_rect.mLeft = (S32)(cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect)); seg_start = clipped_end + cur_segment->getStart(); @@ -604,7 +622,8 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s else { // create default editable segment to hold new text - default_segment = new LLNormalTextSegment( new LLStyle(getDefaultStyle()), pos, pos + insert_len, *this); + LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); + default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this); } // shift remaining segments to right @@ -637,13 +656,11 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s if ( truncate() ) { - // The user's not getting everything he's hoping for - make_ui_sound("UISndBadKeystroke"); insert_len = getLength() - old_len; } onValueChange(pos, pos + insert_len); - needsReflow(); + needsReflow(pos); return insert_len; } @@ -703,7 +720,7 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) createDefaultSegment(); onValueChange(pos, pos); - needsReflow(); + needsReflow(pos); return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length } @@ -719,7 +736,7 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) getViewModel()->setDisplay(text); onValueChange(pos, pos + 1); - needsReflow(); + needsReflow(pos); return 1; } @@ -730,7 +747,8 @@ void LLTextBase::createDefaultSegment() // ensures that there is always at least one segment if (mSegments.empty()) { - LLTextSegmentPtr default_segment = new LLNormalTextSegment( new LLStyle(getDefaultStyle()), 0, getLength() + 1, *this); + LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); + LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this); mSegments.insert(default_segment); default_segment->linkToDocument(this); } @@ -744,15 +762,18 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) } segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart()); + S32 reflow_start_index = 0; if (cur_seg_iter == mSegments.end()) { mSegments.insert(segment_to_insert); segment_to_insert->linkToDocument(this); + reflow_start_index = segment_to_insert->getStart(); } else { LLTextSegmentPtr cur_segmentp = *cur_seg_iter; + reflow_start_index = cur_segmentp->getStart(); if (cur_segmentp->getStart() < segment_to_insert->getStart()) { S32 old_segment_end = cur_segmentp->getEnd(); @@ -760,7 +781,8 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) cur_segmentp->setEnd(segment_to_insert->getStart()); // advance to next segment // insert remainder of old segment - LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( cur_segmentp->getStyle(), segment_to_insert->getStart(), old_segment_end, *this); + LLStyleConstSP sp = cur_segmentp->getStyle(); + LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); mSegments.insert(cur_seg_iter, remainder_segment); remainder_segment->linkToDocument(this); // insert new segment before remainder of old segment @@ -810,7 +832,7 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) } // layout potentially changed - needsReflow(); + needsReflow(reflow_start_index); } BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) @@ -831,7 +853,7 @@ BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) { // Did we just click on a link? if (cur_segment->getStyle() - && cur_segment->getStyle()->isLink()) + && cur_segment->getStyle()->isLink()) { // *TODO: send URL here? mURLClickSignal(this, LLSD() ); @@ -933,13 +955,16 @@ BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) { - LLUICtrl::reshape( width, height, called_from_parent ); + if (width != getRect().getWidth() || height != getRect().getHeight()) + { + LLUICtrl::reshape( width, height, called_from_parent ); - // do this first after reshape, because other things depend on - // up-to-date mTextRect - updateRects(); - - needsReflow(); + // do this first after reshape, because other things depend on + // up-to-date mVisibleTextRect + updateRects(); + + needsReflow(); + } } void LLTextBase::draw() @@ -948,7 +973,10 @@ void LLTextBase::draw() reflow(); // then update scroll position, as cursor may have moved - updateScrollFromCursor(); + if (!mReadOnly) + { + updateScrollFromCursor(); + } LLRect doc_rect; if (mScroller) @@ -970,7 +998,7 @@ void LLTextBase::draw() : hasFocus() ? mFocusBgColor.get() : mWriteableBgColor.get(); - gl_rect_2d(mTextRect, bg_color, TRUE); + gl_rect_2d(mVisibleTextRect, bg_color, TRUE); } // draw document view @@ -992,6 +1020,22 @@ void LLTextBase::setColor( const LLColor4& c ) mFgColor = c; } +//virtual +void LLTextBase::setReadOnlyColor(const LLColor4 &c) +{ + mReadOnlyFgColor = c; +} + +//virtual +void LLTextBase::handleVisibilityChange( BOOL new_visibility ) +{ + if(!new_visibility && mPopupMenu) + { + mPopupMenu->hide(); + } + LLUICtrl::handleVisibilityChange(new_visibility); +} + //virtual void LLTextBase::setValue(const LLSD& value ) { @@ -1033,9 +1077,9 @@ S32 LLTextBase::getLeftOffset(S32 width) case LLFontGL::LEFT: return mHPad; case LLFontGL::HCENTER: - return mHPad + (mTextRect.getWidth() - width - mHPad) / 2; + return mHPad + (mVisibleTextRect.getWidth() - width - mHPad) / 2; case LLFontGL::RIGHT: - return mTextRect.getWidth() - width; + return mVisibleTextRect.getWidth() - width; default: return mHPad; } @@ -1043,20 +1087,27 @@ S32 LLTextBase::getLeftOffset(S32 width) static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow"); -void LLTextBase::reflow(S32 start_index) +void LLTextBase::reflow() { LLFastTimer ft(FTM_TEXT_REFLOW); updateSegments(); - while(mReflowNeeded) + while(mReflowIndex < S32_MAX) { - mReflowNeeded = FALSE; + S32 start_index = mReflowIndex; + mReflowIndex = S32_MAX; + + // shrink document to minimum size (visible portion of text widget) + // to force inlined widgets with follows set to shrink + mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; LLRect old_cursor_rect = getLocalRectFromDocIndex(mCursorPos); - bool follow_selection = mTextRect.overlaps(old_cursor_rect); // cursor is visible + bool follow_selection = mVisibleTextRect.overlaps(old_cursor_rect); // cursor is visible + old_cursor_rect.translate(-mVisibleTextRect.mLeft, -mVisibleTextRect.mBottom); + S32 first_line = getFirstVisibleLine(); // if scroll anchor not on first line, update it to first character of first line @@ -1067,15 +1118,16 @@ void LLTextBase::reflow(S32 start_index) mScrollIndex = mLineInfoList[first_line].mDocIndexStart; } LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex); + // subtract off effect of horizontal scrollbar from local position of first char + first_char_rect.translate(-mVisibleTextRect.mLeft, -mVisibleTextRect.mBottom); S32 cur_top = 0; segment_set_t::iterator seg_iter = mSegments.begin(); S32 seg_offset = 0; S32 line_start_index = 0; - const S32 text_width = mTextRect.getWidth() - mHPad; // reserve room for margin - S32 remaining_pixels = text_width; - LLWString text(getWText()); + const S32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin + S32 remaining_pixels = text_available_width; S32 line_count = 0; // find and erase line info structs starting at start_index and going to end of document @@ -1085,6 +1137,7 @@ void LLTextBase::reflow(S32 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; + cur_top = iter->mRect.mTop; getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); mLineInfoList.erase(iter, mLineInfoList.end()); } @@ -1105,19 +1158,25 @@ void LLTextBase::reflow(S32 start_index) S32_MAX); S32 segment_width, segment_height; - segment->getDimensions(seg_offset, character_count, segment_width, segment_height); + bool force_newline = segment->getDimensions(seg_offset, character_count, segment_width, segment_height); // grow line height as necessary based on reported height of this segment line_height = llmax(line_height, segment_height); remaining_pixels -= segment_width; + if (remaining_pixels < 0) + { + // getNumChars() and getDimensions() should return consistent results + remaining_pixels = 0; + } seg_offset += character_count; S32 last_segment_char_on_line = segment->getStart() + seg_offset; - S32 text_left = getLeftOffset(text_width - remaining_pixels); + S32 text_actual_width = text_available_width - remaining_pixels; + S32 text_left = getLeftOffset(text_actual_width); LLRect line_rect(text_left, cur_top, - text_left + (text_width - remaining_pixels), + text_left + text_actual_width, cur_top - line_height); // if we didn't finish the current segment... @@ -1132,7 +1191,7 @@ void LLTextBase::reflow(S32 start_index) line_start_index = segment->getStart() + seg_offset; cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - remaining_pixels = text_width; + remaining_pixels = text_available_width; line_height = 0; } // ...just consumed last segment.. @@ -1150,9 +1209,25 @@ void LLTextBase::reflow(S32 start_index) else { // subtract pixels used and increment segment + if (force_newline) + { + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + line_start_index = segment->getStart() + seg_offset; + cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + line_height = 0; + remaining_pixels = text_available_width; + } ++seg_iter; seg_offset = 0; } + if (force_newline) + { + line_count++; + } } // calculate visible region for diplaying text @@ -1170,7 +1245,6 @@ void LLTextBase::reflow(S32 start_index) // apply scroll constraints after reflowing text if (!hasMouseCapture() && mScroller) { - LLRect visible_content_rect = getVisibleDocumentRect(); if (scrolled_to_bottom && mTrackEnd) { // keep bottom of text buffer visible @@ -1179,31 +1253,26 @@ void LLTextBase::reflow(S32 start_index) else if (hasSelection() && follow_selection) { // keep cursor in same vertical position on screen when selecting text - LLRect new_cursor_rect_doc = getLocalRectFromDocIndex(mCursorPos); - new_cursor_rect_doc.translate(visible_content_rect.mLeft, visible_content_rect.mBottom); + LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect); - //llassert_always(getLocalRectFromDocIndex(mCursorPos).mBottom == old_cursor_rect.mBottom); } else { // keep first line of text visible - LLRect new_first_char_rect = getLocalRectFromDocIndex(mScrollIndex); - new_first_char_rect.translate(visible_content_rect.mLeft, visible_content_rect.mBottom); + LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex); mScroller->scrollToShowRect(new_first_char_rect, first_char_rect); - //llassert_always(getLocalRectFromDocIndex(mScrollIndex).mBottom == first_char_rect.mBottom); } } - } - - // reset desired x cursor position - updateCursorXPos(); + // reset desired x cursor position + updateCursorXPos(); + } } -LLRect LLTextBase::getContentsRect() +LLRect LLTextBase::getTextBoundingRect() { reflow(); - return mContentsRect; + return mTextBoundingRect; } @@ -1301,13 +1370,11 @@ std::pair<S32, S32> LLTextBase::getVisibleLines(bool fully_visible) 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()); } @@ -1378,10 +1445,10 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i } // Finds the text segment (if any) at the give local screen position -LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y ) +LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) { // Find the cursor position at the requested local screen position - S32 offset = getDocIndexFromLocalCoord( x, y, FALSE ); + S32 offset = getDocIndexFromLocalCoord( x, y, FALSE, hit_past_end_of_line); segment_set_t::iterator seg_iter = getSegIterContaining(offset); if (seg_iter != mSegments.end()) { @@ -1417,6 +1484,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url)); registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); + registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); @@ -1431,7 +1499,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) } } -void LLTextBase::setText(const LLStringExplicit &utf8str) +void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) { // clear out the existing text and segments getViewModel()->setDisplay(LLWStringUtil::null); @@ -1439,18 +1507,18 @@ void LLTextBase::setText(const LLStringExplicit &utf8str) clearSegments(); // createDefaultSegment(); - startOfDoc(); deselect(); // append the new text (supports Url linking) std::string text(utf8str); LLStringUtil::removeCRLF(text); - appendText(text, false); + // appendText modifies mCursorPos... + appendText(text, false, input_params); + // ...so move cursor to top after appending text + startOfDoc(); - //resetDirty(); onValueChange(0, getLength()); - needsReflow(); } //virtual @@ -1462,16 +1530,7 @@ std::string LLTextBase::getText() const void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) { LLStyle::Params style_params(input_params); - style_params.fillFrom(getDefaultStyle()); - - if (!style_params.font.isProvided()) - { - style_params.font = mDefaultFont; - } - if (!style_params.drop_shadow.isProvided()) - { - style_params.drop_shadow = mFontShadow; - } + style_params.fillFrom(getDefaultStyleParams()); S32 part = (S32)LLTextParser::WHOLE; if(mParseHTML) @@ -1487,10 +1546,8 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c LLStyle::Params link_params = style_params; link_params.color = match.getColor(); - // apply font name from requested style_params - std::string font_name = LLFontGL::nameFromFont(style_params.font()); - link_params.font.name.setIfNotProvided(font_name); - link_params.font.style = "UNDERLINE"; + link_params.readonly_color = match.getColor(); + link_params.font.style("UNDERLINE"); link_params.link_href = match.getUrl(); // output the text before the Url @@ -1518,25 +1575,35 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c { LLStyle::Params icon; icon.image = image; - // HACK: fix spacing of images and remove the fixed char spacing - appendAndHighlightText(" ", prepend_newline, part, icon); + // Text will be replaced during rendering with the icon, + // but string cannot be empty or the segment won't be + // added (or drawn). + appendAndHighlightText(" ", prepend_newline, part, icon); prepend_newline = false; } } - // output the styled Url - appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params); - prepend_newline = false; - // set the tooltip for the Url label - if (! match.getTooltip().empty()) + // output the styled Url (unless we've been asked to suppress hyperlinking) + if (match.isLinkDisabled()) { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) + appendAndHighlightText(match.getLabel(), prepend_newline, part, style_params); + } + else + { + appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params); + + // set the tooltip for the Url label + if (! match.getTooltip().empty()) { - LLTextSegmentPtr segment = *it; - segment->setToolTip(match.getTooltip()); + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(match.getTooltip()); + } } } + prepend_newline = false; // move on to the rest of the text after the Url if (end < (S32)text.length()) @@ -1559,9 +1626,15 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c } } -void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepend_newline, S32 highlight_part, const LLStyle::Params& stylep) +void LLTextBase::needsReflow(S32 index) +{ + lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl; + mReflowIndex = llmin(mReflowIndex, index); +} + +void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepend_newline, S32 highlight_part, const LLStyle::Params& style_params) { - if (new_text.empty()) return; + if (new_text.empty()) return; // Save old state S32 selection_start = mSelectionStart; @@ -1579,7 +1652,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen if (mParseHighlights && highlight) { - LLStyle::Params highlight_params = stylep; + LLStyle::Params highlight_params(style_params); LLSD pieces = highlight->parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); for (S32 i = 0; i < pieces.size(); i++) @@ -1599,7 +1672,8 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); } S32 cur_length = getLength(); - LLTextSegmentPtr segmentp = new LLNormalTextSegment(new LLStyle(highlight_params), cur_length, cur_length + wide_text.size(), *this); + LLStyleConstSP sp(new LLStyle(highlight_params)); + LLTextSegmentPtr segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); segment_vec_t segments; segments.push_back(segmentp); insertStringNoUndo(cur_length, wide_text, &segments); @@ -1623,13 +1697,12 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen segment_vec_t segments; S32 segment_start = old_length; S32 segment_end = old_length + wide_text.size(); - segments.push_back(new LLNormalTextSegment(new LLStyle(stylep), segment_start, segment_end, *this )); + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); insertStringNoUndo(getLength(), wide_text, &segments); } - needsReflow(); - // Set the cursor and scroll position if( selection_start != selection_end ) { @@ -1669,7 +1742,7 @@ void LLTextBase::replaceUrlLabel(const std::string &url, for (it = mSegments.begin(); it != mSegments.end(); ++it) { LLTextSegment *seg = *it; - const LLStyleSP style = seg->getStyle(); + LLStyleConstSP style = seg->getStyle(); // update segment start/end length in case we replaced text earlier S32 seg_length = seg->getEnd() - seg->getStart(); @@ -1706,7 +1779,7 @@ void LLTextBase::setWText(const LLWString& text) setText(wstring_to_utf8str(text)); } -LLWString LLTextBase::getWText() const +const LLWString& LLTextBase::getWText() const { return getViewModel()->getDisplay(); } @@ -1715,13 +1788,13 @@ LLWString LLTextBase::getWText() const // will be put to its right. If round is false, the cursor will always be put to the // character's left. -S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const +S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line) const { // Figure out which line we're nearest to. LLRect visible_region = getVisibleDocumentRect(); // binary search for line that starts before local_y - line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mTextRect.mBottom + visible_region.mBottom, compare_bottom()); + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mVisibleTextRect.mBottom + visible_region.mBottom, compare_bottom()); if (line_iter == mLineInfoList.end()) { @@ -1729,7 +1802,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round } S32 pos = getLength(); - S32 start_x = mTextRect.mLeft + line_iter->mRect.mLeft; + S32 start_x = mVisibleTextRect.mLeft + line_iter->mRect.mLeft; segment_set_t::iterator line_seg_iter; S32 line_seg_offset; @@ -1744,7 +1817,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round S32 text_width, text_height; segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height); if (local_x < start_x + text_width // cursor to left of right edge of text - || segmentp->getEnd() >= line_iter->mDocIndexEnd - 1) // or this segment wraps to next line + || (hit_past_end_of_line && (segmentp->getEnd() >= line_iter->mDocIndexEnd - 1))) // or this segment wraps to next line { // Figure out which character we're nearest to. S32 offset; @@ -1840,7 +1913,7 @@ LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const if (mLineInfoList.empty()) { // return default height rect in upper left - local_rect = mTextRect; + local_rect = mVisibleTextRect; local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->getLineHeight()); return local_rect; } @@ -1851,8 +1924,8 @@ LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const // compensate for scrolled, inset view of doc LLRect scrolled_view_rect = getVisibleDocumentRect(); local_rect = doc_rect; - local_rect.translate(mTextRect.mLeft - scrolled_view_rect.mLeft, - mTextRect.mBottom - scrolled_view_rect.mBottom); + local_rect.translate(mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, + mVisibleTextRect.mBottom - scrolled_view_rect.mBottom); return local_rect; } @@ -1887,11 +1960,19 @@ void LLTextBase::endOfLine() void LLTextBase::startOfDoc() { setCursorPos(0); + if (mScroller) + { + mScroller->goToTop(); + } } void LLTextBase::endOfDoc() { setCursorPos(getLength()); + if (mScroller) + { + mScroller->goToBottom(); + } } void LLTextBase::changePage( S32 delta ) @@ -1952,10 +2033,20 @@ void LLTextBase::changeLine( S32 delta ) LLRect visible_region = getVisibleDocumentRect(); - S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mTextRect.mBottom - visible_region.mBottom, TRUE); + S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE); setCursorPos(new_cursor_pos, true); } +bool LLTextBase::scrolledToStart() +{ + return mScroller->isAtTop(); +} + +bool LLTextBase::scrolledToEnd() +{ + return mScroller->isAtBottom(); +} + bool LLTextBase::setCursor(S32 row, S32 column) { @@ -2028,54 +2119,64 @@ void LLTextBase::updateRects() { if (mLineInfoList.empty()) { - mContentsRect = LLRect(0, mVPad, mHPad, 0); + mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); } else { - mContentsRect = mLineInfoList.begin()->mRect; + mTextBoundingRect = mLineInfoList.begin()->mRect; for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin(); line_iter != mLineInfoList.end(); ++line_iter) { - mContentsRect.unionWith(line_iter->mRect); + mTextBoundingRect.unionWith(line_iter->mRect); } - mContentsRect.mLeft = 0; - mContentsRect.mTop += mVPad; + mTextBoundingRect.mTop += mVPad; + // subtract a pixel off the bottom to deal with rounding errors in measuring font height + mTextBoundingRect.mBottom -= 1; - S32 delta_pos = -mContentsRect.mBottom; + S32 delta_pos = -mTextBoundingRect.mBottom; // move line segments to fit new document rect for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) { it->mRect.translate(0, delta_pos); } - mContentsRect.translate(0, delta_pos); + mTextBoundingRect.translate(0, delta_pos); } // update document container dimensions according to text contents - LLRect doc_rect = mContentsRect; - // use old mTextRect constraint document to width of viewable region - doc_rect.mRight = doc_rect.mLeft + mTextRect.getWidth(); + LLRect doc_rect = mTextBoundingRect; + // use old mVisibleTextRect constraint document to width of viewable region + doc_rect.mLeft = 0; + + // allow horizontal scrolling? + // if so, use entire width of text contents + // otherwise, stop at width of mVisibleTextRect + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); mDocumentView->setShape(doc_rect); - //update mTextRect *after* mDocumentView has been resized + //update mVisibleTextRect *after* mDocumentView has been resized // so that scrollbars are added if document needs to scroll - // since mTextRect does not include scrollbars - LLRect old_text_rect = mTextRect; - mTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + // since mVisibleTextRect does not include scrollbars + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); //FIXME: replace border with image? if (mBorderVisible) { - mTextRect.stretch(-1); + mVisibleTextRect.stretch(-1); } - if (mTextRect != old_text_rect) + if (mVisibleTextRect != old_text_rect) { needsReflow(); } - // update document container again, using new mTextRect - doc_rect.mRight = doc_rect.mLeft + mTextRect.getWidth(); + // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); mDocumentView->setShape(doc_rect); } @@ -2108,9 +2209,12 @@ LLRect LLTextBase::getVisibleDocumentRect() const } else { - // entire document rect when not scrolling + // entire document rect is visible when not scrolling + // but offset according to height of widget LLRect doc_rect = mDocumentView->getLocalRect(); - doc_rect.translate(-mDocumentView->getRect().mLeft, -mDocumentView->getRect().mBottom); + doc_rect.mLeft -= mDocumentView->getRect().mLeft; + // adjust for height of text above widget baseline + doc_rect.mBottom = doc_rect.getHeight() - mVisibleTextRect.getHeight(); return doc_rect; } } @@ -2122,7 +2226,7 @@ LLRect LLTextBase::getVisibleDocumentRect() const LLTextSegment::~LLTextSegment() {} -void LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; height = 0; } +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; height = 0; return false;} S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; } S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return 0; } void LLTextSegment::updateLayout(const LLTextBase& editor) {} @@ -2131,9 +2235,9 @@ bool LLTextSegment::canEdit() const { return false; } void LLTextSegment::unlinkFromDocument(LLTextBase*) {} void LLTextSegment::linkToDocument(LLTextBase*) {} const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; } -void LLTextSegment::setColor(const LLColor4 &color) {} -const LLStyleSP LLTextSegment::getStyle() const {static LLStyleSP sp(new LLStyle()); return sp; } -void LLTextSegment::setStyle(const LLStyleSP &style) {} +//void LLTextSegment::setColor(const LLColor4 &color) {} +LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; } +void LLTextSegment::setStyle(LLStyleConstSP style) {} void LLTextSegment::setToken( LLKeywordToken* token ) {} LLKeywordToken* LLTextSegment::getToken() const { return NULL; } void LLTextSegment::setToolTip( const std::string &msg ) {} @@ -2158,13 +2262,19 @@ BOOL LLTextSegment::hasMouseCapture() { return FALSE; } // LLNormalTextSegment // -LLNormalTextSegment::LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor ) +LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) : LLTextSegment(start, end), mStyle( style ), mToken(NULL), mEditor(editor) { mFontHeight = llceil(mStyle->getFont()->getLineHeight()); + + LLUIImagePtr image = mStyle->getImage(); + if (image.notNull()) + { + mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start)); + } } LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) @@ -2177,20 +2287,33 @@ LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 mFontHeight = llceil(mStyle->getFont()->getLineHeight()); } +LLNormalTextSegment::~LLNormalTextSegment() +{ + mImageLoadedConnection.disconnect(); +} + + F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { if( end - start > 0 ) { if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart)) { + // ...for images, only render the image, not the underlying text, + // which is only a placeholder space LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; LLUIImagePtr image = mStyle->getImage(); S32 style_image_height = image->getHeight(); S32 style_image_width = image->getWidth(); - // Center the image vertically - S32 image_bottom = draw_rect.getCenterY() - (style_image_height/2); + // Text is drawn from the top of the draw_rect downward + S32 text_center = draw_rect.mTop - (mFontHeight / 2); + // Align image to center of text + S32 image_bottom = text_center - (style_image_height / 2); image->draw(draw_rect.mLeft, image_bottom, - style_image_width, style_image_height); + style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 3; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; } return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); @@ -2218,9 +2341,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele const LLFontGL* font = mStyle->getFont(); - LLColor4 color = mStyle->getColor() % alpha; - - font = mStyle->getFont(); + LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; if( selection_start > seg_start ) { @@ -2229,14 +2350,14 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele 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()); + rect.mLeft, rect.mTop, + color, + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, rect.getWidth(), + &right_x, + mEditor.getUseEllipses()); } rect.mLeft = (S32)ceil(right_x); @@ -2248,14 +2369,14 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele 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()); + 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, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + length, rect.getWidth(), + &right_x, + mEditor.getUseEllipses()); } rect.mLeft = (S32)ceil(right_x); if( selection_end < seg_end ) @@ -2265,14 +2386,14 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele 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()); + rect.mLeft, rect.mTop, + color, + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, rect.getWidth(), + &right_x, + mEditor.getUseEllipses()); } return right_x; } @@ -2281,8 +2402,12 @@ BOOL LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - LLUI::getWindow()->setCursor(UI_CURSOR_HAND); - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUI::getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; + } } return FALSE; } @@ -2291,8 +2416,12 @@ BOOL LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); + return TRUE; + } } return FALSE; } @@ -2301,8 +2430,12 @@ BOOL LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - // eat mouse down event on hyperlinks, so we get the mouse up - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + // eat mouse down event on hyperlinks, so we get the mouse up + return TRUE; + } } return FALSE; @@ -2312,8 +2445,12 @@ BOOL LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - LLUrlAction::clickAction(getStyle()->getLinkHREF()); - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUrlAction::clickAction(getStyle()->getLinkHREF()); + return TRUE; + } } return FALSE; @@ -2350,17 +2487,42 @@ void LLNormalTextSegment::setToolTip(const std::string& tooltip) mTooltip = tooltip; } -void LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { - LLWString text = mEditor.getWText(); + height = 0; + width = 0; + bool force_newline = false; + if (num_chars > 0) + { + height = mFontHeight; + const LLWString &text = mEditor.getWText(); + // if last character is a newline, then return true, forcing line break + llwchar last_char = text[mStart + first_char + num_chars - 1]; + if (last_char == '\n') + { + force_newline = true; + // don't count newline in font width + width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars - 1); + } + else + { + width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); + } + } + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + width += image->getWidth(); + height = llmax(height, image->getHeight()); + } - height = mFontHeight; - width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); + return force_newline; } S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { - LLWString text = mEditor.getWText(); + const LLWString &text = mEditor.getWText(); return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, (F32)segment_local_x_coord, F32_MAX, @@ -2370,7 +2532,13 @@ S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { - LLWString text = mEditor.getWText(); + const LLWString &text = mEditor.getWText(); + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + num_pixels = llmax(0, num_pixels - image->getWidth()); + } // search for newline and if found, truncate there S32 last_char = mStart + segment_offset; @@ -2385,10 +2553,15 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin // set max characters to length of segment, or to first newline max_chars = llmin(max_chars, last_char - (mStart + segment_offset)); + // if no character yet displayed on this line, don't require word wrapping since + // we can just move to the next line, otherwise insist on it so we make forward progress + LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) + ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE + : LLFontGL::ONLY_WORD_BOUNDARIES; S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, (F32)num_pixels, max_chars, - TRUE); + word_wrap_style); if (num_chars == 0 && line_offset == 0 @@ -2402,8 +2575,9 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin // but not both S32 last_char_in_run = mStart + segment_offset + num_chars; // check length first to avoid indexing off end of string - if (last_char_in_run >= mEditor.getLength() - || text[last_char_in_run] == '\n') + if (last_char_in_run < mEnd + && (last_char_in_run >= mEditor.getLength() + || text[last_char_in_run] == '\n')) { num_chars++; } @@ -2442,7 +2616,7 @@ LLInlineViewSegment::~LLInlineViewSegment() mView->die(); } -void LLInlineViewSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +bool LLInlineViewSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { if (first_char == 0 && num_chars == 0) { @@ -2456,6 +2630,8 @@ void LLInlineViewSegment::getDimensions(S32 first_char, S32 num_chars, S32& widt width = mLeftPad + mRightPad + mView->getRect().getWidth(); height = mBottomPad + mTopPad + mView->getRect().getHeight(); } + + return false; } S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const |