PageRenderTime 69ms CodeModel.GetById 17ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llui/llspinctrl.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 463 lines | 344 code | 75 blank | 44 comment | 44 complexity | 39035f2343a03566499c9ee4fdd4605b MD5 | raw file
  1/** 
  2 * @file llspinctrl.cpp
  3 * @brief LLSpinCtrl base class
  4 *
  5 * $LicenseInfo:firstyear=2001&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 "linden_common.h"
 28 
 29#include "llspinctrl.h"
 30
 31#include "llgl.h"
 32#include "llui.h"
 33#include "lluiconstants.h"
 34
 35#include "llstring.h"
 36#include "llfontgl.h"
 37#include "lllineeditor.h"
 38#include "llbutton.h"
 39#include "lltextbox.h"
 40#include "llkeyboard.h"
 41#include "llmath.h"
 42#include "llcontrol.h"
 43#include "llfocusmgr.h"
 44#include "llresmgr.h"
 45#include "lluictrlfactory.h"
 46
 47const U32 MAX_STRING_LENGTH = 255;
 48
 49static LLDefaultChildRegistry::Register<LLSpinCtrl> r2("spinner");
 50
 51LLSpinCtrl::Params::Params()
 52:	label_width("label_width"),
 53	decimal_digits("decimal_digits"),
 54	allow_text_entry("allow_text_entry", true),
 55	label_wrap("label_wrap", false),
 56	text_enabled_color("text_enabled_color"),
 57	text_disabled_color("text_disabled_color"),
 58	up_button("up_button"),
 59	down_button("down_button")
 60{}
 61
 62LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)
 63:	LLF32UICtrl(p),
 64	mLabelBox(NULL),
 65	mbHasBeenSet( FALSE ),
 66	mPrecision(p.decimal_digits),
 67	mTextEnabledColor(p.text_enabled_color()),
 68	mTextDisabledColor(p.text_disabled_color())
 69{
 70	static LLUICachedControl<S32> spinctrl_spacing ("UISpinctrlSpacing", 0);
 71	static LLUICachedControl<S32> spinctrl_btn_width ("UISpinctrlBtnWidth", 0);
 72	static LLUICachedControl<S32> spinctrl_btn_height ("UISpinctrlBtnHeight", 0);
 73	S32 centered_top = getRect().getHeight();
 74	S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height;
 75	S32 btn_left = 0;
 76	// reserve space for spinner
 77	S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40));
 78
 79	// Label
 80	if( !p.label().empty() )
 81	{
 82		LLRect label_rect( 0, centered_top, label_width, centered_bottom );
 83		LLTextBox::Params params;
 84		params.wrap(p.label_wrap);
 85		params.name("SpinCtrl Label");
 86		params.rect(label_rect);
 87		params.initial_value(p.label());
 88		if (p.font.isProvided())
 89		{
 90			params.font(p.font);
 91		}
 92		mLabelBox = LLUICtrlFactory::create<LLTextBox> (params);
 93		addChild(mLabelBox);
 94
 95		btn_left += label_rect.mRight + spinctrl_spacing;
 96	}
 97
 98	S32 btn_right = btn_left + spinctrl_btn_width;
 99	
100	// Spin buttons
101	LLButton::Params up_button_params(p.up_button);
102	up_button_params.rect = LLRect(btn_left, getRect().getHeight(), btn_right, getRect().getHeight() - spinctrl_btn_height);
103	up_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2));
104	up_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2));
105
106	mUpBtn = LLUICtrlFactory::create<LLButton>(up_button_params);
107	addChild(mUpBtn);
108
109	LLButton::Params down_button_params(p.down_button);
110	down_button_params.rect = LLRect(btn_left, getRect().getHeight() - spinctrl_btn_height, btn_right, getRect().getHeight() - 2 * spinctrl_btn_height);
111	down_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2));
112	down_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2));
113	mDownBtn = LLUICtrlFactory::create<LLButton>(down_button_params);
114	addChild(mDownBtn);
115
116	LLRect editor_rect( btn_right + 1, centered_top, getRect().getWidth(), centered_bottom );
117	LLLineEditor::Params params;
118	params.name("SpinCtrl Editor");
119	params.rect(editor_rect);
120	if (p.font.isProvided())
121	{
122		params.font(p.font);
123	}
124	params.max_length.bytes(MAX_STRING_LENGTH);
125	params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2)));
126	
127	//*NOTE: allow entering of any chars for LLCalc, proper input will be evaluated on commit
128	
129	params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM);
130	mEditor = LLUICtrlFactory::create<LLLineEditor> (params);
131	mEditor->setFocusReceivedCallback( boost::bind(&LLSpinCtrl::onEditorGainFocus, _1, this ));
132	//RN: this seems to be a BAD IDEA, as it makes the editor behavior different when it has focus
133	// than when it doesn't.  Instead, if you always have to double click to select all the text, 
134	// it's easier to understand
135	//mEditor->setSelectAllonFocusReceived(TRUE);
136	mEditor->setSelectAllonCommit(FALSE);
137	addChild(mEditor);
138
139	updateEditor();
140	setUseBoundingRect( TRUE );
141}
142
143F32 clamp_precision(F32 value, S32 decimal_precision)
144{
145	// pow() isn't perfect
146	
147	F64 clamped_value = value;
148	for (S32 i = 0; i < decimal_precision; i++)
149		clamped_value *= 10.0;
150
151	clamped_value = llround((F32)clamped_value);
152
153	for (S32 i = 0; i < decimal_precision; i++)
154		clamped_value /= 10.0;
155	
156	return (F32)clamped_value;
157}
158
159
160void LLSpinCtrl::onUpBtn( const LLSD& data )
161{
162	if( getEnabled() )
163	{
164		std::string text = mEditor->getText();
165		if( LLLineEditor::postvalidateFloat( text ) )
166		{
167			
168			LLLocale locale(LLLocale::USER_LOCALE);
169			F32 cur_val = (F32) atof(text.c_str());
170		
171			// use getValue()/setValue() to force reload from/to control
172			F32 val = cur_val + mIncrement;
173			val = clamp_precision(val, mPrecision);
174			val = llmin( val, mMaxValue );
175			if (val < mMinValue) val = mMinValue;
176			if (val > mMaxValue) val = mMaxValue;
177		
178			F32 saved_val = (F32)getValue().asReal();
179			setValue(val);
180			if( mValidateSignal && !(*mValidateSignal)( this, val ) )
181			{
182				setValue( saved_val );
183				reportInvalidData();
184				updateEditor();
185				return;
186			}
187
188		updateEditor();
189		onCommit();
190		}
191	}
192}
193
194void LLSpinCtrl::onDownBtn( const LLSD& data )
195{
196	if( getEnabled() )
197	{
198		std::string text = mEditor->getText();
199		if( LLLineEditor::postvalidateFloat( text ) )
200		{
201
202			LLLocale locale(LLLocale::USER_LOCALE);
203			F32 cur_val = (F32) atof(text.c_str());
204		
205			F32 val = cur_val - mIncrement;
206			val = clamp_precision(val, mPrecision);
207			val = llmax( val, mMinValue );
208
209			if (val < mMinValue) val = mMinValue;
210			if (val > mMaxValue) val = mMaxValue;
211			
212			F32 saved_val = (F32)getValue().asReal();
213			setValue(val);
214			if( mValidateSignal && !(*mValidateSignal)( this, val ) )
215			{
216				setValue( saved_val );
217				reportInvalidData();
218				updateEditor();
219				return;
220			}
221		
222			updateEditor();
223			onCommit();
224		}
225	}
226}
227
228// static
229void LLSpinCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata )
230{
231	LLSpinCtrl* self = (LLSpinCtrl*) userdata;
232	llassert( caller == self->mEditor );
233
234	self->onFocusReceived();
235}
236
237void LLSpinCtrl::setValue(const LLSD& value )
238{
239	F32 v = (F32)value.asReal();
240	if (getValueF32() != v || !mbHasBeenSet)
241	{
242		mbHasBeenSet = TRUE;
243        LLF32UICtrl::setValue(value);
244		
245		if (!mEditor->hasFocus())
246		{
247			updateEditor();
248		}
249	}
250}
251
252//no matter if Editor has the focus, update the value
253void LLSpinCtrl::forceSetValue(const LLSD& value )
254{
255	F32 v = (F32)value.asReal();
256	if (getValueF32() != v || !mbHasBeenSet)
257	{
258		mbHasBeenSet = TRUE;
259        LLF32UICtrl::setValue(value);
260		
261		updateEditor();
262	}
263}
264
265void LLSpinCtrl::clear()
266{
267	setValue(mMinValue);
268	mEditor->clear();
269	mbHasBeenSet = FALSE;
270}
271
272void LLSpinCtrl::updateLabelColor()
273{
274	if( mLabelBox )
275	{
276		mLabelBox->setColor( getEnabled() ? mTextEnabledColor.get() : mTextDisabledColor.get() );
277	}
278}
279
280void LLSpinCtrl::updateEditor()
281{
282	LLLocale locale(LLLocale::USER_LOCALE);
283
284	// Don't display very small negative valu	es as -0.000
285	F32 displayed_value = clamp_precision((F32)getValue().asReal(), mPrecision);
286
287//	if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 )
288//	{
289//		displayed_value = 0.f;
290//	}
291
292	std::string format = llformat("%%.%df", mPrecision);
293	std::string text = llformat(format.c_str(), displayed_value);
294	mEditor->setText( text );
295}
296
297void LLSpinCtrl::onEditorCommit( const LLSD& data )
298{
299	BOOL success = FALSE;
300	
301	if( mEditor->evaluateFloat() )
302	{
303		std::string text = mEditor->getText();
304
305		LLLocale locale(LLLocale::USER_LOCALE);
306		F32 val = (F32) atof(text.c_str());
307
308		if (val < mMinValue) val = mMinValue;
309		if (val > mMaxValue) val = mMaxValue;
310
311		F32 saved_val = getValueF32();
312		setValue(val);
313		if( !mValidateSignal || (*mValidateSignal)( this, val ) )
314		{
315			success = TRUE;
316			onCommit();
317		}
318		else
319		{
320			setValue(saved_val);
321		}
322	}
323	updateEditor();
324
325	if( success )
326	{
327		updateEditor();
328	}
329	else
330	{
331		reportInvalidData();		
332	}
333}
334
335
336void LLSpinCtrl::forceEditorCommit()
337{
338	onEditorCommit( LLSD() );
339}
340
341
342void LLSpinCtrl::setFocus(BOOL b)
343{
344	LLUICtrl::setFocus( b );
345	mEditor->setFocus( b );
346}
347
348void LLSpinCtrl::setEnabled(BOOL b)
349{
350	LLView::setEnabled( b );
351	mEditor->setEnabled( b );
352	updateLabelColor();
353}
354
355
356void LLSpinCtrl::setTentative(BOOL b)
357{
358	mEditor->setTentative(b);
359	LLUICtrl::setTentative(b);
360}
361
362
363BOOL LLSpinCtrl::isMouseHeldDown() const
364{
365	return 
366		mDownBtn->hasMouseCapture()
367		|| mUpBtn->hasMouseCapture();
368}
369
370void LLSpinCtrl::onCommit()
371{
372	setTentative(FALSE);
373	setControlValue(getValueF32());
374	LLF32UICtrl::onCommit();
375}
376
377
378void LLSpinCtrl::setPrecision(S32 precision)
379{
380	if (precision < 0 || precision > 10)
381	{
382		llerrs << "LLSpinCtrl::setPrecision - precision out of range" << llendl;
383		return;
384	}
385
386	mPrecision = precision;
387	updateEditor();
388}
389
390void LLSpinCtrl::setLabel(const LLStringExplicit& label)
391{
392	if (mLabelBox)
393	{
394		mLabelBox->setText(label);
395	}
396	else
397	{
398		llwarns << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << llendl;
399	}
400	updateLabelColor();
401}
402
403void LLSpinCtrl::setAllowEdit(BOOL allow_edit)
404{
405	mEditor->setEnabled(allow_edit);
406	mAllowEdit = allow_edit;
407}
408
409void LLSpinCtrl::onTabInto()
410{
411	mEditor->onTabInto(); 
412}
413
414
415void LLSpinCtrl::reportInvalidData()
416{
417	make_ui_sound("UISndBadKeystroke");
418}
419
420BOOL LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
421{
422	if( clicks > 0 )
423	{
424		while( clicks-- )
425		{
426			onDownBtn(getValue());
427		}
428	}
429	else
430	while( clicks++ )
431	{
432		onUpBtn(getValue());
433	}
434
435	return TRUE;
436}
437
438BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask)
439{
440	if (mEditor->hasFocus())
441	{
442		if(key == KEY_ESCAPE)
443		{
444			// text editors don't support revert normally (due to user confusion)
445			// but not allowing revert on a spinner seems dangerous
446			updateEditor();
447			mEditor->setFocus(FALSE);
448			return TRUE;
449		}
450		if(key == KEY_UP)
451		{
452			onUpBtn(getValue());
453			return TRUE;
454		}
455		if(key == KEY_DOWN)
456		{
457			onDownBtn(getValue());
458			return TRUE;
459		}
460	}
461	return FALSE;
462}
463