llspinctrl.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. /**
  2. * @file llspinctrl.cpp
  3. * @brief LLSpinCtrl base class
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewergpl$
  6. *
  7. * Copyright (c) 2001-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "llspinctrl.h"
  34. #include "llbutton.h"
  35. #include "llcontrol.h"
  36. #include "llgl.h"
  37. #include "llkeyboard.h"
  38. #include "lllineeditor.h"
  39. #include "lllocale.h"
  40. #include "llmath.h"
  41. #include "lltextbox.h"
  42. constexpr U32 MAX_SPIN_STR_LEN = 32;
  43. static const std::string LL_SPIN_CTRL_TAG = "spinner";
  44. static LLRegisterWidget<LLSpinCtrl> r24(LL_SPIN_CTRL_TAG);
  45. LLSpinCtrl::LLSpinCtrl(const std::string& name, const LLRect& spin_rect,
  46. const std::string& label, const LLFontGL* font,
  47. void (*commit_callback)(LLUICtrl*, void*),
  48. void* callback_user_data, F32 initial_value,
  49. F32 min_value, F32 max_value, F32 increment,
  50. const std::string& control_name, S32 label_width)
  51. : LLUICtrl(name, spin_rect, true, commit_callback, callback_user_data,
  52. FOLLOWS_LEFT | FOLLOWS_TOP),
  53. mValue(initial_value),
  54. mInitialValue(initial_value),
  55. mMaxValue(max_value),
  56. mMinValue(min_value),
  57. mIncrement(increment),
  58. mPrecision(3),
  59. mPreProcessCallback(NULL),
  60. mLabelBox(NULL),
  61. mTextEnabledColor(LLUI::sLabelTextColor),
  62. mTextDisabledColor(LLUI::sLabelDisabledColor),
  63. mHasBeenSet(false),
  64. mDirty(true)
  65. {
  66. S32 top = getRect().getHeight();
  67. S32 bottom = top - 2 * SPINCTRL_BTN_HEIGHT;
  68. S32 centered_top = top;
  69. S32 centered_bottom = bottom;
  70. S32 btn_left = 0;
  71. LLRect rect;
  72. // Label
  73. if (!label.empty())
  74. {
  75. rect.set(0, centered_top, label_width, centered_bottom);
  76. mLabelBox = new LLTextBox("SpinCtrl Label", rect, label, font);
  77. addChild(mLabelBox);
  78. btn_left += rect.mRight + SPINCTRL_SPACING;
  79. }
  80. S32 btn_right = btn_left + SPINCTRL_BTN_WIDTH;
  81. // Spin buttons
  82. LLFontGL* btnfont = LLFontGL::getFontSansSerif();
  83. rect.set(btn_left, top, btn_right, top - SPINCTRL_BTN_HEIGHT);
  84. std::string out_id = "UIImgBtnSpinUpOutUUID";
  85. std::string in_id = "UIImgBtnSpinUpInUUID";
  86. mUpBtn = new LLButton("SpinCtrl Up", rect, out_id, in_id, NULL,
  87. onUpBtn, this, btnfont);
  88. mUpBtn->setFollowsLeft();
  89. mUpBtn->setFollowsBottom();
  90. mUpBtn->setHeldDownCallback(onUpBtn);
  91. mUpBtn->setTabStop(false);
  92. addChild(mUpBtn);
  93. rect.set(btn_left, top - SPINCTRL_BTN_HEIGHT, btn_right, bottom);
  94. out_id = "UIImgBtnSpinDownOutUUID";
  95. in_id = "UIImgBtnSpinDownInUUID";
  96. mDownBtn = new LLButton("SpinCtrl Down", rect, out_id, in_id, NULL,
  97. onDownBtn, this, btnfont);
  98. mDownBtn->setFollowsLeft();
  99. mDownBtn->setFollowsBottom();
  100. mDownBtn->setHeldDownCallback(onDownBtn);
  101. mDownBtn->setTabStop(false);
  102. addChild(mDownBtn);
  103. // Line editor
  104. rect.set(btn_right + 1, centered_top, getRect().getWidth(),
  105. centered_bottom);
  106. mEditor = new LLLineEditor("SpinCtrl Editor", rect, LLStringUtil::null,
  107. font, MAX_SPIN_STR_LEN, onEditorCommit, NULL,
  108. NULL, this, LLLineEditor::prevalidateFloat);
  109. mEditor->setFollowsLeft();
  110. mEditor->setFollowsBottom();
  111. mEditor->setFocusReceivedCallback(onEditorGainFocus, this);
  112. mEditor->setFocusLostCallback(onEditorLostFocus, this);
  113. mEditor->setIgnoreTab(true);
  114. addChild(mEditor);
  115. updateEditor();
  116. setUseBoundingRect(true);
  117. }
  118. F32 clamp_precision(F32 value, S32 decimal_precision)
  119. {
  120. // powf() is not perfect
  121. F64 clamped_value = value;
  122. for (S32 i = 0; i < decimal_precision; ++i)
  123. {
  124. clamped_value *= 10.0;
  125. }
  126. clamped_value = floor(clamped_value + 0.5);
  127. for (S32 i = 0; i < decimal_precision; ++i)
  128. {
  129. clamped_value /= 10.0;
  130. }
  131. return (F32)clamped_value;
  132. }
  133. //static
  134. void LLSpinCtrl::onUpBtn(void* userdata)
  135. {
  136. LLSpinCtrl* self = (LLSpinCtrl*) userdata;
  137. if (self && self->getEnabled())
  138. {
  139. // Use getValue()/setValue() to force reload from/to control
  140. F32 val = (F32)self->getValue().asReal() + self->mIncrement;
  141. val = clamp_precision(val, self->mPrecision);
  142. val = llmin(val, self->mMaxValue);
  143. if (self->mValidateCallback)
  144. {
  145. F32 saved_val = (F32)self->getValue().asReal();
  146. self->setValue(val);
  147. if (!self->mValidateCallback(self, self->mCallbackUserData))
  148. {
  149. self->setValue(saved_val);
  150. self->reportInvalidData();
  151. self->updateEditor();
  152. return;
  153. }
  154. }
  155. else
  156. {
  157. self->setValue(val);
  158. }
  159. self->updateEditor();
  160. self->onCommit();
  161. }
  162. }
  163. //static
  164. void LLSpinCtrl::onDownBtn(void* userdata)
  165. {
  166. LLSpinCtrl* self = (LLSpinCtrl*)userdata;
  167. if (self && self->getEnabled())
  168. {
  169. F32 val = (F32)self->getValue().asReal() - self->mIncrement;
  170. val = clamp_precision(val, self->mPrecision);
  171. val = llmax(val, self->mMinValue);
  172. if (self->mValidateCallback)
  173. {
  174. F32 saved_val = (F32)self->getValue().asReal();
  175. self->setValue(val);
  176. if (!self->mValidateCallback(self, self->mCallbackUserData))
  177. {
  178. self->setValue(saved_val);
  179. self->reportInvalidData();
  180. self->updateEditor();
  181. return;
  182. }
  183. }
  184. else
  185. {
  186. self->setValue(val);
  187. }
  188. self->updateEditor();
  189. self->onCommit();
  190. }
  191. }
  192. //static
  193. void LLSpinCtrl::onEditorGainFocus(LLFocusableElement* caller, void* userdata)
  194. {
  195. LLSpinCtrl* self = (LLSpinCtrl*)userdata;
  196. if (self && caller && self->mEditor == caller)
  197. {
  198. self->onFocusReceived();
  199. }
  200. }
  201. //static
  202. void LLSpinCtrl::onEditorLostFocus(LLFocusableElement* caller, void* userdata)
  203. {
  204. LLSpinCtrl* self = (LLSpinCtrl*)userdata;
  205. if (self && caller && self->mEditor == caller)
  206. {
  207. self->onFocusLost();
  208. if (!self->mEditor->isDirty())
  209. {
  210. LLLocale locale(LLLocale::USER_LOCALE);
  211. std::string val_str = self->mEditor->getText();
  212. if ((F32)atof(val_str.c_str()) != (F32)self->getValue().asReal())
  213. {
  214. // Editor was focused when value update arrived, the string in
  215. // editor is different from the one in spin control. Since
  216. // editor is not dirty, it won't commit, so either attempt to
  217. // commit value from editor or revert to a more recent value
  218. // from spin control.
  219. self->updateEditor();
  220. }
  221. }
  222. }
  223. }
  224. //virtual
  225. void LLSpinCtrl::setValue(const LLSD& value)
  226. {
  227. F32 v = (F32)value.asReal();
  228. if (mValue != v || !mHasBeenSet)
  229. {
  230. mHasBeenSet = true;
  231. mValue = v;
  232. if (!mEditor->hasFocus())
  233. {
  234. updateEditor();
  235. }
  236. }
  237. }
  238. // No matter if mEditor has the focus, update the value
  239. //virtual
  240. void LLSpinCtrl::forceSetValue(const LLSD& value)
  241. {
  242. F32 v = (F32)value.asReal();
  243. if (mValue != v || !mHasBeenSet)
  244. {
  245. mHasBeenSet = true;
  246. mValue = v;
  247. updateEditor();
  248. mEditor->resetScrollPosition();
  249. }
  250. }
  251. //virtual
  252. void LLSpinCtrl::clear()
  253. {
  254. setValue(mMinValue);
  255. mEditor->clear();
  256. mHasBeenSet = false;
  257. }
  258. void LLSpinCtrl::updateEditor()
  259. {
  260. LLLocale locale(LLLocale::USER_LOCALE);
  261. // Do not display very small negative values as -0.000
  262. F32 displayed_value = clamp_precision((F32)getValue().asReal(),
  263. mPrecision);
  264. std::string format = llformat("%%.%df", mPrecision);
  265. std::string text = llformat(format.c_str(), displayed_value);
  266. mEditor->setText(text);
  267. }
  268. //static
  269. void LLSpinCtrl::onEditorCommit(LLUICtrl* caller, void* userdata)
  270. {
  271. LLSpinCtrl* self = (LLSpinCtrl*)userdata;
  272. if (!self || caller != self->mEditor) return;
  273. bool success = false;
  274. std::string text = self->mEditor->getText();
  275. if (self->mPreProcessCallback)
  276. {
  277. text = self->mPreProcessCallback(text);
  278. }
  279. if (LLLineEditor::postvalidateFloat(text))
  280. {
  281. LLLocale locale(LLLocale::USER_LOCALE);
  282. F32 val = (F32)atof(text.c_str());
  283. if (val < self->mMinValue) val = self->mMinValue;
  284. if (val > self->mMaxValue) val = self->mMaxValue;
  285. if (self->mValidateCallback)
  286. {
  287. F32 saved_val = self->mValue;
  288. self->mValue = val;
  289. if (self->mValidateCallback(self, self->mCallbackUserData))
  290. {
  291. success = true;
  292. self->onCommit();
  293. }
  294. else
  295. {
  296. self->mValue = saved_val;
  297. }
  298. }
  299. else
  300. {
  301. self->mValue = val;
  302. self->onCommit();
  303. success = true;
  304. }
  305. }
  306. self->updateEditor();
  307. if (success)
  308. {
  309. // We commited and clamped value; try to display as much as possible
  310. self->mEditor->resetScrollPosition();
  311. }
  312. else
  313. {
  314. self->reportInvalidData();
  315. }
  316. }
  317. void LLSpinCtrl::forceEditorCommit()
  318. {
  319. onEditorCommit(mEditor, this);
  320. }
  321. //virtual
  322. void LLSpinCtrl::setFocus(bool b)
  323. {
  324. LLUICtrl::setFocus(b);
  325. mEditor->setFocus(b);
  326. mDirty = true;
  327. }
  328. //virtual
  329. void LLSpinCtrl::setEnabled(bool b)
  330. {
  331. LLView::setEnabled(b);
  332. mEditor->setEnabled(b);
  333. mDirty = true;
  334. }
  335. //virtual
  336. void LLSpinCtrl::setTentative(bool b)
  337. {
  338. mEditor->setTentative(b);
  339. LLUICtrl::setTentative(b);
  340. }
  341. bool LLSpinCtrl::isMouseHeldDown() const
  342. {
  343. return mDownBtn->hasMouseCapture() || mUpBtn->hasMouseCapture();
  344. }
  345. //virtual
  346. void LLSpinCtrl::onCommit()
  347. {
  348. setTentative(false);
  349. setControlValue(mValue);
  350. LLUICtrl::onCommit();
  351. }
  352. //virtual
  353. void LLSpinCtrl::setPrecision(S32 precision)
  354. {
  355. if (precision < 0 || precision > 10)
  356. {
  357. llwarns << "Precision out of range, ignoring." << llendl;
  358. llassert(false);
  359. }
  360. else
  361. {
  362. mPrecision = precision;
  363. updateEditor();
  364. }
  365. }
  366. void LLSpinCtrl::setLabel(const std::string& label)
  367. {
  368. if (mLabelBox)
  369. {
  370. mLabelBox->setText(label);
  371. }
  372. else
  373. {
  374. llwarns << "Attempting to set label on LLSpinCtrl constructed without one "
  375. << getName() << llendl;
  376. }
  377. }
  378. void LLSpinCtrl::setAllowEdit(bool allow_edit)
  379. {
  380. mEditor->setEnabled(allow_edit);
  381. }
  382. //virtual
  383. void LLSpinCtrl::onTabInto()
  384. {
  385. mEditor->onTabInto();
  386. }
  387. void LLSpinCtrl::reportInvalidData()
  388. {
  389. make_ui_sound("UISndBadKeystroke");
  390. }
  391. //virtual
  392. void LLSpinCtrl::draw()
  393. {
  394. if (mDirty && mLabelBox)
  395. {
  396. mLabelBox->setColor(getEnabled() ? mTextEnabledColor
  397. : mTextDisabledColor);
  398. mDirty = false;
  399. }
  400. LLUICtrl::draw();
  401. }
  402. //virtual
  403. bool LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
  404. {
  405. if (clicks > 0)
  406. {
  407. while (clicks--)
  408. {
  409. onDownBtn(this);
  410. }
  411. }
  412. else
  413. {
  414. while (clicks++)
  415. {
  416. onUpBtn(this);
  417. }
  418. }
  419. return true;
  420. }
  421. //virtual
  422. bool LLSpinCtrl::handleKeyHere(KEY key, MASK mask)
  423. {
  424. if (mEditor->hasFocus())
  425. {
  426. if (key == KEY_ESCAPE && mask == MASK_NONE)
  427. {
  428. // Text editors do not support revert normally (due to user
  429. // confusion) but not allowing revert on a spinner seems dangerous
  430. updateEditor();
  431. mEditor->resetScrollPosition();
  432. mEditor->setFocus(false);
  433. return true;
  434. }
  435. if (key == KEY_UP)
  436. {
  437. onUpBtn(this);
  438. return true;
  439. }
  440. if (key == KEY_DOWN)
  441. {
  442. onDownBtn(this);
  443. return true;
  444. }
  445. }
  446. return false;
  447. }
  448. //virtual
  449. const std::string& LLSpinCtrl::getTag() const
  450. {
  451. return LL_SPIN_CTRL_TAG;
  452. }
  453. //virtual
  454. LLXMLNodePtr LLSpinCtrl::getXML(bool save_children) const
  455. {
  456. LLXMLNodePtr node = LLUICtrl::getXML();
  457. node->setName(LL_SPIN_CTRL_TAG);
  458. node->createChild("decimal_digits", true)->setIntValue(mPrecision);
  459. if (mLabelBox)
  460. {
  461. node->createChild("label", true)->setStringValue(mLabelBox->getText());
  462. node->createChild("label_width",
  463. true)->setIntValue(mLabelBox->getRect().getWidth());
  464. }
  465. node->createChild("initial_val", true)->setFloatValue(mInitialValue);
  466. node->createChild("min_val", true)->setFloatValue(mMinValue);
  467. node->createChild("max_val", true)->setFloatValue(mMaxValue);
  468. node->createChild("increment", true)->setFloatValue(mIncrement);
  469. addColorXML(node, mTextEnabledColor, "text_enabled_color",
  470. "LabelTextColor");
  471. addColorXML(node, mTextDisabledColor, "text_disabled_color",
  472. "LabelDisabledColor");
  473. return node;
  474. }
  475. //static
  476. LLView* LLSpinCtrl::fromXML(LLXMLNodePtr node, LLView* parent,
  477. LLUICtrlFactory* factory)
  478. {
  479. std::string name = LL_SPIN_CTRL_TAG;
  480. node->getAttributeString("name", name);
  481. std::string label;
  482. node->getAttributeString("label", label);
  483. LLRect rect;
  484. createRect(node, rect, parent, LLRect());
  485. LLFontGL* font = LLView::selectFont(node);
  486. F32 initial_value = 0.f;
  487. node->getAttributeF32("initial_val", initial_value);
  488. F32 min_value = 0.f;
  489. node->getAttributeF32("min_val", min_value);
  490. F32 max_value = 1.f;
  491. node->getAttributeF32("max_val", max_value);
  492. F32 increment = 0.1f;
  493. node->getAttributeF32("increment", increment);
  494. U32 precision = 3;
  495. node->getAttributeU32("decimal_digits", precision);
  496. S32 label_width = llmin(40, rect.getWidth() - 40);
  497. node->getAttributeS32("label_width", label_width);
  498. bool allow_text_entry = true;
  499. node->getAttributeBool("allow_text_entry", allow_text_entry);
  500. LLUICtrlCallback callback = NULL;
  501. if (label.empty())
  502. {
  503. label.assign(node->getValue());
  504. }
  505. LLSpinCtrl* spinner = new LLSpinCtrl(name, rect, label, font,
  506. callback, NULL,
  507. initial_value, min_value, max_value,
  508. increment, LLStringUtil::null,
  509. label_width);
  510. spinner->setPrecision(precision);
  511. spinner->initFromXML(node, parent);
  512. spinner->setAllowEdit(allow_text_entry);
  513. return spinner;
  514. }