PageRenderTime 43ms CodeModel.GetById 10ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llexpandabletextbox.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 449 lines | 306 code | 72 blank | 71 comment | 25 complexity | 790644e5adf14b20b7a67dd906dd573d MD5 | raw file
  1/** 
  2 * @file llexpandabletextbox.cpp
  3 * @brief LLExpandableTextBox and related class implementations
  4 *
  5 * $LicenseInfo:firstyear=2004&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "llviewerprecompiledheaders.h"
 28#include "llexpandabletextbox.h"
 29
 30#include "llscrollcontainer.h"
 31#include "lltrans.h"
 32#include "llwindow.h"
 33#include "llviewerwindow.h"
 34
 35static LLDefaultChildRegistry::Register<LLExpandableTextBox> t1("expandable_text");
 36
 37class LLExpanderSegment : public LLTextSegment
 38{
 39public:
 40	LLExpanderSegment(const LLStyleSP& style, S32 start, S32 end, const std::string& more_text, LLTextBase& editor )
 41	:	LLTextSegment(start, end),
 42		mEditor(editor),
 43		mStyle(style),
 44		mExpanderLabel(more_text)
 45	{}
 46
 47	/*virtual*/ bool	getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const 
 48	{
 49		// more label always spans width of text box
 50		if (num_chars == 0)
 51		{
 52			width = 0; 
 53			height = 0;
 54		}
 55		else
 56		{
 57			width = mEditor.getDocumentView()->getRect().getWidth() - mEditor.getHPad(); 
 58			height = llceil(mStyle->getFont()->getLineHeight());
 59		}
 60		return true;
 61	}
 62	/*virtual*/ S32		getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const 
 63	{ 
 64		return start_offset;
 65	}
 66	/*virtual*/ S32		getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const 
 67	{ 
 68		// require full line to ourselves
 69		if (line_offset == 0) 
 70		{
 71			// print all our text
 72			return getEnd() - getStart(); 
 73		}
 74		else
 75		{
 76			// wait for next line
 77			return 0;
 78		}
 79	}
 80	/*virtual*/ F32		draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
 81	{
 82		F32 right_x;
 83		mStyle->getFont()->renderUTF8(mExpanderLabel, start, 
 84									draw_rect.mRight, draw_rect.mTop, 
 85									mStyle->getColor(), 
 86									LLFontGL::RIGHT, LLFontGL::TOP, 
 87									0, 
 88									mStyle->getShadowType(), 
 89									end - start, draw_rect.getWidth(), 
 90									&right_x, 
 91									mEditor.getUseEllipses());
 92		return right_x;
 93	}
 94	/*virtual*/ bool	canEdit() const { return false; }
 95	// eat handleMouseDown event so we get the mouseup event
 96	/*virtual*/ BOOL	handleMouseDown(S32 x, S32 y, MASK mask) { return TRUE; }
 97	/*virtual*/ BOOL	handleMouseUp(S32 x, S32 y, MASK mask) { mEditor.onCommit(); return TRUE; }
 98	/*virtual*/ BOOL	handleHover(S32 x, S32 y, MASK mask) 
 99	{
100		LLUI::getWindow()->setCursor(UI_CURSOR_HAND);
101		return TRUE; 
102	}
103private:
104	LLTextBase& mEditor;
105	LLStyleSP	mStyle;
106	std::string	mExpanderLabel;
107};
108
109LLExpandableTextBox::LLTextBoxEx::Params::Params()
110{
111}
112
113LLExpandableTextBox::LLTextBoxEx::LLTextBoxEx(const Params& p)
114:	LLTextEditor(p),
115	mExpanderLabel(p.label.isProvided() ? p.label : LLTrans::getString("More")),
116	mExpanderVisible(false)
117{
118	setIsChrome(TRUE);
119
120}
121
122void LLExpandableTextBox::LLTextBoxEx::reshape(S32 width, S32 height, BOOL called_from_parent)
123{
124	LLTextEditor::reshape(width, height, called_from_parent);
125
126	hideOrShowExpandTextAsNeeded();
127}
128
129void LLExpandableTextBox::LLTextBoxEx::setText(const LLStringExplicit& text,const LLStyle::Params& input_params)
130{
131	// LLTextBox::setText will obliterate the expander segment, so make sure
132	// we generate it again by clearing mExpanderVisible
133	mExpanderVisible = false;
134	LLTextEditor::setText(text, input_params);
135
136	hideOrShowExpandTextAsNeeded();
137}
138
139
140void LLExpandableTextBox::LLTextBoxEx::showExpandText()
141{
142	if (!mExpanderVisible)
143	{
144		// make sure we're scrolled to top when collapsing
145		if (mScroller)
146		{
147			mScroller->goToTop();
148		}
149		// get fully visible lines
150		std::pair<S32, S32> visible_lines = getVisibleLines(true);
151		S32 last_line = visible_lines.second - 1;
152
153		LLStyle::Params expander_style(getDefaultStyleParams());
154		expander_style.font.style = "UNDERLINE";
155		expander_style.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
156		LLExpanderSegment* expanderp = new LLExpanderSegment(new LLStyle(expander_style), getLineStart(last_line), getLength() + 1, mExpanderLabel, *this);
157		insertSegment(expanderp);
158		mExpanderVisible = true;
159	}
160
161}
162
163//NOTE: obliterates existing styles (including hyperlinks)
164void LLExpandableTextBox::LLTextBoxEx::hideExpandText() 
165{ 
166	if (mExpanderVisible)
167	{
168		// this will overwrite the expander segment and all text styling with a single style
169		LLStyleConstSP sp(new LLStyle(getDefaultStyleParams()));
170		LLNormalTextSegment* segmentp = new LLNormalTextSegment(sp, 0, getLength() + 1, *this);
171		insertSegment(segmentp);
172		
173		mExpanderVisible = false;
174	}
175}
176
177S32 LLExpandableTextBox::LLTextBoxEx::getVerticalTextDelta()
178{
179	S32 text_height = getTextPixelHeight();
180	S32 textbox_height = getRect().getHeight();
181
182	return text_height - textbox_height;
183}
184
185S32 LLExpandableTextBox::LLTextBoxEx::getTextPixelHeight()
186{
187	return getTextBoundingRect().getHeight();
188}
189
190void LLExpandableTextBox::LLTextBoxEx::hideOrShowExpandTextAsNeeded()
191{
192	// Restore the text box contents to calculate the text height properly,
193	// otherwise if a part of the text is hidden under "More" link
194	// getTextPixelHeight() returns only the height of currently visible text
195	// including the "More" link. See STORM-250.
196	hideExpandText();
197
198	// Show the expander a.k.a. "More" link if we need it, depending on text
199	// contents height. If not, keep it hidden.
200	if (getTextPixelHeight() > getRect().getHeight())
201	{
202		showExpandText();
203	}
204}
205
206//////////////////////////////////////////////////////////////////////////
207//////////////////////////////////////////////////////////////////////////
208//////////////////////////////////////////////////////////////////////////
209
210LLExpandableTextBox::Params::Params()
211:	textbox("textbox"),
212	scroll("scroll"),
213	max_height("max_height", 0),
214	bg_visible("bg_visible", false),
215	expanded_bg_visible("expanded_bg_visible", true),
216	bg_color("bg_color", LLColor4::black),
217	expanded_bg_color("expanded_bg_color", LLColor4::black)
218{
219}
220
221LLExpandableTextBox::LLExpandableTextBox(const Params& p)
222:	LLUICtrl(p),
223	mMaxHeight(p.max_height),
224	mBGVisible(p.bg_visible),
225	mExpandedBGVisible(p.expanded_bg_visible),
226	mBGColor(p.bg_color),
227	mExpandedBGColor(p.expanded_bg_color),
228	mExpanded(false)
229{
230	LLRect rc = getLocalRect();
231
232	LLScrollContainer::Params scroll_params = p.scroll;
233	scroll_params.rect(rc);
234	mScroll = LLUICtrlFactory::create<LLScrollContainer>(scroll_params);
235	addChild(mScroll);
236
237	LLTextBoxEx::Params textbox_params = p.textbox;
238	textbox_params.rect(rc);
239	mTextBox = LLUICtrlFactory::create<LLTextBoxEx>(textbox_params);
240	mScroll->addChild(mTextBox);
241
242	updateTextBoxRect();
243
244	mTextBox->setCommitCallback(boost::bind(&LLExpandableTextBox::onExpandClicked, this));
245}
246
247void LLExpandableTextBox::draw()
248{
249	if(mBGVisible && !mExpanded)
250	{
251		gl_rect_2d(getLocalRect(), mBGColor.get(), TRUE);
252	}
253	if(mExpandedBGVisible && mExpanded)
254	{
255		gl_rect_2d(getLocalRect(), mExpandedBGColor.get(), TRUE);
256	}
257
258	collapseIfPosChanged();
259
260	LLUICtrl::draw();
261}
262
263void LLExpandableTextBox::collapseIfPosChanged()
264{
265	if(mExpanded)
266	{
267		LLView* parentp = getParent();
268		LLRect parent_rect = parentp->getRect();
269		parentp->localRectToOtherView(parent_rect, &parent_rect, getRootView());
270
271		if(parent_rect.mLeft != mParentRect.mLeft 
272			|| parent_rect.mTop != mParentRect.mTop)
273		{
274			collapseTextBox();
275		}
276	}
277}
278
279void LLExpandableTextBox::onExpandClicked()
280{
281	expandTextBox();
282}
283
284void LLExpandableTextBox::updateTextBoxRect()
285{
286	LLRect rc = getLocalRect();
287
288	rc.mLeft += mScroll->getBorderWidth();
289	rc.mRight -= mScroll->getBorderWidth();
290	rc.mTop -= mScroll->getBorderWidth();
291	rc.mBottom += mScroll->getBorderWidth();
292
293	mTextBox->reshape(rc.getWidth(), rc.getHeight());
294	mTextBox->setRect(rc);
295}
296
297S32 LLExpandableTextBox::recalculateTextDelta(S32 text_delta)
298{
299	LLRect expanded_rect = getLocalRect();
300	LLView* root_view = getRootView();
301	LLRect window_rect = root_view->getRect();
302
303	LLRect expanded_screen_rect;
304	localRectToOtherView(expanded_rect, &expanded_screen_rect, root_view);
305
306	// don't allow expanded text box bottom go off screen
307	if(expanded_screen_rect.mBottom - text_delta < window_rect.mBottom)
308	{
309		text_delta = expanded_screen_rect.mBottom - window_rect.mBottom;
310	}
311	// show scroll bar if max_height is valid 
312	// and expanded size is greater that max_height
313	else if(mMaxHeight > 0 && expanded_rect.getHeight() + text_delta > mMaxHeight)
314	{
315		text_delta = mMaxHeight - expanded_rect.getHeight();
316	}
317
318	return text_delta;
319}
320
321void LLExpandableTextBox::expandTextBox()
322{
323	// hide "more" link, and show full text contents
324	mTextBox->hideExpandText();
325
326	// *HACK dz
327	// hideExpandText brakes text styles (replaces hyper-links with plain text), see ticket EXT-3290
328	// Set text again to make text box re-apply styles.
329	// *TODO Find proper solution to fix this issue.
330	// Maybe add removeSegment to LLTextBase
331	mTextBox->setTextBase(mText);
332
333	S32 text_delta = mTextBox->getVerticalTextDelta();
334	text_delta += mTextBox->getVPad() * 2;
335	text_delta += mScroll->getBorderWidth() * 2;
336	// no need to expand
337	if(text_delta <= 0)
338	{
339		return;
340	}
341
342	saveCollapsedState();
343
344	LLRect expanded_rect = getLocalRect();
345	LLRect expanded_screen_rect;
346
347	S32 updated_text_delta = recalculateTextDelta(text_delta);
348	// actual expand
349	expanded_rect.mBottom -= updated_text_delta;
350
351	LLRect text_box_rect = mTextBox->getRect();
352
353	// check if we need to show scrollbar
354	if(text_delta != updated_text_delta)
355	{
356		static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
357
358		// disable horizontal scrollbar
359		text_box_rect.mRight -= scrollbar_size;
360
361		// text box size has changed - redo text wrap
362		// Should be handled automatically in reshape() below. JC
363		//mTextBox->setWrappedText(mText, text_box_rect.getWidth());
364
365		// recalculate text delta since text wrap changed text height
366		text_delta = mTextBox->getVerticalTextDelta() + mTextBox->getVPad() * 2;
367	}
368
369	// expand text
370	text_box_rect.mBottom -= text_delta;
371	mTextBox->reshape(text_box_rect.getWidth(), text_box_rect.getHeight());
372	mTextBox->setRect(text_box_rect);
373
374	// expand text box
375	localRectToOtherView(expanded_rect, &expanded_screen_rect, getParent());
376	reshape(expanded_screen_rect.getWidth(), expanded_screen_rect.getHeight(), FALSE);
377	setRect(expanded_screen_rect);
378
379	setFocus(TRUE);
380	// this lets us receive top_lost event(needed to collapse text box)
381	// it also draws text box above all other ui elements
382	gViewerWindow->addPopup(this);
383
384	mExpanded = true;
385}
386
387void LLExpandableTextBox::collapseTextBox()
388{
389	if(!mExpanded)
390	{
391		return;
392	}
393
394	mExpanded = false;
395
396	reshape(mCollapsedRect.getWidth(), mCollapsedRect.getHeight(), FALSE);
397	setRect(mCollapsedRect);
398
399	updateTextBoxRect();
400
401	gViewerWindow->removePopup(this);
402}
403
404void LLExpandableTextBox::onFocusLost()
405{
406	collapseTextBox();
407
408	LLUICtrl::onFocusLost();
409}
410
411void LLExpandableTextBox::onTopLost()
412{
413	collapseTextBox();
414
415	LLUICtrl::onTopLost();
416}
417
418void LLExpandableTextBox::updateTextShape()
419{
420	// I guess this should be done on every reshape(),
421	// but adding this code to reshape() currently triggers bug VWR-26455,
422	// which makes the text virtually unreadable.
423	llassert(!mExpanded);
424	updateTextBoxRect();
425}
426
427void LLExpandableTextBox::setValue(const LLSD& value)
428{
429	collapseTextBox();
430	mText = value.asString();
431	mTextBox->setValue(value);
432}
433
434void LLExpandableTextBox::setText(const std::string& str)
435{
436	collapseTextBox();
437	mText = str;
438	mTextBox->setText(str);
439}
440
441void LLExpandableTextBox::saveCollapsedState()
442{
443	mCollapsedRect = getRect();
444
445	mParentRect = getParent()->getRect();
446	// convert parent rect to screen coordinates, 
447	// this will allow to track parent's position change
448	getParent()->localRectToOtherView(mParentRect, &mParentRect, getRootView());
449}