lltextbase.cpp
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:73k
源码类别:

游戏引擎

开发平台:

C++ Builder

  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=viewergpl$
  7.  * 
  8.  * Copyright (c) 2009-2010, Linden Research, Inc.
  9.  * 
  10.  * Second Life Viewer Source Code
  11.  * The source code in this file ("Source Code") is provided by Linden Lab
  12.  * to you under the terms of the GNU General Public License, version 2.0
  13.  * ("GPL"), unless you have obtained a separate licensing agreement
  14.  * ("Other License"), formally executed by you and Linden Lab.  Terms of
  15.  * the GPL can be found in doc/GPL-license.txt in this distribution, or
  16.  * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  17.  * 
  18.  * There are special exceptions to the terms and conditions of the GPL as
  19.  * it is applied to this Source Code. View the full text of the exception
  20.  * in the file doc/FLOSS-exception.txt in this software distribution, or
  21.  * online at
  22.  * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  23.  * 
  24.  * By copying, modifying or distributing this software, you acknowledge
  25.  * that you have read and understood your obligations described above,
  26.  * and agree to abide by those obligations.
  27.  * 
  28.  * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  29.  * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  30.  * COMPLETENESS OR PERFORMANCE.
  31.  * $/LicenseInfo$
  32.  */
  33. #include "linden_common.h"
  34. #include "lltextbase.h"
  35. #include "lllocalcliprect.h"
  36. #include "llmenugl.h"
  37. #include "llscrollcontainer.h"
  38. #include "llstl.h"
  39. #include "lltextparser.h"
  40. #include "lltooltip.h"
  41. #include "lluictrl.h"
  42. #include "llurlaction.h"
  43. #include "llurlregistry.h"
  44. #include "llview.h"
  45. #include "llwindow.h"
  46. #include <boost/bind.hpp>
  47. const F32 CURSOR_FLASH_DELAY = 1.0f;  // in seconds
  48. const S32 CURSOR_THICKNESS = 2;
  49. LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) 
  50. : mDocIndexStart(index_start), 
  51. mDocIndexEnd(index_end),
  52. mRect(rect),
  53. mLineNum(line_num)
  54. {}
  55. bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const
  56. {
  57. // sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11)
  58. if (a->getEnd() == b->getEnd())
  59. {
  60. return a->getStart() < b->getStart();
  61. }
  62. return a->getEnd() < b->getEnd();
  63. }
  64. // helper functors
  65. struct LLTextBase::compare_bottom
  66. {
  67. bool operator()(const S32& a, const LLTextBase::line_info& b) const
  68. {
  69. return a > b.mRect.mBottom; // bottom of a is higher than bottom of b
  70. }
  71. bool operator()(const LLTextBase::line_info& a, const S32& b) const
  72. {
  73. return a.mRect.mBottom > b; // bottom of a is higher than bottom of b
  74. }
  75. bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
  76. {
  77. return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b
  78. }
  79. };
  80. // helper functors
  81. struct LLTextBase::compare_top
  82. {
  83. bool operator()(const S32& a, const LLTextBase::line_info& b) const
  84. {
  85. return a > b.mRect.mTop; // top of a is higher than top of b
  86. }
  87. bool operator()(const LLTextBase::line_info& a, const S32& b) const
  88. {
  89. return a.mRect.mTop > b; // top of a is higher than top of b
  90. }
  91. bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
  92. {
  93. return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b
  94. }
  95. };
  96. struct LLTextBase::line_end_compare
  97. {
  98. bool operator()(const S32& pos, const LLTextBase::line_info& info) const
  99. {
  100. return (pos < info.mDocIndexEnd);
  101. }
  102. bool operator()(const LLTextBase::line_info& info, const S32& pos) const
  103. {
  104. return (info.mDocIndexEnd < pos);
  105. }
  106. bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const
  107. {
  108. return (a.mDocIndexEnd < b.mDocIndexEnd);
  109. }
  110. };
  111. //////////////////////////////////////////////////////////////////////////
  112. //
  113. // LLTextBase
  114. //
  115. // register LLTextBase::Params under name "textbase"
  116. static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase");
  117. LLTextBase::LineSpacingParams::LineSpacingParams()
  118. : multiple("multiple", 1.f),
  119. pixels("pixels", 0)
  120. {
  121. }
  122. LLTextBase::Params::Params()
  123. : cursor_color("cursor_color"),
  124. text_color("text_color"),
  125. text_readonly_color("text_readonly_color"),
  126. bg_visible("bg_visible", false),
  127. border_visible("border_visible", false),
  128. bg_readonly_color("bg_readonly_color"),
  129. bg_writeable_color("bg_writeable_color"),
  130. bg_focus_color("bg_focus_color"),
  131. allow_scroll("allow_scroll", true),
  132. track_end("track_end", false),
  133. read_only("read_only", false),
  134. v_pad("v_pad", 0),
  135. h_pad("h_pad", 0),
  136. clip_partial("clip_partial", true),
  137. line_spacing("line_spacing"),
  138. max_text_length("max_length", 255),
  139. font_shadow("font_shadow"),
  140. wrap("wrap"),
  141. use_ellipses("use_ellipses", false),
  142. allow_html("allow_html", false),
  143. parse_highlights("parse_highlights", false)
  144. {
  145. addSynonym(track_end, "track_bottom");
  146. addSynonym(wrap, "word_wrap");
  147. }
  148. LLTextBase::LLTextBase(const LLTextBase::Params &p) 
  149. : LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)),
  150. mURLClickSignal(),
  151. mMaxTextByteLength( p.max_text_length ),
  152. mDefaultFont(p.font),
  153. mFontShadow(p.font_shadow),
  154. mPopupMenu(NULL),
  155. mReadOnly(p.read_only),
  156. mCursorColor(p.cursor_color),
  157. mFgColor(p.text_color),
  158. mBorderVisible( p.border_visible ),
  159. mReadOnlyFgColor(p.text_readonly_color),
  160. mWriteableBgColor(p.bg_writeable_color),
  161. mReadOnlyBgColor(p.bg_readonly_color),
  162. mFocusBgColor(p.bg_focus_color),
  163. mReflowIndex(S32_MAX),
  164. mCursorPos( 0 ),
  165. mScrollNeeded(FALSE),
  166. mDesiredXPixel(-1),
  167. mHPad(p.h_pad),
  168. mVPad(p.v_pad),
  169. mHAlign(p.font_halign),
  170. mLineSpacingMult(p.line_spacing.multiple),
  171. mLineSpacingPixels(p.line_spacing.pixels),
  172. mClipPartial(p.clip_partial && !p.allow_scroll),
  173. mTrackEnd( p.track_end ),
  174. mScrollIndex(-1),
  175. mSelectionStart( 0 ),
  176. mSelectionEnd( 0 ),
  177. mIsSelecting( FALSE ),
  178. mWordWrap(p.wrap),
  179. mUseEllipses( p.use_ellipses ),
  180. mParseHTML(p.allow_html),
  181. mParseHighlights(p.parse_highlights),
  182. mBGVisible(p.bg_visible),
  183. mScroller(NULL)
  184. {
  185. if(p.allow_scroll)
  186. {
  187. LLScrollContainer::Params scroll_params;
  188. scroll_params.name = "text scroller";
  189. scroll_params.rect = getLocalRect();
  190. scroll_params.follows.flags = FOLLOWS_ALL;
  191. scroll_params.is_opaque = false;
  192. scroll_params.mouse_opaque = false;
  193. scroll_params.min_auto_scroll_rate = 200;
  194. scroll_params.max_auto_scroll_rate = 800;
  195. scroll_params.border_visible = p.border_visible;
  196. mScroller = LLUICtrlFactory::create<LLScrollContainer>(scroll_params);
  197. addChild(mScroller);
  198. }
  199. LLView::Params view_params;
  200. view_params.name = "text_contents";
  201. view_params.rect =  LLRect(0, 500, 500, 0);
  202. view_params.mouse_opaque = false;
  203. mDocumentView = LLUICtrlFactory::create<LLView>(view_params);
  204. if (mScroller)
  205. {
  206. mScroller->addChild(mDocumentView);
  207. }
  208. else
  209. {
  210. addChild(mDocumentView);
  211. }
  212. createDefaultSegment();
  213. updateRects();
  214. }
  215. LLTextBase::~LLTextBase()
  216. {
  217. // Menu, like any other LLUICtrl, is deleted by its parent - gMenuHolder
  218. clearSegments();
  219. }
  220. void LLTextBase::initFromParams(const LLTextBase::Params& p)
  221. {
  222. LLUICtrl::initFromParams(p);
  223. resetDirty(); // Update saved text state
  224. updateSegments();
  225. // HACK: work around enabled == readonly design bug -- RN
  226. // setEnabled will modify our read only status, so do this after
  227. // LLTextBase::initFromParams
  228. if (p.read_only.isProvided())
  229. {
  230. mReadOnly = p.read_only;
  231. }
  232. // HACK:  text editors always need to be enabled so that we can scroll
  233. LLView::setEnabled(true);
  234. }
  235. bool LLTextBase::truncate()
  236. {
  237. BOOL did_truncate = FALSE;
  238. // First rough check - if we're less than 1/4th the size, we're OK
  239. if (getLength() >= S32(mMaxTextByteLength / 4))
  240. {
  241. // Have to check actual byte size
  242.         LLWString text(getWText());
  243. S32 utf8_byte_size = wstring_utf8_length(text);
  244. if ( utf8_byte_size > mMaxTextByteLength )
  245. {
  246. // Truncate safely in UTF-8
  247. std::string temp_utf8_text = wstring_to_utf8str(text);
  248. temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength );
  249. LLWString text = utf8str_to_wstring( temp_utf8_text );
  250. // remove extra bit of current string, to preserve formatting, etc.
  251. removeStringNoUndo(text.size(), getWText().size() - text.size());
  252. did_truncate = TRUE;
  253. }
  254. }
  255. return did_truncate;
  256. }
  257. LLStyle::Params LLTextBase::getDefaultStyleParams()
  258. {
  259. return LLStyle::Params()
  260. .color(LLUIColor(&mFgColor))
  261. .readonly_color(LLUIColor(&mReadOnlyFgColor))
  262. .font(mDefaultFont)
  263. .drop_shadow(mFontShadow);
  264. }
  265. void LLTextBase::onValueChange(S32 start, S32 end)
  266. {
  267. }
  268. // Draws the black box behind the selected text
  269. void LLTextBase::drawSelectionBackground()
  270. {
  271. // Draw selection even if we don't have keyboard focus for search/replace
  272. if( hasSelection() && !mLineInfoList.empty())
  273. {
  274. std::vector<LLRect> selection_rects;
  275. S32 selection_left = llmin( mSelectionStart, mSelectionEnd );
  276. S32 selection_right = llmax( mSelectionStart, mSelectionEnd );
  277. LLRect selection_rect = mVisibleTextRect;
  278. // Skip through the lines we aren't drawing.
  279. LLRect content_display_rect = getVisibleDocumentRect();
  280. // binary search for line that starts before top of visible buffer
  281. line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom());
  282. line_list_t::const_iterator end_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top());
  283. bool done = false;
  284. // Find the coordinates of the selected area
  285. for (;line_iter != end_iter && !done; ++line_iter)
  286. {
  287. // is selection visible on this line?
  288. if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right)
  289. {
  290. segment_set_t::iterator segment_iter;
  291. S32 segment_offset;
  292. getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset);
  293. LLRect selection_rect;
  294. selection_rect.mLeft = line_iter->mRect.mLeft;
  295. selection_rect.mRight = line_iter->mRect.mLeft;
  296. selection_rect.mBottom = line_iter->mRect.mBottom;
  297. selection_rect.mTop = line_iter->mRect.mTop;
  298. for(;segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0)
  299. {
  300. LLTextSegmentPtr segmentp = *segment_iter;
  301. S32 segment_line_start = segmentp->getStart() + segment_offset;
  302. S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd);
  303. S32 segment_width = 0;
  304. S32 segment_height = 0;
  305. // if selection after beginning of segment
  306. if(selection_left >= segment_line_start)
  307. {
  308. S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start;
  309. segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
  310. selection_rect.mLeft += segment_width;
  311. }
  312. // if selection spans end of current segment...
  313. if (selection_right > segment_line_end)
  314. {
  315. // extend selection slightly beyond end of line
  316. // to indicate selection of newline character (use "n" character to determine width)
  317. S32 num_chars = segment_line_end - segment_line_start;
  318. segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
  319. selection_rect.mRight += segment_width;
  320. }
  321. // else if selection ends on current segment...
  322. else
  323. {
  324. S32 num_chars = selection_right - segment_line_start;
  325. segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
  326. selection_rect.mRight += segment_width;
  327. break;
  328. }
  329. }
  330. selection_rects.push_back(selection_rect);
  331. }
  332. }
  333. // Draw the selection box (we're using a box instead of reversing the colors on the selected text).
  334. gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
  335. const LLColor4& color = mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get();
  336. F32 alpha = hasFocus() ? 0.7f : 0.3f;
  337. alpha *= getDrawContext().mAlpha;
  338. LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha);
  339. for (std::vector<LLRect>::iterator rect_it = selection_rects.begin();
  340. rect_it != selection_rects.end();
  341. ++rect_it)
  342. {
  343. LLRect selection_rect = *rect_it;
  344. selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
  345. gl_rect_2d(selection_rect, selection_color);
  346. }
  347. }
  348. }
  349. void LLTextBase::drawCursor()
  350. {
  351. F32 alpha = getDrawContext().mAlpha;
  352. if( hasFocus()
  353. && gFocusMgr.getAppHasFocus()
  354. && !mReadOnly)
  355. {
  356. const LLWString &wtext = getWText();
  357. const llwchar* text = wtext.c_str();
  358. LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
  359. cursor_rect.translate(-1, 0);
  360. segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos);
  361. // take style from last segment
  362. LLTextSegmentPtr segmentp;
  363. if (seg_it != mSegments.end())
  364. {
  365. segmentp = *seg_it;
  366. }
  367. else
  368. {
  369. //segmentp = mSegments.back();
  370. return;
  371. }
  372. // Draw the cursor
  373. // (Flash the cursor every half second starting a fixed time after the last keystroke)
  374. F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32();
  375. if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
  376. {
  377. if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
  378. {
  379. S32 segment_width = 0;
  380. S32 segment_height = 0;
  381. segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height);
  382. S32 width = llmax(CURSOR_THICKNESS, segment_width);
  383. cursor_rect.mRight = cursor_rect.mLeft + width;
  384. }
  385. else
  386. {
  387. cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS;
  388. }
  389. gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
  390. LLColor4 cursor_color = mCursorColor.get() % alpha;
  391. gGL.color4fv( cursor_color.mV );
  392. gl_rect_2d(cursor_rect);
  393. if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != 'n')
  394. {
  395. LLColor4 text_color;
  396. const LLFontGL* fontp;
  397. if (segmentp)
  398. {
  399. text_color = segmentp->getColor();
  400. fontp = segmentp->getStyle()->getFont();
  401. }
  402. else if (mReadOnly)
  403. {
  404. text_color = mReadOnlyFgColor.get();
  405. fontp = mDefaultFont;
  406. }
  407. else
  408. {
  409. text_color = mFgColor.get();
  410. fontp = mDefaultFont;
  411. }
  412. fontp->render(text, mCursorPos, cursor_rect.mLeft, cursor_rect.mTop, 
  413. LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha),
  414. LLFontGL::LEFT, LLFontGL::TOP,
  415. LLFontGL::NORMAL,
  416. LLFontGL::NO_SHADOW,
  417. 1);
  418. }
  419. // Make sure the IME is in the right place
  420. LLRect screen_pos = calcScreenRect();
  421. LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) );
  422. ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]);
  423. ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]);
  424. getWindow()->setLanguageTextInput( ime_pos );
  425. }
  426. }
  427. }
  428. void LLTextBase::drawText()
  429. {
  430. const S32 text_len = getLength();
  431. if( text_len <= 0 )
  432. {
  433. return;
  434. }
  435. S32 selection_left = -1;
  436. S32 selection_right = -1;
  437. // Draw selection even if we don't have keyboard focus for search/replace
  438. if( hasSelection())
  439. {
  440. selection_left = llmin( mSelectionStart, mSelectionEnd );
  441. selection_right = llmax( mSelectionStart, mSelectionEnd );
  442. }
  443. LLRect scrolled_view_rect = getVisibleDocumentRect();
  444. std::pair<S32, S32> line_range = getVisibleLines(mClipPartial);
  445. S32 first_line = line_range.first;
  446. S32 last_line = line_range.second;
  447. if (first_line >= last_line)
  448. {
  449. return;
  450. }
  451. S32 line_start = getLineStart(first_line);
  452. // find first text segment that spans top of visible portion of text buffer
  453. segment_set_t::iterator seg_iter = getSegIterContaining(line_start);
  454. if (seg_iter == mSegments.end()) 
  455. {
  456. return;
  457. }
  458. LLTextSegmentPtr cur_segment = *seg_iter;
  459. for (S32 cur_line = first_line; cur_line < last_line; cur_line++)
  460. {
  461. S32 next_line = cur_line + 1;
  462. line_info& line = mLineInfoList[cur_line];
  463. S32 next_start = -1;
  464. S32 line_end = text_len;
  465. if (next_line < getLineCount())
  466. {
  467. next_start = getLineStart(next_line);
  468. line_end = next_start;
  469. }
  470. LLRect text_rect(line.mRect.mLeft + mVisibleTextRect.mLeft - scrolled_view_rect.mLeft,
  471. line.mRect.mTop - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom,
  472. llmin(mDocumentView->getRect().getWidth(), line.mRect.mRight) - scrolled_view_rect.mLeft,
  473. line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom);
  474. // draw a single line of text
  475. S32 seg_start = line_start;
  476. while( seg_start < line_end )
  477. {
  478. while( cur_segment->getEnd() <= seg_start )
  479. {
  480. seg_iter++;
  481. if (seg_iter == mSegments.end())
  482. {
  483. llwarns << "Ran off the segmentation end!" << llendl;
  484. return;
  485. }
  486. cur_segment = *seg_iter;
  487. }
  488. S32 clipped_end = llmin( line_end, cur_segment->getEnd() )  - cur_segment->getStart();
  489. if (mUseEllipses // using ellipses
  490. && clipped_end == line_end // last segment on line
  491. && next_line == last_line // this is the last visible line
  492. && last_line < (S32)mLineInfoList.size()) // and there is more text to display
  493. {
  494. // more lines of text to go, but we can't fit them
  495. // so shrink text rect to force ellipses
  496. text_rect.mRight -= 2;
  497. }
  498. text_rect.mLeft = (S32)(cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect));
  499. seg_start = clipped_end + cur_segment->getStart();
  500. }
  501. line_start = next_start;
  502. }
  503. }
  504. ///////////////////////////////////////////////////////////////////
  505. // Returns change in number of characters in mWText
  506. S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments )
  507. {
  508. LLWString text(getWText());
  509. S32 old_len = text.length(); // length() returns character length
  510. S32 insert_len = wstr.length();
  511. pos = getEditableIndex(pos, true);
  512. segment_set_t::iterator seg_iter = getSegIterContaining(pos);
  513. LLTextSegmentPtr default_segment;
  514. LLTextSegmentPtr segmentp;
  515. if (seg_iter != mSegments.end())
  516. {
  517. segmentp = *seg_iter;
  518. }
  519. else
  520. {
  521. //segmentp = mSegments.back();
  522. return pos;
  523. }
  524. if (segmentp->canEdit())
  525. {
  526. segmentp->setEnd(segmentp->getEnd() + insert_len);
  527. if (seg_iter != mSegments.end())
  528. {
  529. ++seg_iter;
  530. }
  531. }
  532. else
  533. {
  534. // create default editable segment to hold new text
  535. LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
  536. default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this);
  537. }
  538. // shift remaining segments to right
  539. for(;seg_iter != mSegments.end(); ++seg_iter)
  540. {
  541. LLTextSegmentPtr segmentp = *seg_iter;
  542. segmentp->setStart(segmentp->getStart() + insert_len);
  543. segmentp->setEnd(segmentp->getEnd() + insert_len);
  544. }
  545. // insert new segments
  546. if (segments)
  547. {
  548. if (default_segment.notNull())
  549. {
  550. // potentially overwritten by segments passed in
  551. insertSegment(default_segment);
  552. }
  553. for (segment_vec_t::iterator seg_iter = segments->begin();
  554. seg_iter != segments->end();
  555. ++seg_iter)
  556. {
  557. LLTextSegment* segmentp = *seg_iter;
  558. insertSegment(segmentp);
  559. }
  560. }
  561. text.insert(pos, wstr);
  562.     getViewModel()->setDisplay(text);
  563. if ( truncate() )
  564. {
  565. insert_len = getLength() - old_len;
  566. }
  567. onValueChange(pos, pos + insert_len);
  568. needsReflow(pos);
  569. return insert_len;
  570. }
  571. S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
  572. {
  573.     LLWString text(getWText());
  574. segment_set_t::iterator seg_iter = getSegIterContaining(pos);
  575. while(seg_iter != mSegments.end())
  576. {
  577. LLTextSegmentPtr segmentp = *seg_iter;
  578. S32 end = pos + length;
  579. if (segmentp->getStart() < pos)
  580. {
  581. // deleting from middle of segment
  582. if (segmentp->getEnd() > end)
  583. {
  584. segmentp->setEnd(segmentp->getEnd() - length);
  585. }
  586. // truncating segment
  587. else
  588. {
  589. segmentp->setEnd(pos);
  590. }
  591. }
  592. else if (segmentp->getStart() < end)
  593. {
  594. // deleting entire segment
  595. if (segmentp->getEnd() <= end)
  596. {
  597. // remove segment
  598. segmentp->unlinkFromDocument(this);
  599. segment_set_t::iterator seg_to_erase(seg_iter++);
  600. mSegments.erase(seg_to_erase);
  601. continue;
  602. }
  603. // deleting head of segment
  604. else
  605. {
  606. segmentp->setStart(pos);
  607. segmentp->setEnd(segmentp->getEnd() - length);
  608. }
  609. }
  610. else
  611. {
  612. // shifting segments backward to fill deleted portion
  613. segmentp->setStart(segmentp->getStart() - length);
  614. segmentp->setEnd(segmentp->getEnd() - length);
  615. }
  616. ++seg_iter;
  617. }
  618. text.erase(pos, length);
  619.     getViewModel()->setDisplay(text);
  620. // recreate default segment in case we erased everything
  621. createDefaultSegment();
  622. onValueChange(pos, pos);
  623. needsReflow(pos);
  624. return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length
  625. }
  626. S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc)
  627. {
  628. if (pos > (S32)getLength())
  629. {
  630. return 0;
  631. }
  632.     LLWString text(getWText());
  633. text[pos] = wc;
  634.     getViewModel()->setDisplay(text);
  635. onValueChange(pos, pos + 1);
  636. needsReflow(pos);
  637. return 1;
  638. }
  639. void LLTextBase::createDefaultSegment()
  640. {
  641. // ensures that there is always at least one segment
  642. if (mSegments.empty())
  643. {
  644. LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
  645. LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this);
  646. mSegments.insert(default_segment);
  647. default_segment->linkToDocument(this);
  648. }
  649. }
  650. void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert)
  651. {
  652. if (segment_to_insert.isNull()) 
  653. {
  654. return;
  655. }
  656. segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart());
  657. S32 reflow_start_index = 0;
  658. if (cur_seg_iter == mSegments.end())
  659. {
  660. mSegments.insert(segment_to_insert);
  661. segment_to_insert->linkToDocument(this);
  662. reflow_start_index = segment_to_insert->getStart();
  663. }
  664. else
  665. {
  666. LLTextSegmentPtr cur_segmentp = *cur_seg_iter;
  667. reflow_start_index = cur_segmentp->getStart();
  668. if (cur_segmentp->getStart() < segment_to_insert->getStart())
  669. {
  670. S32 old_segment_end = cur_segmentp->getEnd();
  671. // split old at start point for new segment
  672. cur_segmentp->setEnd(segment_to_insert->getStart());
  673. // advance to next segment
  674. // insert remainder of old segment
  675. LLStyleConstSP sp = cur_segmentp->getStyle();
  676. LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this);
  677. mSegments.insert(cur_seg_iter, remainder_segment);
  678. remainder_segment->linkToDocument(this);
  679. // insert new segment before remainder of old segment
  680. mSegments.insert(cur_seg_iter, segment_to_insert);
  681. segment_to_insert->linkToDocument(this);
  682. // at this point, there will be two overlapping segments owning the text
  683. // associated with the incoming segment
  684. }
  685. else
  686. {
  687. mSegments.insert(cur_seg_iter, segment_to_insert);
  688. segment_to_insert->linkToDocument(this);
  689. }
  690. // now delete/truncate remaining segments as necessary
  691. // cur_seg_iter points to segment before incoming segment
  692. while(cur_seg_iter != mSegments.end())
  693. {
  694. cur_segmentp = *cur_seg_iter;
  695. if (cur_segmentp == segment_to_insert) 
  696. {
  697. ++cur_seg_iter;
  698. continue;
  699. }
  700. if (cur_segmentp->getStart() >= segment_to_insert->getStart())
  701. {
  702. if(cur_segmentp->getEnd() <= segment_to_insert->getEnd())
  703. {
  704. cur_segmentp->unlinkFromDocument(this);
  705. // grab copy of iterator to erase, and bump it
  706. segment_set_t::iterator seg_to_erase(cur_seg_iter++);
  707. mSegments.erase(seg_to_erase);
  708. continue;
  709. }
  710. else
  711. {
  712. // last overlapping segment, clip to end of incoming segment
  713. // and stop traversal
  714. cur_segmentp->setStart(segment_to_insert->getEnd());
  715. break;
  716. }
  717. }
  718. ++cur_seg_iter;
  719. }
  720. }
  721. // layout potentially changed
  722. needsReflow(reflow_start_index);
  723. }
  724. BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
  725. {
  726. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  727. if (cur_segment && cur_segment->handleMouseDown(x, y, mask))
  728. {
  729. return TRUE;
  730. }
  731. return LLUICtrl::handleMouseDown(x, y, mask);
  732. }
  733. BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
  734. {
  735. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  736. if (cur_segment && cur_segment->handleMouseUp(x, y, mask))
  737. {
  738. // Did we just click on a link?
  739. if (cur_segment->getStyle()
  740.     && cur_segment->getStyle()->isLink())
  741. {
  742. // *TODO: send URL here?
  743. mURLClickSignal(this, LLSD() );
  744. }
  745. return TRUE;
  746. }
  747. return LLUICtrl::handleMouseUp(x, y, mask);
  748. }
  749. BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
  750. {
  751. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  752. if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask))
  753. {
  754. return TRUE;
  755. }
  756. return LLUICtrl::handleMiddleMouseDown(x, y, mask);
  757. }
  758. BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask)
  759. {
  760. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  761. if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask))
  762. {
  763. return TRUE;
  764. }
  765. return LLUICtrl::handleMiddleMouseUp(x, y, mask);
  766. }
  767. BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask)
  768. {
  769. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  770. if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask))
  771. {
  772. return TRUE;
  773. }
  774. return LLUICtrl::handleRightMouseDown(x, y, mask);
  775. }
  776. BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask)
  777. {
  778. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  779. if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask))
  780. {
  781. return TRUE;
  782. }
  783. return LLUICtrl::handleRightMouseUp(x, y, mask);
  784. }
  785. BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
  786. {
  787. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  788. if (cur_segment && cur_segment->handleDoubleClick(x, y, mask))
  789. {
  790. return TRUE;
  791. }
  792. return LLUICtrl::handleDoubleClick(x, y, mask);
  793. }
  794. BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask)
  795. {
  796. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  797. if (cur_segment && cur_segment->handleHover(x, y, mask))
  798. {
  799. return TRUE;
  800. }
  801. return LLUICtrl::handleHover(x, y, mask);
  802. }
  803. BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks)
  804. {
  805. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  806. if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks))
  807. {
  808. return TRUE;
  809. }
  810. return LLUICtrl::handleScrollWheel(x, y, clicks);
  811. }
  812. BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask)
  813. {
  814. LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
  815. if (cur_segment && cur_segment->handleToolTip(x, y, mask))
  816. {
  817. return TRUE;
  818. }
  819. return LLUICtrl::handleToolTip(x, y, mask);
  820. }
  821. void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent)
  822. {
  823. if (width != getRect().getWidth() || height != getRect().getHeight())
  824. {
  825. LLUICtrl::reshape( width, height, called_from_parent );
  826. // do this first after reshape, because other things depend on
  827. // up-to-date mVisibleTextRect
  828. updateRects();
  829. needsReflow();
  830. }
  831. }
  832. void LLTextBase::draw()
  833. {
  834. // reflow if needed, on demand
  835. reflow();
  836. // then update scroll position, as cursor may have moved
  837. if (!mReadOnly)
  838. {
  839. updateScrollFromCursor();
  840. }
  841. LLRect doc_rect;
  842. if (mScroller)
  843. {
  844. mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &doc_rect, this);
  845. }
  846. else
  847. {
  848. doc_rect = getLocalRect();
  849. }
  850. if (mBGVisible)
  851. {
  852. // clip background rect against extents, if we support scrolling
  853. LLLocalClipRect clip(doc_rect, mScroller != NULL);
  854. LLColor4 bg_color = mReadOnly 
  855. ? mReadOnlyBgColor.get()
  856. : hasFocus() 
  857. ? mFocusBgColor.get() 
  858. : mWriteableBgColor.get();
  859. gl_rect_2d(mVisibleTextRect, bg_color, TRUE);
  860. }
  861. // draw document view
  862. LLUICtrl::draw();
  863. {
  864. // only clip if we support scrolling (mScroller != NULL)
  865. LLLocalClipRect clip(doc_rect, mScroller != NULL);
  866. drawSelectionBackground();
  867. drawText();
  868. drawCursor();
  869. }
  870. }
  871. //virtual
  872. void LLTextBase::setColor( const LLColor4& c )
  873. {
  874. mFgColor = c;
  875. }
  876. //virtual 
  877. void LLTextBase::setReadOnlyColor(const LLColor4 &c)
  878. {
  879. mReadOnlyFgColor = c;
  880. }
  881. //virtual
  882. void LLTextBase::handleVisibilityChange( BOOL new_visibility )
  883. {
  884. if(!new_visibility && mPopupMenu)
  885. {
  886. mPopupMenu->hide();
  887. }
  888. LLUICtrl::handleVisibilityChange(new_visibility);
  889. }
  890. //virtual
  891. void LLTextBase::setValue(const LLSD& value )
  892. {
  893. setText(value.asString());
  894. }
  895. //virtual
  896. void LLTextBase::deselect()
  897. {
  898. mSelectionStart = 0;
  899. mSelectionEnd = 0;
  900. mIsSelecting = FALSE;
  901. }
  902. // Sets the scrollbar from the cursor position
  903. void LLTextBase::updateScrollFromCursor()
  904. {
  905. // Update scroll position even in read-only mode (when there's no cursor displayed)
  906. // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736.
  907. if (!mScrollNeeded || !mScroller)
  908. {
  909. return;
  910. }
  911. mScrollNeeded = FALSE; 
  912. // scroll so that the cursor is at the top of the page
  913. LLRect scroller_doc_window = getVisibleDocumentRect();
  914. LLRect cursor_rect_doc = getLocalRectFromDocIndex(mCursorPos);
  915. cursor_rect_doc.translate(scroller_doc_window.mLeft, scroller_doc_window.mBottom);
  916. mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5));
  917. }
  918. S32 LLTextBase::getLeftOffset(S32 width)
  919. {
  920. switch (mHAlign)
  921. {
  922. case LLFontGL::LEFT:
  923. return mHPad;
  924. case LLFontGL::HCENTER:
  925. return mHPad + (mVisibleTextRect.getWidth() - width - mHPad) / 2;
  926. case LLFontGL::RIGHT:
  927. return mVisibleTextRect.getWidth() - width;
  928. default:
  929. return mHPad;
  930. }
  931. }
  932. static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow");
  933. void LLTextBase::reflow()
  934. {
  935. LLFastTimer ft(FTM_TEXT_REFLOW);
  936. updateSegments();
  937. while(mReflowIndex < S32_MAX)
  938. {
  939. S32 start_index = mReflowIndex;
  940. mReflowIndex = S32_MAX;
  941. // shrink document to minimum size (visible portion of text widget)
  942. // to force inlined widgets with follows set to shrink
  943. mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight());
  944. bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false;
  945. LLRect old_cursor_rect = getLocalRectFromDocIndex(mCursorPos);
  946. bool follow_selection = mVisibleTextRect.overlaps(old_cursor_rect); // cursor is visible
  947. old_cursor_rect.translate(-mVisibleTextRect.mLeft, -mVisibleTextRect.mBottom);
  948. S32 first_line = getFirstVisibleLine();
  949. // if scroll anchor not on first line, update it to first character of first line
  950. if (!mLineInfoList.empty()
  951. && (mScrollIndex <  mLineInfoList[first_line].mDocIndexStart
  952. || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd))
  953. {
  954. mScrollIndex = mLineInfoList[first_line].mDocIndexStart;
  955. }
  956. LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex);
  957. // subtract off effect of horizontal scrollbar from local position of first char
  958. first_char_rect.translate(-mVisibleTextRect.mLeft, -mVisibleTextRect.mBottom);
  959. S32 cur_top = 0;
  960. segment_set_t::iterator seg_iter = mSegments.begin();
  961. S32 seg_offset = 0;
  962. S32 line_start_index = 0;
  963. const S32 text_available_width = mVisibleTextRect.getWidth() - mHPad;  // reserve room for margin
  964. S32 remaining_pixels = text_available_width;
  965. S32 line_count = 0;
  966. // find and erase line info structs starting at start_index and going to end of document
  967. if (!mLineInfoList.empty())
  968. {
  969. // find first element whose end comes after start_index
  970. line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare());
  971. line_start_index = iter->mDocIndexStart;
  972. line_count = iter->mLineNum;
  973. cur_top = iter->mRect.mTop;
  974. getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset);
  975. mLineInfoList.erase(iter, mLineInfoList.end());
  976. }
  977. S32 line_height = 0;
  978. while(seg_iter != mSegments.end())
  979. {
  980. LLTextSegmentPtr segment = *seg_iter;
  981. // track maximum height of any segment on this line
  982. S32 cur_index = segment->getStart() + seg_offset;
  983. // ask segment how many character fit in remaining space
  984. S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX,
  985. seg_offset, 
  986. cur_index - line_start_index, 
  987. S32_MAX);
  988. S32 segment_width, segment_height;
  989. bool force_newline = segment->getDimensions(seg_offset, character_count, segment_width, segment_height);
  990. // grow line height as necessary based on reported height of this segment
  991. line_height = llmax(line_height, segment_height);
  992. remaining_pixels -= segment_width;
  993. if (remaining_pixels < 0)
  994. {
  995. // getNumChars() and getDimensions() should return consistent results
  996. remaining_pixels = 0;
  997. }
  998. seg_offset += character_count;
  999. S32 last_segment_char_on_line = segment->getStart() + seg_offset;
  1000. S32 text_actual_width = text_available_width - remaining_pixels;
  1001. S32 text_left = getLeftOffset(text_actual_width);
  1002. LLRect line_rect(text_left, 
  1003. cur_top, 
  1004. text_left + text_actual_width, 
  1005. cur_top - line_height);
  1006. // if we didn't finish the current segment...
  1007. if (last_segment_char_on_line < segment->getEnd())
  1008. {
  1009. // add line info and keep going
  1010. mLineInfoList.push_back(line_info(
  1011. line_start_index, 
  1012. last_segment_char_on_line, 
  1013. line_rect, 
  1014. line_count));
  1015. line_start_index = segment->getStart() + seg_offset;
  1016. cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
  1017. remaining_pixels = text_available_width;
  1018. line_height = 0;
  1019. }
  1020. // ...just consumed last segment..
  1021. else if (++segment_set_t::iterator(seg_iter) == mSegments.end())
  1022. {
  1023. mLineInfoList.push_back(line_info(
  1024. line_start_index, 
  1025. last_segment_char_on_line, 
  1026. line_rect, 
  1027. line_count));
  1028. cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
  1029. break;
  1030. }
  1031. // ...or finished a segment and there are segments remaining on this line
  1032. else
  1033. {
  1034. // subtract pixels used and increment segment
  1035. if (force_newline)
  1036. {
  1037. mLineInfoList.push_back(line_info(
  1038. line_start_index, 
  1039. last_segment_char_on_line, 
  1040. line_rect, 
  1041. line_count));
  1042. line_start_index = segment->getStart() + seg_offset;
  1043. cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels;
  1044. line_height = 0;
  1045. remaining_pixels = text_available_width;
  1046. }
  1047. ++seg_iter;
  1048. seg_offset = 0;
  1049. }
  1050. if (force_newline) 
  1051. {
  1052. line_count++;
  1053. }
  1054. }
  1055. // calculate visible region for diplaying text
  1056. updateRects();
  1057. for (segment_set_t::iterator segment_it = mSegments.begin();
  1058. segment_it != mSegments.end();
  1059. ++segment_it)
  1060. {
  1061. LLTextSegmentPtr segmentp = *segment_it;
  1062. segmentp->updateLayout(*this);
  1063. }
  1064. // apply scroll constraints after reflowing text
  1065. if (!hasMouseCapture() && mScroller)
  1066. {
  1067. if (scrolled_to_bottom && mTrackEnd)
  1068. {
  1069. // keep bottom of text buffer visible
  1070. endOfDoc();
  1071. }
  1072. else if (hasSelection() && follow_selection)
  1073. {
  1074. // keep cursor in same vertical position on screen when selecting text
  1075. LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos);
  1076. mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect);
  1077. }
  1078. else
  1079. {
  1080. // keep first line of text visible
  1081. LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex);
  1082. mScroller->scrollToShowRect(new_first_char_rect, first_char_rect);
  1083. }
  1084. }
  1085. // reset desired x cursor position
  1086. updateCursorXPos();
  1087. }
  1088. }
  1089. LLRect LLTextBase::getTextBoundingRect()
  1090. {
  1091. reflow();
  1092. return mTextBoundingRect;
  1093. }
  1094. void LLTextBase::clearSegments()
  1095. {
  1096. mSegments.clear();
  1097. createDefaultSegment();
  1098. }
  1099. S32 LLTextBase::getLineStart( S32 line ) const
  1100. {
  1101. S32 num_lines = getLineCount();
  1102. if (num_lines == 0)
  1103.     {
  1104. return 0;
  1105.     }
  1106. line = llclamp(line, 0, num_lines-1);
  1107. return mLineInfoList[line].mDocIndexStart;
  1108. }
  1109. S32 LLTextBase::getLineEnd( S32 line ) const
  1110. {
  1111. S32 num_lines = getLineCount();
  1112. if (num_lines == 0)
  1113.     {
  1114. return 0;
  1115.     }
  1116. line = llclamp(line, 0, num_lines-1);
  1117. return mLineInfoList[line].mDocIndexEnd;
  1118. }
  1119. S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const
  1120. {
  1121. if (mLineInfoList.empty())
  1122. {
  1123. return 0;
  1124. }
  1125. else
  1126. {
  1127. line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare());
  1128. if (include_wordwrap)
  1129. {
  1130. return iter - mLineInfoList.begin();
  1131. }
  1132. else
  1133. {
  1134. if (iter == mLineInfoList.end())
  1135. {
  1136. return mLineInfoList.back().mLineNum;
  1137. }
  1138. else
  1139. {
  1140. return iter->mLineNum;
  1141. }
  1142. }
  1143. }
  1144. }
  1145. // Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line.
  1146. S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const
  1147. {
  1148. if (mLineInfoList.empty())
  1149. {
  1150. return startpos;
  1151. }
  1152. else
  1153. {
  1154. line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare());
  1155. return startpos - iter->mDocIndexStart;
  1156. }
  1157. }
  1158. S32 LLTextBase::getFirstVisibleLine() const
  1159. {
  1160. LLRect visible_region = getVisibleDocumentRect();
  1161. // binary search for line that starts before top of visible buffer
  1162. line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
  1163. return iter - mLineInfoList.begin();
  1164. }
  1165. std::pair<S32, S32> LLTextBase::getVisibleLines(bool fully_visible) 
  1166. {
  1167. LLRect visible_region = getVisibleDocumentRect();
  1168. line_list_t::const_iterator first_iter;
  1169. line_list_t::const_iterator last_iter;
  1170. // make sure we have an up-to-date mLineInfoList
  1171. reflow();
  1172. if (fully_visible)
  1173. {
  1174. first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top());
  1175. last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom());
  1176. }
  1177. else
  1178. {
  1179. first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom());
  1180. last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top());
  1181. }
  1182. return std::pair<S32, S32>(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin());
  1183. }
  1184. LLTextViewModel* LLTextBase::getViewModel() const
  1185. {
  1186. return (LLTextViewModel*)mViewModel.get();
  1187. }
  1188. void LLTextBase::addDocumentChild(LLView* view) 
  1189. mDocumentView->addChild(view); 
  1190. }
  1191. void LLTextBase::removeDocumentChild(LLView* view) 
  1192. mDocumentView->removeChild(view); 
  1193. }
  1194. static LLFastTimer::DeclareTimer FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments");
  1195. void LLTextBase::updateSegments()
  1196. {
  1197. LLFastTimer ft(FTM_UPDATE_TEXT_SEGMENTS);
  1198. createDefaultSegment();
  1199. }
  1200. void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const
  1201. {
  1202. *seg_iter = getSegIterContaining(startpos);
  1203. if (*seg_iter == mSegments.end())
  1204. {
  1205. *offsetp = 0;
  1206. }
  1207. else
  1208. {
  1209. *offsetp = startpos - (**seg_iter)->getStart();
  1210. }
  1211. }
  1212. void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp )
  1213. {
  1214. *seg_iter = getSegIterContaining(startpos);
  1215. if (*seg_iter == mSegments.end())
  1216. {
  1217. *offsetp = 0;
  1218. }
  1219. else
  1220. {
  1221. *offsetp = startpos - (**seg_iter)->getStart();
  1222. }
  1223. }
  1224. LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index)
  1225. {
  1226. segment_set_t::iterator it = mSegments.upper_bound(new LLIndexSegment(index));
  1227. return it;
  1228. }
  1229. LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const
  1230. {
  1231. LLTextBase::segment_set_t::const_iterator it =  mSegments.upper_bound(new LLIndexSegment(index));
  1232. return it;
  1233. }
  1234. // Finds the text segment (if any) at the give local screen position
  1235. LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line)
  1236. {
  1237. // Find the cursor position at the requested local screen position
  1238. S32 offset = getDocIndexFromLocalCoord( x, y, FALSE, hit_past_end_of_line);
  1239. segment_set_t::iterator seg_iter = getSegIterContaining(offset);
  1240. if (seg_iter != mSegments.end())
  1241. {
  1242. return *seg_iter;
  1243. }
  1244. else
  1245. {
  1246. return LLTextSegmentPtr();
  1247. }
  1248. }
  1249. void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
  1250. {
  1251. // work out the XUI menu file to use for this url
  1252. LLUrlMatch match;
  1253. std::string url = in_url;
  1254. if (! LLUrlRegistry::instance().findUrl(url, match))
  1255. {
  1256. return;
  1257. }
  1258. std::string xui_file = match.getMenuName();
  1259. if (xui_file.empty())
  1260. {
  1261. return;
  1262. }
  1263. // set up the callbacks for all of the potential menu items, N.B. we
  1264. // don't use const ref strings in callbacks in case url goes out of scope
  1265. LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
  1266. registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url));
  1267. registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url));
  1268. registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url));
  1269. registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url));
  1270. registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url));
  1271. registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url));
  1272. registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url));
  1273. registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url));
  1274. // create and return the context menu from the XUI file
  1275. delete mPopupMenu;
  1276. mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer,
  1277.  LLMenuHolderGL::child_registry_t::instance());
  1278. if (mPopupMenu)
  1279. {
  1280. mPopupMenu->show(x, y);
  1281. LLMenuGL::showPopup(this, mPopupMenu, x, y);
  1282. }
  1283. }
  1284. void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params)
  1285. {
  1286. // clear out the existing text and segments
  1287. getViewModel()->setDisplay(LLWStringUtil::null);
  1288. clearSegments();
  1289. // createDefaultSegment();
  1290. deselect();
  1291. // append the new text (supports Url linking)
  1292. std::string text(utf8str);
  1293. LLStringUtil::removeCRLF(text);
  1294. // appendText modifies mCursorPos...
  1295. appendText(text, false, input_params);
  1296. // ...so move cursor to top after appending text
  1297. startOfDoc();
  1298. onValueChange(0, getLength());
  1299. }
  1300. //virtual
  1301. std::string LLTextBase::getText() const
  1302. {
  1303. return getViewModel()->getValue().asString();
  1304. }
  1305. void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params)
  1306. {
  1307. LLStyle::Params style_params(input_params);
  1308. style_params.fillFrom(getDefaultStyleParams());
  1309. S32 part = (S32)LLTextParser::WHOLE;
  1310. if(mParseHTML)
  1311. {
  1312. S32 start=0,end=0;
  1313. LLUrlMatch match;
  1314. std::string text = new_text;
  1315. while ( LLUrlRegistry::instance().findUrl(text, match,
  1316.         boost::bind(&LLTextBase::replaceUrlLabel, this, _1, _2)) )
  1317. {
  1318. start = match.getStart();
  1319. end = match.getEnd()+1;
  1320. LLStyle::Params link_params = style_params;
  1321. link_params.color = match.getColor();
  1322. link_params.readonly_color =  match.getColor();
  1323. link_params.font.style("UNDERLINE");
  1324. link_params.link_href = match.getUrl();
  1325. // output the text before the Url
  1326. if (start > 0)
  1327. {
  1328. if (part == (S32)LLTextParser::WHOLE ||
  1329. part == (S32)LLTextParser::START)
  1330. {
  1331. part = (S32)LLTextParser::START;
  1332. }
  1333. else
  1334. {
  1335. part = (S32)LLTextParser::MIDDLE;
  1336. }
  1337. std::string subtext=text.substr(0,start);
  1338. appendAndHighlightText(subtext, prepend_newline, part, style_params); 
  1339. prepend_newline = false;
  1340. }
  1341. // output an optional icon before the Url
  1342. if (! match.getIcon().empty())
  1343. {
  1344. LLUIImagePtr image = LLUI::getUIImage(match.getIcon());
  1345. if (image)
  1346. {
  1347. LLStyle::Params icon;
  1348. icon.image = image;
  1349. // Text will be replaced during rendering with the icon,
  1350. // but string cannot be empty or the segment won't be
  1351. // added (or drawn).
  1352. appendAndHighlightText(" ", prepend_newline, part, icon);
  1353. prepend_newline = false;
  1354. }
  1355. }
  1356. // output the styled Url (unless we've been asked to suppress hyperlinking)
  1357. if (match.isLinkDisabled())
  1358. {
  1359. appendAndHighlightText(match.getLabel(), prepend_newline, part, style_params);
  1360. }
  1361. else
  1362. {
  1363. appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params);
  1364. // set the tooltip for the Url label
  1365. if (! match.getTooltip().empty())
  1366. {
  1367. segment_set_t::iterator it = getSegIterContaining(getLength()-1);
  1368. if (it != mSegments.end())
  1369. {
  1370. LLTextSegmentPtr segment = *it;
  1371. segment->setToolTip(match.getTooltip());
  1372. }
  1373. }
  1374. }
  1375. prepend_newline = false;
  1376. // move on to the rest of the text after the Url
  1377. if (end < (S32)text.length()) 
  1378. {
  1379. text = text.substr(end,text.length() - end);
  1380. end=0;
  1381. part=(S32)LLTextParser::END;
  1382. }
  1383. else
  1384. {
  1385. break;
  1386. }
  1387. }
  1388. if (part != (S32)LLTextParser::WHOLE) part=(S32)LLTextParser::END;
  1389. if (end < (S32)text.length()) appendAndHighlightText(text, prepend_newline, part, style_params);
  1390. }
  1391. else
  1392. {
  1393. appendAndHighlightText(new_text, prepend_newline, part, style_params);
  1394. }
  1395. }
  1396. void LLTextBase::needsReflow(S32 index)
  1397. {
  1398. lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl;
  1399. mReflowIndex = llmin(mReflowIndex, index);
  1400. }
  1401. void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepend_newline, S32 highlight_part, const LLStyle::Params& style_params)
  1402. {
  1403. if (new_text.empty()) return;                                                                                 
  1404. // Save old state
  1405. S32 selection_start = mSelectionStart;
  1406. S32 selection_end = mSelectionEnd;
  1407. BOOL was_selecting = mIsSelecting;
  1408. S32 cursor_pos = mCursorPos;
  1409. S32 old_length = getLength();
  1410. BOOL cursor_was_at_end = (mCursorPos == old_length);
  1411. deselect();
  1412. setCursorPos(old_length);
  1413. LLTextParser* highlight = LLTextParser::getInstance();
  1414. if (mParseHighlights && highlight)
  1415. {
  1416. LLStyle::Params highlight_params(style_params);
  1417. LLSD pieces = highlight->parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part);
  1418. for (S32 i = 0; i < pieces.size(); i++)
  1419. {
  1420. LLSD color_llsd = pieces[i]["color"];
  1421. LLColor4 lcolor;
  1422. lcolor.setValue(color_llsd);
  1423. highlight_params.color = lcolor;
  1424. LLWString wide_text;
  1425. if (prepend_newline && (i == 0 || pieces.size() <= 1 )) 
  1426. {
  1427. wide_text = utf8str_to_wstring(std::string("n") + pieces[i]["text"].asString());
  1428. }
  1429. else
  1430. {
  1431. wide_text = utf8str_to_wstring(pieces[i]["text"].asString());
  1432. }
  1433. S32 cur_length = getLength();
  1434. LLStyleConstSP sp(new LLStyle(highlight_params));
  1435. LLTextSegmentPtr segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this);
  1436. segment_vec_t segments;
  1437. segments.push_back(segmentp);
  1438. insertStringNoUndo(cur_length, wide_text, &segments);
  1439. }
  1440. }
  1441. else
  1442. {
  1443. LLWString wide_text;
  1444. // Add carriage return if not first line
  1445. if (getLength() != 0
  1446. && prepend_newline)
  1447. {
  1448. wide_text = utf8str_to_wstring(std::string("n") + new_text);
  1449. }
  1450. else
  1451. {
  1452. wide_text = utf8str_to_wstring(new_text);
  1453. }
  1454. segment_vec_t segments;
  1455. S32 segment_start = old_length;
  1456. S32 segment_end = old_length + wide_text.size();
  1457. LLStyleConstSP sp(new LLStyle(style_params));
  1458. segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this ));
  1459. insertStringNoUndo(getLength(), wide_text, &segments);
  1460. }
  1461. // Set the cursor and scroll position
  1462. if( selection_start != selection_end )
  1463. {
  1464. mSelectionStart = selection_start;
  1465. mSelectionEnd = selection_end;
  1466. mIsSelecting = was_selecting;
  1467. setCursorPos(cursor_pos);
  1468. }
  1469. else if( cursor_was_at_end )
  1470. {
  1471. setCursorPos(getLength());
  1472. }
  1473. else
  1474. {
  1475. setCursorPos(cursor_pos);
  1476. }
  1477. //if( !allow_undo )
  1478. //{
  1479. // blockUndo();
  1480. //}
  1481. }
  1482. void LLTextBase::replaceUrlLabel(const std::string &url,
  1483.    const std::string &label)
  1484. {
  1485. // get the full (wide) text for the editor so we can change it
  1486. LLWString text = getWText();
  1487. LLWString wlabel = utf8str_to_wstring(label);
  1488. bool modified = false;
  1489. S32 seg_start = 0;
  1490. // iterate through each segment looking for ones styled as links
  1491. segment_set_t::iterator it;
  1492. for (it = mSegments.begin(); it != mSegments.end(); ++it)
  1493. {
  1494. LLTextSegment *seg = *it;
  1495. LLStyleConstSP style = seg->getStyle();
  1496. // update segment start/end length in case we replaced text earlier
  1497. S32 seg_length = seg->getEnd() - seg->getStart();
  1498. seg->setStart(seg_start);
  1499. seg->setEnd(seg_start + seg_length);
  1500. // if we find a link with our Url, then replace the label
  1501. if (style->isLink() && style->getLinkHREF() == url)
  1502. {
  1503. S32 start = seg->getStart();
  1504. S32 end = seg->getEnd();
  1505. text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1);
  1506. seg->setEnd(start + wlabel.size());
  1507. modified = true;
  1508. }
  1509. // work out the character offset for the next segment
  1510. seg_start = seg->getEnd();
  1511. }
  1512. // update the editor with the new (wide) text string
  1513. if (modified)
  1514. {
  1515. getViewModel()->setDisplay(text);
  1516. deselect();
  1517. setCursorPos(mCursorPos);
  1518. needsReflow();
  1519. }
  1520. }
  1521. void LLTextBase::setWText(const LLWString& text)
  1522. {
  1523. setText(wstring_to_utf8str(text));
  1524. }
  1525. const LLWString& LLTextBase::getWText() const
  1526. {
  1527.     return getViewModel()->getDisplay();
  1528. }
  1529. // If round is true, if the position is on the right half of a character, the cursor
  1530. // will be put to its right.  If round is false, the cursor will always be put to the
  1531. // character's left.
  1532. S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line) const
  1533. {
  1534. // Figure out which line we're nearest to.
  1535. LLRect visible_region = getVisibleDocumentRect();
  1536. // binary search for line that starts before local_y
  1537. line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mVisibleTextRect.mBottom + visible_region.mBottom, compare_bottom());
  1538. if (line_iter == mLineInfoList.end())
  1539. {
  1540. return getLength(); // past the end
  1541. }
  1542. S32 pos = getLength();
  1543. S32 start_x = mVisibleTextRect.mLeft + line_iter->mRect.mLeft;
  1544. segment_set_t::iterator line_seg_iter;
  1545. S32 line_seg_offset;
  1546. for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset);
  1547. line_seg_iter != mSegments.end(); 
  1548. ++line_seg_iter, line_seg_offset = 0)
  1549. {
  1550. const LLTextSegmentPtr segmentp = *line_seg_iter;
  1551. S32 segment_line_start = segmentp->getStart() + line_seg_offset;
  1552. S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd - 1) - segment_line_start;
  1553. S32 text_width, text_height;
  1554. segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height);
  1555. if (local_x < start_x + text_width // cursor to left of right edge of text
  1556. || (hit_past_end_of_line && (segmentp->getEnd() >= line_iter->mDocIndexEnd - 1))) // or this segment wraps to next line
  1557. {
  1558. // Figure out which character we're nearest to.
  1559. S32 offset;
  1560. if (!segmentp->canEdit())
  1561. {
  1562. S32 segment_width, segment_height;
  1563. segmentp->getDimensions(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height);
  1564. if (round && local_x - start_x > segment_width / 2)
  1565. {
  1566. offset = segment_line_length;
  1567. }
  1568. else
  1569. {
  1570. offset = 0;
  1571. }
  1572. }
  1573. else
  1574. {
  1575. offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round);
  1576. }
  1577. pos = segment_line_start + offset;
  1578. break;
  1579. }
  1580. start_x += text_width;
  1581. }
  1582. return pos;
  1583. }
  1584. // returns rectangle of insertion caret 
  1585. // in document coordinate frame from given index into text
  1586. LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const
  1587. {
  1588. if (mLineInfoList.empty()) 
  1589. return LLRect();
  1590. }
  1591. LLRect doc_rect;
  1592. // clamp pos to valid values
  1593. pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1);
  1594. // find line that contains cursor
  1595. line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare());
  1596. doc_rect.mLeft = line_iter->mRect.mLeft; 
  1597. doc_rect.mBottom = line_iter->mRect.mBottom;
  1598. doc_rect.mTop = line_iter->mRect.mTop;
  1599. segment_set_t::iterator line_seg_iter;
  1600. S32 line_seg_offset;
  1601. segment_set_t::iterator cursor_seg_iter;
  1602. S32 cursor_seg_offset;
  1603. getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset);
  1604. getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset);
  1605. while(line_seg_iter != mSegments.end())
  1606. {
  1607. const LLTextSegmentPtr segmentp = *line_seg_iter;
  1608. if (line_seg_iter == cursor_seg_iter)
  1609. {
  1610. // cursor advanced to right based on difference in offset of cursor to start of line
  1611. S32 segment_width, segment_height;
  1612. segmentp->getDimensions(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height);
  1613. doc_rect.mLeft += segment_width;
  1614. break;
  1615. }
  1616. else
  1617. {
  1618. // add remainder of current text segment to cursor position
  1619. S32 segment_width, segment_height;
  1620. segmentp->getDimensions(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height);
  1621. doc_rect.mLeft += segment_width;
  1622. // offset will be 0 for all segments after the first
  1623. line_seg_offset = 0;
  1624. // go to next text segment on this line
  1625. ++line_seg_iter;
  1626. }
  1627. }
  1628. // set rect to 0 width
  1629. doc_rect.mRight = doc_rect.mLeft; 
  1630. return doc_rect;
  1631. }
  1632. LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const
  1633. {
  1634. LLRect local_rect;
  1635. if (mLineInfoList.empty()) 
  1636. // return default height rect in upper left
  1637. local_rect = mVisibleTextRect;
  1638. local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->getLineHeight());
  1639. return local_rect;
  1640. }
  1641. // get the rect in document coordinates
  1642. LLRect doc_rect = getDocRectFromDocIndex(pos);
  1643. // compensate for scrolled, inset view of doc
  1644. LLRect scrolled_view_rect = getVisibleDocumentRect();
  1645. local_rect = doc_rect;
  1646. local_rect.translate(mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, 
  1647. mVisibleTextRect.mBottom - scrolled_view_rect.mBottom);
  1648. return local_rect;
  1649. }
  1650. void LLTextBase::updateCursorXPos()
  1651. {
  1652. // reset desired x cursor position
  1653. mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft;
  1654. }
  1655. void LLTextBase::startOfLine()
  1656. {
  1657. S32 offset = getLineOffsetFromDocIndex(mCursorPos);
  1658. setCursorPos(mCursorPos - offset);
  1659. }
  1660. void LLTextBase::endOfLine()
  1661. {
  1662. S32 line = getLineNumFromDocIndex(mCursorPos);
  1663. S32 num_lines = getLineCount();
  1664. if (line + 1 >= num_lines)
  1665. {
  1666. setCursorPos(getLength());
  1667. }
  1668. else
  1669. {
  1670. setCursorPos( getLineStart(line + 1) - 1 );
  1671. }
  1672. }
  1673. void LLTextBase::startOfDoc()
  1674. {
  1675. setCursorPos(0);
  1676. if (mScroller)
  1677. {
  1678. mScroller->goToTop();
  1679. }
  1680. }
  1681. void LLTextBase::endOfDoc()
  1682. {
  1683. setCursorPos(getLength());
  1684. if (mScroller)
  1685. {
  1686. mScroller->goToBottom();
  1687. }
  1688. }
  1689. void LLTextBase::changePage( S32 delta )
  1690. {
  1691. const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10;
  1692. if (delta == 0 || !mScroller) return;
  1693. LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos);
  1694. if( delta == -1 )
  1695. {
  1696. mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE);
  1697. }
  1698. else
  1699. if( delta == 1 )
  1700. {
  1701. mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE);
  1702. }
  1703. if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect)
  1704. {
  1705. // cursor didn't change apparent position, so move to top or bottom of document, respectively
  1706. if (delta < 0)
  1707. {
  1708. startOfDoc();
  1709. }
  1710. else
  1711. {
  1712. endOfDoc();
  1713. }
  1714. }
  1715. else
  1716. {
  1717. setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false);
  1718. }
  1719. }
  1720. // Picks a new cursor position based on the screen size of text being drawn.
  1721. void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset )
  1722. {
  1723. setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset);
  1724. }
  1725. void LLTextBase::changeLine( S32 delta )
  1726. {
  1727. S32 line = getLineNumFromDocIndex(mCursorPos);
  1728. S32 new_line = line;
  1729. if( (delta < 0) && (line > 0 ) )
  1730. {
  1731. new_line = line - 1;
  1732. }
  1733. else if( (delta > 0) && (line < (getLineCount() - 1)) )
  1734. {
  1735. new_line = line + 1;
  1736. }
  1737. LLRect visible_region = getVisibleDocumentRect();
  1738. S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE);
  1739. setCursorPos(new_cursor_pos, true);
  1740. }
  1741. bool LLTextBase::scrolledToStart()
  1742. {
  1743. return mScroller->isAtTop();
  1744. }
  1745. bool LLTextBase::scrolledToEnd()
  1746. {
  1747. return mScroller->isAtBottom();
  1748. }
  1749. bool LLTextBase::setCursor(S32 row, S32 column)
  1750. {
  1751. if (0 <= row && row < (S32)mLineInfoList.size())
  1752. {
  1753. S32 doc_pos = mLineInfoList[row].mDocIndexStart;
  1754. column = llclamp(column, 0, mLineInfoList[row].mDocIndexEnd - mLineInfoList[row].mDocIndexStart - 1);
  1755. doc_pos += column;
  1756. updateCursorXPos();
  1757. return setCursorPos(doc_pos);
  1758. }
  1759. return false;
  1760. }
  1761. bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset)
  1762. {
  1763. S32 new_cursor_pos = cursor_pos;
  1764. if (new_cursor_pos != mCursorPos)
  1765. {
  1766. new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos);
  1767. }
  1768. mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength());
  1769. needsScroll();
  1770. if (!keep_cursor_offset)
  1771. updateCursorXPos();
  1772. // did we get requested position?
  1773. return new_cursor_pos == cursor_pos;
  1774. }
  1775. // constraint cursor to editable segments of document
  1776. S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction)
  1777. {
  1778. segment_set_t::iterator segment_iter;
  1779. S32 offset;
  1780. getSegmentAndOffset(index, &segment_iter, &offset);
  1781. if (segment_iter == mSegments.end())
  1782. {
  1783. return 0;
  1784. }
  1785. LLTextSegmentPtr segmentp = *segment_iter;
  1786. if (segmentp->canEdit()) 
  1787. {
  1788. return segmentp->getStart() + offset;
  1789. }
  1790. else if (segmentp->getStart() < index && index < segmentp->getEnd())
  1791. {
  1792. // bias towards document end
  1793. if (increasing_direction)
  1794. {
  1795. return segmentp->getEnd();
  1796. }
  1797. // bias towards document start
  1798. else
  1799. {
  1800. return segmentp->getStart();
  1801. }
  1802. }
  1803. else
  1804. {
  1805. return index;
  1806. }
  1807. }
  1808. void LLTextBase::updateRects()
  1809. {
  1810. if (mLineInfoList.empty()) 
  1811. {
  1812. mTextBoundingRect = LLRect(0, mVPad, mHPad, 0);
  1813. }
  1814. else
  1815. {
  1816. mTextBoundingRect = mLineInfoList.begin()->mRect;
  1817. for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin();
  1818. line_iter != mLineInfoList.end();
  1819. ++line_iter)
  1820. {
  1821. mTextBoundingRect.unionWith(line_iter->mRect);
  1822. }
  1823. mTextBoundingRect.mTop += mVPad;
  1824. // subtract a pixel off the bottom to deal with rounding errors in measuring font height
  1825. mTextBoundingRect.mBottom -= 1;
  1826. S32 delta_pos = -mTextBoundingRect.mBottom;
  1827. // move line segments to fit new document rect
  1828. for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it)
  1829. {
  1830. it->mRect.translate(0, delta_pos);
  1831. }
  1832. mTextBoundingRect.translate(0, delta_pos);
  1833. }
  1834. // update document container dimensions according to text contents
  1835. LLRect doc_rect = mTextBoundingRect;
  1836. // use old mVisibleTextRect constraint document to width of viewable region
  1837. doc_rect.mLeft = 0;
  1838. // allow horizontal scrolling?
  1839. // if so, use entire width of text contents
  1840. // otherwise, stop at width of mVisibleTextRect
  1841. doc_rect.mRight = mScroller 
  1842. ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight)
  1843. : mVisibleTextRect.getWidth();
  1844. mDocumentView->setShape(doc_rect);
  1845. //update mVisibleTextRect *after* mDocumentView has been resized
  1846. // so that scrollbars are added if document needs to scroll
  1847. // since mVisibleTextRect does not include scrollbars
  1848. LLRect old_text_rect = mVisibleTextRect;
  1849. mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
  1850. //FIXME: replace border with image?
  1851. if (mBorderVisible)
  1852. {
  1853. mVisibleTextRect.stretch(-1);
  1854. }
  1855. if (mVisibleTextRect != old_text_rect)
  1856. {
  1857. needsReflow();
  1858. }
  1859. // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed)
  1860. doc_rect.mRight = mScroller 
  1861. ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight)
  1862. : mVisibleTextRect.getWidth();
  1863. mDocumentView->setShape(doc_rect);
  1864. }
  1865. void LLTextBase::startSelection()
  1866. {
  1867. if( !mIsSelecting )
  1868. {
  1869. mIsSelecting = TRUE;
  1870. mSelectionStart = mCursorPos;
  1871. mSelectionEnd = mCursorPos;
  1872. }
  1873. }
  1874. void LLTextBase::endSelection()
  1875. {
  1876. if( mIsSelecting )
  1877. {
  1878. mIsSelecting = FALSE;
  1879. mSelectionEnd = mCursorPos;
  1880. }
  1881. }
  1882. // get portion of document that is visible in text editor
  1883. LLRect LLTextBase::getVisibleDocumentRect() const
  1884. {
  1885. if (mScroller)
  1886. {
  1887. return mScroller->getVisibleContentRect();
  1888. }
  1889. else
  1890. {
  1891. // entire document rect is visible when not scrolling
  1892. // but offset according to height of widget
  1893. LLRect doc_rect = mDocumentView->getLocalRect();
  1894. doc_rect.mLeft -= mDocumentView->getRect().mLeft;
  1895. // adjust for height of text above widget baseline
  1896. doc_rect.mBottom = doc_rect.getHeight() - mVisibleTextRect.getHeight();
  1897. return doc_rect;
  1898. }
  1899. }
  1900. //
  1901. // LLTextSegment
  1902. //
  1903. LLTextSegment::~LLTextSegment()
  1904. {}
  1905. bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; height = 0; return false;}
  1906. S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; }
  1907. S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return 0; }
  1908. void LLTextSegment::updateLayout(const LLTextBase& editor) {}
  1909. F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { return draw_rect.mLeft; }
  1910. bool LLTextSegment::canEdit() const { return false; }
  1911. void LLTextSegment::unlinkFromDocument(LLTextBase*) {}
  1912. void LLTextSegment::linkToDocument(LLTextBase*) {}
  1913. const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; }
  1914. //void LLTextSegment::setColor(const LLColor4 &color) {}
  1915. LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; }
  1916. void LLTextSegment::setStyle(LLStyleConstSP style) {}
  1917. void LLTextSegment::setToken( LLKeywordToken* token ) {}
  1918. LLKeywordToken* LLTextSegment::getToken() const { return NULL; }
  1919. void LLTextSegment::setToolTip( const std::string &msg ) {}
  1920. void LLTextSegment::dump() const {}
  1921. BOOL LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }
  1922. BOOL LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return FALSE; }
  1923. BOOL LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }
  1924. BOOL LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return FALSE; }
  1925. BOOL LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }
  1926. BOOL LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return FALSE; }
  1927. BOOL LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return FALSE; }
  1928. BOOL LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return FALSE; }
  1929. BOOL LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return FALSE; }
  1930. BOOL LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return FALSE; }
  1931. std::string LLTextSegment::getName() const { return ""; }
  1932. void LLTextSegment::onMouseCaptureLost() {}
  1933. void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {}
  1934. void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {}
  1935. BOOL LLTextSegment::hasMouseCapture() { return FALSE; }
  1936. //
  1937. // LLNormalTextSegment
  1938. //
  1939. LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) 
  1940. : LLTextSegment(start, end),
  1941. mStyle( style ),
  1942. mToken(NULL),
  1943. mEditor(editor)
  1944. {
  1945. mFontHeight = llceil(mStyle->getFont()->getLineHeight());
  1946. LLUIImagePtr image = mStyle->getImage();
  1947. if (image.notNull())
  1948. {
  1949. mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start));
  1950. }
  1951. }
  1952. LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) 
  1953. : LLTextSegment(start, end),
  1954. mToken(NULL),
  1955. mEditor(editor)
  1956. {
  1957. mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color));
  1958. mFontHeight = llceil(mStyle->getFont()->getLineHeight());
  1959. }
  1960. LLNormalTextSegment::~LLNormalTextSegment()
  1961. {
  1962. mImageLoadedConnection.disconnect();
  1963. }
  1964. F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
  1965. {
  1966. if( end - start > 0 )
  1967. {
  1968. if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart))
  1969. {
  1970. // ...for images, only render the image, not the underlying text,
  1971. // which is only a placeholder space
  1972. LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha;
  1973. LLUIImagePtr image = mStyle->getImage();
  1974. S32 style_image_height = image->getHeight();
  1975. S32 style_image_width = image->getWidth();
  1976. // Text is drawn from the top of the draw_rect downward
  1977. S32 text_center = draw_rect.mTop - (mFontHeight / 2);
  1978. // Align image to center of text
  1979. S32 image_bottom = text_center - (style_image_height / 2);
  1980. image->draw(draw_rect.mLeft, image_bottom, 
  1981. style_image_width, style_image_height, color);
  1982. const S32 IMAGE_HPAD = 3;
  1983. return draw_rect.mLeft + style_image_width + IMAGE_HPAD;
  1984. }
  1985. return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect);
  1986. }
  1987. return draw_rect.mLeft;
  1988. }
  1989. // Draws a single text segment, reversing the color for selection if needed.
  1990. F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRect rect)
  1991. {
  1992. F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha;
  1993. const LLWString &text = mEditor.getWText();
  1994. if ( text[seg_end-1] == 'n' )
  1995. {
  1996. --seg_end;
  1997. }
  1998. F32 right_x = rect.mLeft;
  1999. if (!mStyle->isVisible())
  2000. {
  2001. return right_x;
  2002. }
  2003. const LLFontGL* font = mStyle->getFont();
  2004. LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor())  % alpha;
  2005.    if( selection_start > seg_start )
  2006. {
  2007. // Draw normally
  2008. S32 start = seg_start;
  2009. S32 end = llmin( selection_start, seg_end );
  2010. S32 length =  end - start;
  2011. font->render(text, start, 
  2012.      rect.mLeft, rect.mTop, 
  2013.      color, 
  2014.      LLFontGL::LEFT, LLFontGL::TOP, 
  2015.      LLFontGL::NORMAL, 
  2016.      mStyle->getShadowType(), 
  2017.      length, rect.getWidth(), 
  2018.      &right_x, 
  2019.      mEditor.getUseEllipses());
  2020. }
  2021. rect.mLeft = (S32)ceil(right_x);
  2022. if( (selection_start < seg_end) && (selection_end > seg_start) )
  2023. {
  2024. // Draw reversed
  2025. S32 start = llmax( selection_start, seg_start );
  2026. S32 end = llmin( selection_end, seg_end );
  2027. S32 length = end - start;
  2028. font->render(text, start, 
  2029.      rect.mLeft, rect.mTop,
  2030.      LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
  2031.      LLFontGL::LEFT, LLFontGL::TOP, 
  2032.      LLFontGL::NORMAL, 
  2033.      LLFontGL::NO_SHADOW, 
  2034.      length, rect.getWidth(), 
  2035.      &right_x, 
  2036.      mEditor.getUseEllipses());
  2037. }
  2038. rect.mLeft = (S32)ceil(right_x);
  2039. if( selection_end < seg_end )
  2040. {
  2041. // Draw normally
  2042. S32 start = llmax( selection_end, seg_start );
  2043. S32 end = seg_end;
  2044. S32 length = end - start;
  2045. font->render(text, start, 
  2046.      rect.mLeft, rect.mTop, 
  2047.      color, 
  2048.      LLFontGL::LEFT, LLFontGL::TOP, 
  2049.      LLFontGL::NORMAL, 
  2050.      mStyle->getShadowType(), 
  2051.      length, rect.getWidth(), 
  2052.      &right_x, 
  2053.      mEditor.getUseEllipses());
  2054. }
  2055. return right_x;
  2056. }
  2057. BOOL LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask)
  2058. {
  2059. if (getStyle() && getStyle()->isLink())
  2060. {
  2061. // Only process the click if it's actually in this segment, not to the right of the end-of-line.
  2062. if(mEditor.getSegmentAtLocalPos(x, y, false) == this)
  2063. {
  2064. LLUI::getWindow()->setCursor(UI_CURSOR_HAND);
  2065. return TRUE;
  2066. }
  2067. }
  2068. return FALSE;
  2069. }
  2070. BOOL LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask)
  2071. {
  2072. if (getStyle() && getStyle()->isLink())
  2073. {
  2074. // Only process the click if it's actually in this segment, not to the right of the end-of-line.
  2075. if(mEditor.getSegmentAtLocalPos(x, y, false) == this)
  2076. {
  2077. mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF());
  2078. return TRUE;
  2079. }
  2080. }
  2081. return FALSE;
  2082. }
  2083. BOOL LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask)
  2084. {
  2085. if (getStyle() && getStyle()->isLink())
  2086. {
  2087. // Only process the click if it's actually in this segment, not to the right of the end-of-line.
  2088. if(mEditor.getSegmentAtLocalPos(x, y, false) == this)
  2089. {
  2090. // eat mouse down event on hyperlinks, so we get the mouse up
  2091. return TRUE;
  2092. }
  2093. }
  2094. return FALSE;
  2095. }
  2096. BOOL LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask)
  2097. {
  2098. if (getStyle() && getStyle()->isLink())
  2099. {
  2100. // Only process the click if it's actually in this segment, not to the right of the end-of-line.
  2101. if(mEditor.getSegmentAtLocalPos(x, y, false) == this)
  2102. {
  2103. LLUrlAction::clickAction(getStyle()->getLinkHREF());
  2104. return TRUE;
  2105. }
  2106. }
  2107. return FALSE;
  2108. }
  2109. BOOL LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask)
  2110. {
  2111. std::string msg;
  2112. // do we have a tooltip for a loaded keyword (for script editor)?
  2113. if (mToken && !mToken->getToolTip().empty())
  2114. {
  2115. const LLWString& wmsg = mToken->getToolTip();
  2116. LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg));
  2117. return TRUE;
  2118. }
  2119. // or do we have an explicitly set tooltip (e.g., for Urls)
  2120. if (!mTooltip.empty())
  2121. {
  2122. LLToolTipMgr::instance().show(mTooltip);
  2123. return TRUE;
  2124. }
  2125. return FALSE;
  2126. }
  2127. void LLNormalTextSegment::setToolTip(const std::string& tooltip)
  2128. {
  2129. // we cannot replace a keyword tooltip that's loaded from a file
  2130. if (mToken)
  2131. {
  2132. llwarns << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << llendl;
  2133. return;
  2134. }
  2135. mTooltip = tooltip;
  2136. }
  2137. bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const
  2138. {
  2139. height = 0;
  2140. width = 0;
  2141. bool force_newline = false;
  2142. if (num_chars > 0)
  2143. {
  2144. height = mFontHeight;
  2145. const LLWString &text = mEditor.getWText();
  2146. // if last character is a newline, then return true, forcing line break
  2147. llwchar last_char = text[mStart + first_char + num_chars - 1];
  2148. if (last_char == 'n')
  2149. {
  2150. force_newline = true;
  2151. // don't count newline in font width
  2152. width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars - 1);
  2153. }
  2154. else
  2155. {
  2156. width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars);
  2157. }
  2158. }
  2159. LLUIImagePtr image = mStyle->getImage();
  2160. if( image.notNull())
  2161. {
  2162. width += image->getWidth();
  2163. height = llmax(height, image->getHeight());
  2164. }
  2165. return force_newline;
  2166. }
  2167. S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const
  2168. {
  2169. const LLWString &text = mEditor.getWText();
  2170. return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset,
  2171.    (F32)segment_local_x_coord,
  2172.    F32_MAX,
  2173.    num_chars,
  2174.    round);
  2175. }
  2176. S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const
  2177. {
  2178. const LLWString &text = mEditor.getWText();
  2179. LLUIImagePtr image = mStyle->getImage();
  2180. if( image.notNull())
  2181. {
  2182. num_pixels = llmax(0, num_pixels - image->getWidth());
  2183. }
  2184. // search for newline and if found, truncate there
  2185. S32 last_char = mStart + segment_offset;
  2186. for (; last_char != mEnd; ++last_char)
  2187. {
  2188. if (text[last_char] == 'n') 
  2189. {
  2190. break;
  2191. }
  2192. }
  2193. // set max characters to length of segment, or to first newline
  2194. max_chars = llmin(max_chars, last_char - (mStart + segment_offset));
  2195. // if no character yet displayed on this line, don't require word wrapping since
  2196. // we can just move to the next line, otherwise insist on it so we make forward progress
  2197. LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) 
  2198. ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE 
  2199. : LLFontGL::ONLY_WORD_BOUNDARIES;
  2200. S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, 
  2201. (F32)num_pixels,
  2202. max_chars, 
  2203. word_wrap_style);
  2204. if (num_chars == 0 
  2205. && line_offset == 0 
  2206. && max_chars > 0)
  2207. {
  2208. // If at the beginning of a line, and a single character won't fit, draw it anyway
  2209. num_chars = 1;
  2210. }
  2211. // include *either* the EOF or newline character in this run of text
  2212. // but not both
  2213. S32 last_char_in_run = mStart + segment_offset + num_chars;
  2214. // check length first to avoid indexing off end of string
  2215. if (last_char_in_run < mEnd 
  2216. && (last_char_in_run >= mEditor.getLength() 
  2217. || text[last_char_in_run] == 'n'))
  2218. {
  2219. num_chars++;
  2220. }
  2221. return num_chars;
  2222. }
  2223. void LLNormalTextSegment::dump() const
  2224. {
  2225. llinfos << "Segment [" << 
  2226. // mColor.mV[VX] << ", " <<
  2227. // mColor.mV[VY] << ", " <<
  2228. // mColor.mV[VZ] << "]t[" <<
  2229. mStart << ", " <<
  2230. getEnd() << "]" <<
  2231. llendl;
  2232. }
  2233. //
  2234. // LLInlineViewSegment
  2235. //
  2236. LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end)
  2237. : LLTextSegment(start, end),
  2238. mView(p.view),
  2239. mForceNewLine(p.force_newline),
  2240. mLeftPad(p.left_pad),
  2241. mRightPad(p.right_pad),
  2242. mTopPad(p.top_pad),
  2243. mBottomPad(p.bottom_pad)
  2244. {
  2245. LLInlineViewSegment::~LLInlineViewSegment()
  2246. {
  2247. mView->die();
  2248. }
  2249. bool LLInlineViewSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const
  2250. {
  2251. if (first_char == 0 && num_chars == 0) 
  2252. {
  2253. // we didn't fit on a line, the widget will fall on the next line
  2254. // so dimensions here are 0
  2255. width = 0;
  2256. height = 0;
  2257. }
  2258. else
  2259. {
  2260. width = mLeftPad + mRightPad + mView->getRect().getWidth();
  2261. height = mBottomPad + mTopPad + mView->getRect().getHeight();
  2262. }
  2263. return false;
  2264. }
  2265. S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const
  2266. {
  2267. // if putting a widget anywhere but at the beginning of a line
  2268. // and the widget doesn't fit or mForceNewLine is true
  2269. // then return 0 chars for that line, and all characters for the next
  2270. if (line_offset != 0 
  2271. && (mForceNewLine || num_pixels < mView->getRect().getWidth())) 
  2272. {
  2273. return 0;
  2274. }
  2275. else
  2276. {
  2277. return mEnd - mStart;
  2278. }
  2279. }
  2280. void LLInlineViewSegment::updateLayout(const LLTextBase& editor)
  2281. {
  2282. LLRect start_rect = editor.getDocRectFromDocIndex(mStart);
  2283. mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad);
  2284. }
  2285. F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
  2286. {
  2287. // return padded width of widget
  2288. // widget is actually drawn during mDocumentView's draw()
  2289. return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad);
  2290. }
  2291. void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor)
  2292. {
  2293. editor->removeDocumentChild(mView);
  2294. }
  2295. void LLInlineViewSegment::linkToDocument(LLTextBase* editor)
  2296. {
  2297. editor->addDocumentChild(mView);
  2298. }