PageRenderTime 64ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/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
  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. segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size()));
  1542. insertStringNoUndo(getLength(), widget_wide_text, &segments);
  1543. }
  1544. void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
  1545. {
  1546. // Save old state
  1547. S32 selection_start = mSelectionStart;
  1548. S32 selection_end = mSelectionEnd;
  1549. BOOL was_selecting = mIsSelecting;
  1550. S32 cursor_pos = mCursorPos;
  1551. S32 old_length = getLength();
  1552. BOOL cursor_was_at_end = (mCursorPos == old_length);
  1553. deselect();
  1554. setCursorPos(old_length);
  1555. if (mParseHighlights)
  1556. {
  1557. LLStyle::Params highlight_params(style_params);
  1558. LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part);
  1559. for (S32 i = 0; i < pieces.size(); i++)
  1560. {
  1561. LLSD color_llsd = pieces[i]["color"];
  1562. LLColor4 lcolor;
  1563. lcolor.setValue(color_llsd);
  1564. highlight_params.color = lcolor;
  1565. LLWString wide_text;
  1566. wide_text = utf8str_to_wstring(pieces[i]["text"].asString());
  1567. S32 cur_length = getLength();
  1568. LLStyleConstSP sp(new LLStyle(highlight_params));
  1569. LLTextSegmentPtr segmentp;
  1570. if(underline_on_hover_only)
  1571. {
  1572. highlight_params.font.style("NORMAL");
  1573. LLStyleConstSP normal_sp(new LLStyle(highlight_params));
  1574. segmentp = new LLOnHoverChangeableTextSegment(sp, normal_sp, cur_length, cur_length + wide_text.size(), *this);
  1575. }
  1576. else
  1577. {
  1578. segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this);
  1579. }
  1580. segment_vec_t segments;
  1581. segments.push_back(segmentp);
  1582. insertStringNoUndo(cur_length, wide_text, &segments);
  1583. }
  1584. }
  1585. else
  1586. {
  1587. LLWString wide_text;
  1588. wide_text = utf8str_to_wstring(new_text);
  1589. segment_vec_t segments;
  1590. S32 segment_start = old_length;
  1591. S32 segment_end = old_length + wide_text.size();
  1592. LLStyleConstSP sp(new LLStyle(style_params));
  1593. if (underline_on_hover_only)
  1594. {
  1595. LLStyle::Params normal_style_params(style_params);
  1596. normal_style_params.font.style("NORMAL");
  1597. LLStyleConstSP normal_sp(new LLStyle(normal_style_params));
  1598. segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this ));
  1599. }
  1600. else
  1601. {
  1602. segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this ));
  1603. }
  1604. insertStringNoUndo(getLength(), wide_text, &segments);
  1605. }
  1606. // Set the cursor and scroll position
  1607. if( selection_start != selection_end )
  1608. {
  1609. mSelectionStart = selection_start;
  1610. mSelectionEnd = selection_end;
  1611. mIsSelecting = was_selecting;
  1612. setCursorPos(cursor_pos);
  1613. }
  1614. else if( cursor_was_at_end )
  1615. {
  1616. setCursorPos(getLength());
  1617. }
  1618. else
  1619. {
  1620. setCursorPos(cursor_pos);
  1621. }
  1622. }
  1623. void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
  1624. {
  1625. if (new_text.empty()) return;
  1626. std::string::size_type start = 0;
  1627. std::string::size_type pos = new_text.find("\n",start);
  1628. while(pos!=-1)
  1629. {
  1630. if(pos!=start)
  1631. {
  1632. std::string str = std::string(new_text,start,pos-start);
  1633. appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
  1634. }
  1635. appendLineBreakSegment(style_params);
  1636. start = pos+1;
  1637. pos = new_text.find("\n",start);
  1638. }
  1639. std::string str = std::string(new_text,start,new_text.length()-start);
  1640. appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
  1641. }
  1642. void LLTextBase::replaceUrl(const std::string &url,
  1643. const std::string &label,
  1644. const std::string &icon)
  1645. {
  1646. // get the full (wide) text for the editor so we can change it
  1647. LLWString text = getWText();
  1648. LLWString wlabel = utf8str_to_wstring(label);
  1649. bool modified = false;
  1650. S32 seg_start = 0;
  1651. // iterate through each segment looking for ones styled as links
  1652. segment_set_t::iterator it;
  1653. for (it = mSegments.begin(); it != mSegments.end(); ++it)
  1654. {
  1655. LLTextSegment *seg = *it;
  1656. LLStyleConstSP style = seg->getStyle();
  1657. // update segment start/end length in case we replaced text earlier
  1658. S32 seg_length = seg->getEnd() - seg->getStart();
  1659. seg->setStart(seg_start);
  1660. seg->setEnd(seg_start + seg_length);
  1661. // if we find a link with our Url, then replace the label
  1662. if (style->getLinkHREF() == url)
  1663. {
  1664. S32 start = seg->getStart();
  1665. S32 end = seg->getEnd();
  1666. text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1);
  1667. seg->setEnd(start + wlabel.size());
  1668. modified = true;
  1669. }
  1670. // Icon might be updated when more avatar or group info
  1671. // becomes available
  1672. if (style->isImage() && style->getLinkHREF() == url)
  1673. {
  1674. LLUIImagePtr image = image_from_icon_name( icon );
  1675. if (image)
  1676. {
  1677. LLStyle::Params icon_params;
  1678. icon_params.image = image;
  1679. LLStyleConstSP new_style(new LLStyle(icon_params));
  1680. seg->setStyle(new_style);
  1681. modified = true;
  1682. }
  1683. }
  1684. // work out the character offset for the next segment
  1685. seg_start = seg->getEnd();
  1686. }
  1687. // update the editor with the new (wide) text string
  1688. if (modified)
  1689. {
  1690. getViewModel()->setDisplay(text);
  1691. deselect();
  1692. setCursorPos(mCursorPos);
  1693. needsReflow();
  1694. }
  1695. }
  1696. void LLTextBase::setWText(const LLWString& text)
  1697. {
  1698. setText(wstring_to_utf8str(text));
  1699. }
  1700. const LLWString& LLTextBase::getWText() const
  1701. {
  1702. return getViewModel()->getDisplay();
  1703. }
  1704. // If round is true, if the position is on the right half of a character, the cursor
  1705. // will be put to its right. If round is false, the cursor will always be put to the
  1706. // character's left.
  1707. S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line) const
  1708. {
  1709. // Figure out which line we're nearest to.
  1710. LLRect visible_region = getVisibleDocumentRect();
  1711. LLRect doc_rect = mDocumentView->getRect();
  1712. S32 doc_y = local_y - doc_rect.mBottom;
  1713. // binary search for line that starts before local_y
  1714. line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_y, compare_bottom());
  1715. if (line_iter == mLineInfoList.end())
  1716. {
  1717. return getLength(); // past the end
  1718. }
  1719. S32 pos = getLength();
  1720. S32 start_x = line_iter->mRect.mLeft + doc_rect.mLeft;
  1721. segment_set_t::iterator line_seg_iter;
  1722. S32 line_seg_offset;
  1723. for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset);
  1724. line_seg_iter != mSegments.end();
  1725. ++line_seg_iter, line_seg_offset = 0)
  1726. {
  1727. const LLTextSegmentPtr segmentp = *line_seg_iter;
  1728. S32 segment_line_start = segmentp->getStart() + line_seg_offset;
  1729. S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start;
  1730. S32 text_width, text_height;
  1731. bool newline = segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height);
  1732. if(newline)
  1733. {
  1734. pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round);
  1735. break;
  1736. }
  1737. // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line
  1738. if (hit_past_end_of_line && doc_y > line_iter->mRect.mTop)
  1739. {
  1740. pos = segment_line_start;
  1741. break;
  1742. }
  1743. if (local_x < start_x + text_width) // cursor to left of right edge of text
  1744. {
  1745. // Figure out which character we're nearest to.
  1746. S32 offset;
  1747. if (!segmentp->canEdit())
  1748. {
  1749. S32 segment_width, segment_height;
  1750. segmentp->getDimensions(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height);
  1751. if (round && local_x - start_x > segment_width / 2)
  1752. {
  1753. offset = segment_line_length;
  1754. }
  1755. else
  1756. {
  1757. offset = 0;
  1758. }
  1759. }
  1760. else
  1761. {
  1762. offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round);
  1763. }
  1764. pos = segment_line_start + offset;
  1765. break;
  1766. }
  1767. else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd)
  1768. {
  1769. if (getLineNumFromDocIndex(line_iter->mDocIndexEnd - 1) == line_iter->mLineNum)
  1770. {
  1771. // if segment wraps to the next line we should step one char back
  1772. // to compensate for the space char between words
  1773. // which is removed due to wrapping
  1774. pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength());
  1775. }
  1776. else
  1777. {
  1778. pos = llclamp(line_iter->mDocIndexEnd, 0, getLength());
  1779. }
  1780. break;
  1781. }
  1782. start_x += text_width;
  1783. }
  1784. return pos;
  1785. }
  1786. // returns rectangle of insertion caret
  1787. // in document coordinate frame from given index into text
  1788. LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const
  1789. {
  1790. if (mLineInfoList.empty())
  1791. {
  1792. return LLRect();
  1793. }
  1794. LLRect doc_rect;
  1795. // clamp pos to valid values
  1796. pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1);
  1797. // find line that contains cursor
  1798. line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare());
  1799. doc_rect.mLeft = line_iter->mRect.mLeft;
  1800. doc_rect.mBottom = line_iter->mRect.mBottom;
  1801. doc_rect.mTop = line_iter->mRect.mTop;
  1802. segment_set_t::iterator line_seg_iter;
  1803. S32 line_seg_offset;
  1804. segment_set_t::iterator cursor_seg_iter;
  1805. S32 cursor_seg_offset;
  1806. getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset);
  1807. getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset);
  1808. while(line_seg_iter != mSegments.end())
  1809. {
  1810. const LLTextSegmentPtr segmentp = *line_seg_iter;
  1811. if (line_seg_iter == cursor_seg_iter)
  1812. {
  1813. // cursor advanced to right based on difference in offset of cursor to start of line
  1814. S32 segment_width, segment_height;
  1815. segmentp->getDimensions(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height);
  1816. doc_rect.mLeft += segment_width;
  1817. break;
  1818. }
  1819. else
  1820. {
  1821. // add remainder of current text segment to cursor position
  1822. S32 segment_width, segment_height;
  1823. segmentp->getDimensions(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height);
  1824. doc_rect.mLeft += segment_width;
  1825. // offset will be 0 for all segments after the first
  1826. line_seg_offset = 0;
  1827. // go to next text segment on this line
  1828. ++line_seg_iter;
  1829. }
  1830. }
  1831. // set rect to 0 width
  1832. doc_rect.mRight = doc_rect.mLeft;
  1833. return doc_rect;
  1834. }
  1835. LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const
  1836. {
  1837. LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
  1838. if (mBorderVisible)
  1839. {
  1840. content_window_rect.stretch(-1);
  1841. }
  1842. LLRect local_rect;
  1843. if (mLineInfoList.empty())
  1844. {
  1845. // return default height rect in upper left
  1846. local_rect = content_window_rect;
  1847. local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->getLineHeight());
  1848. return local_rect;
  1849. }
  1850. // get the rect in document coordinates
  1851. LLRect doc_rect = getDocRectFromDocIndex(pos);
  1852. // compensate for scrolled, inset view of doc
  1853. LLRect scrolled_view_rect = getVisibleDocumentRect();
  1854. local_rect = doc_rect;
  1855. local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft,
  1856. content_window_rect.mBottom - scrolled_view_rect.mBottom);
  1857. return local_rect;
  1858. }
  1859. void LLTextBase::updateCursorXPos()
  1860. {
  1861. // reset desired x cursor position
  1862. mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft;
  1863. }
  1864. void LLTextBase::startOfLine()
  1865. {
  1866. S32 offset = getLineOffsetFromDocIndex(mCursorPos);
  1867. setCursorPos(mCursorPos - offset);
  1868. }
  1869. void LLTextBase::endOfLine()
  1870. {
  1871. S32 line = getLineNumFromDocIndex(mCursorPos);
  1872. S32 num_lines = getLineCount();
  1873. if (line + 1 >= num_lines)
  1874. {
  1875. setCursorPos(getLength());
  1876. }
  1877. else
  1878. {
  1879. setCursorPos( getLineStart(line + 1) - 1 );
  1880. }
  1881. }
  1882. void LLTextBase::startOfDoc()
  1883. {
  1884. setCursorPos(0);
  1885. if (mScroller)
  1886. {
  1887. mScroller->goToTop();
  1888. }
  1889. }
  1890. void LLTextBase::endOfDoc()
  1891. {
  1892. setCursorPos(getLength());
  1893. if (mScroller)
  1894. {
  1895. mScroller->goToBottom();
  1896. }
  1897. }
  1898. void LLTextBase::changePage( S32 delta )
  1899. {
  1900. const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10;
  1901. if (delta == 0 || !mScroller) return;
  1902. LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
  1903. if( delta == -1 )
  1904. {
  1905. mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE);
  1906. }
  1907. else
  1908. if( delta == 1 )
  1909. {
  1910. mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE);
  1911. }
  1912. if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect)
  1913. {
  1914. // cursor didn't change apparent position, so move to top or bottom of document, respectively
  1915. if (delta < 0)
  1916. {
  1917. startOfDoc();
  1918. }
  1919. else
  1920. {
  1921. endOfDoc();
  1922. }
  1923. }
  1924. else
  1925. {
  1926. setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false);
  1927. }
  1928. }
  1929. // Picks a new cursor position based on the screen size of text being drawn.
  1930. void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset )
  1931. {
  1932. setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset);
  1933. }
  1934. void LLTextBase::changeLine( S32 delta )
  1935. {
  1936. S32 line = getLineNumFromDocIndex(mCursorPos);
  1937. S32 new_line = line;
  1938. if( (delta < 0) && (line > 0 ) )
  1939. {
  1940. new_line = line - 1;
  1941. }
  1942. else if( (delta > 0) && (line < (getLineCount() - 1)) )
  1943. {
  1944. new_line = line + 1;
  1945. }
  1946. LLRect visible_region = getVisibleDocumentRect();
  1947. S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE);
  1948. setCursorPos(new_cursor_pos, true);
  1949. }
  1950. bool LLTextBase::scrolledToStart()
  1951. {
  1952. return mScroller->isAtTop();
  1953. }
  1954. bool LLTextBase::scrolledToEnd()
  1955. {
  1956. return mScroller->isAtBottom();
  1957. }
  1958. bool LLTextBase::setCursor(S32 row, S32 column)
  1959. {
  1960. if (row < 0 || column < 0) return false;
  1961. S32 n_lines = mLineInfoList.size();
  1962. for (S32 line = row; line < n_lines; ++line)
  1963. {
  1964. const line_info& li = mLineInfoList[line];
  1965. if (li.mLineNum < row)
  1966. {
  1967. continue;
  1968. }
  1969. else if (li.mLineNum > row)
  1970. {
  1971. break; // invalid column specified
  1972. }
  1973. // Found the given row.
  1974. S32 line_length = li.mDocIndexEnd - li.mDocIndexStart;;
  1975. if (column >= line_length)
  1976. {
  1977. column -= line_length;
  1978. continue;
  1979. }
  1980. // Found the given column.
  1981. updateCursorXPos();
  1982. S32 doc_pos = li.mDocIndexStart + column;
  1983. return setCursorPos(doc_pos);
  1984. }
  1985. return false; // invalid row or column specified
  1986. }
  1987. bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset)
  1988. {
  1989. S32 new_cursor_pos = cursor_pos;
  1990. if (new_cursor_pos != mCursorPos)
  1991. {
  1992. new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos);
  1993. }
  1994. mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength());
  1995. needsScroll();
  1996. if (!keep_cursor_offset)
  1997. updateCursorXPos();
  1998. // did we get requested position?
  1999. return new_cursor_pos == cursor_pos;
  2000. }
  2001. // constraint cursor to editable segments of document
  2002. S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction)
  2003. {
  2004. segment_set_t::iterator segment_iter;
  2005. S32 offset;
  2006. getSegmentAndOffset(index, &segment_iter, &offset);
  2007. if (segment_iter == mSegments.end())
  2008. {
  2009. return 0;
  2010. }
  2011. LLTextSegmentPtr segmentp = *segment_iter;
  2012. if (segmentp->canEdit())
  2013. {
  2014. return segmentp->getStart() + offset;
  2015. }
  2016. else if (segmentp->getStart() < index && index < segmentp->getEnd())
  2017. {
  2018. // bias towards document end
  2019. if (increasing_direction)
  2020. {
  2021. return segmentp->getEnd();
  2022. }
  2023. // bias towards document start
  2024. else
  2025. {
  2026. return segmentp->getStart();
  2027. }
  2028. }
  2029. else
  2030. {
  2031. return index;
  2032. }
  2033. }
  2034. void LLTextBase::updateRects()
  2035. {
  2036. if (mLineInfoList.empty())
  2037. {
  2038. mTextBoundingRect = LLRect(0, mVPad, mHPad, 0);
  2039. }
  2040. else
  2041. {
  2042. mTextBoundingRect = mLineInfoList.begin()->mRect;
  2043. for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin();
  2044. line_iter != mLineInfoList.end();
  2045. ++line_iter)
  2046. {
  2047. mTextBoundingRect.unionWith(line_iter->mRec