diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 21:25:21 +0200 |
---|---|---|
committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2024-05-22 22:40:26 +0300 |
commit | e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch) | |
tree | 1bb897489ce524986f6196201c10ac0d8861aa5f /indra/llui/lltextbase.cpp | |
parent | 069ea06848f766466f1a281144c82a0f2bd79f3a (diff) |
Fix line endlings
Diffstat (limited to 'indra/llui/lltextbase.cpp')
-rw-r--r-- | indra/llui/lltextbase.cpp | 7780 |
1 files changed, 3890 insertions, 3890 deletions
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index f6dd38bce5..1249461be9 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1,3890 +1,3890 @@ -/**
- * @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 "llemojidictionary.h"
-#include "llemojihelper.h"
-#include "lllocalcliprect.h"
-#include "llmenugl.h"
-#include "llscrollcontainer.h"
-#include "llspellcheck.h"
-#include "llstl.h"
-#include "lltextparser.h"
-#include "lltextutil.h"
-#include "lltooltip.h"
-#include "lltrans.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;
-const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click.
-
-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
-bool LLTextBase::compare_bottom::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 LLTextBase::compare_bottom::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 LLTextBase::compare_bottom::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
-bool LLTextBase::compare_top::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 LLTextBase::compare_top::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 LLTextBase::compare_top::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"),
- text_tentative_color("text_tentative_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),
- skip_link_underline("skip_link_underline", false),
- spellcheck("spellcheck", 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"),
- text_valign("text_valign"),
- wrap("wrap"),
- trusted_content("trusted_content", true),
- always_show_icons("always_show_icons", false),
- use_ellipses("use_ellipses", false),
- use_emoji("use_emoji", true),
- use_color("use_color", true),
- parse_urls("parse_urls", false),
- force_urls_external("force_urls_external", 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),
- mIsFriendSignal(NULL),
- mIsObjectBlockedSignal(NULL),
- mMaxTextByteLength( p.max_text_length ),
- mFont(p.font),
- mFontShadow(p.font_shadow),
- mPopupMenuHandle(),
- mReadOnly(p.read_only),
- mSkipTripleClick(false),
- mSkipLinkUnderline(p.skip_link_underline),
- mSpellCheck(p.spellcheck),
- mSpellCheckStart(-1),
- mSpellCheckEnd(-1),
- mCursorColor(p.cursor_color),
- mFgColor(p.text_color),
- mBorderVisible( p.border_visible ),
- mReadOnlyFgColor(p.text_readonly_color),
- mTentativeFgColor(p.text_tentative_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),
- mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()),
- mLineSpacingMult(p.line_spacing.multiple),
- mLineSpacingPixels(p.line_spacing.pixels),
- mClip(p.clip),
- mClipPartial(p.clip_partial && !p.allow_scroll),
- mTrustedContent(p.trusted_content),
- mAlwaysShowIcons(p.always_show_icons),
- mTrackEnd( p.track_end ),
- mScrollIndex(-1),
- mSelectionStart( 0 ),
- mSelectionEnd( 0 ),
- mIsSelecting( false ),
- mPlainText ( p.plain_text ),
- mWordWrap(p.wrap),
- mUseEllipses( p.use_ellipses ),
- mUseEmoji(p.use_emoji),
- mUseColor(p.use_color),
- mParseHTML(p.parse_urls),
- mForceUrlsExternal(p.force_urls_external),
- 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);
- }
-
- if (mSpellCheck)
- {
- LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this));
- }
- mSpellCheckTimer.reset();
-
- createDefaultSegment();
-
- updateRects();
-}
-
-LLTextBase::~LLTextBase()
-{
- mSegments.clear();
- LLContextMenu* menu = static_cast<LLContextMenu*>(mPopupMenuHandle.get());
- if (menu)
- {
- menu->die();
- mPopupMenuHandle.markDead();
- }
- delete mURLClickSignal;
- delete mIsFriendSignal;
- delete mIsObjectBlockedSignal;
-}
-
-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
- S32 utf8_byte_size = 0;
- LLSD value = getViewModel()->getValue();
- if (value.type() == LLSD::TypeString)
- {
- // save a copy for strings.
- utf8_byte_size = value.size();
- }
- else
- {
- // non string LLSDs need explicit conversion to string
- utf8_byte_size = value.asString().size();
- }
-
- if ( utf8_byte_size > mMaxTextByteLength )
- {
- // Truncate safely in UTF-8
- std::string temp_utf8_text = value.asString();
- 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::getStyleParams()
-{
- //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)
- {
- mStyle
- .color(LLUIColor(&mFgColor)) // pass linked color instead of copy of mFGColor
- .readonly_color(LLUIColor(&mReadOnlyFgColor))
- .selected_color(LLUIColor(&mTextSelectedColor))
- .font(mFont)
- .drop_shadow(mFontShadow);
- mStyleDirty = false;
- }
- return mStyle;
-}
-
-void LLTextBase::beforeValueChange()
-{
-
-}
-
-void LLTextBase::onValueChange(S32 start, S32 end)
-{
-}
-
-std::vector<LLRect> LLTextBase::getSelectionRects()
-{
- // Nor supposed to be called without selection
- llassert(hasSelection());
- llassert(!mLineInfoList.empty());
-
- std::vector<LLRect> selection_rects;
-
- S32 selection_left = llmin(mSelectionStart, mSelectionEnd);
- S32 selection_right = llmax(mSelectionStart, mSelectionEnd);
-
- // 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);
-
- // Use F32 otherwise a string of multiple segments
- // will accumulate a large error
- F32 left_precise = line_iter->mRect.mLeft;
- F32 right_precise = line_iter->mRect.mLeft;
-
- 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;
-
- F32 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->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height);
- left_precise += 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->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height);
- right_precise += segment_width;
- }
- // else if selection ends on current segment...
- else
- {
- S32 num_chars = selection_right - segment_line_start;
- segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height);
- right_precise += segment_width;
-
- break;
- }
- }
-
- LLRect selection_rect;
- selection_rect.mLeft = left_precise;
- selection_rect.mRight = right_precise;
- selection_rect.mBottom = line_iter->mRect.mBottom;
- selection_rect.mTop = line_iter->mRect.mTop;
-
- selection_rects.push_back(selection_rect);
- }
- }
-
- return selection_rects;
-}
-
-// 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 = getSelectionRects();
-
- // 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);
- LLRect content_display_rect = getVisibleDocumentRect();
-
- for (std::vector<LLRect>::iterator rect_it = selection_rects.begin();
- rect_it != selection_rects.end();
- ++rect_it)
- {
- LLRect selection_rect = *rect_it;
- if (mScroller)
- {
- // If scroller is On content_display_rect has correct rect and safe to use as is
- // Note: we might need to account for border
- selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
- }
- else
- {
- // If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position
- // and we have to acount for offset depending on position
- S32 v_delta = 0;
- S32 h_delta = 0;
- switch (mVAlign)
- {
- case LLFontGL::TOP:
- v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad;
- break;
- case LLFontGL::VCENTER:
- v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2;
- break;
- case LLFontGL::BOTTOM:
- v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom;
- break;
- default:
- break;
- }
- switch (mHAlign)
- {
- case LLFontGL::LEFT:
- h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad;
- break;
- case LLFontGL::HCENTER:
- h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2;
- break;
- case LLFontGL::RIGHT:
- h_delta = mVisibleTextRect.mRight - content_display_rect.mRight;
- break;
- default:
- break;
- }
- selection_rect.translate(h_delta, v_delta);
- }
- 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, mTextVAlign,
- 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::getScaleFactor().mV[VX]);
- ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]);
- getWindow()->setLanguageTextInput( ime_pos );
- }
- }
-}
-
-void LLTextBase::drawText()
-{
- S32 text_len = getLength();
-
- if (text_len <= 0 && mLabel.empty())
- {
- return;
- }
- else if (useLabel())
- {
- text_len = mLabel.getWString().length();
- }
-
- 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;
- }
-
- // Perform spell check if needed
- if ( (getSpellCheck()) && (getWText().length() > 2) )
- {
- // Calculate start and end indices for the spell checking range
- S32 start = line_start;
- S32 end = getLineEnd(last_line);
-
- if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) )
- {
- const LLWString& wstrText = getWText();
- mMisspellRanges.clear();
-
- segment_set_t::const_iterator seg_it = getSegIterContaining(start);
- while (mSegments.end() != seg_it)
- {
- LLTextSegmentPtr text_segment = *seg_it;
- if ( (text_segment.isNull()) || (text_segment->getStart() >= end) )
- {
- break;
- }
-
- if (!text_segment->canEdit())
- {
- ++seg_it;
- continue;
- }
-
- // Combine adjoining text segments into one
- U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end);
- while (mSegments.end() != ++seg_it)
- {
- text_segment = *seg_it;
- if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) )
- {
- break;
- }
- seg_end = llmin(text_segment->getEnd(), end);
- }
-
- // Find the start of the first word
- U32 word_start = seg_start, word_end = -1;
- U32 text_length = wstrText.length();
- while ( (word_start < text_length) && (!LLStringOps::isAlpha(wstrText[word_start])) )
- {
- word_start++;
- }
-
- // Iterate over all words in the text block and check them one by one
- while (word_start < seg_end)
- {
- // Find the end of the current word (special case handling for "'" when it's used as a contraction)
- word_end = word_start + 1;
- while ( (word_end < seg_end) &&
- ((LLWStringUtil::isPartOfWord(wstrText[word_end])) ||
- ((L'\'' == wstrText[word_end]) &&
- (LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) )
- {
- word_end++;
- }
- if (word_end > seg_end)
- {
- break;
- }
-
- if (word_start < text_length && word_end <= text_length && word_end > word_start)
- {
- std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start));
-
- // Don't process words shorter than 3 characters
- if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) )
- {
- mMisspellRanges.push_back(std::pair<U32, U32>(word_start, word_end));
- }
- }
-
- // Find the start of the next word
- word_start = word_end + 1;
- while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) )
- {
- word_start++;
- }
- }
- }
-
- mSpellCheckStart = start;
- mSpellCheckEnd = end;
- }
- }
- else
- {
- mMisspellRanges.clear();
- }
-
- LLTextSegmentPtr cur_segment = *seg_iter;
-
- std::list<std::pair<U32, U32> >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair<U32, U32>(line_start, 0));
- 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;
- }
-
- LLRectf text_rect(line.mRect.mLeft, line.mRect.mTop, line.mRect.mRight, line.mRect.mBottom);
- 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())
- {
- LL_WARNS() << "Ran off the segmentation end!" << LL_ENDL;
-
- return;
- }
- cur_segment = *seg_iter;
- }
-
- S32 seg_end = llmin(line_end, cur_segment->getEnd());
- S32 clipped_end = seg_end - 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;
- }
-
- // Draw squiggly lines under any visible misspelled words
- while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) )
- {
- // Skip the current word if the user is still busy editing it
- if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) )
- {
- ++misspell_it;
- continue;
- }
-
- U32 misspell_start = llmax<U32>(misspell_it->first, seg_start), misspell_end = llmin<U32>(misspell_it->second, seg_end);
- S32 squiggle_start = 0, squiggle_end = 0, pony = 0;
- cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_start - seg_start, squiggle_start, pony);
- cur_segment->getDimensions(misspell_start - cur_segment->getStart(), misspell_end - misspell_start, squiggle_end, pony);
- squiggle_start += text_rect.mLeft;
-
- pony = (squiggle_end + 3) / 6;
- squiggle_start += squiggle_end / 2 - pony * 3;
- squiggle_end = squiggle_start + pony * 6;
-
- S32 squiggle_bottom = text_rect.mBottom + (S32)cur_segment->getStyle()->getFont()->getDescenderHeight();
-
- gGL.color4ub(255, 0, 0, 200);
- while (squiggle_start + 1 < squiggle_end)
- {
- gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2);
- if (squiggle_start + 3 < squiggle_end)
- {
- gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1);
- }
- squiggle_start += 4;
- }
-
- if (misspell_it->second > seg_end)
- {
- break;
- }
- ++misspell_it;
- }
-
- text_rect.mLeft = 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 )
-{
- beforeValueChange();
-
- S32 old_len = getLength(); // length() returns character length
- S32 insert_len = wstr.length();
-
- pos = getEditableIndex(pos, true);
- if (pos > old_len)
- {
- pos = old_len;
- // Should not happen,
- // if you encounter this, check where wrong position comes from
- llassert(false);
- }
-
- 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(getStyleParams()));
- 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);
- }
- }
-
- // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us)
- if (mUseEmoji)
- {
- LLStyleSP emoji_style;
- LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL;
- for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++)
- {
- llwchar code = wstr[text_kitty];
- bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code);
- if (isEmoji)
- {
- if (!emoji_style)
- {
- emoji_style = new LLStyle(getStyleParams());
- emoji_style->setFont(LLFontGL::getFontEmojiLarge());
- }
-
- S32 new_seg_start = pos + text_kitty;
- insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this));
- }
- }
- }
-
- getViewModel()->getEditableDisplay().insert(pos, wstr);
-
- if ( truncate() )
- {
- insert_len = getLength() - old_len;
- }
-
- onValueChange(pos, pos + insert_len);
- needsReflow(pos);
-
- return insert_len;
-}
-
-S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
-{
-
- beforeValueChange();
- 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;
- }
-
- getViewModel()->getEditableDisplay().erase(pos, length);
-
- // 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)
-{
- beforeValueChange();
-
- if (pos > (S32)getLength())
- {
- return 0;
- }
- getViewModel()->getEditableDisplay()[pos] = wc;
-
- 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(getStyleParams()));
- 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);
-}
-
-//virtual
-bool LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
-{
- // handle triple click
- if (!mTripleClickTimer.hasExpired())
- {
- if (mSkipTripleClick)
- {
- return true;
- }
-
- S32 real_line = getLineNumFromDocIndex(mCursorPos, false);
- S32 line_start = -1;
- S32 line_end = -1;
- for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end();
- it != end_it;
- ++it)
- {
- if (it->mLineNum < real_line)
- {
- continue;
- }
- if (it->mLineNum > real_line)
- {
- break;
- }
- if (line_start == -1)
- {
- line_start = it->mDocIndexStart;
- }
- line_end = it->mDocIndexEnd;
- line_end = llclamp(line_end, 0, getLength());
- }
-
- if (line_start == -1)
- {
- return true;
- }
-
- mSelectionEnd = line_start;
- mSelectionStart = line_end;
- setCursorPos(line_start);
-
- return true;
- }
-
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleMouseDown(x, y, mask))
- {
- return true;
- }
-
- return LLUICtrl::handleMouseDown(x, y, mask);
-}
-
-//virtual
-bool LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
-{
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (hasMouseCapture() && 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);
-}
-
-//virtual
-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);
-}
-
-//virtual
-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);
-}
-
-//virtual
-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);
-}
-
-//virtual
-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);
-}
-
-//virtual
-bool LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
-{
- //Don't start triple click timer if user have clicked on scrollbar
- mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
- if (x >= mVisibleTextRect.mLeft && x <= mVisibleTextRect.mRight
- && y >= mVisibleTextRect.mBottom && y <= mVisibleTextRect.mTop)
- {
- mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL);
- }
-
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment && cur_segment->handleDoubleClick(x, y, mask))
- {
- return true;
- }
-
- return LLUICtrl::handleDoubleClick(x, y, mask);
-}
-
-//virtual
-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);
-}
-
-//virtual
-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);
-}
-
-//virtual
-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);
-}
-
-//virtual
-void LLTextBase::reshape(S32 width, S32 height, bool called_from_parent)
-{
- if (width != getRect().getWidth() || height != getRect().getHeight() || LLView::sForceReshape)
- {
- 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();
- }
-}
-
-//virtual
-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);
- }
-
- // Draw highlighted if needed
- if( ll::ui::SearchableControl::getHighlighted() )
- {
- LLColor4 bg_color = ll::ui::SearchableControl::getHighlightColor();
- LLRect bg_rect = mVisibleTextRect;
- if( mScroller )
- bg_rect.intersectWith( text_rect );
-
- gl_rect_2d( text_rect, bg_color, 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->setVisibleDirect(false);
- LLUICtrl::draw();
- mDocumentView->setVisibleDirect(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::onVisibilityChange( bool new_visibility )
-{
- LLContextMenu* menu = static_cast<LLContextMenu*>(mPopupMenuHandle.get());
- if(!new_visibility && menu)
- {
- menu->hide();
- }
- LLUICtrl::onVisibilityChange(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;
-}
-
-bool LLTextBase::getSpellCheck() const
-{
- return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
-}
-
-const std::string& LLTextBase::getSuggestion(U32 index) const
-{
- return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null;
-}
-
-U32 LLTextBase::getSuggestionCount() const
-{
- return mSuggestionList.size();
-}
-
-void LLTextBase::replaceWithSuggestion(U32 index)
-{
- for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
- {
- if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
- {
- deselect();
- // Insert the suggestion in its place
- LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
- insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index]));
-
- // Delete the misspelled word
- removeStringNoUndo(it->first + (S32)suggestion.length(), it->second - it->first);
-
-
- setCursorPos(it->first + (S32)suggestion.length());
- onSpellCheckPerformed();
-
- break;
- }
- }
- mSpellCheckStart = mSpellCheckEnd = -1;
-}
-
-void LLTextBase::addToDictionary()
-{
- if (canAddToDictionary())
- {
- LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos));
- }
-}
-
-bool LLTextBase::canAddToDictionary() const
-{
- return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
-}
-
-void LLTextBase::addToIgnore()
-{
- if (canAddToIgnore())
- {
- LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos));
- }
-}
-
-bool LLTextBase::canAddToIgnore() const
-{
- return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
-}
-
-std::string LLTextBase::getMisspelledWord(U32 pos) const
-{
- for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
- {
- if ( (it->first <= pos) && (it->second >= pos) )
- {
- return wstring_to_utf8str(getWText().substr(it->first, it->second - it->first));
- }
- }
- return LLStringUtil::null;
-}
-
-bool LLTextBase::isMisspelledWord(U32 pos) const
-{
- for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
- {
- if ( (it->first <= pos) && (it->second >= pos) )
- {
- return true;
- }
- }
- return false;
-}
-
-void LLTextBase::onSpellCheckSettingsChange()
-{
- // Recheck the spelling on every change
- mMisspellRanges.clear();
- mSpellCheckStart = mSpellCheckEnd = -1;
-}
-
-void LLTextBase::onFocusReceived()
-{
- LLUICtrl::onFocusReceived();
- if (!getLength() && !mLabel.empty())
- {
- // delete label which is LLLabelTextSegment
- clearSegments();
- }
-}
-
-void LLTextBase::onFocusLost()
-{
- LLUICtrl::onFocusLost();
- if (!getLength() && !mLabel.empty())
- {
- resetLabel();
- }
-}
-
-// 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:
- {
- // Font's rendering rounds string size, if value gets rounded
- // down last symbol might not have enough space to render,
- // compensate by adding an extra pixel as padding
- const S32 right_padding = 1;
- return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding);
- }
- default:
- return mHPad;
- }
-}
-
-void LLTextBase::reflow()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
-
- 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)
- {
- LL_DEBUGS() << "Breaking out of reflow due to possible infinite loop in " << getName() << LL_ENDL;
- 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
- if (mWordWrap)
- {
- 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 F32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin
- F32 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());
- if (iter != mLineInfoList.end())
- {
- 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;
- S32 seg_line_offset = line_count + 1;
-
- 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, ll_round(remaining_pixels)) : S32_MAX,
- seg_offset,
- cur_index - line_start_index,
- S32_MAX,
- line_count - seg_line_offset);
-
- F32 segment_width;
- S32 segment_height;
- bool force_newline = segment->getDimensionsF32(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;
-
- // Note: make sure text will fit in width - use ceil, but also make sure
- // ceil is used only once per line
- S32 text_actual_width = llceil(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 -= ll_round((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 -= ll_round((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 -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
- line_height = 0;
- remaining_pixels = text_available_width;
- }
- ++seg_iter;
- seg_offset = 0;
- seg_line_offset = force_newline ? line_count + 1 : line_count;
- }
- 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);
-}
-
-
-void LLTextBase::updateSegments()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
- 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();
-
- // 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);
- 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();
-
- // 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, true));
- registrar.add("Url.Block", boost::bind(&LLUrlAction::blockObject, url));
- registrar.add("Url.Unblock", boost::bind(&LLUrlAction::unblockObject, url));
- registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url));
- registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url));
- registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url));
- registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url));
- registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url));
- registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, 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
-
- LLContextMenu* menu = static_cast<LLContextMenu*>(mPopupMenuHandle.get());
- if (menu)
- {
- menu->die();
- mPopupMenuHandle.markDead();
- }
- llassert(LLMenuGL::sMenuContainer != NULL);
- menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer,
- LLMenuHolderGL::child_registry_t::instance());
- if (menu)
- {
- mPopupMenuHandle = menu->getHandle();
-
- if (mIsFriendSignal)
- {
- bool isFriend = *(*mIsFriendSignal)(LLUUID(LLUrlAction::getUserID(url)));
- LLView* addFriendButton = menu->getChild<LLView>("add_friend");
- LLView* removeFriendButton = menu->getChild<LLView>("remove_friend");
-
- if (addFriendButton && removeFriendButton)
- {
- addFriendButton->setEnabled(!isFriend);
- removeFriendButton->setEnabled(isFriend);
- }
- }
-
- if (mIsObjectBlockedSignal)
- {
- bool is_blocked = *(*mIsObjectBlockedSignal)(LLUUID(LLUrlAction::getObjectId(url)), LLUrlAction::getObjectName(url));
- LLView* blockButton = menu->getChild<LLView>("block_object");
- LLView* unblockButton = menu->getChild<LLView>("unblock_object");
-
- if (blockButton && unblockButton)
- {
- blockButton->setVisible(!is_blocked);
- unblockButton->setVisible(is_blocked);
- }
- }
- menu->show(x, y);
- LLMenuGL::showPopup(this, menu, 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
-const std::string& LLTextBase::getText() const
-{
- return getViewModel()->getStringValue();
-}
-
-// 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)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
- LLStyle::Params style_params(input_params);
- style_params.fillFrom(getStyleParams());
-
- 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), isContentTrusted() || mAlwaysShowIcons))
- {
- 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);
- }
-
- // add icon before url if need
- LLTextUtil::processUrlMatch(&match, this, isContentTrusted() || match.isTrusted() || mAlwaysShowIcons);
- if ((isContentTrusted() || match.isTrusted()) && !match.getIcon().empty() )
- {
- setLastSegmentToolTip(LLTrans::getString("TooltipSLIcon"));
- }
-
- // output the styled Url
- appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly());
- bool tooltip_required = !match.getTooltip().empty();
-
- // set the tooltip for the Url label
- if (tooltip_required)
- {
- setLastSegmentToolTip(match.getTooltip());
- }
-
- // show query part of url with gray color only for LLUrlEntryHTTP url entries
- std::string label = match.getQuery();
- if (label.size())
- {
- link_params.color = LLColor4::grey;
- link_params.readonly_color = LLColor4::grey;
- appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly());
-
- // set the tooltip for the query part of url
- if (tooltip_required)
- {
- setLastSegmentToolTip(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::setLastSegmentToolTip(const std::string &tooltip)
-{
- segment_set_t::iterator it = getSegIterContaining(getLength()-1);
- if (it != mSegments.end())
- {
- LLTextSegmentPtr segment = *it;
- segment->setToolTip(tooltip);
- }
-}
-
-void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
- if (new_text.empty())
- return;
-
- if(prepend_newline)
- appendLineBreakSegment(input_params);
- appendTextImpl(new_text,input_params);
-}
-
-void LLTextBase::setLabel(const LLStringExplicit& label)
-{
- mLabel = label;
- resetLabel();
-}
-
-bool LLTextBase::setLabelArg(const std::string& key, const LLStringExplicit& text )
-{
- mLabel.setArg(key, text);
- return true;
-}
-
-void LLTextBase::resetLabel()
-{
- if (useLabel())
- {
- clearSegments();
-
- LLStyle* style = new LLStyle(getStyleParams());
- style->setColor(mTentativeFgColor);
- LLStyleConstSP sp(style);
-
- LLTextSegmentPtr label = new LLLabelTextSegment(sp, 0, mLabel.getWString().length() + 1, *this);
- insertSegment(label);
- }
-}
-
-bool LLTextBase::useLabel() const
-{
- return !getLength() && !mLabel.empty() && !hasFocus();
-}
-
-void LLTextBase::setFont(const LLFontGL* font)
-{
- mFont = font;
- mStyleDirty = true;
-}
-
-void LLTextBase::needsReflow(S32 index)
-{
- LL_DEBUGS() << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << LL_ENDL;
- mReflowIndex = llmin(mReflowIndex, index);
-}
-
-S32 LLTextBase::removeFirstLine()
-{
- if (!mLineInfoList.empty())
- {
- S32 length = getLineEnd(0);
- deselect();
- removeStringNoUndo(0, length);
- return length;
- }
- return 0;
-}
-
-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);
- segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size()));
-
- insertStringNoUndo(getLength(), widget_wide_text, &segments);
-}
-
-void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
-{
- // Save old state
- S32 selection_start = mSelectionStart;
- S32 selection_end = mSelectionEnd;
- bool was_selecting = mIsSelecting;
- S32 cursor_pos = mCursorPos;
- S32 old_length = getLength();
- bool cursor_was_at_end = (mCursorPos == old_length);
-
- deselect();
-
- setCursorPos(old_length);
-
- if (mParseHighlights)
- {
- LLStyle::Params highlight_params(style_params);
-
- LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part);
- for (S32 i = 0; i < pieces.size(); i++)
- {
- LLSD color_llsd = pieces[i]["color"];
- LLColor4 lcolor;
- lcolor.setValue(color_llsd);
- highlight_params.color = lcolor;
-
- LLWString wide_text;
- wide_text = utf8str_to_wstring(pieces[i]["text"].asString());
-
- S32 cur_length = getLength();
- LLStyleConstSP sp(new LLStyle(highlight_params));
- LLTextSegmentPtr segmentp;
- if (underline_on_hover_only || mSkipLinkUnderline)
- {
- highlight_params.font.style("NORMAL");
- LLStyleConstSP normal_sp(new LLStyle(highlight_params));
- segmentp = new LLOnHoverChangeableTextSegment(sp, normal_sp, cur_length, cur_length + wide_text.size(), *this);
- }
- else
- {
- 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);
- }
- }
- else
- {
- LLWString wide_text;
- wide_text = utf8str_to_wstring(new_text);
-
- segment_vec_t segments;
- S32 segment_start = old_length;
- S32 segment_end = old_length + wide_text.size();
- LLStyleConstSP sp(new LLStyle(style_params));
- if (underline_on_hover_only || mSkipLinkUnderline)
- {
- LLStyle::Params normal_style_params(style_params);
- normal_style_params.font.style("NORMAL");
- LLStyleConstSP normal_sp(new LLStyle(normal_style_params));
- segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this));
- }
- else
- {
- segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this));
- }
-
- insertStringNoUndo(getLength(), wide_text, &segments);
- }
-
- // Set the cursor and scroll position
- if (selection_start != selection_end)
- {
- mSelectionStart = selection_start;
- mSelectionEnd = selection_end;
-
- mIsSelecting = was_selecting;
- setCursorPos(cursor_pos);
- }
- else if (cursor_was_at_end)
- {
- setCursorPos(getLength());
- }
- else
- {
- setCursorPos(cursor_pos);
- }
-}
-
-void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
-{
- if (new_text.empty())
- {
- return;
- }
-
- std::string::size_type start = 0;
- std::string::size_type pos = new_text.find("\n", start);
-
- while (pos != std::string::npos)
- {
- if (pos != start)
- {
- std::string str = std::string(new_text,start,pos-start);
- appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
- }
- appendLineBreakSegment(style_params);
- start = pos+1;
- pos = new_text.find("\n", start);
- }
-
- std::string str = std::string(new_text, start, new_text.length() - start);
- appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
-}
-
-
-void LLTextBase::replaceUrl(const std::string &url,
- const std::string &label,
- const std::string &icon)
-{
- // get the full (wide) text for the editor so we can change it
- LLWString text = getWText();
- LLWString wlabel = utf8str_to_wstring(label);
- bool modified = false;
- S32 seg_start = 0;
-
- // iterate through each segment looking for ones styled as links
- segment_set_t::iterator it;
- for (it = mSegments.begin(); it != mSegments.end(); ++it)
- {
- LLTextSegment *seg = *it;
- LLStyleConstSP style = seg->getStyle();
-
- // update segment start/end length in case we replaced text earlier
- S32 seg_length = seg->getEnd() - seg->getStart();
- seg->setStart(seg_start);
- seg->setEnd(seg_start + seg_length);
-
- // if we find a link with our Url, then replace the label
- if (style->getLinkHREF() == url)
- {
- S32 start = seg->getStart();
- S32 end = seg->getEnd();
- text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1);
- seg->setEnd(start + wlabel.size());
- modified = true;
- }
-
- // Icon might be updated when more avatar or group info
- // becomes available
- if (style->isImage() && style->getLinkHREF() == url)
- {
- LLUIImagePtr image = image_from_icon_name( icon );
- if (image)
- {
- LLStyle::Params icon_params;
- icon_params.image = image;
- LLStyleConstSP new_style(new LLStyle(icon_params));
- seg->setStyle(new_style);
- modified = true;
- }
- }
-
- // work out the character offset for the next segment
- seg_start = seg->getEnd();
- }
-
- // update the editor with the new (wide) text string
- if (modified)
- {
- getViewModel()->setDisplay(text);
- deselect();
- setCursorPos(mCursorPos);
- needsReflow();
- }
-}
-
-
-void LLTextBase::setWText(const LLWString& text)
-{
- setText(wstring_to_utf8str(text));
-}
-
-const LLWString& LLTextBase::getWText() const
-{
- return getViewModel()->getDisplay();
-}
-
-// If round is true, if the position is on the right half of a character, the cursor
-// 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, bool hit_past_end_of_line) const
-{
- // Figure out which line we're nearest to.
- LLRect doc_rect = mDocumentView->getRect();
- S32 doc_y = local_y - doc_rect.mBottom;
-
- // binary search for line that starts before local_y
- line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_y, compare_bottom());
-
- if (!mLineInfoList.size() || line_iter == mLineInfoList.end())
- {
- return getLength(); // past the end
- }
-
- S32 pos = getLength();
- F32 start_x = line_iter->mRect.mLeft + doc_rect.mLeft;
-
- segment_set_t::iterator line_seg_iter;
- S32 line_seg_offset;
- for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset);
- line_seg_iter != mSegments.end();
- ++line_seg_iter, line_seg_offset = 0)
- {
- const LLTextSegmentPtr segmentp = *line_seg_iter;
-
- S32 segment_line_start = segmentp->getStart() + line_seg_offset;
- S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start;
- F32 text_width;
- S32 text_height;
- bool newline = segmentp->getDimensionsF32(line_seg_offset, segment_line_length, text_width, text_height);
-
- if(newline)
- {
- pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round);
- break;
- }
-
- // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line
- if (hit_past_end_of_line && doc_y > line_iter->mRect.mTop)
- {
- pos = segment_line_start;
- break;
- }
- if (local_x < start_x + text_width) // cursor to left of right edge of text
- {
- // Figure out which character we're nearest to.
- S32 offset;
- if (!segmentp->canEdit())
- {
- F32 segment_width;
- S32 segment_height;
- segmentp->getDimensionsF32(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height);
- if (round && local_x - start_x > segment_width / 2)
- {
- offset = segment_line_length;
- }
- else
- {
- offset = 0;
- }
- }
- else
- {
- offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round);
- }
- pos = segment_line_start + offset;
- break;
- }
- else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd)
- {
- if (getLineNumFromDocIndex(line_iter->mDocIndexEnd - 1) == line_iter->mLineNum)
- {
- // if segment wraps to the next line we should step one char back
- // to compensate for the space char between words
- // which is removed due to wrapping
- pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength());
- }
- else
- {
- pos = llclamp(line_iter->mDocIndexEnd, 0, getLength());
- }
- break;
- }
- start_x += text_width;
- }
-
- return pos;
-}
-
-// returns rectangle of insertion caret
-// in document coordinate frame from given index into text
-LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const
-{
- if (mLineInfoList.empty())
- {
- return LLRect();
- }
-
- // clamp pos to valid values
- pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1);
-
- line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare());
-
- segment_set_t::iterator line_seg_iter;
- S32 line_seg_offset;
- segment_set_t::iterator cursor_seg_iter;
- S32 cursor_seg_offset;
- getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset);
- getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset);
-
- F32 doc_left_precise = line_iter->mRect.mLeft;
-
- while(line_seg_iter != mSegments.end())
- {
- const LLTextSegmentPtr segmentp = *line_seg_iter;
-
- if (line_seg_iter == cursor_seg_iter)
- {
- // cursor advanced to right based on difference in offset of cursor to start of line
- F32 segment_width;
- S32 segment_height;
- segmentp->getDimensionsF32(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height);
- doc_left_precise += segment_width;
-
- break;
- }
- else
- {
- // add remainder of current text segment to cursor position
- F32 segment_width;
- S32 segment_height;
- segmentp->getDimensionsF32(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height);
- doc_left_precise += segment_width;
- // offset will be 0 for all segments after the first
- line_seg_offset = 0;
- // go to next text segment on this line
- ++line_seg_iter;
- }
- }
-
- LLRect doc_rect;
- doc_rect.mLeft = doc_left_precise;
- doc_rect.mBottom = line_iter->mRect.mBottom;
- doc_rect.mTop = line_iter->mRect.mTop;
-
- // set rect to 0 width
- doc_rect.mRight = doc_rect.mLeft;
-
- return doc_rect;
-}
-
-LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const
-{
- LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
- if (mBorderVisible)
- {
- content_window_rect.stretch(-1);
- }
-
- LLRect local_rect;
-
- if (mLineInfoList.empty())
- {
- // return default height rect in upper left
- local_rect = content_window_rect;
- local_rect.mBottom = local_rect.mTop - mFont->getLineHeight();
- return local_rect;
- }
-
- // get the rect in document coordinates
- LLRect doc_rect = getDocRectFromDocIndex(pos);
-
- // compensate for scrolled, inset view of doc
- LLRect scrolled_view_rect = getVisibleDocumentRect();
- local_rect = doc_rect;
- local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft,
- content_window_rect.mBottom - scrolled_view_rect.mBottom);
-
- return local_rect;
-}
-
-void LLTextBase::updateCursorXPos()
-{
- // reset desired x cursor position
- mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft;
-}
-
-
-void LLTextBase::startOfLine()
-{
- S32 offset = getLineOffsetFromDocIndex(mCursorPos);
- setCursorPos(mCursorPos - offset);
-}
-
-void LLTextBase::endOfLine()
-{
- S32 line = getLineNumFromDocIndex(mCursorPos);
- S32 num_lines = getLineCount();
- if (line + 1 >= num_lines)
- {
- setCursorPos(getLength());
- }
- else
- {
- setCursorPos( getLineStart(line + 1) - 1 );
- }
-}
-
-void LLTextBase::startOfDoc()
-{
- setCursorPos(0);
- if (mScroller)
- {
- mScroller->goToTop();
- }
-}
-
-void LLTextBase::endOfDoc()
-{
- setCursorPos(getLength());
- if (mScroller)
- {
- mScroller->goToBottom();
- }
-}
-
-void LLTextBase::changePage( S32 delta )
-{
- const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10;
- if (delta == 0 || !mScroller) return;
-
- LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
-
- if( delta == -1 )
- {
- mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE);
- }
- else
- if( delta == 1 )
- {
- mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE);
- }
-
- if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect)
- {
- // cursor didn't change apparent position, so move to top or bottom of document, respectively
- if (delta < 0)
- {
- startOfDoc();
- }
- else
- {
- endOfDoc();
- }
- }
- else
- {
- setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false);
- }
-}
-
-// Picks a new cursor position based on the screen size of text being drawn.
-void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset )
-{
- setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset);
-}
-
-
-void LLTextBase::changeLine( S32 delta )
-{
- S32 line = getLineNumFromDocIndex(mCursorPos);
- S32 max_line_nb = getLineCount() - 1;
- max_line_nb = (max_line_nb < 0 ? 0 : max_line_nb);
-
- S32 new_line = llclamp(line + delta, 0, max_line_nb);
-
- if (new_line != line)
- {
- LLRect visible_region = getVisibleDocumentRect();
- S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel,
- mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, true);
- S32 actual_line = getLineNumFromDocIndex(new_cursor_pos);
- if (actual_line != new_line)
- {
- // line edge, correcting position by 1 to move onto proper line
- new_cursor_pos += new_line - actual_line;
- }
- setCursorPos(new_cursor_pos, true);
- }
-}
-
-bool LLTextBase::scrolledToStart()
-{
- return mScroller->isAtTop();
-}
-
-bool LLTextBase::scrolledToEnd()
-{
- return mScroller->isAtBottom();
-}
-
-bool LLTextBase::setCursor(S32 row, S32 column)
-{
- if (row < 0 || column < 0) return false;
-
- S32 n_lines = mLineInfoList.size();
- for (S32 line = row; line < n_lines; ++line)
- {
- const line_info& li = mLineInfoList[line];
-
- if (li.mLineNum < row)
- {
- continue;
- }
- else if (li.mLineNum > row)
- {
- break; // invalid column specified
- }
-
- // Found the given row.
- S32 line_length = li.mDocIndexEnd - li.mDocIndexStart;;
- if (column >= line_length)
- {
- column -= line_length;
- continue;
- }
-
- // Found the given column.
- updateCursorXPos();
- S32 doc_pos = li.mDocIndexStart + column;
- return setCursorPos(doc_pos);
- }
-
- return false; // invalid row or column specified
-}
-
-
-bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset)
-{
- S32 new_cursor_pos = cursor_pos;
- if (new_cursor_pos != mCursorPos)
- {
- new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos);
- }
-
- mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength());
- needsScroll();
- if (!keep_cursor_offset)
- updateCursorXPos();
- // did we get requested position?
- return new_cursor_pos == cursor_pos;
-}
-
-// constraint cursor to editable segments of document
-S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction)
-{
- segment_set_t::iterator segment_iter;
- S32 offset;
- getSegmentAndOffset(index, &segment_iter, &offset);
- if (segment_iter == mSegments.end())
- {
- return 0;
- }
-
- LLTextSegmentPtr segmentp = *segment_iter;
-
- if (segmentp->canEdit())
- {
- return segmentp->getStart() + offset;
- }
- else if (segmentp->getStart() < index && index < segmentp->getEnd())
- {
- // bias towards document end
- if (increasing_direction)
- {
- return segmentp->getEnd();
- }
- // bias towards document start
- else
- {
- return segmentp->getStart();
- }
- }
- else
- {
- return index;
- }
-}
-
-void LLTextBase::updateRects()
-{
- LLRect old_text_rect = mVisibleTextRect;
- mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
-
- if (mLineInfoList.empty())
- {
- mTextBoundingRect = LLRect(0, mVPad, mHPad, 0);
- }
- else
- {
- mTextBoundingRect = mLineInfoList.begin()->mRect;
- for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin();
- line_iter != mLineInfoList.end();
- ++line_iter)
- {
- mTextBoundingRect.unionWith(line_iter->mRect);
- }
-
- mTextBoundingRect.mTop += mVPad;
-
- S32 delta_pos = 0;
-
- switch(mVAlign)
- {
- case LLFontGL::TOP:
- delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom);
- break;
- case LLFontGL::VCENTER:
- delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2;
- break;
- case LLFontGL::BOTTOM:
- delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom;
- break;
- case LLFontGL::BASELINE:
- // do nothing
- break;
- }
- // 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);
- }
- mTextBoundingRect.translate(0, delta_pos);
- }
-
- // update document container dimensions according to text contents
- LLRect doc_rect;
- // use old mVisibleTextRect constraint document to width of viewable region
- doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom);
- doc_rect.mLeft = 0;
-
- // allow horizontal scrolling?
- // if so, use entire width of text contents
- // otherwise, stop at width of mVisibleTextRect
- //FIXME: consider use of getWordWrap() instead
- doc_rect.mRight = mScroller
- ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight)
- : mVisibleTextRect.getWidth();
- doc_rect.mTop = llmax(mVisibleTextRect.mTop, mTextBoundingRect.mTop);
-
- if (!mScroller)
- {
- // push doc rect to top of text widget
- switch(mVAlign)
- {
- case LLFontGL::TOP:
- doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop);
- break;
- case LLFontGL::VCENTER:
- doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2);
- case LLFontGL::BOTTOM:
- default:
- break;
- }
- }
-
- mDocumentView->setShape(doc_rect);
-
- //update mVisibleTextRect *after* mDocumentView has been resized
- // so that scrollbars are added if document needs to scroll
- // since mVisibleTextRect does not include scrollbars
- mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
- //FIXME: replace border with image?
- if (mBorderVisible)
- {
- mVisibleTextRect.stretch(-1);
- }
- if (mVisibleTextRect != old_text_rect)
- {
- needsReflow();
- }
-
- // update mTextBoundingRect after mVisibleTextRect took scrolls into account
- if (!mLineInfoList.empty() && mScroller)
- {
- S32 delta_pos = 0;
-
- switch(mVAlign)
- {
- case LLFontGL::TOP:
- delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom);
- break;
- case LLFontGL::VCENTER:
- delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2;
- break;
- case LLFontGL::BOTTOM:
- delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom;
- break;
- case LLFontGL::BASELINE:
- // do nothing
- break;
- }
- // move line segments to fit new visible rect
- if (delta_pos != 0)
- {
- for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it)
- {
- it->mRect.translate(0, delta_pos);
- }
- mTextBoundingRect.translate(0, delta_pos);
- }
- }
-
- // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed)
- doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom);
- doc_rect.mLeft = 0;
- doc_rect.mRight = mScroller
- ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight)
- : mVisibleTextRect.getWidth();
- doc_rect.mTop = llmax(mVisibleTextRect.getHeight(), mTextBoundingRect.getHeight()) + doc_rect.mBottom;
- if (!mScroller)
- {
- // push doc rect to top of text widget
- switch(mVAlign)
- {
- case LLFontGL::TOP:
- doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop);
- break;
- case LLFontGL::VCENTER:
- doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2);
- case LLFontGL::BOTTOM:
- default:
- break;
- }
- }
- mDocumentView->setShape(doc_rect);
-}
-
-
-void LLTextBase::startSelection()
-{
- if( !mIsSelecting )
- {
- mIsSelecting = true;
- mSelectionStart = mCursorPos;
- mSelectionEnd = mCursorPos;
- }
-}
-
-void LLTextBase::endSelection()
-{
- if( mIsSelecting )
- {
- mIsSelecting = false;
- mSelectionEnd = mCursorPos;
- }
-}
-
-// get portion of document that is visible in text editor
-LLRect LLTextBase::getVisibleDocumentRect() const
-{
- if (mScroller)
- {
- return mScroller->getVisibleContentRect();
- }
- else if (mClip)
- {
- LLRect visible_text_rect = getVisibleTextRect();
- LLRect doc_rect = mDocumentView->getRect();
- visible_text_rect.translate(-doc_rect.mLeft, -doc_rect.mBottom);
-
- // reject partially visible lines
- LLRect visible_lines_rect;
- for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end();
- it != end_it;
- ++it)
- {
- bool line_visible = mClipPartial ? visible_text_rect.contains(it->mRect) : visible_text_rect.overlaps(it->mRect);
- if (line_visible)
- {
- if (visible_lines_rect.isEmpty())
- {
- visible_lines_rect = it->mRect;
- }
- else
- {
- visible_lines_rect.unionWith(it->mRect);
- }
- }
- }
- return visible_lines_rect;
- }
- else
- { // entire document rect is visible
- // but offset according to height of widget
-
- LLRect doc_rect = mDocumentView->getLocalRect();
- 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;
- }
-}
-
-boost::signals2::connection LLTextBase::setURLClickedCallback(const commit_signal_t::slot_type& cb)
-{
- if (!mURLClickSignal)
- {
- mURLClickSignal = new commit_signal_t();
- }
- return mURLClickSignal->connect(cb);
-}
-
-boost::signals2::connection LLTextBase::setIsFriendCallback(const is_friend_signal_t::slot_type& cb)
-{
- if (!mIsFriendSignal)
- {
- mIsFriendSignal = new is_friend_signal_t();
- }
- return mIsFriendSignal->connect(cb);
-}
-
-boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb)
-{
- if (!mIsObjectBlockedSignal)
- {
- mIsObjectBlockedSignal = new is_blocked_signal_t();
- }
- return mIsObjectBlockedSignal->connect(cb);
-}
-
-//
-// LLTextSegment
-//
-
-LLTextSegment::~LLTextSegment()
-{}
-
-bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; }
-bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const
-{
- F32 fwidth = 0;
- bool result = getDimensionsF32(first_char, num_chars, fwidth, height);
- width = ll_round(fwidth);
- return result;
-}
-
-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, S32 line_ind) const { return 0; }
-void LLTextSegment::updateLayout(const LLTextBase& editor) {}
-F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { return draw_rect.mLeft; }
-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) {}
-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 ) {}
-void LLTextSegment::dump() const {}
-bool LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return false; }
-bool LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return false; }
-bool LLTextSegment::handleScrollHWheel(S32 x, S32 y, S32 clicks) { return false; }
-bool LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return false; }
-const std::string& LLTextSegment::getName() const
-{
- return LLStringUtil::null;
-}
-void LLTextSegment::onMouseCaptureLost() {}
-void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {}
-void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {}
-bool LLTextSegment::hasMouseCapture() { return false; }
-
-//
-// LLNormalTextSegment
-//
-
-LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor )
-: LLTextSegment(start, end),
- mStyle( style ),
- mToken(NULL),
- mEditor(editor)
-{
- mFontHeight = 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)
-: LLTextSegment(start, end),
- mToken(NULL),
- mEditor(editor)
-{
- mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color));
-
- mFontHeight = mStyle->getFont()->getLineHeight();
-}
-
-LLNormalTextSegment::~LLNormalTextSegment()
-{
- mImageLoadedConnection.disconnect();
-}
-
-
-F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect)
-{
- if( end - start > 0 )
- {
- return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect);
- }
- return draw_rect.mLeft;
-}
-
-// Draws a single text segment, reversing the color for selection if needed.
-F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRectf rect)
-{
- F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha;
-
- const LLWString &text = getWText();
-
- F32 right_x = rect.mLeft;
- if (!mStyle->isVisible())
- {
- return right_x;
- }
-
- const LLFontGL* font = mStyle->getFont();
-
- LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha;
-
- if( selection_start > seg_start )
- {
- // Draw normally
- S32 start = seg_start;
- S32 end = llmin( selection_start, seg_end );
- S32 length = end - start;
- font->render(text, start,
- rect,
- color,
- LLFontGL::LEFT, mEditor.mTextVAlign,
- LLFontGL::NORMAL,
- mStyle->getShadowType(),
- length,
- &right_x,
- mEditor.getUseEllipses(),
- mEditor.getUseColor());
- }
- rect.mLeft = right_x;
-
- if( (selection_start < seg_end) && (selection_end > seg_start) )
- {
- // Draw reversed
- S32 start = llmax( selection_start, seg_start );
- S32 end = llmin( selection_end, seg_end );
- S32 length = end - start;
-
- font->render(text, start,
- rect,
- mStyle->getSelectedColor().get(),
- LLFontGL::LEFT, mEditor.mTextVAlign,
- LLFontGL::NORMAL,
- LLFontGL::NO_SHADOW,
- length,
- &right_x,
- mEditor.getUseEllipses(),
- mEditor.getUseColor());
- }
- rect.mLeft = right_x;
- if( selection_end < seg_end )
- {
- // Draw normally
- S32 start = llmax( selection_end, seg_start );
- S32 end = seg_end;
- S32 length = end - start;
- font->render(text, start,
- rect,
- color,
- LLFontGL::LEFT, mEditor.mTextVAlign,
- LLFontGL::NORMAL,
- mStyle->getShadowType(),
- length,
- &right_x,
- mEditor.getUseEllipses(),
- mEditor.getUseColor());
- }
- return right_x;
-}
-
-bool LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask)
-{
- if (getStyle() && getStyle()->isLink())
- {
- // 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::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND);
- return true;
- }
- }
- return false;
-}
-
-bool LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask)
-{
- if (getStyle() && getStyle()->isLink())
- {
- // 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;
-}
-
-bool LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask)
-{
- if (getStyle() && getStyle()->isLink())
- {
- // 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;
-}
-
-bool LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask)
-{
- if (getStyle() && getStyle()->isLink())
- {
- // 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)
- {
- std::string url = getStyle()->getLinkHREF();
- if (!mEditor.mForceUrlsExternal)
- {
- LLUrlAction::clickAction(url, mEditor.isContentTrusted());
- }
- else if (!LLUrlAction::executeSLURL(url, mEditor.isContentTrusted()))
- {
- LLUrlAction::openURLExternal(url);
- }
- return true;
- }
- }
-
- return false;
-}
-
-bool LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask)
-{
- std::string msg;
- // do we have a tooltip for a loaded keyword (for script editor)?
- if (mToken && !mToken->getToolTip().empty())
- {
- const LLWString& wmsg = mToken->getToolTip();
- LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg), (mToken->getType() == LLKeywordToken::TT_FUNCTION));
- return true;
- }
- // or do we have an explicitly set tooltip (e.g., for Urls)
- if (!mTooltip.empty())
- {
- LLToolTipMgr::instance().show(mTooltip);
- return true;
- }
-
- return false;
-}
-
-void LLNormalTextSegment::setToolTip(const std::string& tooltip)
-{
- // we cannot replace a keyword tooltip that's loaded from a file
- if (mToken)
- {
- LL_WARNS() << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << LL_ENDL;
- return;
- }
- mTooltip = tooltip;
-}
-
-bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
-{
- height = 0;
- width = 0;
- if (num_chars > 0)
- {
- height = mFontHeight;
- const LLWString &text = getWText();
- // if last character is a newline, then return true, forcing line break
- width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true);
- }
- return false;
-}
-
-S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const
-{
- const LLWString &text = getWText();
- return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset,
- (F32)segment_local_x_coord,
- F32_MAX,
- num_chars,
- round);
-}
-
-S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const
-{
- const LLWString &text = getWText();
-
- LLUIImagePtr image = mStyle->getImage();
- if( image.notNull())
- {
- num_pixels = llmax(0, num_pixels - image->getWidth());
- }
-
- S32 last_char = mEnd;
-
- // 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 offsetLength = text.length() - (segment_offset + mStart);
-
- if(getLength() < segment_offset + mStart)
- {
- LL_INFOS() << "getLength() < segment_offset + mStart\t getLength()\t" << getLength() << "\tsegment_offset:\t"
- << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << "\tmax_chars\t" << max_chars << LL_ENDL;
- }
-
- if( (offsetLength + 1) < max_chars)
- {
- LL_INFOS() << "offsetString.length() + 1 < max_chars\t max_chars:\t" << max_chars << "\toffsetString.length():\t" << offsetLength << " getLength() : "
- << getLength() << "\tsegment_offset:\t" << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << LL_ENDL;
- }
-
- S32 num_chars = mStyle->getFont()->maxDrawableChars( text.c_str() + (segment_offset + mStart),
- (F32)num_pixels,
- max_chars,
- word_wrap_style);
-
- if (num_chars == 0
- && line_offset == 0
- && max_chars > 0)
- {
- // If at the beginning of a line, and a single character won't fit, draw it anyway
- num_chars = 1;
- }
-
- // include *either* the EOF or newline character in this run of text
- // 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 < mEnd
- && (last_char_in_run >= getLength()))
- {
- num_chars++;
- }
- return num_chars;
-}
-
-void LLNormalTextSegment::dump() const
-{
- LL_INFOS() << "Segment [" <<
-// mColor.mV[VX] << ", " <<
-// mColor.mV[VY] << ", " <<
-// mColor.mV[VZ] << "]\t[" <<
- mStart << ", " <<
- getEnd() << "]" <<
- LL_ENDL;
-}
-
-/*virtual*/
-const LLWString& LLNormalTextSegment::getWText() const
-{
- return mEditor.getWText();
-}
-
-/*virtual*/
-const S32 LLNormalTextSegment::getLength() const
-{
- return mEditor.getLength();
-}
-
-LLLabelTextSegment::LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor )
-: LLNormalTextSegment(style, start, end, editor)
-{
-}
-
-LLLabelTextSegment::LLLabelTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible)
-: LLNormalTextSegment(color, start, end, editor, is_visible)
-{
-}
-
-/*virtual*/
-const LLWString& LLLabelTextSegment::getWText() const
-{
- return mEditor.getWlabel();
-}
-/*virtual*/
-const S32 LLLabelTextSegment::getLength() const
-{
- return mEditor.getWlabel().length();
-}
-
-//
-// LLEmojiTextSegment
-//
-LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor)
- : LLNormalTextSegment(style, start, end, editor)
-{
-}
-
-LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible)
- : LLNormalTextSegment(color, start, end, editor, is_visible)
-{
-}
-
-bool LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask)
-{
- if (mTooltip.empty())
- {
- LLWString emoji = getWText().substr(getStart(), getEnd() - getStart());
- if (!emoji.empty())
- {
- mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]);
- }
- }
-
- return LLNormalTextSegment::handleToolTip(x, y, mask);
-}
-
-//
-// LLOnHoverChangeableTextSegment
-//
-
-LLOnHoverChangeableTextSegment::LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ):
- LLNormalTextSegment(normal_style, start, end, editor),
- mHoveredStyle(style),
- mNormalStyle(normal_style){}
-
-/*virtual*/
-F32 LLOnHoverChangeableTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect)
-{
- F32 result = LLNormalTextSegment::draw(start, end, selection_start, selection_end, draw_rect);
- if (end == mEnd - mStart)
- {
- mStyle = mNormalStyle;
- }
- return result;
-}
-
-/*virtual*/
-bool LLOnHoverChangeableTextSegment::handleHover(S32 x, S32 y, MASK mask)
-{
- mStyle = mEditor.getSkipLinkUnderline() ? mNormalStyle : mHoveredStyle;
- return LLNormalTextSegment::handleHover(x, y, mask);
-}
-
-
-//
-// LLInlineViewSegment
-//
-
-LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end)
-: LLTextSegment(start, end),
- mView(p.view),
- mForceNewLine(p.force_newline),
- mLeftPad(p.left_pad),
- mRightPad(p.right_pad),
- mTopPad(p.top_pad),
- mBottomPad(p.bottom_pad)
-{
-}
-
-LLInlineViewSegment::~LLInlineViewSegment()
-{
- mView->die();
-}
-
-bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
-{
- if (first_char == 0 && num_chars == 0)
- {
- // We didn't fit on a line or were forced to new string
- // the widget will fall on the next line, so width here is 0
- width = 0;
-
- if (mForceNewLine)
- {
- // Chat, string can't be smaller then font height even if it is empty
- LLStyleSP s(new LLStyle(LLStyle::Params().visible(true)));
- height = s->getFont()->getLineHeight();
-
- return true; // new line
- }
- else
- {
- // height from previous segment in same string will be used, word-wrap
- height = 0;
- }
-
- }
- else
- {
- 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, S32 line_ind) const
-{
- // if putting a widget anywhere but at the beginning of a line
- // and the widget doesn't fit or mForceNewLine is true
- // then return 0 chars for that line, and all characters for the next
- if (mForceNewLine && line_ind == 0)
- {
- return 0;
- }
- else if (line_offset != 0 && num_pixels < mView->getRect().getWidth())
- {
- return 0;
- }
- else
- {
- return mEnd - mStart;
- }
-}
-
-void LLInlineViewSegment::updateLayout(const LLTextBase& editor)
-{
- LLRect start_rect = editor.getDocRectFromDocIndex(mStart);
- mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad);
-}
-
-F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect)
-{
- // return padded width of widget
- // widget is actually drawn during mDocumentView's draw()
- return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad);
-}
-
-void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor)
-{
- editor->removeDocumentChild(mView);
-}
-
-void LLInlineViewSegment::linkToDocument(LLTextBase* editor)
-{
- editor->addDocumentChild(mView);
-}
-
-LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1)
-{
- LLStyleSP s( new LLStyle(LLStyle::Params().visible(true)));
-
- mFontHeight = s->getFont()->getLineHeight();
-}
-LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1)
-{
- mFontHeight = style->getFont()->getLineHeight();
-}
-LLLineBreakTextSegment::~LLLineBreakTextSegment()
-{
-}
-bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
-{
- width = 0;
- height = mFontHeight;
-
- return true;
-}
-S32 LLLineBreakTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const
-{
- return 1;
-}
-F32 LLLineBreakTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect)
-{
- return draw_rect.mLeft;
-}
-
-LLImageTextSegment::LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor)
-: LLTextSegment(pos,pos+1),
- mStyle( style ),
- mEditor(editor)
-{
-}
-
-LLImageTextSegment::~LLImageTextSegment()
-{
-}
-
-static const S32 IMAGE_HPAD = 3;
-
-bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
-{
- width = 0;
- height = mStyle->getFont()->getLineHeight();
-
- LLUIImagePtr image = mStyle->getImage();
- if( num_chars>0 && image.notNull())
- {
- width += image->getWidth() + IMAGE_HPAD;
- height = llmax(height, image->getHeight() + IMAGE_HPAD );
- }
- return false;
-}
-
-S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const
-{
- LLUIImagePtr image = mStyle->getImage();
-
- if (image.isNull())
- {
- return 1;
- }
-
- S32 image_width = image->getWidth();
- if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD)
- {
- return 1;
- }
-
- return 0;
-}
-
-bool LLImageTextSegment::handleToolTip(S32 x, S32 y, MASK mask)
-{
- if (!mTooltip.empty())
- {
- LLToolTipMgr::instance().show(mTooltip);
- return true;
- }
-
- return false;
-}
-
-void LLImageTextSegment::setToolTip(const std::string& tooltip)
-{
- mTooltip = tooltip;
-}
-
-F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect)
-{
- if ( (start >= 0) && (end <= mEnd - mStart))
- {
- LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha;
- LLUIImagePtr image = mStyle->getImage();
- if (image.notNull())
- {
- S32 style_image_height = image->getHeight();
- S32 style_image_width = image->getWidth();
- // Text is drawn from the top of the draw_rect downward
-
- S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2);
- // Align image to center of draw rect
- S32 image_bottom = text_center - (style_image_height / 2);
- image->draw(draw_rect.mLeft, image_bottom,
- style_image_width, style_image_height, color);
-
- const S32 IMAGE_HPAD = 3;
- return draw_rect.mLeft + style_image_width + IMAGE_HPAD;
- }
- }
- return 0.0;
-}
-
-void LLTextBase::setWordWrap(bool wrap)
-{
- mWordWrap = wrap;
-}
+/** + * @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 "llemojidictionary.h" +#include "llemojihelper.h" +#include "lllocalcliprect.h" +#include "llmenugl.h" +#include "llscrollcontainer.h" +#include "llspellcheck.h" +#include "llstl.h" +#include "lltextparser.h" +#include "lltextutil.h" +#include "lltooltip.h" +#include "lltrans.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; +const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. + +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 +bool LLTextBase::compare_bottom::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 LLTextBase::compare_bottom::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 LLTextBase::compare_bottom::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 +bool LLTextBase::compare_top::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 LLTextBase::compare_top::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 LLTextBase::compare_top::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"), + text_tentative_color("text_tentative_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), + skip_link_underline("skip_link_underline", false), + spellcheck("spellcheck", 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"), + text_valign("text_valign"), + wrap("wrap"), + trusted_content("trusted_content", true), + always_show_icons("always_show_icons", false), + use_ellipses("use_ellipses", false), + use_emoji("use_emoji", true), + use_color("use_color", true), + parse_urls("parse_urls", false), + force_urls_external("force_urls_external", 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), + mIsFriendSignal(NULL), + mIsObjectBlockedSignal(NULL), + mMaxTextByteLength( p.max_text_length ), + mFont(p.font), + mFontShadow(p.font_shadow), + mPopupMenuHandle(), + mReadOnly(p.read_only), + mSkipTripleClick(false), + mSkipLinkUnderline(p.skip_link_underline), + mSpellCheck(p.spellcheck), + mSpellCheckStart(-1), + mSpellCheckEnd(-1), + mCursorColor(p.cursor_color), + mFgColor(p.text_color), + mBorderVisible( p.border_visible ), + mReadOnlyFgColor(p.text_readonly_color), + mTentativeFgColor(p.text_tentative_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), + mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()), + mLineSpacingMult(p.line_spacing.multiple), + mLineSpacingPixels(p.line_spacing.pixels), + mClip(p.clip), + mClipPartial(p.clip_partial && !p.allow_scroll), + mTrustedContent(p.trusted_content), + mAlwaysShowIcons(p.always_show_icons), + mTrackEnd( p.track_end ), + mScrollIndex(-1), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mIsSelecting( false ), + mPlainText ( p.plain_text ), + mWordWrap(p.wrap), + mUseEllipses( p.use_ellipses ), + mUseEmoji(p.use_emoji), + mUseColor(p.use_color), + mParseHTML(p.parse_urls), + mForceUrlsExternal(p.force_urls_external), + 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); + } + + if (mSpellCheck) + { + LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this)); + } + mSpellCheckTimer.reset(); + + createDefaultSegment(); + + updateRects(); +} + +LLTextBase::~LLTextBase() +{ + mSegments.clear(); + LLContextMenu* menu = static_cast<LLContextMenu*>(mPopupMenuHandle.get()); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } + delete mURLClickSignal; + delete mIsFriendSignal; + delete mIsObjectBlockedSignal; +} + +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 + S32 utf8_byte_size = 0; + LLSD value = getViewModel()->getValue(); + if (value.type() == LLSD::TypeString) + { + // save a copy for strings. + utf8_byte_size = value.size(); + } + else + { + // non string LLSDs need explicit conversion to string + utf8_byte_size = value.asString().size(); + } + + if ( utf8_byte_size > mMaxTextByteLength ) + { + // Truncate safely in UTF-8 + std::string temp_utf8_text = value.asString(); + 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::getStyleParams() +{ + //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) + { + mStyle + .color(LLUIColor(&mFgColor)) // pass linked color instead of copy of mFGColor + .readonly_color(LLUIColor(&mReadOnlyFgColor)) + .selected_color(LLUIColor(&mTextSelectedColor)) + .font(mFont) + .drop_shadow(mFontShadow); + mStyleDirty = false; + } + return mStyle; +} + +void LLTextBase::beforeValueChange() +{ + +} + +void LLTextBase::onValueChange(S32 start, S32 end) +{ +} + +std::vector<LLRect> LLTextBase::getSelectionRects() +{ + // Nor supposed to be called without selection + llassert(hasSelection()); + llassert(!mLineInfoList.empty()); + + std::vector<LLRect> selection_rects; + + S32 selection_left = llmin(mSelectionStart, mSelectionEnd); + S32 selection_right = llmax(mSelectionStart, mSelectionEnd); + + // 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); + + // Use F32 otherwise a string of multiple segments + // will accumulate a large error + F32 left_precise = line_iter->mRect.mLeft; + F32 right_precise = line_iter->mRect.mLeft; + + 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; + + F32 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->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); + left_precise += 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->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); + right_precise += segment_width; + } + // else if selection ends on current segment... + else + { + S32 num_chars = selection_right - segment_line_start; + segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); + right_precise += segment_width; + + break; + } + } + + LLRect selection_rect; + selection_rect.mLeft = left_precise; + selection_rect.mRight = right_precise; + selection_rect.mBottom = line_iter->mRect.mBottom; + selection_rect.mTop = line_iter->mRect.mTop; + + selection_rects.push_back(selection_rect); + } + } + + return selection_rects; +} + +// 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 = getSelectionRects(); + + // 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); + LLRect content_display_rect = getVisibleDocumentRect(); + + for (std::vector<LLRect>::iterator rect_it = selection_rects.begin(); + rect_it != selection_rects.end(); + ++rect_it) + { + LLRect selection_rect = *rect_it; + if (mScroller) + { + // If scroller is On content_display_rect has correct rect and safe to use as is + // Note: we might need to account for border + selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); + } + else + { + // If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position + // and we have to acount for offset depending on position + S32 v_delta = 0; + S32 h_delta = 0; + switch (mVAlign) + { + case LLFontGL::TOP: + v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad; + break; + case LLFontGL::VCENTER: + v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom; + break; + default: + break; + } + switch (mHAlign) + { + case LLFontGL::LEFT: + h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad; + break; + case LLFontGL::HCENTER: + h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2; + break; + case LLFontGL::RIGHT: + h_delta = mVisibleTextRect.mRight - content_display_rect.mRight; + break; + default: + break; + } + selection_rect.translate(h_delta, v_delta); + } + 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, mTextVAlign, + 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::getScaleFactor().mV[VX]); + ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]); + getWindow()->setLanguageTextInput( ime_pos ); + } + } +} + +void LLTextBase::drawText() +{ + S32 text_len = getLength(); + + if (text_len <= 0 && mLabel.empty()) + { + return; + } + else if (useLabel()) + { + text_len = mLabel.getWString().length(); + } + + 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; + } + + // Perform spell check if needed + if ( (getSpellCheck()) && (getWText().length() > 2) ) + { + // Calculate start and end indices for the spell checking range + S32 start = line_start; + S32 end = getLineEnd(last_line); + + if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) + { + const LLWString& wstrText = getWText(); + mMisspellRanges.clear(); + + segment_set_t::const_iterator seg_it = getSegIterContaining(start); + while (mSegments.end() != seg_it) + { + LLTextSegmentPtr text_segment = *seg_it; + if ( (text_segment.isNull()) || (text_segment->getStart() >= end) ) + { + break; + } + + if (!text_segment->canEdit()) + { + ++seg_it; + continue; + } + + // Combine adjoining text segments into one + U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end); + while (mSegments.end() != ++seg_it) + { + text_segment = *seg_it; + if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) ) + { + break; + } + seg_end = llmin(text_segment->getEnd(), end); + } + + // Find the start of the first word + U32 word_start = seg_start, word_end = -1; + U32 text_length = wstrText.length(); + while ( (word_start < text_length) && (!LLStringOps::isAlpha(wstrText[word_start])) ) + { + word_start++; + } + + // Iterate over all words in the text block and check them one by one + while (word_start < seg_end) + { + // Find the end of the current word (special case handling for "'" when it's used as a contraction) + word_end = word_start + 1; + while ( (word_end < seg_end) && + ((LLWStringUtil::isPartOfWord(wstrText[word_end])) || + ((L'\'' == wstrText[word_end]) && + (LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) ) + { + word_end++; + } + if (word_end > seg_end) + { + break; + } + + if (word_start < text_length && word_end <= text_length && word_end > word_start) + { + std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start)); + + // Don't process words shorter than 3 characters + if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) + { + mMisspellRanges.push_back(std::pair<U32, U32>(word_start, word_end)); + } + } + + // Find the start of the next word + word_start = word_end + 1; + while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) ) + { + word_start++; + } + } + } + + mSpellCheckStart = start; + mSpellCheckEnd = end; + } + } + else + { + mMisspellRanges.clear(); + } + + LLTextSegmentPtr cur_segment = *seg_iter; + + std::list<std::pair<U32, U32> >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair<U32, U32>(line_start, 0)); + 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; + } + + LLRectf text_rect(line.mRect.mLeft, line.mRect.mTop, line.mRect.mRight, line.mRect.mBottom); + 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()) + { + LL_WARNS() << "Ran off the segmentation end!" << LL_ENDL; + + return; + } + cur_segment = *seg_iter; + } + + S32 seg_end = llmin(line_end, cur_segment->getEnd()); + S32 clipped_end = seg_end - 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; + } + + // Draw squiggly lines under any visible misspelled words + while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) ) + { + // Skip the current word if the user is still busy editing it + if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) ) + { + ++misspell_it; + continue; + } + + U32 misspell_start = llmax<U32>(misspell_it->first, seg_start), misspell_end = llmin<U32>(misspell_it->second, seg_end); + S32 squiggle_start = 0, squiggle_end = 0, pony = 0; + cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_start - seg_start, squiggle_start, pony); + cur_segment->getDimensions(misspell_start - cur_segment->getStart(), misspell_end - misspell_start, squiggle_end, pony); + squiggle_start += text_rect.mLeft; + + pony = (squiggle_end + 3) / 6; + squiggle_start += squiggle_end / 2 - pony * 3; + squiggle_end = squiggle_start + pony * 6; + + S32 squiggle_bottom = text_rect.mBottom + (S32)cur_segment->getStyle()->getFont()->getDescenderHeight(); + + gGL.color4ub(255, 0, 0, 200); + while (squiggle_start + 1 < squiggle_end) + { + gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2); + if (squiggle_start + 3 < squiggle_end) + { + gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1); + } + squiggle_start += 4; + } + + if (misspell_it->second > seg_end) + { + break; + } + ++misspell_it; + } + + text_rect.mLeft = 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 ) +{ + beforeValueChange(); + + S32 old_len = getLength(); // length() returns character length + S32 insert_len = wstr.length(); + + pos = getEditableIndex(pos, true); + if (pos > old_len) + { + pos = old_len; + // Should not happen, + // if you encounter this, check where wrong position comes from + llassert(false); + } + + 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(getStyleParams())); + 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); + } + } + + // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us) + if (mUseEmoji) + { + LLStyleSP emoji_style; + LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL; + for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++) + { + llwchar code = wstr[text_kitty]; + bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code); + if (isEmoji) + { + if (!emoji_style) + { + emoji_style = new LLStyle(getStyleParams()); + emoji_style->setFont(LLFontGL::getFontEmojiLarge()); + } + + S32 new_seg_start = pos + text_kitty; + insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this)); + } + } + } + + getViewModel()->getEditableDisplay().insert(pos, wstr); + + if ( truncate() ) + { + insert_len = getLength() - old_len; + } + + onValueChange(pos, pos + insert_len); + needsReflow(pos); + + return insert_len; +} + +S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) +{ + + beforeValueChange(); + 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; + } + + getViewModel()->getEditableDisplay().erase(pos, length); + + // 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) +{ + beforeValueChange(); + + if (pos > (S32)getLength()) + { + return 0; + } + getViewModel()->getEditableDisplay()[pos] = wc; + + 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(getStyleParams())); + 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); +} + +//virtual +bool LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // handle triple click + if (!mTripleClickTimer.hasExpired()) + { + if (mSkipTripleClick) + { + return true; + } + + S32 real_line = getLineNumFromDocIndex(mCursorPos, false); + S32 line_start = -1; + S32 line_end = -1; + for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); + it != end_it; + ++it) + { + if (it->mLineNum < real_line) + { + continue; + } + if (it->mLineNum > real_line) + { + break; + } + if (line_start == -1) + { + line_start = it->mDocIndexStart; + } + line_end = it->mDocIndexEnd; + line_end = llclamp(line_end, 0, getLength()); + } + + if (line_start == -1) + { + return true; + } + + mSelectionEnd = line_start; + mSelectionStart = line_end; + setCursorPos(line_start); + + return true; + } + + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMouseDown(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleMouseDown(x, y, mask); +} + +//virtual +bool LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (hasMouseCapture() && 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); +} + +//virtual +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); +} + +//virtual +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); +} + +//virtual +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); +} + +//virtual +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); +} + +//virtual +bool LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + //Don't start triple click timer if user have clicked on scrollbar + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + if (x >= mVisibleTextRect.mLeft && x <= mVisibleTextRect.mRight + && y >= mVisibleTextRect.mBottom && y <= mVisibleTextRect.mTop) + { + mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL); + } + + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleDoubleClick(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleDoubleClick(x, y, mask); +} + +//virtual +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); +} + +//virtual +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); +} + +//virtual +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); +} + +//virtual +void LLTextBase::reshape(S32 width, S32 height, bool called_from_parent) +{ + if (width != getRect().getWidth() || height != getRect().getHeight() || LLView::sForceReshape) + { + 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(); + } +} + +//virtual +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); + } + + // Draw highlighted if needed + if( ll::ui::SearchableControl::getHighlighted() ) + { + LLColor4 bg_color = ll::ui::SearchableControl::getHighlightColor(); + LLRect bg_rect = mVisibleTextRect; + if( mScroller ) + bg_rect.intersectWith( text_rect ); + + gl_rect_2d( text_rect, bg_color, 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->setVisibleDirect(false); + LLUICtrl::draw(); + mDocumentView->setVisibleDirect(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::onVisibilityChange( bool new_visibility ) +{ + LLContextMenu* menu = static_cast<LLContextMenu*>(mPopupMenuHandle.get()); + if(!new_visibility && menu) + { + menu->hide(); + } + LLUICtrl::onVisibilityChange(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; +} + +bool LLTextBase::getSpellCheck() const +{ + return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); +} + +const std::string& LLTextBase::getSuggestion(U32 index) const +{ + return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; +} + +U32 LLTextBase::getSuggestionCount() const +{ + return mSuggestionList.size(); +} + +void LLTextBase::replaceWithSuggestion(U32 index) +{ + for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + { + deselect(); + // Insert the suggestion in its place + LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); + insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index])); + + // Delete the misspelled word + removeStringNoUndo(it->first + (S32)suggestion.length(), it->second - it->first); + + + setCursorPos(it->first + (S32)suggestion.length()); + onSpellCheckPerformed(); + + break; + } + } + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLTextBase::addToDictionary() +{ + if (canAddToDictionary()) + { + LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); + } +} + +bool LLTextBase::canAddToDictionary() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +void LLTextBase::addToIgnore() +{ + if (canAddToIgnore()) + { + LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); + } +} + +bool LLTextBase::canAddToIgnore() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +std::string LLTextBase::getMisspelledWord(U32 pos) const +{ + for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + { + return wstring_to_utf8str(getWText().substr(it->first, it->second - it->first)); + } + } + return LLStringUtil::null; +} + +bool LLTextBase::isMisspelledWord(U32 pos) const +{ + for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + { + return true; + } + } + return false; +} + +void LLTextBase::onSpellCheckSettingsChange() +{ + // Recheck the spelling on every change + mMisspellRanges.clear(); + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLTextBase::onFocusReceived() +{ + LLUICtrl::onFocusReceived(); + if (!getLength() && !mLabel.empty()) + { + // delete label which is LLLabelTextSegment + clearSegments(); + } +} + +void LLTextBase::onFocusLost() +{ + LLUICtrl::onFocusLost(); + if (!getLength() && !mLabel.empty()) + { + resetLabel(); + } +} + +// 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: + { + // Font's rendering rounds string size, if value gets rounded + // down last symbol might not have enough space to render, + // compensate by adding an extra pixel as padding + const S32 right_padding = 1; + return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding); + } + default: + return mHPad; + } +} + +void LLTextBase::reflow() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + + 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) + { + LL_DEBUGS() << "Breaking out of reflow due to possible infinite loop in " << getName() << LL_ENDL; + 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 + if (mWordWrap) + { + 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 F32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin + F32 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()); + if (iter != mLineInfoList.end()) + { + 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; + S32 seg_line_offset = line_count + 1; + + 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, ll_round(remaining_pixels)) : S32_MAX, + seg_offset, + cur_index - line_start_index, + S32_MAX, + line_count - seg_line_offset); + + F32 segment_width; + S32 segment_height; + bool force_newline = segment->getDimensionsF32(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; + + // Note: make sure text will fit in width - use ceil, but also make sure + // ceil is used only once per line + S32 text_actual_width = llceil(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 -= ll_round((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 -= ll_round((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 -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + line_height = 0; + remaining_pixels = text_available_width; + } + ++seg_iter; + seg_offset = 0; + seg_line_offset = force_newline ? line_count + 1 : line_count; + } + 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); +} + + +void LLTextBase::updateSegments() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + 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(); + + // 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); + 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(); + + // 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, true)); + registrar.add("Url.Block", boost::bind(&LLUrlAction::blockObject, url)); + registrar.add("Url.Unblock", boost::bind(&LLUrlAction::unblockObject, url)); + registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); + registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url)); + registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url)); + registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url)); + registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url)); + registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, 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 + + LLContextMenu* menu = static_cast<LLContextMenu*>(mPopupMenuHandle.get()); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } + llassert(LLMenuGL::sMenuContainer != NULL); + menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandle = menu->getHandle(); + + if (mIsFriendSignal) + { + bool isFriend = *(*mIsFriendSignal)(LLUUID(LLUrlAction::getUserID(url))); + LLView* addFriendButton = menu->getChild<LLView>("add_friend"); + LLView* removeFriendButton = menu->getChild<LLView>("remove_friend"); + + if (addFriendButton && removeFriendButton) + { + addFriendButton->setEnabled(!isFriend); + removeFriendButton->setEnabled(isFriend); + } + } + + if (mIsObjectBlockedSignal) + { + bool is_blocked = *(*mIsObjectBlockedSignal)(LLUUID(LLUrlAction::getObjectId(url)), LLUrlAction::getObjectName(url)); + LLView* blockButton = menu->getChild<LLView>("block_object"); + LLView* unblockButton = menu->getChild<LLView>("unblock_object"); + + if (blockButton && unblockButton) + { + blockButton->setVisible(!is_blocked); + unblockButton->setVisible(is_blocked); + } + } + menu->show(x, y); + LLMenuGL::showPopup(this, menu, 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 +const std::string& LLTextBase::getText() const +{ + return getViewModel()->getStringValue(); +} + +// 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) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + LLStyle::Params style_params(input_params); + style_params.fillFrom(getStyleParams()); + + 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), isContentTrusted() || mAlwaysShowIcons)) + { + 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); + } + + // add icon before url if need + LLTextUtil::processUrlMatch(&match, this, isContentTrusted() || match.isTrusted() || mAlwaysShowIcons); + if ((isContentTrusted() || match.isTrusted()) && !match.getIcon().empty() ) + { + setLastSegmentToolTip(LLTrans::getString("TooltipSLIcon")); + } + + // output the styled Url + appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); + bool tooltip_required = !match.getTooltip().empty(); + + // set the tooltip for the Url label + if (tooltip_required) + { + setLastSegmentToolTip(match.getTooltip()); + } + + // show query part of url with gray color only for LLUrlEntryHTTP url entries + std::string label = match.getQuery(); + if (label.size()) + { + link_params.color = LLColor4::grey; + link_params.readonly_color = LLColor4::grey; + appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly()); + + // set the tooltip for the query part of url + if (tooltip_required) + { + setLastSegmentToolTip(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::setLastSegmentToolTip(const std::string &tooltip) +{ + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(tooltip); + } +} + +void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (new_text.empty()) + return; + + if(prepend_newline) + appendLineBreakSegment(input_params); + appendTextImpl(new_text,input_params); +} + +void LLTextBase::setLabel(const LLStringExplicit& label) +{ + mLabel = label; + resetLabel(); +} + +bool LLTextBase::setLabelArg(const std::string& key, const LLStringExplicit& text ) +{ + mLabel.setArg(key, text); + return true; +} + +void LLTextBase::resetLabel() +{ + if (useLabel()) + { + clearSegments(); + + LLStyle* style = new LLStyle(getStyleParams()); + style->setColor(mTentativeFgColor); + LLStyleConstSP sp(style); + + LLTextSegmentPtr label = new LLLabelTextSegment(sp, 0, mLabel.getWString().length() + 1, *this); + insertSegment(label); + } +} + +bool LLTextBase::useLabel() const +{ + return !getLength() && !mLabel.empty() && !hasFocus(); +} + +void LLTextBase::setFont(const LLFontGL* font) +{ + mFont = font; + mStyleDirty = true; +} + +void LLTextBase::needsReflow(S32 index) +{ + LL_DEBUGS() << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << LL_ENDL; + mReflowIndex = llmin(mReflowIndex, index); +} + +S32 LLTextBase::removeFirstLine() +{ + if (!mLineInfoList.empty()) + { + S32 length = getLineEnd(0); + deselect(); + removeStringNoUndo(0, length); + return length; + } + return 0; +} + +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); + segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size())); + + insertStringNoUndo(getLength(), widget_wide_text, &segments); +} + +void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +{ + // Save old state + S32 selection_start = mSelectionStart; + S32 selection_end = mSelectionEnd; + bool was_selecting = mIsSelecting; + S32 cursor_pos = mCursorPos; + S32 old_length = getLength(); + bool cursor_was_at_end = (mCursorPos == old_length); + + deselect(); + + setCursorPos(old_length); + + if (mParseHighlights) + { + LLStyle::Params highlight_params(style_params); + + LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); + for (S32 i = 0; i < pieces.size(); i++) + { + LLSD color_llsd = pieces[i]["color"]; + LLColor4 lcolor; + lcolor.setValue(color_llsd); + highlight_params.color = lcolor; + + LLWString wide_text; + wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); + + S32 cur_length = getLength(); + LLStyleConstSP sp(new LLStyle(highlight_params)); + LLTextSegmentPtr segmentp; + if (underline_on_hover_only || mSkipLinkUnderline) + { + highlight_params.font.style("NORMAL"); + LLStyleConstSP normal_sp(new LLStyle(highlight_params)); + segmentp = new LLOnHoverChangeableTextSegment(sp, normal_sp, cur_length, cur_length + wide_text.size(), *this); + } + else + { + 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); + } + } + else + { + LLWString wide_text; + wide_text = utf8str_to_wstring(new_text); + + segment_vec_t segments; + S32 segment_start = old_length; + S32 segment_end = old_length + wide_text.size(); + LLStyleConstSP sp(new LLStyle(style_params)); + if (underline_on_hover_only || mSkipLinkUnderline) + { + LLStyle::Params normal_style_params(style_params); + normal_style_params.font.style("NORMAL"); + LLStyleConstSP normal_sp(new LLStyle(normal_style_params)); + segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this)); + } + else + { + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this)); + } + + insertStringNoUndo(getLength(), wide_text, &segments); + } + + // Set the cursor and scroll position + if (selection_start != selection_end) + { + mSelectionStart = selection_start; + mSelectionEnd = selection_end; + + mIsSelecting = was_selecting; + setCursorPos(cursor_pos); + } + else if (cursor_was_at_end) + { + setCursorPos(getLength()); + } + else + { + setCursorPos(cursor_pos); + } +} + +void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +{ + if (new_text.empty()) + { + return; + } + + std::string::size_type start = 0; + std::string::size_type pos = new_text.find("\n", start); + + while (pos != std::string::npos) + { + if (pos != start) + { + std::string str = std::string(new_text,start,pos-start); + appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); + } + appendLineBreakSegment(style_params); + start = pos+1; + pos = new_text.find("\n", start); + } + + std::string str = std::string(new_text, start, new_text.length() - start); + appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); +} + + +void LLTextBase::replaceUrl(const std::string &url, + const std::string &label, + const std::string &icon) +{ + // get the full (wide) text for the editor so we can change it + LLWString text = getWText(); + LLWString wlabel = utf8str_to_wstring(label); + bool modified = false; + S32 seg_start = 0; + + // iterate through each segment looking for ones styled as links + segment_set_t::iterator it; + for (it = mSegments.begin(); it != mSegments.end(); ++it) + { + LLTextSegment *seg = *it; + LLStyleConstSP style = seg->getStyle(); + + // update segment start/end length in case we replaced text earlier + S32 seg_length = seg->getEnd() - seg->getStart(); + seg->setStart(seg_start); + seg->setEnd(seg_start + seg_length); + + // if we find a link with our Url, then replace the label + if (style->getLinkHREF() == url) + { + S32 start = seg->getStart(); + S32 end = seg->getEnd(); + text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1); + seg->setEnd(start + wlabel.size()); + modified = true; + } + + // Icon might be updated when more avatar or group info + // becomes available + if (style->isImage() && style->getLinkHREF() == url) + { + LLUIImagePtr image = image_from_icon_name( icon ); + if (image) + { + LLStyle::Params icon_params; + icon_params.image = image; + LLStyleConstSP new_style(new LLStyle(icon_params)); + seg->setStyle(new_style); + modified = true; + } + } + + // work out the character offset for the next segment + seg_start = seg->getEnd(); + } + + // update the editor with the new (wide) text string + if (modified) + { + getViewModel()->setDisplay(text); + deselect(); + setCursorPos(mCursorPos); + needsReflow(); + } +} + + +void LLTextBase::setWText(const LLWString& text) +{ + setText(wstring_to_utf8str(text)); +} + +const LLWString& LLTextBase::getWText() const +{ + return getViewModel()->getDisplay(); +} + +// If round is true, if the position is on the right half of a character, the cursor +// 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, bool hit_past_end_of_line) const +{ + // Figure out which line we're nearest to. + LLRect doc_rect = mDocumentView->getRect(); + S32 doc_y = local_y - doc_rect.mBottom; + + // binary search for line that starts before local_y + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_y, compare_bottom()); + + if (!mLineInfoList.size() || line_iter == mLineInfoList.end()) + { + return getLength(); // past the end + } + + S32 pos = getLength(); + F32 start_x = line_iter->mRect.mLeft + doc_rect.mLeft; + + segment_set_t::iterator line_seg_iter; + S32 line_seg_offset; + for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); + line_seg_iter != mSegments.end(); + ++line_seg_iter, line_seg_offset = 0) + { + const LLTextSegmentPtr segmentp = *line_seg_iter; + + S32 segment_line_start = segmentp->getStart() + line_seg_offset; + S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; + F32 text_width; + S32 text_height; + bool newline = segmentp->getDimensionsF32(line_seg_offset, segment_line_length, text_width, text_height); + + if(newline) + { + pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); + break; + } + + // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line + if (hit_past_end_of_line && doc_y > line_iter->mRect.mTop) + { + pos = segment_line_start; + break; + } + if (local_x < start_x + text_width) // cursor to left of right edge of text + { + // Figure out which character we're nearest to. + S32 offset; + if (!segmentp->canEdit()) + { + F32 segment_width; + S32 segment_height; + segmentp->getDimensionsF32(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height); + if (round && local_x - start_x > segment_width / 2) + { + offset = segment_line_length; + } + else + { + offset = 0; + } + } + else + { + offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); + } + pos = segment_line_start + offset; + break; + } + else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd) + { + if (getLineNumFromDocIndex(line_iter->mDocIndexEnd - 1) == line_iter->mLineNum) + { + // if segment wraps to the next line we should step one char back + // to compensate for the space char between words + // which is removed due to wrapping + pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength()); + } + else + { + pos = llclamp(line_iter->mDocIndexEnd, 0, getLength()); + } + break; + } + start_x += text_width; + } + + return pos; +} + +// returns rectangle of insertion caret +// in document coordinate frame from given index into text +LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const +{ + if (mLineInfoList.empty()) + { + return LLRect(); + } + + // clamp pos to valid values + pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1); + + line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare()); + + segment_set_t::iterator line_seg_iter; + S32 line_seg_offset; + segment_set_t::iterator cursor_seg_iter; + S32 cursor_seg_offset; + getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); + getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset); + + F32 doc_left_precise = line_iter->mRect.mLeft; + + while(line_seg_iter != mSegments.end()) + { + const LLTextSegmentPtr segmentp = *line_seg_iter; + + if (line_seg_iter == cursor_seg_iter) + { + // cursor advanced to right based on difference in offset of cursor to start of line + F32 segment_width; + S32 segment_height; + segmentp->getDimensionsF32(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height); + doc_left_precise += segment_width; + + break; + } + else + { + // add remainder of current text segment to cursor position + F32 segment_width; + S32 segment_height; + segmentp->getDimensionsF32(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height); + doc_left_precise += segment_width; + // offset will be 0 for all segments after the first + line_seg_offset = 0; + // go to next text segment on this line + ++line_seg_iter; + } + } + + LLRect doc_rect; + doc_rect.mLeft = doc_left_precise; + doc_rect.mBottom = line_iter->mRect.mBottom; + doc_rect.mTop = line_iter->mRect.mTop; + + // set rect to 0 width + doc_rect.mRight = doc_rect.mLeft; + + return doc_rect; +} + +LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const +{ + LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + if (mBorderVisible) + { + content_window_rect.stretch(-1); + } + + LLRect local_rect; + + if (mLineInfoList.empty()) + { + // return default height rect in upper left + local_rect = content_window_rect; + local_rect.mBottom = local_rect.mTop - mFont->getLineHeight(); + return local_rect; + } + + // get the rect in document coordinates + LLRect doc_rect = getDocRectFromDocIndex(pos); + + // compensate for scrolled, inset view of doc + LLRect scrolled_view_rect = getVisibleDocumentRect(); + local_rect = doc_rect; + local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft, + content_window_rect.mBottom - scrolled_view_rect.mBottom); + + return local_rect; +} + +void LLTextBase::updateCursorXPos() +{ + // reset desired x cursor position + mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft; +} + + +void LLTextBase::startOfLine() +{ + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + setCursorPos(mCursorPos - offset); +} + +void LLTextBase::endOfLine() +{ + S32 line = getLineNumFromDocIndex(mCursorPos); + S32 num_lines = getLineCount(); + if (line + 1 >= num_lines) + { + setCursorPos(getLength()); + } + else + { + setCursorPos( getLineStart(line + 1) - 1 ); + } +} + +void LLTextBase::startOfDoc() +{ + setCursorPos(0); + if (mScroller) + { + mScroller->goToTop(); + } +} + +void LLTextBase::endOfDoc() +{ + setCursorPos(getLength()); + if (mScroller) + { + mScroller->goToBottom(); + } +} + +void LLTextBase::changePage( S32 delta ) +{ + const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10; + if (delta == 0 || !mScroller) return; + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + + if( delta == -1 ) + { + mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE); + } + else + if( delta == 1 ) + { + mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE); + } + + if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect) + { + // cursor didn't change apparent position, so move to top or bottom of document, respectively + if (delta < 0) + { + startOfDoc(); + } + else + { + endOfDoc(); + } + } + else + { + setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false); + } +} + +// Picks a new cursor position based on the screen size of text being drawn. +void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset ) +{ + setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset); +} + + +void LLTextBase::changeLine( S32 delta ) +{ + S32 line = getLineNumFromDocIndex(mCursorPos); + S32 max_line_nb = getLineCount() - 1; + max_line_nb = (max_line_nb < 0 ? 0 : max_line_nb); + + S32 new_line = llclamp(line + delta, 0, max_line_nb); + + if (new_line != line) + { + LLRect visible_region = getVisibleDocumentRect(); + S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, + mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, true); + S32 actual_line = getLineNumFromDocIndex(new_cursor_pos); + if (actual_line != new_line) + { + // line edge, correcting position by 1 to move onto proper line + new_cursor_pos += new_line - actual_line; + } + setCursorPos(new_cursor_pos, true); + } +} + +bool LLTextBase::scrolledToStart() +{ + return mScroller->isAtTop(); +} + +bool LLTextBase::scrolledToEnd() +{ + return mScroller->isAtBottom(); +} + +bool LLTextBase::setCursor(S32 row, S32 column) +{ + if (row < 0 || column < 0) return false; + + S32 n_lines = mLineInfoList.size(); + for (S32 line = row; line < n_lines; ++line) + { + const line_info& li = mLineInfoList[line]; + + if (li.mLineNum < row) + { + continue; + } + else if (li.mLineNum > row) + { + break; // invalid column specified + } + + // Found the given row. + S32 line_length = li.mDocIndexEnd - li.mDocIndexStart;; + if (column >= line_length) + { + column -= line_length; + continue; + } + + // Found the given column. + updateCursorXPos(); + S32 doc_pos = li.mDocIndexStart + column; + return setCursorPos(doc_pos); + } + + return false; // invalid row or column specified +} + + +bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset) +{ + S32 new_cursor_pos = cursor_pos; + if (new_cursor_pos != mCursorPos) + { + new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos); + } + + mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength()); + needsScroll(); + if (!keep_cursor_offset) + updateCursorXPos(); + // did we get requested position? + return new_cursor_pos == cursor_pos; +} + +// constraint cursor to editable segments of document +S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction) +{ + segment_set_t::iterator segment_iter; + S32 offset; + getSegmentAndOffset(index, &segment_iter, &offset); + if (segment_iter == mSegments.end()) + { + return 0; + } + + LLTextSegmentPtr segmentp = *segment_iter; + + if (segmentp->canEdit()) + { + return segmentp->getStart() + offset; + } + else if (segmentp->getStart() < index && index < segmentp->getEnd()) + { + // bias towards document end + if (increasing_direction) + { + return segmentp->getEnd(); + } + // bias towards document start + else + { + return segmentp->getStart(); + } + } + else + { + return index; + } +} + +void LLTextBase::updateRects() +{ + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + + if (mLineInfoList.empty()) + { + mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); + } + else + { + mTextBoundingRect = mLineInfoList.begin()->mRect; + for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin(); + line_iter != mLineInfoList.end(); + ++line_iter) + { + mTextBoundingRect.unionWith(line_iter->mRect); + } + + mTextBoundingRect.mTop += mVPad; + + S32 delta_pos = 0; + + switch(mVAlign) + { + case LLFontGL::TOP: + delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); + break; + case LLFontGL::VCENTER: + delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; + break; + case LLFontGL::BASELINE: + // do nothing + break; + } + // 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); + } + mTextBoundingRect.translate(0, delta_pos); + } + + // update document container dimensions according to text contents + LLRect doc_rect; + // use old mVisibleTextRect constraint document to width of viewable region + doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); + doc_rect.mLeft = 0; + + // allow horizontal scrolling? + // if so, use entire width of text contents + // otherwise, stop at width of mVisibleTextRect + //FIXME: consider use of getWordWrap() instead + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + doc_rect.mTop = llmax(mVisibleTextRect.mTop, mTextBoundingRect.mTop); + + if (!mScroller) + { + // push doc rect to top of text widget + switch(mVAlign) + { + case LLFontGL::TOP: + doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + break; + case LLFontGL::VCENTER: + doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); + case LLFontGL::BOTTOM: + default: + break; + } + } + + mDocumentView->setShape(doc_rect); + + //update mVisibleTextRect *after* mDocumentView has been resized + // so that scrollbars are added if document needs to scroll + // since mVisibleTextRect does not include scrollbars + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + //FIXME: replace border with image? + if (mBorderVisible) + { + mVisibleTextRect.stretch(-1); + } + if (mVisibleTextRect != old_text_rect) + { + needsReflow(); + } + + // update mTextBoundingRect after mVisibleTextRect took scrolls into account + if (!mLineInfoList.empty() && mScroller) + { + S32 delta_pos = 0; + + switch(mVAlign) + { + case LLFontGL::TOP: + delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); + break; + case LLFontGL::VCENTER: + delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; + break; + case LLFontGL::BASELINE: + // do nothing + break; + } + // move line segments to fit new visible rect + if (delta_pos != 0) + { + for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) + { + it->mRect.translate(0, delta_pos); + } + mTextBoundingRect.translate(0, delta_pos); + } + } + + // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) + doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); + doc_rect.mLeft = 0; + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + doc_rect.mTop = llmax(mVisibleTextRect.getHeight(), mTextBoundingRect.getHeight()) + doc_rect.mBottom; + if (!mScroller) + { + // push doc rect to top of text widget + switch(mVAlign) + { + case LLFontGL::TOP: + doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + break; + case LLFontGL::VCENTER: + doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); + case LLFontGL::BOTTOM: + default: + break; + } + } + mDocumentView->setShape(doc_rect); +} + + +void LLTextBase::startSelection() +{ + if( !mIsSelecting ) + { + mIsSelecting = true; + mSelectionStart = mCursorPos; + mSelectionEnd = mCursorPos; + } +} + +void LLTextBase::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = false; + mSelectionEnd = mCursorPos; + } +} + +// get portion of document that is visible in text editor +LLRect LLTextBase::getVisibleDocumentRect() const +{ + if (mScroller) + { + return mScroller->getVisibleContentRect(); + } + else if (mClip) + { + LLRect visible_text_rect = getVisibleTextRect(); + LLRect doc_rect = mDocumentView->getRect(); + visible_text_rect.translate(-doc_rect.mLeft, -doc_rect.mBottom); + + // reject partially visible lines + LLRect visible_lines_rect; + for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); + it != end_it; + ++it) + { + bool line_visible = mClipPartial ? visible_text_rect.contains(it->mRect) : visible_text_rect.overlaps(it->mRect); + if (line_visible) + { + if (visible_lines_rect.isEmpty()) + { + visible_lines_rect = it->mRect; + } + else + { + visible_lines_rect.unionWith(it->mRect); + } + } + } + return visible_lines_rect; + } + else + { // entire document rect is visible + // but offset according to height of widget + + LLRect doc_rect = mDocumentView->getLocalRect(); + 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; + } +} + +boost::signals2::connection LLTextBase::setURLClickedCallback(const commit_signal_t::slot_type& cb) +{ + if (!mURLClickSignal) + { + mURLClickSignal = new commit_signal_t(); + } + return mURLClickSignal->connect(cb); +} + +boost::signals2::connection LLTextBase::setIsFriendCallback(const is_friend_signal_t::slot_type& cb) +{ + if (!mIsFriendSignal) + { + mIsFriendSignal = new is_friend_signal_t(); + } + return mIsFriendSignal->connect(cb); +} + +boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb) +{ + if (!mIsObjectBlockedSignal) + { + mIsObjectBlockedSignal = new is_blocked_signal_t(); + } + return mIsObjectBlockedSignal->connect(cb); +} + +// +// LLTextSegment +// + +LLTextSegment::~LLTextSegment() +{} + +bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + F32 fwidth = 0; + bool result = getDimensionsF32(first_char, num_chars, fwidth, height); + width = ll_round(fwidth); + return result; +} + +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, S32 line_ind) const { return 0; } +void LLTextSegment::updateLayout(const LLTextBase& editor) {} +F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { return draw_rect.mLeft; } +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) {} +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 ) {} +void LLTextSegment::dump() const {} +bool LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return false; } +bool LLTextSegment::handleScrollHWheel(S32 x, S32 y, S32 clicks) { return false; } +bool LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return false; } +const std::string& LLTextSegment::getName() const +{ + return LLStringUtil::null; +} +void LLTextSegment::onMouseCaptureLost() {} +void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {} +void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {} +bool LLTextSegment::hasMouseCapture() { return false; } + +// +// LLNormalTextSegment +// + +LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) +: LLTextSegment(start, end), + mStyle( style ), + mToken(NULL), + mEditor(editor) +{ + mFontHeight = 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) +: LLTextSegment(start, end), + mToken(NULL), + mEditor(editor) +{ + mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color)); + + mFontHeight = mStyle->getFont()->getLineHeight(); +} + +LLNormalTextSegment::~LLNormalTextSegment() +{ + mImageLoadedConnection.disconnect(); +} + + +F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + if( end - start > 0 ) + { + return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); + } + return draw_rect.mLeft; +} + +// Draws a single text segment, reversing the color for selection if needed. +F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRectf rect) +{ + F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha; + + const LLWString &text = getWText(); + + F32 right_x = rect.mLeft; + if (!mStyle->isVisible()) + { + return right_x; + } + + const LLFontGL* font = mStyle->getFont(); + + LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; + + if( selection_start > seg_start ) + { + // Draw normally + S32 start = seg_start; + S32 end = llmin( selection_start, seg_end ); + S32 length = end - start; + font->render(text, start, + rect, + color, + LLFontGL::LEFT, mEditor.mTextVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses(), + mEditor.getUseColor()); + } + rect.mLeft = right_x; + + if( (selection_start < seg_end) && (selection_end > seg_start) ) + { + // Draw reversed + S32 start = llmax( selection_start, seg_start ); + S32 end = llmin( selection_end, seg_end ); + S32 length = end - start; + + font->render(text, start, + rect, + mStyle->getSelectedColor().get(), + LLFontGL::LEFT, mEditor.mTextVAlign, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + length, + &right_x, + mEditor.getUseEllipses(), + mEditor.getUseColor()); + } + rect.mLeft = right_x; + if( selection_end < seg_end ) + { + // Draw normally + S32 start = llmax( selection_end, seg_start ); + S32 end = seg_end; + S32 length = end - start; + font->render(text, start, + rect, + color, + LLFontGL::LEFT, mEditor.mTextVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses(), + mEditor.getUseColor()); + } + return right_x; +} + +bool LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // 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::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } + } + return false; +} + +bool LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // 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; +} + +bool LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // 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; +} + +bool LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // 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) + { + std::string url = getStyle()->getLinkHREF(); + if (!mEditor.mForceUrlsExternal) + { + LLUrlAction::clickAction(url, mEditor.isContentTrusted()); + } + else if (!LLUrlAction::executeSLURL(url, mEditor.isContentTrusted())) + { + LLUrlAction::openURLExternal(url); + } + return true; + } + } + + return false; +} + +bool LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + std::string msg; + // do we have a tooltip for a loaded keyword (for script editor)? + if (mToken && !mToken->getToolTip().empty()) + { + const LLWString& wmsg = mToken->getToolTip(); + LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg), (mToken->getType() == LLKeywordToken::TT_FUNCTION)); + return true; + } + // or do we have an explicitly set tooltip (e.g., for Urls) + if (!mTooltip.empty()) + { + LLToolTipMgr::instance().show(mTooltip); + return true; + } + + return false; +} + +void LLNormalTextSegment::setToolTip(const std::string& tooltip) +{ + // we cannot replace a keyword tooltip that's loaded from a file + if (mToken) + { + LL_WARNS() << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << LL_ENDL; + return; + } + mTooltip = tooltip; +} + +bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + height = 0; + width = 0; + if (num_chars > 0) + { + height = mFontHeight; + const LLWString &text = getWText(); + // if last character is a newline, then return true, forcing line break + width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true); + } + return false; +} + +S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const +{ + const LLWString &text = getWText(); + return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, + (F32)segment_local_x_coord, + F32_MAX, + num_chars, + round); +} + +S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + const LLWString &text = getWText(); + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + num_pixels = llmax(0, num_pixels - image->getWidth()); + } + + S32 last_char = mEnd; + + // 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 offsetLength = text.length() - (segment_offset + mStart); + + if(getLength() < segment_offset + mStart) + { + LL_INFOS() << "getLength() < segment_offset + mStart\t getLength()\t" << getLength() << "\tsegment_offset:\t" + << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << "\tmax_chars\t" << max_chars << LL_ENDL; + } + + if( (offsetLength + 1) < max_chars) + { + LL_INFOS() << "offsetString.length() + 1 < max_chars\t max_chars:\t" << max_chars << "\toffsetString.length():\t" << offsetLength << " getLength() : " + << getLength() << "\tsegment_offset:\t" << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << LL_ENDL; + } + + S32 num_chars = mStyle->getFont()->maxDrawableChars( text.c_str() + (segment_offset + mStart), + (F32)num_pixels, + max_chars, + word_wrap_style); + + if (num_chars == 0 + && line_offset == 0 + && max_chars > 0) + { + // If at the beginning of a line, and a single character won't fit, draw it anyway + num_chars = 1; + } + + // include *either* the EOF or newline character in this run of text + // 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 < mEnd + && (last_char_in_run >= getLength())) + { + num_chars++; + } + return num_chars; +} + +void LLNormalTextSegment::dump() const +{ + LL_INFOS() << "Segment [" << +// mColor.mV[VX] << ", " << +// mColor.mV[VY] << ", " << +// mColor.mV[VZ] << "]\t[" << + mStart << ", " << + getEnd() << "]" << + LL_ENDL; +} + +/*virtual*/ +const LLWString& LLNormalTextSegment::getWText() const +{ + return mEditor.getWText(); +} + +/*virtual*/ +const S32 LLNormalTextSegment::getLength() const +{ + return mEditor.getLength(); +} + +LLLabelTextSegment::LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) +: LLNormalTextSegment(style, start, end, editor) +{ +} + +LLLabelTextSegment::LLLabelTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) +: LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + +/*virtual*/ +const LLWString& LLLabelTextSegment::getWText() const +{ + return mEditor.getWlabel(); +} +/*virtual*/ +const S32 LLLabelTextSegment::getLength() const +{ + return mEditor.getWlabel().length(); +} + +// +// LLEmojiTextSegment +// +LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor) + : LLNormalTextSegment(style, start, end, editor) +{ +} + +LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) + : LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + +bool LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (mTooltip.empty()) + { + LLWString emoji = getWText().substr(getStart(), getEnd() - getStart()); + if (!emoji.empty()) + { + mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]); + } + } + + return LLNormalTextSegment::handleToolTip(x, y, mask); +} + +// +// LLOnHoverChangeableTextSegment +// + +LLOnHoverChangeableTextSegment::LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ): + LLNormalTextSegment(normal_style, start, end, editor), + mHoveredStyle(style), + mNormalStyle(normal_style){} + +/*virtual*/ +F32 LLOnHoverChangeableTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + F32 result = LLNormalTextSegment::draw(start, end, selection_start, selection_end, draw_rect); + if (end == mEnd - mStart) + { + mStyle = mNormalStyle; + } + return result; +} + +/*virtual*/ +bool LLOnHoverChangeableTextSegment::handleHover(S32 x, S32 y, MASK mask) +{ + mStyle = mEditor.getSkipLinkUnderline() ? mNormalStyle : mHoveredStyle; + return LLNormalTextSegment::handleHover(x, y, mask); +} + + +// +// LLInlineViewSegment +// + +LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end) +: LLTextSegment(start, end), + mView(p.view), + mForceNewLine(p.force_newline), + mLeftPad(p.left_pad), + mRightPad(p.right_pad), + mTopPad(p.top_pad), + mBottomPad(p.bottom_pad) +{ +} + +LLInlineViewSegment::~LLInlineViewSegment() +{ + mView->die(); +} + +bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + if (first_char == 0 && num_chars == 0) + { + // We didn't fit on a line or were forced to new string + // the widget will fall on the next line, so width here is 0 + width = 0; + + if (mForceNewLine) + { + // Chat, string can't be smaller then font height even if it is empty + LLStyleSP s(new LLStyle(LLStyle::Params().visible(true))); + height = s->getFont()->getLineHeight(); + + return true; // new line + } + else + { + // height from previous segment in same string will be used, word-wrap + height = 0; + } + + } + else + { + 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, S32 line_ind) const +{ + // if putting a widget anywhere but at the beginning of a line + // and the widget doesn't fit or mForceNewLine is true + // then return 0 chars for that line, and all characters for the next + if (mForceNewLine && line_ind == 0) + { + return 0; + } + else if (line_offset != 0 && num_pixels < mView->getRect().getWidth()) + { + return 0; + } + else + { + return mEnd - mStart; + } +} + +void LLInlineViewSegment::updateLayout(const LLTextBase& editor) +{ + LLRect start_rect = editor.getDocRectFromDocIndex(mStart); + mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad); +} + +F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + // return padded width of widget + // widget is actually drawn during mDocumentView's draw() + return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad); +} + +void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor) +{ + editor->removeDocumentChild(mView); +} + +void LLInlineViewSegment::linkToDocument(LLTextBase* editor) +{ + editor->addDocumentChild(mView); +} + +LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1) +{ + LLStyleSP s( new LLStyle(LLStyle::Params().visible(true))); + + mFontHeight = s->getFont()->getLineHeight(); +} +LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1) +{ + mFontHeight = style->getFont()->getLineHeight(); +} +LLLineBreakTextSegment::~LLLineBreakTextSegment() +{ +} +bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + width = 0; + height = mFontHeight; + + return true; +} +S32 LLLineBreakTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + return 1; +} +F32 LLLineBreakTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + return draw_rect.mLeft; +} + +LLImageTextSegment::LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor) +: LLTextSegment(pos,pos+1), + mStyle( style ), + mEditor(editor) +{ +} + +LLImageTextSegment::~LLImageTextSegment() +{ +} + +static const S32 IMAGE_HPAD = 3; + +bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + width = 0; + height = mStyle->getFont()->getLineHeight(); + + LLUIImagePtr image = mStyle->getImage(); + if( num_chars>0 && image.notNull()) + { + width += image->getWidth() + IMAGE_HPAD; + height = llmax(height, image->getHeight() + IMAGE_HPAD ); + } + return false; +} + +S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + LLUIImagePtr image = mStyle->getImage(); + + if (image.isNull()) + { + return 1; + } + + S32 image_width = image->getWidth(); + if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD) + { + return 1; + } + + return 0; +} + +bool LLImageTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (!mTooltip.empty()) + { + LLToolTipMgr::instance().show(mTooltip); + return true; + } + + return false; +} + +void LLImageTextSegment::setToolTip(const std::string& tooltip) +{ + mTooltip = tooltip; +} + +F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + if ( (start >= 0) && (end <= mEnd - mStart)) + { + LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; + LLUIImagePtr image = mStyle->getImage(); + if (image.notNull()) + { + S32 style_image_height = image->getHeight(); + S32 style_image_width = image->getWidth(); + // Text is drawn from the top of the draw_rect downward + + S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); + // Align image to center of draw rect + S32 image_bottom = text_center - (style_image_height / 2); + image->draw(draw_rect.mLeft, image_bottom, + style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 3; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; + } + } + return 0.0; +} + +void LLTextBase::setWordWrap(bool wrap) +{ + mWordWrap = wrap; +} |