PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llui/lltextbase.cpp

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

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

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

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