123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- /**
- * @file llspinctrl.cpp
- * @brief LLSpinCtrl base class
- *
- * $LicenseInfo:firstyear=2001&license=viewergpl$
- *
- * Copyright (c) 2001-2009, Linden Research, Inc.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/LicenseInfo$
- */
- #include "linden_common.h"
- #include "llspinctrl.h"
- #include "llbutton.h"
- #include "llcontrol.h"
- #include "llgl.h"
- #include "llkeyboard.h"
- #include "lllineeditor.h"
- #include "lllocale.h"
- #include "llmath.h"
- #include "lltextbox.h"
- constexpr U32 MAX_SPIN_STR_LEN = 32;
- static const std::string LL_SPIN_CTRL_TAG = "spinner";
- static LLRegisterWidget<LLSpinCtrl> r24(LL_SPIN_CTRL_TAG);
- LLSpinCtrl::LLSpinCtrl(const std::string& name, const LLRect& spin_rect,
- const std::string& 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 std::string& control_name, S32 label_width)
- : LLUICtrl(name, spin_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),
- mPreProcessCallback(NULL),
- mLabelBox(NULL),
- mTextEnabledColor(LLUI::sLabelTextColor),
- mTextDisabledColor(LLUI::sLabelDisabledColor),
- mHasBeenSet(false),
- mDirty(true)
- {
- S32 top = getRect().getHeight();
- S32 bottom = top - 2 * SPINCTRL_BTN_HEIGHT;
- S32 centered_top = top;
- S32 centered_bottom = bottom;
- S32 btn_left = 0;
- LLRect rect;
- // Label
- if (!label.empty())
- {
- rect.set(0, centered_top, label_width, centered_bottom);
- mLabelBox = new LLTextBox("SpinCtrl Label", rect, label, font);
- addChild(mLabelBox);
- btn_left += rect.mRight + SPINCTRL_SPACING;
- }
- S32 btn_right = btn_left + SPINCTRL_BTN_WIDTH;
- // Spin buttons
- LLFontGL* btnfont = LLFontGL::getFontSansSerif();
- rect.set(btn_left, top, btn_right, top - SPINCTRL_BTN_HEIGHT);
- std::string out_id = "UIImgBtnSpinUpOutUUID";
- std::string in_id = "UIImgBtnSpinUpInUUID";
- mUpBtn = new LLButton("SpinCtrl Up", rect, out_id, in_id, NULL,
- onUpBtn, this, btnfont);
- mUpBtn->setFollowsLeft();
- mUpBtn->setFollowsBottom();
- mUpBtn->setHeldDownCallback(onUpBtn);
- mUpBtn->setTabStop(false);
- addChild(mUpBtn);
- rect.set(btn_left, top - SPINCTRL_BTN_HEIGHT, btn_right, bottom);
- out_id = "UIImgBtnSpinDownOutUUID";
- in_id = "UIImgBtnSpinDownInUUID";
- mDownBtn = new LLButton("SpinCtrl Down", rect, out_id, in_id, NULL,
- onDownBtn, this, btnfont);
- mDownBtn->setFollowsLeft();
- mDownBtn->setFollowsBottom();
- mDownBtn->setHeldDownCallback(onDownBtn);
- mDownBtn->setTabStop(false);
- addChild(mDownBtn);
- // Line editor
- rect.set(btn_right + 1, centered_top, getRect().getWidth(),
- centered_bottom);
- mEditor = new LLLineEditor("SpinCtrl Editor", rect, LLStringUtil::null,
- font, MAX_SPIN_STR_LEN, onEditorCommit, NULL,
- NULL, this, LLLineEditor::prevalidateFloat);
- mEditor->setFollowsLeft();
- mEditor->setFollowsBottom();
- mEditor->setFocusReceivedCallback(onEditorGainFocus, this);
- mEditor->setFocusLostCallback(onEditorLostFocus, this);
- mEditor->setIgnoreTab(true);
- addChild(mEditor);
- updateEditor();
- setUseBoundingRect(true);
- }
- F32 clamp_precision(F32 value, S32 decimal_precision)
- {
- // powf() is not perfect
- F64 clamped_value = value;
- for (S32 i = 0; i < decimal_precision; ++i)
- {
- clamped_value *= 10.0;
- }
- clamped_value = floor(clamped_value + 0.5);
- for (S32 i = 0; i < decimal_precision; ++i)
- {
- clamped_value /= 10.0;
- }
- return (F32)clamped_value;
- }
- //static
- void LLSpinCtrl::onUpBtn(void* userdata)
- {
- LLSpinCtrl* self = (LLSpinCtrl*) userdata;
- if (self && self->getEnabled())
- {
- // Use getValue()/setValue() to force reload from/to control
- F32 val = (F32)self->getValue().asReal() + self->mIncrement;
- val = clamp_precision(val, self->mPrecision);
- 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 && self->getEnabled())
- {
- F32 val = (F32)self->getValue().asReal() - self->mIncrement;
- val = clamp_precision(val, self->mPrecision);
- 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(LLFocusableElement* caller, void* userdata)
- {
- LLSpinCtrl* self = (LLSpinCtrl*)userdata;
- if (self && caller && self->mEditor == caller)
- {
- self->onFocusReceived();
- }
- }
- //static
- void LLSpinCtrl::onEditorLostFocus(LLFocusableElement* caller, void* userdata)
- {
- LLSpinCtrl* self = (LLSpinCtrl*)userdata;
- if (self && caller && self->mEditor == caller)
- {
- self->onFocusLost();
- if (!self->mEditor->isDirty())
- {
- LLLocale locale(LLLocale::USER_LOCALE);
- std::string val_str = self->mEditor->getText();
- if ((F32)atof(val_str.c_str()) != (F32)self->getValue().asReal())
- {
- // Editor was focused when value update arrived, the string in
- // editor is different from the one in spin control. Since
- // editor is not dirty, it won't commit, so either attempt to
- // commit value from editor or revert to a more recent value
- // from spin control.
- self->updateEditor();
- }
- }
- }
- }
- //virtual
- void LLSpinCtrl::setValue(const LLSD& value)
- {
- F32 v = (F32)value.asReal();
- if (mValue != v || !mHasBeenSet)
- {
- mHasBeenSet = true;
- mValue = v;
- if (!mEditor->hasFocus())
- {
- updateEditor();
- }
- }
- }
- // No matter if mEditor has the focus, update the value
- //virtual
- void LLSpinCtrl::forceSetValue(const LLSD& value)
- {
- F32 v = (F32)value.asReal();
- if (mValue != v || !mHasBeenSet)
- {
- mHasBeenSet = true;
- mValue = v;
- updateEditor();
- mEditor->resetScrollPosition();
- }
- }
- //virtual
- void LLSpinCtrl::clear()
- {
- setValue(mMinValue);
- mEditor->clear();
- mHasBeenSet = false;
- }
- void LLSpinCtrl::updateEditor()
- {
- LLLocale locale(LLLocale::USER_LOCALE);
- // Do not display very small negative values as -0.000
- F32 displayed_value = clamp_precision((F32)getValue().asReal(),
- mPrecision);
- std::string format = llformat("%%.%df", mPrecision);
- std::string text = llformat(format.c_str(), displayed_value);
- mEditor->setText(text);
- }
- //static
- void LLSpinCtrl::onEditorCommit(LLUICtrl* caller, void* userdata)
- {
- LLSpinCtrl* self = (LLSpinCtrl*)userdata;
- if (!self || caller != self->mEditor) return;
- bool success = false;
- std::string text = self->mEditor->getText();
- if (self->mPreProcessCallback)
- {
- text = self->mPreProcessCallback(text);
- }
- 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)
- {
- // We commited and clamped value; try to display as much as possible
- self->mEditor->resetScrollPosition();
- }
- else
- {
- self->reportInvalidData();
- }
- }
- void LLSpinCtrl::forceEditorCommit()
- {
- onEditorCommit(mEditor, this);
- }
- //virtual
- void LLSpinCtrl::setFocus(bool b)
- {
- LLUICtrl::setFocus(b);
- mEditor->setFocus(b);
- mDirty = true;
- }
- //virtual
- void LLSpinCtrl::setEnabled(bool b)
- {
- LLView::setEnabled(b);
- mEditor->setEnabled(b);
- mDirty = true;
- }
- //virtual
- void LLSpinCtrl::setTentative(bool b)
- {
- mEditor->setTentative(b);
- LLUICtrl::setTentative(b);
- }
- bool LLSpinCtrl::isMouseHeldDown() const
- {
- return mDownBtn->hasMouseCapture() || mUpBtn->hasMouseCapture();
- }
- //virtual
- void LLSpinCtrl::onCommit()
- {
- setTentative(false);
- setControlValue(mValue);
- LLUICtrl::onCommit();
- }
- //virtual
- void LLSpinCtrl::setPrecision(S32 precision)
- {
- if (precision < 0 || precision > 10)
- {
- llwarns << "Precision out of range, ignoring." << llendl;
- llassert(false);
- }
- else
- {
- mPrecision = precision;
- updateEditor();
- }
- }
- void LLSpinCtrl::setLabel(const std::string& label)
- {
- if (mLabelBox)
- {
- mLabelBox->setText(label);
- }
- else
- {
- llwarns << "Attempting to set label on LLSpinCtrl constructed without one "
- << getName() << llendl;
- }
- }
- void LLSpinCtrl::setAllowEdit(bool allow_edit)
- {
- mEditor->setEnabled(allow_edit);
- }
- //virtual
- void LLSpinCtrl::onTabInto()
- {
- mEditor->onTabInto();
- }
- void LLSpinCtrl::reportInvalidData()
- {
- make_ui_sound("UISndBadKeystroke");
- }
- //virtual
- void LLSpinCtrl::draw()
- {
- if (mDirty && mLabelBox)
- {
- mLabelBox->setColor(getEnabled() ? mTextEnabledColor
- : mTextDisabledColor);
- mDirty = false;
- }
- LLUICtrl::draw();
- }
- //virtual
- bool LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
- {
- if (clicks > 0)
- {
- while (clicks--)
- {
- onDownBtn(this);
- }
- }
- else
- {
- while (clicks++)
- {
- onUpBtn(this);
- }
- }
- return true;
- }
- //virtual
- bool LLSpinCtrl::handleKeyHere(KEY key, MASK mask)
- {
- if (mEditor->hasFocus())
- {
- if (key == KEY_ESCAPE && mask == MASK_NONE)
- {
- // Text editors do not support revert normally (due to user
- // confusion) but not allowing revert on a spinner seems dangerous
- updateEditor();
- mEditor->resetScrollPosition();
- mEditor->setFocus(false);
- return true;
- }
- if (key == KEY_UP)
- {
- onUpBtn(this);
- return true;
- }
- if (key == KEY_DOWN)
- {
- onDownBtn(this);
- return true;
- }
- }
- return false;
- }
- //virtual
- const std::string& LLSpinCtrl::getTag() const
- {
- return LL_SPIN_CTRL_TAG;
- }
- //virtual
- LLXMLNodePtr LLSpinCtrl::getXML(bool save_children) const
- {
- LLXMLNodePtr node = LLUICtrl::getXML();
- node->setName(LL_SPIN_CTRL_TAG);
- 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;
- }
- //static
- LLView* LLSpinCtrl::fromXML(LLXMLNodePtr node, LLView* parent,
- LLUICtrlFactory* factory)
- {
- std::string name = LL_SPIN_CTRL_TAG;
- node->getAttributeString("name", name);
- std::string 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);
- bool allow_text_entry = true;
- node->getAttributeBool("allow_text_entry", allow_text_entry);
- 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, LLStringUtil::null,
- label_width);
- spinner->setPrecision(precision);
- spinner->initFromXML(node, parent);
- spinner->setAllowEdit(allow_text_entry);
- return spinner;
- }
|