diff options
Diffstat (limited to 'indra/llui/llspinctrl.cpp')
-rw-r--r-- | indra/llui/llspinctrl.cpp | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp new file mode 100644 index 0000000000..332372011e --- /dev/null +++ b/indra/llui/llspinctrl.cpp @@ -0,0 +1,509 @@ +/** + * @file llspinctrl.cpp + * @brief LLSpinCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llspinctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lllineeditor.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llkeyboard.h" +#include "llmath.h" +#include "sound_ids.h" +#include "audioengine.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" + +const U32 MAX_STRING_LENGTH = 32; + + +LLSpinCtrl::LLSpinCtrl( const LLString& name, const LLRect& rect, const LLString& label, const LLFontGL* font, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_user_data, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_name, + S32 label_width) + : + LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP ), + mValue( initial_value ), + mInitialValue( initial_value ), + mMaxValue( max_value ), + mMinValue( min_value ), + mIncrement( increment ), + mPrecision( 3 ), + mLabelBox( NULL ), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mbHasBeenSet( FALSE ) +{ + S32 top = mRect.getHeight(); + S32 bottom = top - 2 * SPINCTRL_BTN_HEIGHT; + S32 centered_top = top; + S32 centered_bottom = bottom; + S32 btn_left = 0; + + // Label + if( !label.empty() ) + { + LLRect label_rect( 0, centered_top, label_width, centered_bottom ); + mLabelBox = new LLTextBox( "SpinCtrl Label", label_rect, label.c_str(), font ); + addChild(mLabelBox); + + btn_left += label_rect.mRight + SPINCTRL_SPACING; + } + + S32 btn_right = btn_left + SPINCTRL_BTN_WIDTH; + + // Spin buttons + LLRect up_rect( btn_left, top, btn_right, top - SPINCTRL_BTN_HEIGHT ); + LLString out_id = "UIImgBtnSpinUpOutUUID"; + LLString in_id = "UIImgBtnSpinUpInUUID"; + mUpBtn = new LLButton( + "SpinCtrl Up", up_rect, + out_id, + in_id, + "", + &LLSpinCtrl::onUpBtn, this, LLFontGL::sSansSerif ); + mUpBtn->setFollowsLeft(); + mUpBtn->setFollowsBottom(); + mUpBtn->setHeldDownCallback( &LLSpinCtrl::onUpBtn ); + mUpBtn->setTabStop(FALSE); + addChild(mUpBtn); + + LLRect down_rect( btn_left, top - SPINCTRL_BTN_HEIGHT, btn_right, bottom ); + out_id = "UIImgBtnSpinDownOutUUID"; + in_id = "UIImgBtnSpinDownInUUID"; + mDownBtn = new LLButton( + "SpinCtrl Down", down_rect, + out_id, + in_id, + "", + &LLSpinCtrl::onDownBtn, this, LLFontGL::sSansSerif ); + mDownBtn->setFollowsLeft(); + mDownBtn->setFollowsBottom(); + mDownBtn->setHeldDownCallback( &LLSpinCtrl::onDownBtn ); + mDownBtn->setTabStop(FALSE); + addChild(mDownBtn); + + LLRect editor_rect( btn_right + 1, centered_top, mRect.getWidth(), centered_bottom ); + mEditor = new LLLineEditor( "SpinCtrl Editor", editor_rect, "", font, + MAX_STRING_LENGTH, + &LLSpinCtrl::onEditorCommit, NULL, NULL, this, + &LLLineEditor::prevalidateFloat ); + mEditor->setFollowsLeft(); + mEditor->setFollowsBottom(); + mEditor->setFocusReceivedCallback( &LLSpinCtrl::onEditorGainFocus ); + //RN: this seems to be a BAD IDEA, as it makes the editor behavior different when it has focus + // than when it doesn't. Instead, if you always have to double click to select all the text, + // it's easier to understand + //mEditor->setSelectAllonFocusReceived(TRUE); + mEditor->setIgnoreTab(TRUE); + addChild(mEditor); + + updateEditor(); + setSpanChildren( TRUE ); +} + +LLSpinCtrl::~LLSpinCtrl() +{ + // Children all cleaned up by default view destructor. +} + + +// static +void LLSpinCtrl::onUpBtn( void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + if( self->getEnabled() ) + { + // use getValue()/setValue() to force reload from/to control + F32 val = (F32)self->getValue().asReal() + self->mIncrement; + val = llmin( val, self->mMaxValue ); + + if( self->mValidateCallback ) + { + F32 saved_val = (F32)self->getValue().asReal(); + self->setValue(val); + if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + { + self->setValue( saved_val ); + self->reportInvalidData(); + self->updateEditor(); + return; + } + } + else + { + self->setValue(val); + } + + self->updateEditor(); + self->onCommit(); + } +} + +// static +void LLSpinCtrl::onDownBtn( void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + + if( self->getEnabled() ) + { + F32 val = (F32)self->getValue().asReal() - self->mIncrement; + val = llmax( val, self->mMinValue ); + + if( self->mValidateCallback ) + { + F32 saved_val = (F32)self->getValue().asReal(); + self->setValue(val); + if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + { + self->setValue( saved_val ); + self->reportInvalidData(); + self->updateEditor(); + return; + } + } + else + { + self->setValue(val); + } + + self->updateEditor(); + self->onCommit(); + } +} + +// static +void LLSpinCtrl::onEditorGainFocus( LLUICtrl* caller, void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + +void LLSpinCtrl::setValue(const LLSD& value ) +{ + F32 v = (F32)value.asReal(); + if (mValue != v || !mbHasBeenSet) + { + mbHasBeenSet = TRUE; + mValue = v; + + if (!mEditor->hasFocus()) + { + updateEditor(); + } + } +} + +LLSD LLSpinCtrl::getValue() const +{ + return mValue; +} + +void LLSpinCtrl::clear() +{ + setValue(mMinValue); + mEditor->clear(); + mbHasBeenSet = FALSE; +} + + +void LLSpinCtrl::updateEditor() +{ + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)floor(getValue().asReal() * pow(10, mPrecision) + 0.5) / (F32)pow(10, mPrecision); + +// if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 ) +// { +// displayed_value = 0.f; +// } + + LLString format = llformat("%%.%df", mPrecision); + LLString text = llformat(format.c_str(), displayed_value); + mEditor->setText( text ); +} + +void LLSpinCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +{ + BOOL success = FALSE; + + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + LLString text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + F32 val = (F32) atof(text.c_str()); + + if (val < self->mMinValue) val = self->mMinValue; + if (val > self->mMaxValue) val = self->mMaxValue; + + if( self->mValidateCallback ) + { + F32 saved_val = self->mValue; + self->mValue = val; + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + self->onCommit(); + } + else + { + self->mValue = saved_val; + } + } + else + { + self->mValue = val; + self->onCommit(); + success = TRUE; + } + } + self->updateEditor(); + + if( !success ) + { + self->reportInvalidData(); + } +} + + +void LLSpinCtrl::forceEditorCommit() +{ + onEditorCommit(mEditor, this); +} + + +void LLSpinCtrl::setFocus(BOOL b) +{ + LLUICtrl::setFocus( b ); + mEditor->setFocus( b ); +} + +void LLSpinCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled( b ); + mEditor->setEnabled( b ); +} + + +void LLSpinCtrl::setTentative(BOOL b) +{ + mEditor->setTentative(b); + LLUICtrl::setTentative(b); +} + + +BOOL LLSpinCtrl::isMouseHeldDown() +{ + return + gFocusMgr.getMouseCapture() == mDownBtn || + gFocusMgr.getMouseCapture() == mUpBtn; +} + +void LLSpinCtrl::onCommit() +{ + setTentative(FALSE); + + setControlValue(mValue); + + LLUICtrl::onCommit(); +} + + +void LLSpinCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + llerrs << "LLSpinCtrl::setPrecision - precision out of range" << llendl; + return; + } + + mPrecision = precision; + updateEditor(); +} + +void LLSpinCtrl::setLabel(const LLString& label) +{ + if (mLabelBox) + { + mLabelBox->setText(label); + } + else + { + llwarns << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << llendl; + } +} + +void LLSpinCtrl::onTabInto() +{ + mEditor->onTabInto(); +} + + +void LLSpinCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +void LLSpinCtrl::draw() +{ + if( mLabelBox ) + { + mLabelBox->setColor( mEnabled ? mTextEnabledColor : mTextDisabledColor ); + } + LLUICtrl::draw(); +} + + +BOOL LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if( mEnabled ) + { + if( clicks > 0 ) + { + while( clicks-- ) + { + LLSpinCtrl::onDownBtn(this); + } + } + else + while( clicks++ ) + { + LLSpinCtrl::onUpBtn(this); + } + } + + return TRUE; +} + +BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mEditor->hasFocus()) + { + if(key == KEY_ESCAPE) + { + // text editors don't support revert normally (due to user confusion) + // but not allowing revert on a spinner seems dangerous + updateEditor(); + mEditor->setFocus(FALSE); + return TRUE; + } + if(key == KEY_UP) + { + LLSpinCtrl::onUpBtn(this); + return TRUE; + } + if(key == KEY_DOWN) + { + LLSpinCtrl::onDownBtn(this); + return TRUE; + } + } + return FALSE; +} + +// virtual +LLXMLNodePtr LLSpinCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); + + if (mLabelBox) + { + node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); + + node->createChild("label_width", TRUE)->setIntValue(mLabelBox->getRect().getWidth()); + } + + node->createChild("initial_val", TRUE)->setFloatValue(mInitialValue); + + node->createChild("min_val", TRUE)->setFloatValue(mMinValue); + + node->createChild("max_val", TRUE)->setFloatValue(mMaxValue); + + node->createChild("increment", TRUE)->setFloatValue(mIncrement); + + addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); + addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); + + return node; +} + +LLView* LLSpinCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("spinner"); + node->getAttributeString("name", name); + + LLString label; + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLFontGL* font = LLView::selectFont(node); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + U32 precision = 3; + node->getAttributeU32("decimal_digits", precision); + + S32 label_width = llmin(40, rect.getWidth() - 40); + node->getAttributeS32("label_width", label_width); + + LLUICtrlCallback callback = NULL; + + if(label.empty()) + { + label.assign( node->getValue() ); + } + + LLSpinCtrl* spinner = new LLSpinCtrl(name, + rect, + label, + font, + callback, + NULL, + initial_value, + min_value, + max_value, + increment, + "", + label_width); + + spinner->setPrecision(precision); + + spinner->initFromXML(node, parent); + + return spinner; +} |