/indra/llui/lltextbase.cpp
C++ | 2394 lines | 1838 code | 347 blank | 209 comment | 286 complexity | 3afe64d160b1c17f9a4f89e24f848248 MD5 | raw file
Possible License(s): LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- /**
- * @file lltextbase.cpp
- * @author Martin Reddy
- * @brief The base class of text box/editor, providing Url handling support
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2009-2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
- #include "linden_common.h"
- #include "lltextbase.h"
- #include "lllocalcliprect.h"
- #include "llmenugl.h"
- #include "llscrollcontainer.h"
- #include "llstl.h"
- #include "lltextparser.h"
- #include "lltextutil.h"
- #include "lltooltip.h"
- #include "lluictrl.h"
- #include "llurlaction.h"
- #include "llurlregistry.h"
- #include "llview.h"
- #include "llwindow.h"
- #include <boost/bind.hpp>
- const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds
- const S32 CURSOR_THICKNESS = 2;
- LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num)
- : mDocIndexStart(index_start),
- mDocIndexEnd(index_end),
- mRect(rect),
- mLineNum(line_num)
- {}
- 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();
- }
- else
- {
- return a->getEnd() < b->getEnd();
- }
- }
- // helper functors
- struct LLTextBase::compare_bottom
- {
- bool operator()(const S32& a, const LLTextBase::line_info& b) const
- {
- return a > b.mRect.mBottom; // bottom of a is higher than bottom of b
- }
- bool operator()(const LLTextBase::line_info& a, const S32& b) const
- {
- return a.mRect.mBottom > b; // bottom of a is higher than bottom of b
- }
- bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
- {
- return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b
- }
- };
- // helper functors
- struct LLTextBase::compare_top
- {
- bool operator()(const S32& a, const LLTextBase::line_info& b) const
- {
- return a > b.mRect.mTop; // top of a is higher than top of b
- }
- bool operator()(const LLTextBase::line_info& a, const S32& b) const
- {
- return a.mRect.mTop > b; // top of a is higher than top of b
- }
- bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
- {
- return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b
- }
- };
- struct LLTextBase::line_end_compare
- {
- bool operator()(const S32& pos, const LLTextBase::line_info& info) const
- {
- return (pos < info.mDocIndexEnd);
- }
- bool operator()(const LLTextBase::line_info& info, const S32& pos) const
- {
- return (info.mDocIndexEnd < pos);
- }
- bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
- {
- return (a.mDocIndexEnd < b.mDocIndexEnd);
- }
- };
- //////////////////////////////////////////////////////////////////////////
- //
- // LLTextBase
- //
- // register LLTextBase::Params under name "textbase"
- static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase");
- LLTextBase::LineSpacingParams::LineSpacingParams()
- : multiple("multiple", 1.f),
- pixels("pixels", 0)
- {
- }
- LLTextBase::Params::Params()
- : cursor_color("cursor_color"),
- text_color("text_color"),
- text_readonly_color("text_readonly_color"),
- bg_visible("bg_visible", false),
- border_visible("border_visible", false),
- bg_readonly_color("bg_readonly_color"),
- bg_writeable_color("bg_writeable_color"),
- bg_focus_color("bg_focus_color"),
- text_selected_color("text_selected_color"),
- bg_selected_color("bg_selected_color"),
- allow_scroll("allow_scroll", true),
- plain_text("plain_text",false),
- track_end("track_end", false),
- read_only("read_only", false),
- v_pad("v_pad", 0),
- h_pad("h_pad", 0),
- clip("clip", true),
- clip_partial("clip_partial", true),
- line_spacing("line_spacing"),
- max_text_length("max_length", 255),
- font_shadow("font_shadow"),
- wrap("wrap"),
- use_ellipses("use_ellipses", false),
- parse_urls("parse_urls", false),
- parse_highlights("parse_highlights", false)
- {
- addSynonym(track_end, "track_bottom");
- addSynonym(wrap, "word_wrap");
- addSynonym(parse_urls, "allow_html");
- }
- LLTextBase::LLTextBase(const LLTextBase::Params &p)
- : LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)),
- mURLClickSignal(NULL),
- mMaxTextByteLength( p.max_text_length ),
- mDefaultFont(p.font),
- mFontShadow(p.font_shadow),
- mPopupMenu(NULL),
- mReadOnly(p.read_only),
- mCursorColor(p.cursor_color),
- mFgColor(p.text_color),
- mBorderVisible( p.border_visible ),
- mReadOnlyFgColor(p.text_readonly_color),
- mWriteableBgColor(p.bg_writeable_color),
- mReadOnlyBgColor(p.bg_readonly_color),
- mFocusBgColor(p.bg_focus_color),
- mTextSelectedColor(p.text_selected_color),
- mSelectedBGColor(p.bg_selected_color),
- mReflowIndex(S32_MAX),
- mCursorPos( 0 ),
- mScrollNeeded(FALSE),
- mDesiredXPixel(-1),
- mHPad(p.h_pad),
- mVPad(p.v_pad),
- mHAlign(p.font_halign),
- mVAlign(p.font_valign),
- mLineSpacingMult(p.line_spacing.multiple),
- mLineSpacingPixels(p.line_spacing.pixels),
- mClip(p.clip),
- mClipPartial(p.clip_partial && !p.allow_scroll),
- mTrackEnd( p.track_end ),
- mScrollIndex(-1),
- mSelectionStart( 0 ),
- mSelectionEnd( 0 ),
- mIsSelecting( FALSE ),
- mPlainText ( p.plain_text ),
- mWordWrap(p.wrap),
- mUseEllipses( p.use_ellipses ),
- mParseHTML(p.parse_urls),
- mParseHighlights(p.parse_highlights),
- mBGVisible(p.bg_visible),
- mScroller(NULL),
- mStyleDirty(true)
- {
- if(p.allow_scroll)
- {
- LLScrollContainer::Params scroll_params;
- scroll_params.name = "text scroller";
- scroll_params.rect = getLocalRect();
- scroll_params.follows.flags = FOLLOWS_ALL;
- scroll_params.is_opaque = false;
- scroll_params.mouse_opaque = false;
- scroll_params.min_auto_scroll_rate = 200;
- scroll_params.max_auto_scroll_rate = 800;
- scroll_params.border_visible = p.border_visible;
- mScroller = LLUICtrlFactory::create<LLScrollContainer>(scroll_params);
- addChild(mScroller);
- }
- LLView::Params view_params;
- view_params.name = "text_contents";
- view_params.rect = LLRect(0, 500, 500, 0);
- view_params.mouse_opaque = false;
- mDocumentView = LLUICtrlFactory::create<LLView>(view_params);
- if (mScroller)
- {
- mScroller->addChild(mDocumentView);
- }
- else
- {
- addChild(mDocumentView);
- }
- createDefaultSegment();
- updateRects();
- }
- LLTextBase::~LLTextBase()
- {
- mSegments.clear();
- delete mURLClickSignal;
- }
- void LLTextBase::initFromParams(const LLTextBase::Params& p)
- {
- LLUICtrl::initFromParams(p);
- resetDirty(); // Update saved text state
- updateSegments();
- // HACK: work around enabled == readonly design bug -- RN
- // setEnabled will modify our read only status, so do this after
- // LLTextBase::initFromParams
- if (p.read_only.isProvided())
- {
- mReadOnly = p.read_only;
- }
- }
- bool LLTextBase::truncate()
- {
- BOOL did_truncate = FALSE;
- // First rough check - if we're less than 1/4th the size, we're OK
- if (getLength() >= S32(mMaxTextByteLength / 4))
- {
- // Have to check actual byte size
- LLWString text(getWText());
- S32 utf8_byte_size = wstring_utf8_length(text);
- if ( utf8_byte_size > mMaxTextByteLength )
- {
- // Truncate safely in UTF-8
- std::string temp_utf8_text = wstring_to_utf8str(text);
- temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength );
- 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;
- }
- }
- return did_truncate;
- }
- const LLStyle::Params& LLTextBase::getDefaultStyleParams()
- {
- //FIXME: convert mDefaultStyle to a flyweight http://www.boost.org/doc/libs/1_40_0/libs/flyweight/doc/index.html
- //and eliminate color member values
- if (mStyleDirty)
- {
- mDefaultStyle
- .color(LLUIColor(&mFgColor)) // pass linked color instead of copy of mFGColor
- .readonly_color(LLUIColor(&mReadOnlyFgColor))
- .selected_color(LLUIColor(&mTextSelectedColor))
- .font(mDefaultFont)
- .drop_shadow(mFontShadow);
- mStyleDirty = false;
- }
- return mDefaultStyle;
- }
- void LLTextBase::onValueChange(S32 start, S32 end)
- {
- }
- // Draws the black box behind the selected text
- void LLTextBase::drawSelectionBackground()
- {
- // Draw selection even if we don't have keyboard focus for search/replace
- if( hasSelection() && !mLineInfoList.empty())
- {
- std::vector<LLRect> selection_rects;
- S32 selection_left = llmin( mSelectionStart, mSelectionEnd );
- S32 selection_right = llmax( mSelectionStart, mSelectionEnd );
- LLRect selection_rect = mVisibleTextRect;
- // Skip through the lines we aren't drawing.
- LLRect content_display_rect = getVisibleDocumentRect();
- // binary search for line that starts before top of visible buffer
- line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom());
- line_list_t::const_iterator end_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top());
- bool done = false;
- // Find the coordinates of the selected area
- for (;line_iter != end_iter && !done; ++line_iter)
- {
- // is selection visible on this line?
- if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right)
- {
- segment_set_t::iterator segment_iter;
- S32 segment_offset;
- getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset);
-
- LLRect selection_rect;
- selection_rect.mLeft = line_iter->mRect.mLeft;
- selection_rect.mRight = line_iter->mRect.mLeft;
- selection_rect.mBottom = line_iter->mRect.mBottom;
- selection_rect.mTop = line_iter->mRect.mTop;
-
- for(;segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0)
- {
- LLTextSegmentPtr segmentp = *segment_iter;
- S32 segment_line_start = segmentp->getStart() + segment_offset;
- S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd);
- if (segment_line_start > segment_line_end) break;
- S32 segment_width = 0;
- S32 segment_height = 0;
- // if selection after beginning of segment
- if(selection_left >= segment_line_start)
- {
- S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start;
- segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
- selection_rect.mLeft += segment_width;
- }
- // if selection_right == segment_line_end then that means we are the first character of the next segment
- // or first character of the next line, in either case we want to add the length of the current segment
- // to the selection rectangle and continue.
- // if selection right > segment_line_end then selection spans end of current segment...
- if (selection_right >= segment_line_end)
- {
- // extend selection slightly beyond end of line
- // to indicate selection of newline character (use "n" character to determine width)
- S32 num_chars = segment_line_end - segment_line_start;
- segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
- selection_rect.mRight += segment_width;
- }
- // else if selection ends on current segment...
- else
- {
- S32 num_chars = selection_right - segment_line_start;
- segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
- selection_rect.mRight += segment_width;
- break;
- }
- }
- selection_rects.push_back(selection_rect);
- }
- }
-
- // 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 = mSelectedBGColor;
- F32 alpha = hasFocus() ? 0.7f : 0.3f;
- alpha *= getDrawContext().mAlpha;
- 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(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
- gl_rect_2d(selection_rect, selection_color);
- }
- }
- }
- void LLTextBase::drawCursor()
- {
- F32 alpha = getDrawContext().mAlpha;
- if( hasFocus()
- && gFocusMgr.getAppHasFocus()
- && !mReadOnly)
- {
- const LLWString &wtext = getWText();
- const llwchar* text = wtext.c_str();
- LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
- cursor_rect.translate(-1, 0);
- segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos);
- // take style from last segment
- LLTextSegmentPtr segmentp;
- if (seg_it != mSegments.end())
- {
- segmentp = *seg_it;
- }
- else
- {
- return;
- }
- // Draw the cursor
- // (Flash the cursor every half second starting a fixed time after the last keystroke)
- F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32();
- if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
- {
- if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
- {
- 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;
- }
- else
- {
- cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS;
- }
-
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- LLColor4 cursor_color = mCursorColor.get() % alpha;
- gGL.color4fv( cursor_color.mV );
-
- gl_rect_2d(cursor_rect);
- if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n')
- {
- LLColor4 text_color;
- const LLFontGL* fontp;
- text_color = segmentp->getColor();
- fontp = segmentp->getStyle()->getFont();
- fontp->render(text, mCursorPos, cursor_rect,
- LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha),
- LLFontGL::LEFT, mVAlign,
- LLFontGL::NORMAL,
- LLFontGL::NO_SHADOW,
- 1);
- }
- // Make sure the IME is in the right place
- LLRect screen_pos = calcScreenRect();
- LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) );
- ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]);
- ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]);
- getWindow()->setLanguageTextInput( ime_pos );
- }
- }
- }
- void LLTextBase::drawText()
- {
- const S32 text_len = getLength();
- if( text_len <= 0 )
- {
- return;
- }
- S32 selection_left = -1;
- S32 selection_right = -1;
- // Draw selection even if we don't have keyboard focus for search/replace
- if( hasSelection())
- {
- selection_left = llmin( mSelectionStart, mSelectionEnd );
- selection_right = llmax( mSelectionStart, mSelectionEnd );
- }
- 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)
- {
- return;
- }
-
- S32 line_start = getLineStart(first_line);
- // find first text segment that spans top of visible portion of text buffer
- segment_set_t::iterator seg_iter = getSegIterContaining(line_start);
- if (seg_iter == mSegments.end())
- {
- return;
- }
- LLTextSegmentPtr cur_segment = *seg_iter;
- for (S32 cur_line = first_line; cur_line < last_line; cur_line++)
- {
- S32 next_line = cur_line + 1;
- line_info& line = mLineInfoList[cur_line];
- S32 next_start = -1;
- S32 line_end = text_len;
- if (next_line < getLineCount())
- {
- next_start = getLineStart(next_line);
- line_end = next_start;
- }
- LLRect text_rect(line.mRect);
- text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents
- text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position
- // draw a single line of text
- S32 seg_start = line_start;
- while( seg_start < line_end )
- {
- while( cur_segment->getEnd() <= seg_start )
- {
- seg_iter++;
- if (seg_iter == mSegments.end())
- {
- llwarns << "Ran off the segmentation end!" << llendl;
- return;
- }
- cur_segment = *seg_iter;
- }
-
- 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();
- }
- line_start = next_start;
- }
- }
- ///////////////////////////////////////////////////////////////////
- // Returns change in number of characters in mWText
- S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments )
- {
- LLWString text(getWText());
- S32 old_len = text.length(); // length() returns character length
- S32 insert_len = wstr.length();
- pos = getEditableIndex(pos, true);
- segment_set_t::iterator seg_iter = getEditableSegIterContaining(pos);
- LLTextSegmentPtr default_segment;
- LLTextSegmentPtr segmentp;
- if (seg_iter != mSegments.end())
- {
- segmentp = *seg_iter;
- }
- else
- {
- //segmentp = mSegments.back();
- return pos;
- }
- if (segmentp->canEdit())
- {
- segmentp->setEnd(segmentp->getEnd() + insert_len);
- if (seg_iter != mSegments.end())
- {
- ++seg_iter;
- }
- }
- else
- {
- // create default editable segment to hold new text
- LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
- default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this);
- }
- // shift remaining segments to right
- for(;seg_iter != mSegments.end(); ++seg_iter)
- {
- LLTextSegmentPtr segmentp = *seg_iter;
- segmentp->setStart(segmentp->getStart() + insert_len);
- segmentp->setEnd(segmentp->getEnd() + insert_len);
- }
- // insert new segments
- if (segments)
- {
- if (default_segment.notNull())
- {
- // potentially overwritten by segments passed in
- insertSegment(default_segment);
- }
- for (segment_vec_t::iterator seg_iter = segments->begin();
- seg_iter != segments->end();
- ++seg_iter)
- {
- LLTextSegment* segmentp = *seg_iter;
- insertSegment(segmentp);
- }
- }
- text.insert(pos, wstr);
- getViewModel()->setDisplay(text);
- if ( truncate() )
- {
- insert_len = getLength() - old_len;
- }
- onValueChange(pos, pos + insert_len);
- needsReflow(pos);
- return insert_len;
- }
- S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
- {
- LLWString text(getWText());
- segment_set_t::iterator seg_iter = getSegIterContaining(pos);
- while(seg_iter != mSegments.end())
- {
- LLTextSegmentPtr segmentp = *seg_iter;
- S32 end = pos + length;
- if (segmentp->getStart() < pos)
- {
- // deleting from middle of segment
- if (segmentp->getEnd() > end)
- {
- segmentp->setEnd(segmentp->getEnd() - length);
- }
- // truncating segment
- else
- {
- segmentp->setEnd(pos);
- }
- }
- else if (segmentp->getStart() < end)
- {
- // deleting entire segment
- if (segmentp->getEnd() <= end)
- {
- // remove segment
- segmentp->unlinkFromDocument(this);
- segment_set_t::iterator seg_to_erase(seg_iter++);
- mSegments.erase(seg_to_erase);
- continue;
- }
- // deleting head of segment
- else
- {
- segmentp->setStart(pos);
- segmentp->setEnd(segmentp->getEnd() - length);
- }
- }
- else
- {
- // shifting segments backward to fill deleted portion
- segmentp->setStart(segmentp->getStart() - length);
- segmentp->setEnd(segmentp->getEnd() - length);
- }
- ++seg_iter;
- }
- text.erase(pos, length);
- getViewModel()->setDisplay(text);
- // recreate default segment in case we erased everything
- createDefaultSegment();
- onValueChange(pos, pos);
- needsReflow(pos);
- return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length
- }
- S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc)
- {
- if (pos > (S32)getLength())
- {
- return 0;
- }
- LLWString text(getWText());
- text[pos] = wc;
- getViewModel()->setDisplay(text);
- onValueChange(pos, pos + 1);
- needsReflow(pos);
- return 1;
- }
- void LLTextBase::createDefaultSegment()
- {
- // ensures that there is always at least one segment
- if (mSegments.empty())
- {
- LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
- LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this);
- mSegments.insert(default_segment);
- default_segment->linkToDocument(this);
- }
- }
- void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert)
- {
- if (segment_to_insert.isNull())
- {
- return;
- }
- 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();
- // split old at start point for new segment
- cur_segmentp->setEnd(segment_to_insert->getStart());
- // advance to next segment
- // insert remainder of old segment
- 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
- mSegments.insert(cur_seg_iter, segment_to_insert);
- segment_to_insert->linkToDocument(this);
- // at this point, there will be two overlapping segments owning the text
- // associated with the incoming segment
- }
- else
- {
- 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 == segment_to_insert)
- {
- ++cur_seg_iter;
- continue;
- }
- if (cur_segmentp->getStart() >= segment_to_insert->getStart())
- {
- 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;
- }
- }
- // layout potentially changed
- needsReflow(reflow_start_index);
- }
- BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleMouseDown(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleMouseDown(x, y, mask);
- }
- BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleMouseUp(x, y, mask))
- {
- // Did we just click on a link?
- if (mURLClickSignal
- && cur_segment->getStyle()
- && cur_segment->getStyle()->isLink())
- {
- // *TODO: send URL here?
- (*mURLClickSignal)(this, LLSD() );
- }
- return TRUE;
- }
- return LLUICtrl::handleMouseUp(x, y, mask);
- }
- BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleMiddleMouseDown(x, y, mask);
- }
- BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleMiddleMouseUp(x, y, mask);
- }
- BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleRightMouseDown(x, y, mask);
- }
- BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleRightMouseUp(x, y, mask);
- }
- BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleDoubleClick(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleDoubleClick(x, y, mask);
- }
- BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleHover(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleHover(x, y, mask);
- }
- BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks))
- {
- return TRUE;
- }
- return LLUICtrl::handleScrollWheel(x, y, clicks);
- }
- BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask)
- {
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleToolTip(x, y, mask))
- {
- return TRUE;
- }
- return LLUICtrl::handleToolTip(x, y, mask);
- }
- void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent)
- {
- if (width != getRect().getWidth() || height != getRect().getHeight())
- {
- bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false;
- LLUICtrl::reshape( width, height, called_from_parent );
- if (mScroller && scrolled_to_bottom && mTrackEnd)
- {
- // keep bottom of text buffer visible
- // do this here as well as in reflow to handle case
- // where shrinking from top, which causes buffer to temporarily
- // not be scrolled to the bottom, since the scroll index
- // specified the _top_ of the visible document region
- mScroller->goToBottom();
- }
- // do this first after reshape, because other things depend on
- // up-to-date mVisibleTextRect
- updateRects();
-
- needsReflow();
- }
- }
- void LLTextBase::draw()
- {
- // reflow if needed, on demand
- reflow();
- // then update scroll position, as cursor may have moved
- if (!mReadOnly)
- {
- updateScrollFromCursor();
- }
- LLRect text_rect;
- if (mScroller)
- {
- mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &text_rect, this);
- }
- else
- {
- LLRect visible_lines_rect;
- std::pair<S32, S32> line_range = getVisibleLines(mClipPartial);
- for (S32 i = line_range.first; i < line_range.second; i++)
- {
- if (visible_lines_rect.isEmpty())
- {
- visible_lines_rect = mLineInfoList[i].mRect;
- }
- else
- {
- visible_lines_rect.unionWith(mLineInfoList[i].mRect);
- }
- }
- text_rect = visible_lines_rect;
- text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom);
- }
- if (mBGVisible)
- {
- F32 alpha = getCurrentTransparency();
- // clip background rect against extents, if we support scrolling
- LLRect bg_rect = mVisibleTextRect;
- if (mScroller)
- {
- bg_rect.intersectWith(text_rect);
- }
- LLColor4 bg_color = mReadOnly
- ? mReadOnlyBgColor.get()
- : hasFocus()
- ? mFocusBgColor.get()
- : mWriteableBgColor.get();
- gl_rect_2d(text_rect, bg_color % alpha, TRUE);
- }
- bool should_clip = mClip || mScroller != NULL;
- { LLLocalClipRect clip(text_rect, should_clip);
-
- // draw document view
- if (mScroller)
- {
- drawChild(mScroller);
- }
- else
- {
- drawChild(mDocumentView);
- }
-
- drawSelectionBackground();
- drawText();
- drawCursor();
- }
-
- mDocumentView->setVisible(FALSE);
- LLUICtrl::draw();
- mDocumentView->setVisible(TRUE);
- }
- //virtual
- void LLTextBase::setColor( const LLColor4& c )
- {
- mFgColor = c;
- mStyleDirty = true;
- }
- //virtual
- void LLTextBase::setReadOnlyColor(const LLColor4 &c)
- {
- mReadOnlyFgColor = c;
- mStyleDirty = true;
- }
- //virtual
- void LLTextBase::handleVisibilityChange( BOOL new_visibility )
- {
- if(!new_visibility && mPopupMenu)
- {
- mPopupMenu->hide();
- }
- LLUICtrl::handleVisibilityChange(new_visibility);
- }
- //virtual
- void LLTextBase::setValue(const LLSD& value )
- {
- setText(value.asString());
- }
- //virtual
- BOOL LLTextBase::canDeselect() const
- {
- return hasSelection();
- }
- //virtual
- void LLTextBase::deselect()
- {
- mSelectionStart = 0;
- mSelectionEnd = 0;
- mIsSelecting = FALSE;
- }
- // Sets the scrollbar from the cursor position
- void LLTextBase::updateScrollFromCursor()
- {
- // Update scroll position even in read-only mode (when there's no cursor displayed)
- // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736.
- if (!mScrollNeeded || !mScroller)
- {
- return;
- }
- mScrollNeeded = FALSE;
- // scroll so that the cursor is at the top of the page
- LLRect scroller_doc_window = getVisibleDocumentRect();
- LLRect cursor_rect_doc = getDocRectFromDocIndex(mCursorPos);
- mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5));
- }
- S32 LLTextBase::getLeftOffset(S32 width)
- {
- switch (mHAlign)
- {
- case LLFontGL::LEFT:
- return mHPad;
- case LLFontGL::HCENTER:
- return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2);
- case LLFontGL::RIGHT:
- return mVisibleTextRect.getWidth() - width;
- default:
- return mHPad;
- }
- }
- static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow");
- void LLTextBase::reflow()
- {
- LLFastTimer ft(FTM_TEXT_REFLOW);
- updateSegments();
- if (mReflowIndex == S32_MAX)
- {
- return;
- }
- bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false;
- LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
- bool follow_selection = getLocalRect().overlaps(cursor_rect); // cursor is (potentially) visible
- // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing
- cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop;
- cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom;
- S32 first_line = getFirstVisibleLine();
- // if scroll anchor not on first line, update it to first character of first line
- if ((first_line < mLineInfoList.size())
- && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart
- || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd))
- {
- mScrollIndex = mLineInfoList[first_line].mDocIndexStart;
- }
- LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex);
- // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing
- first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop;
- first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom;
- S32 reflow_count = 0;
- while(mReflowIndex < S32_MAX)
- {
- // we can get into an infinite loop if the document height does not monotonically increase
- // with decreasing width (embedded ui elements with alternate layouts). In that case,
- // we want to stop reflowing after 2 iterations. We use 2, since we need to handle the case
- // of introducing a vertical scrollbar causing a reflow with less width. We should also always
- // use an even number of iterations to avoid user visible oscillation of the layout
- if(++reflow_count > 2)
- {
- lldebugs << "Breaking out of reflow due to possible infinite loop in " << getName() << llendl;
- break;
- }
-
- 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());
- S32 cur_top = 0;
- segment_set_t::iterator seg_iter = mSegments.begin();
- S32 seg_offset = 0;
- S32 line_start_index = 0;
- 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
- if (!mLineInfoList.empty())
- {
- // 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;
- cur_top = iter->mRect.mTop;
- getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset);
- mLineInfoList.erase(iter, mLineInfoList.end());
- }
- S32 line_height = 0;
- while(seg_iter != mSegments.end())
- {
- LLTextSegmentPtr segment = *seg_iter;
- // track maximum height of any segment on this line
- S32 cur_index = segment->getStart() + seg_offset;
- // ask segment how many character fit in remaining space
- S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX,
- seg_offset,
- cur_index - line_start_index,
- S32_MAX);
- S32 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;
- seg_offset += character_count;
- S32 last_segment_char_on_line = segment->getStart() + seg_offset;
- 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_actual_width,
- cur_top - line_height);
- // if we didn't finish the current segment...
- if (last_segment_char_on_line < segment->getEnd())
- {
- // add line info and keep going
- 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;
- remaining_pixels = text_available_width;
- line_height = 0;
- }
- // ...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,
- line_rect,
- line_count));
- cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
- break;
- }
- // ...or finished a segment and there are segments remaining on this line
- 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
- updateRects();
- for (segment_set_t::iterator segment_it = mSegments.begin();
- segment_it != mSegments.end();
- ++segment_it)
- {
- LLTextSegmentPtr segmentp = *segment_it;
- segmentp->updateLayout(*this);
- }
- }
- // apply scroll constraints after reflowing text
- if (!hasMouseCapture() && mScroller)
- {
- if (scrolled_to_bottom && mTrackEnd)
- {
- // keep bottom of text buffer visible
- endOfDoc();
- }
- else if (hasSelection() && follow_selection)
- {
- // keep cursor in same vertical position on screen when selecting text
- LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos);
- LLRect old_cursor_rect = cursor_rect;
- old_cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop;
- old_cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom;
- mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect);
- }
- else
- {
- // keep first line of text visible
- LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex);
- // pass in desired rect in the coordinate frame of the document viewport
- LLRect old_first_char_rect = first_char_rect;
- old_first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop;
- old_first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom;
- mScroller->scrollToShowRect(new_first_char_rect, old_first_char_rect);
- }
- }
- // reset desired x cursor position
- updateCursorXPos();
- }
- LLRect LLTextBase::getTextBoundingRect()
- {
- reflow();
- return mTextBoundingRect;
- }
- void LLTextBase::clearSegments()
- {
- mSegments.clear();
- createDefaultSegment();
- }
- S32 LLTextBase::getLineStart( S32 line ) const
- {
- S32 num_lines = getLineCount();
- if (num_lines == 0)
- {
- return 0;
- }
- line = llclamp(line, 0, num_lines-1);
- 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
- {
- if (mLineInfoList.empty())
- {
- return 0;
- }
- else
- {
- line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare());
- if (include_wordwrap)
- {
- return iter - mLineInfoList.begin();
- }
- else
- {
- if (iter == mLineInfoList.end())
- {
- return mLineInfoList.back().mLineNum;
- }
- else
- {
- return iter->mLineNum;
- }
- }
- }
- }
- // Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line.
- S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const
- {
- if (mLineInfoList.empty())
- {
- return startpos;
- }
- else
- {
- line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare());
- return startpos - iter->mDocIndexStart;
- }
- }
- S32 LLTextBase::getFirstVisibleLine() const
- {
- LLRect visible_region = getVisibleDocumentRect();
- // binary search for line that starts before top of visible buffer
- line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
- return iter - mLineInfoList.begin();
- }
- std::pair<S32, S32> LLTextBase::getVisibleLines(bool require_fully_visible)
- {
- LLRect visible_region = getVisibleDocumentRect();
- line_list_t::const_iterator first_iter;
- line_list_t::const_iterator last_iter;
- // make sure we have an up-to-date mLineInfoList
- reflow();
- if (require_fully_visible)
- {
- first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top());
- last_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom());
- }
- else
- {
- first_iter = std::upper_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());
- }
- LLTextViewModel* LLTextBase::getViewModel() const
- {
- return (LLTextViewModel*)mViewModel.get();
- }
- void LLTextBase::addDocumentChild(LLView* view)
- {
- mDocumentView->addChild(view);
- }
- void LLTextBase::removeDocumentChild(LLView* view)
- {
- mDocumentView->removeChild(view);
- }
- static LLFastTimer::DeclareTimer FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments");
- void LLTextBase::updateSegments()
- {
- LLFastTimer ft(FTM_UPDATE_TEXT_SEGMENTS);
- createDefaultSegment();
- }
- void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const
- {
- *seg_iter = getSegIterContaining(startpos);
- if (*seg_iter == mSegments.end())
- {
- *offsetp = 0;
- }
- else
- {
- *offsetp = startpos - (**seg_iter)->getStart();
- }
- }
- void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp )
- {
- *seg_iter = getSegIterContaining(startpos);
- if (*seg_iter == mSegments.end())
- {
- *offsetp = 0;
- }
- else
- {
- *offsetp = startpos - (**seg_iter)->getStart();
- }
- }
- LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 index)
- {
- segment_set_t::iterator it = getSegIterContaining(index);
- segment_set_t::iterator orig_it = it;
- if (it == mSegments.end()) return it;
- if (!(*it)->canEdit()
- && index == (*it)->getStart()
- && it != mSegments.begin())
- {
- it--;
- if ((*it)->canEdit())
- {
- return it;
- }
- }
- return orig_it;
- }
- LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaining(S32 index) const
- {
- segment_set_t::const_iterator it = getSegIterContaining(index);
- segment_set_t::const_iterator orig_it = it;
- if (it == mSegments.end()) return it;
- if (!(*it)->canEdit()
- && index == (*it)->getStart()
- && it != mSegments.begin())
- {
- it--;
- if ((*it)->canEdit())
- {
- return it;
- }
- }
- return orig_it;
- }
- LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index)
- {
- static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
- if (index > getLength()) { return mSegments.end(); }
- // when there are no segments, we return the end iterator, which must be checked by caller
- if (mSegments.size() <= 1) { return mSegments.begin(); }
- //FIXME: avoid operator new somehow (without running into refcount problems)
- index_segment->setStart(index);
- index_segment->setEnd(index);
- segment_set_t::iterator it = mSegments.upper_bound(index_segment);
- return it;
- }
- LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const
- {
- static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
- if (index > getLength()) { return mSegments.end(); }
- // when there are no segments, we return the end iterator, which must be checked by caller
- if (mSegments.size() <= 1) { return mSegments.begin(); }
- index_segment->setStart(index);
- index_segment->setEnd(index);
- LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(index_segment);
- return it;
- }
- // Finds the text segment (if any) at the give local screen position
- 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, hit_past_end_of_line);
- segment_set_t::iterator seg_iter = getSegIterContaining(offset);
- if (seg_iter != mSegments.end())
- {
- return *seg_iter;
- }
- else
- {
- return LLTextSegmentPtr();
- }
- }
- void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
- {
- // work out the XUI menu file to use for this url
- LLUrlMatch match;
- std::string url = in_url;
- if (! LLUrlRegistry::instance().findUrl(url, match))
- {
- return;
- }
-
- std::string xui_file = match.getMenuName();
- if (xui_file.empty())
- {
- return;
- }
- // set up the callbacks for all of the potential menu items, N.B. we
- // don't use const ref strings in callbacks in case url goes out of scope
- LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
- registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url));
- registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, 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.ShowProfile", boost::bind(&LLUrlAction::showProfile, 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));
- // create and return the context menu from the XUI file
- delete mPopupMenu;
- mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer,
- LLMenuHolderGL::child_registry_t::instance());
- if (mPopupMenu)
- {
- mPopupMenu->show(x, y);
- LLMenuGL::showPopup(this, mPopupMenu, x, y);
- }
- }
- void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params)
- {
- // clear out the existing text and segments
- getViewModel()->setDisplay(LLWStringUtil::null);
- clearSegments();
- // createDefaultSegment();
- deselect();
- // append the new text (supports Url linking)
- std::string text(utf8str);
- LLStringUtil::removeCRLF(text);
- // appendText modifies mCursorPos...
- appendText(text, false, input_params);
- // ...so move cursor to top after appending text
- if (!mTrackEnd)
- {
- startOfDoc();
- }
- onValueChange(0, getLength());
- }
- //virtual
- std::string LLTextBase::getText() const
- {
- return getViewModel()->getValue().asString();
- }
- // IDEVO - icons can be UI image names or UUID sent from
- // server with avatar display name
- static LLUIImagePtr image_from_icon_name(const std::string& icon_name)
- {
- if (LLUUID::validate(icon_name))
- {
- return LLUI::getUIImageByID( LLUUID(icon_name) );
- }
- else
- {
- return LLUI::getUIImage(icon_name);
- }
- }
- void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params)
- {
- LLStyle::Params style_params(input_params);
- style_params.fillFrom(getDefaultStyleParams());
- S32 part = (S32)LLTextParser::WHOLE;
- if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
- {
- S32 start=0,end=0;
- LLUrlMatch match;
- std::string text = new_text;
- while ( LLUrlRegistry::instance().findUrl(text, match,
- boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) )
- {
-
- LLTextUtil::processUrlMatch(&match,this);
- start = match.getStart();
- end = match.getEnd()+1;
- LLStyle::Params link_params(style_params);
- link_params.overwriteFrom(match.getStyle());
- // output the text before the Url
- if (start > 0)
- {
- if (part == (S32)LLTextParser::WHOLE ||
- part == (S32)LLTextParser::START)
- {
- part = (S32)LLTextParser::START;
- }
- else
- {
- part = (S32)LLTextParser::MIDDLE;
- }
- std::string subtext=text.substr(0,start);
- appendAndHighlightText(subtext, part, style_params);
- }
- // output the styled Url
- appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly());
-
- // set the tooltip for the Url label
- if (! match.getTooltip().empty())
- {
- segment_set_t::iterator it = getSegIterContaining(getLength()-1);
- if (it != mSegments.end())
- {
- LLTextSegmentPtr segment = *it;
- segment->setToolTip(match.getTooltip());
- }
- }
- // move on to the rest of the text after the Url
- if (end < (S32)text.length())
- {
- text = text.substr(end,text.length() - end);
- end=0;
- part=(S32)LLTextParser::END;
- }
- else
- {
- break;
- }
- }
- if (part != (S32)LLTextParser::WHOLE)
- part=(S32)LLTextParser::END;
- if (end < (S32)text.length())
- appendAndHighlightText(text, part, style_params);
- }
- else
- {
- appendAndHighlightText(new_text, part, style_params);
- }
- }
- void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params)
- {
- if (new_text.empty())
- return;
- if(prepend_newline)
- appendLineBreakSegment(input_params);
- appendTextImpl(new_text,input_params);
- }
- void LLTextBase::needsReflow(S32 index)
- {
- lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl;
- mReflowIndex = llmin(mReflowIndex, index);
- }
- void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params)
- {
- segment_vec_t segments;
- LLStyleConstSP sp(new LLStyle(style_params));
- segments.push_back(new LLLineBreakTextSegment(sp, getLength()));
- insertStringNoUndo(getLength(), utf8str_to_wstring("\n"), &segments);
- }
- void LLTextBase::appendImageSegment(const LLStyle::Params& style_params)
- {
- if(getPlainText())
- {
- return;
- }
- segment_vec_t segments;
- LLStyleConstSP sp(new LLStyle(style_params));
- segments.push_back(new LLImageTextSegment(sp, getLength(),*this));
- insertStringNoUndo(getLength(), utf8str_to_wstring(" "), &segments);
- }
- void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo)
- {
- segment_vec_t segments;
- LLWString widget_wide_text = utf8str_to_wstring(text);
- segmen…
Large files files are truncated, but you can click here to view the full file