/indra/llui/llspinctrl.cpp

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