llmultisliderctrl.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  1. /**
  2. * @file llmultisliderctrl.cpp
  3. * @brief LLMultiSliderCtrl base class
  4. *
  5. * $LicenseInfo:firstyear=2007&license=viewergpl$
  6. *
  7. * Copyright (c) 2007-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 "llmultisliderctrl.h"
  34. #include "llcontrol.h"
  35. #include "llgl.h"
  36. #include "llimagegl.h"
  37. #include "llkeyboard.h" // For the MASK constants
  38. #include "lllineeditor.h"
  39. #include "lllocale.h"
  40. #include "lltextbox.h"
  41. #include "llwindow.h"
  42. static const std::string LL_MULTI_SLIDER_CTRL_TAG = "multi_slider";
  43. static LLRegisterWidget<LLMultiSliderCtrl> r11(LL_MULTI_SLIDER_CTRL_TAG);
  44. constexpr U32 MAX_STRING_LENGTH = 10;
  45. // Space between label, slider, and text
  46. constexpr S32 MULTI_SLIDERCTRL_SPACING = 4;
  47. constexpr S32 MULTI_THUMB_WIDTH = 8;
  48. constexpr S32 MULTI_TRACK_HEIGHT = 6;
  49. constexpr F32 FLOAT_THRESHOLD = 0.00001f;
  50. constexpr S32 EXTRA_TRIANGLE_WIDTH = 2;
  51. constexpr S32 EXTRA_TRIANGLE_HEIGHT = -2;
  52. ///////////////////////////////////////////////////////////////////////////////
  53. // LLMultiSlider class (slider UI element for the milti-slider)
  54. ///////////////////////////////////////////////////////////////////////////////
  55. S32 LLMultiSlider::mNameCounter = 0;
  56. LLMultiSlider::LLMultiSlider(const std::string& name, const LLRect& rect,
  57. void (*on_commit_callback)(LLUICtrl*, void*),
  58. void* callback_userdata, F32 initial_value,
  59. F32 min_value, F32 max_value, F32 increment,
  60. S32 max_sliders, F32 overlap_threshold,
  61. bool allow_overlap, bool loop_overlap,
  62. bool draw_track, bool use_triangle, bool vertical,
  63. const char* control_name)
  64. : LLUICtrl(name, rect, true, on_commit_callback, callback_userdata,
  65. FOLLOWS_LEFT | FOLLOWS_TOP),
  66. mInitialValue(initial_value),
  67. mMinValue(min_value),
  68. mMaxValue(max_value),
  69. mIncrement(increment),
  70. mMaxNumSliders(max_sliders),
  71. mOverlapThreshold(overlap_threshold),
  72. mAllowOverlap(allow_overlap),
  73. mLoopOverlap(loop_overlap),
  74. mDrawTrack(draw_track),
  75. mUseTriangle(use_triangle),
  76. mVertical(vertical),
  77. mMouseOffset(0),
  78. mMouseDownCallback(NULL),
  79. mMouseUpCallback(NULL)
  80. {
  81. if (mVertical)
  82. {
  83. mDragStartThumbRect = LLRect(0, MULTI_THUMB_WIDTH,
  84. getRect().getWidth(), 0);
  85. }
  86. else
  87. {
  88. mDragStartThumbRect = LLRect(0, getRect().getHeight(),
  89. MULTI_THUMB_WIDTH, 0);
  90. }
  91. if (mOverlapThreshold && mOverlapThreshold > mIncrement)
  92. {
  93. mOverlapThreshold -= mIncrement;
  94. }
  95. else
  96. {
  97. mOverlapThreshold = 0.f;
  98. }
  99. // Properly handle setting the starting thumb rect. Do it this way to
  100. // handle both the operating-on-settings and standalone ways of using this.
  101. setControlName(control_name, NULL);
  102. setValue(getValue());
  103. }
  104. void LLMultiSlider::setSliderValue(const std::string& name, F32 value,
  105. bool from_event)
  106. {
  107. // Exit if not there
  108. if (!mValue.has(name))
  109. {
  110. return;
  111. }
  112. // Round to nearest increment (bias towards rounding down)
  113. value = llclamp(value, mMinValue, mMaxValue) - mMinValue +
  114. mIncrement / 2.0001f;
  115. value -= fmodf(value, mIncrement);
  116. F32 new_value = mMinValue + value;
  117. // Now, make sure no overlap if we want that
  118. if (!mAllowOverlap)
  119. {
  120. // Increment is our distance between points; use it to eliminate
  121. // rounding error.
  122. F32 threshold = mOverlapThreshold + mIncrement / 4.f;
  123. // If loop overlap is enabled, check if we overlap with points 'after'
  124. // max value (project to lower).
  125. F32 loop_up_check;
  126. if (mLoopOverlap && value + threshold > mMaxValue)
  127. {
  128. loop_up_check = value + threshold - mMaxValue + mMinValue;
  129. }
  130. else
  131. {
  132. loop_up_check = mMinValue - 1.f;
  133. }
  134. // If loop overlap is enabled, check if we overlap with points 'before'
  135. // min value (project to upper).
  136. F32 loop_down_check;
  137. if (mLoopOverlap && value - threshold < mMinValue)
  138. {
  139. loop_down_check = value - threshold - mMinValue + mMaxValue;
  140. }
  141. else
  142. {
  143. loop_down_check = mMaxValue + 1.f;
  144. }
  145. // Look at the current spot and see if anything is there
  146. for (LLSD::map_iterator it = mValue.beginMap(), end = mValue.endMap();
  147. it != end; ++it)
  148. {
  149. F32 loc_val = (F32)it->second.asReal();
  150. F32 test_val = loc_val - new_value;
  151. if (test_val > -threshold && test_val < threshold &&
  152. it->first != name)
  153. {
  154. // Already occupied !
  155. return;
  156. }
  157. if (mLoopOverlap &&
  158. (loc_val < loop_up_check || loc_val > loop_down_check))
  159. {
  160. return;
  161. }
  162. }
  163. }
  164. // Now set it in the map
  165. mValue[name] = new_value;
  166. // Set the control if it's the current slider and not from an event
  167. if (!from_event && name == mCurSlider)
  168. {
  169. setControlValue(mValue);
  170. }
  171. F32 t = (new_value - mMinValue) / (mMaxValue - mMinValue);
  172. if (mVertical)
  173. {
  174. S32 bottom_edge = MULTI_THUMB_WIDTH / 2;
  175. S32 top_edge = getRect().getHeight() - MULTI_THUMB_WIDTH / 2;
  176. S32 x = bottom_edge + S32(t * (top_edge - bottom_edge));
  177. mThumbRects[name].mTop = x + MULTI_THUMB_WIDTH / 2;
  178. mThumbRects[name].mBottom = x - MULTI_THUMB_WIDTH / 2;
  179. }
  180. else
  181. {
  182. S32 left_edge = MULTI_THUMB_WIDTH / 2;
  183. S32 right_edge = getRect().getWidth() - MULTI_THUMB_WIDTH / 2;
  184. S32 x = left_edge + S32(t * (right_edge - left_edge));
  185. mThumbRects[name].mLeft = x - MULTI_THUMB_WIDTH / 2;
  186. mThumbRects[name].mRight = x + MULTI_THUMB_WIDTH / 2;
  187. }
  188. }
  189. //virtual
  190. void LLMultiSlider::setValue(const LLSD& value)
  191. {
  192. // Only do if it is a map
  193. if (value.isMap())
  194. {
  195. // Add each value... the first in the map becomes the current
  196. LLSD::map_const_iterator it = value.beginMap();
  197. LLSD::map_const_iterator end = value.endMap();
  198. mCurSlider = it->first;
  199. for ( ; it != end; ++it)
  200. {
  201. setSliderValue(it->first, (F32)it->second.asReal(), true);
  202. }
  203. }
  204. }
  205. F32 LLMultiSlider::getSliderValue(const std::string& name) const
  206. {
  207. return mValue.has(name) ? (F32)mValue[name].asReal() : 0.f;
  208. }
  209. void LLMultiSlider::setCurSlider(const std::string& name)
  210. {
  211. if (mValue.has(name))
  212. {
  213. mCurSlider = name;
  214. }
  215. }
  216. F32 LLMultiSlider::getSliderValueFromPos(S32 xpos, S32 ypos) const
  217. {
  218. F32 t;
  219. if (mVertical)
  220. {
  221. S32 bottom_edge = MULTI_THUMB_WIDTH / 2;
  222. S32 top_edge = getRect().getHeight() - MULTI_THUMB_WIDTH / 2;
  223. ypos += mMouseOffset;
  224. ypos = llclamp(ypos, bottom_edge, top_edge);
  225. t = F32(ypos - bottom_edge) / F32(top_edge - bottom_edge);
  226. }
  227. else
  228. {
  229. S32 left_edge = MULTI_THUMB_WIDTH / 2;
  230. S32 right_edge = getRect().getWidth() - MULTI_THUMB_WIDTH / 2;
  231. xpos += mMouseOffset;
  232. xpos = llclamp(xpos, left_edge, right_edge);
  233. t = F32(xpos - left_edge) / F32(right_edge - left_edge);
  234. }
  235. return t * (mMaxValue - mMinValue) + mMinValue;
  236. }
  237. const std::string& LLMultiSlider::addSlider(F32 val)
  238. {
  239. F32 init_val = val;
  240. if (mValue.size() >= mMaxNumSliders)
  241. {
  242. return LLStringUtil::null;
  243. }
  244. // Create a new name
  245. std::string new_name = llformat("sldr%d", mNameCounter++);
  246. if (!findUnusedValue(init_val))
  247. {
  248. return LLStringUtil::null;
  249. }
  250. // Add a new thumb rect
  251. if (mVertical)
  252. {
  253. mThumbRects[new_name] = LLRect(0, MULTI_THUMB_WIDTH,
  254. getRect().getWidth(), 0);
  255. }
  256. else
  257. {
  258. mThumbRects[new_name] = LLRect(0, getRect().getHeight(),
  259. MULTI_THUMB_WIDTH, 0);
  260. }
  261. // Add the value and set the current slider to this one
  262. mValue.insert(new_name, init_val);
  263. mCurSlider = new_name;
  264. // Move the slider
  265. setSliderValue(mCurSlider, init_val, true);
  266. return mCurSlider;
  267. }
  268. bool LLMultiSlider::addSlider(F32 val, const std::string& name)
  269. {
  270. F32 init_val = val;
  271. if (mValue.size() >= mMaxNumSliders)
  272. {
  273. return false;
  274. }
  275. if (!findUnusedValue(init_val))
  276. {
  277. return false;
  278. }
  279. // Add a new thumb rect
  280. if (mVertical)
  281. {
  282. mThumbRects[name] = LLRect(0, MULTI_THUMB_WIDTH,
  283. getRect().getWidth(), 0);
  284. }
  285. else
  286. {
  287. mThumbRects[name] = LLRect(0, getRect().getHeight(),
  288. MULTI_THUMB_WIDTH, 0);
  289. }
  290. // Add the value and set the current slider to this one
  291. mValue.insert(name, init_val);
  292. mCurSlider = name;
  293. // Move the slider
  294. setSliderValue(mCurSlider, init_val, true);
  295. return true;
  296. }
  297. bool LLMultiSlider::findUnusedValue(F32& init_val)
  298. {
  299. bool first_try = true;
  300. // Find the first open slot starting with the initial value
  301. while (true)
  302. {
  303. bool hit = false;
  304. // Look at the current spot and see if anything is there
  305. F32 threshold = mAllowOverlap ? FLOAT_THRESHOLD
  306. : mOverlapThreshold + mIncrement / 4.f;
  307. for (LLSD::map_iterator it = mValue.beginMap(), end = mValue.endMap();
  308. it != end; ++it)
  309. {
  310. F32 test_val = (F32)it->second.asReal() - init_val;
  311. if (test_val > -threshold && test_val < threshold)
  312. {
  313. hit = true;
  314. break;
  315. }
  316. }
  317. // If we found one
  318. if (!hit)
  319. {
  320. break;
  321. }
  322. // Increment and wrap if need be
  323. init_val += mIncrement;
  324. if (init_val > mMaxValue)
  325. {
  326. init_val = mMinValue;
  327. }
  328. // Stop if it is filled
  329. if (init_val == mInitialValue && !first_try)
  330. {
  331. llwarns << "Too many multi slider elements !" << llendl;
  332. return false;
  333. }
  334. first_try = false;
  335. continue;
  336. }
  337. return true;
  338. }
  339. void LLMultiSlider::deleteSlider(const std::string& name)
  340. {
  341. // Cannot delete the last slider
  342. if (mValue.size() <= 0)
  343. {
  344. return;
  345. }
  346. // Get rid of value from mValue and its thumb rect
  347. mValue.erase(name);
  348. mThumbRects.erase(name);
  349. // Set to the last created
  350. if (mValue.size() > 0)
  351. {
  352. rect_map_t::iterator it = mThumbRects.end();
  353. mCurSlider = (--it)->first;
  354. }
  355. }
  356. //virtual
  357. void LLMultiSlider::clear()
  358. {
  359. while (mThumbRects.size() > 0 && mValue.size() > 0)
  360. {
  361. deleteCurSlider();
  362. }
  363. LLUICtrl::clear();
  364. }
  365. //virtual
  366. bool LLMultiSlider::handleHover(S32 x, S32 y, MASK mask)
  367. {
  368. if (gFocusMgr.getMouseCapture() == this)
  369. {
  370. setCurSliderValue(getSliderValueFromPos(x, y));
  371. onCommit();
  372. gWindowp->setCursor(UI_CURSOR_ARROW);
  373. LL_DEBUGS("UserInput") << "hover handled by " << getName()
  374. << " (active)" << LL_ENDL;
  375. }
  376. else
  377. {
  378. gWindowp->setCursor(UI_CURSOR_ARROW);
  379. LL_DEBUGS("UserInput") << "hover handled by " << getName()
  380. << " (inactive)" << LL_ENDL;
  381. }
  382. return true;
  383. }
  384. //virtual
  385. bool LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask)
  386. {
  387. bool handled = false;
  388. if (gFocusMgr.getMouseCapture() == this)
  389. {
  390. gFocusMgr.setMouseCapture(NULL);
  391. if (mMouseUpCallback)
  392. {
  393. mMouseUpCallback(x, y, mCallbackUserData);
  394. }
  395. handled = true;
  396. make_ui_sound("UISndClickRelease");
  397. }
  398. else
  399. {
  400. handled = true;
  401. }
  402. return handled;
  403. }
  404. //virtual
  405. bool LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask)
  406. {
  407. // Only do sticky-focus on non-chrome widgets
  408. if (!getIsChrome())
  409. {
  410. setFocus(true);
  411. }
  412. if (mMouseDownCallback)
  413. {
  414. mMouseDownCallback(x, y, mCallbackUserData);
  415. }
  416. if (mask & MASK_CONTROL) // if CTRL is modifying
  417. {
  418. setCurSliderValue(mInitialValue);
  419. onCommit();
  420. }
  421. else
  422. {
  423. // Scroll through thumbs to see if we have a new one selected and
  424. // select that one
  425. for (rect_map_t::iterator it = mThumbRects.begin(),
  426. end = mThumbRects.end();
  427. it != end; ++it)
  428. {
  429. // Check if inside. If so, set current slider and continue.
  430. if (it->second.pointInRect(x, y))
  431. {
  432. mCurSlider = it->first;
  433. break;
  434. }
  435. }
  436. if (!mCurSlider.empty())
  437. {
  438. // Find the offset of the actual mouse location from the center of
  439. // the thumb.
  440. if (mThumbRects[mCurSlider].pointInRect(x, y))
  441. {
  442. mMouseOffset = mThumbRects[mCurSlider].mLeft +
  443. MULTI_THUMB_WIDTH / 2 - x;
  444. }
  445. else
  446. {
  447. mMouseOffset = 0;
  448. }
  449. // Start dragging the thumb. No handler needed for focus lost since
  450. // this class has no state that depends on it.
  451. gFocusMgr.setMouseCapture(this);
  452. mDragStartThumbRect = mThumbRects[mCurSlider];
  453. }
  454. }
  455. make_ui_sound("UISndClick");
  456. return true;
  457. }
  458. //virtual
  459. bool LLMultiSlider::handleKeyHere(KEY key, MASK mask)
  460. {
  461. switch (key)
  462. {
  463. case KEY_UP:
  464. if (mVertical)
  465. {
  466. setCurSliderValue(getCurSliderValue() + getIncrement());
  467. onCommit();
  468. }
  469. return true;
  470. case KEY_DOWN:
  471. if (mVertical)
  472. {
  473. setCurSliderValue(getCurSliderValue() - getIncrement());
  474. onCommit();
  475. }
  476. return true;
  477. case KEY_LEFT:
  478. if (!mVertical)
  479. {
  480. setCurSliderValue(getCurSliderValue() - getIncrement());
  481. onCommit();
  482. }
  483. return true;
  484. case KEY_RIGHT:
  485. if (!mVertical)
  486. {
  487. setCurSliderValue(getCurSliderValue() + getIncrement());
  488. onCommit();
  489. }
  490. return true;
  491. default:
  492. break;
  493. }
  494. return false;
  495. }
  496. //virtual
  497. void LLMultiSlider::draw()
  498. {
  499. rect_map_t::iterator it;
  500. rect_map_t::iterator begin = mThumbRects.begin();
  501. rect_map_t::iterator end = mThumbRects.end();
  502. rect_map_t::iterator cur_sldr_it;
  503. // Draw background and thumb.
  504. // Drawing solids requires texturing to be disabled
  505. gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
  506. LLRect rect(mDragStartThumbRect);
  507. F32 opacity = getEnabled() ? 1.f : 0.3f;
  508. // Track
  509. S32 height_offset = 0;
  510. S32 width_offset = 0;
  511. if (mVertical)
  512. {
  513. width_offset = (getRect().getWidth() - MULTI_TRACK_HEIGHT) / 2;
  514. }
  515. else
  516. {
  517. height_offset = (getRect().getHeight() - MULTI_TRACK_HEIGHT) / 2;
  518. }
  519. LLRect track_rect(width_offset, getRect().getHeight() - height_offset,
  520. getRect().getWidth() - width_offset, height_offset);
  521. if (mDrawTrack)
  522. {
  523. track_rect.stretch(-1);
  524. LLUIImage::sRoundedSquare->draw(track_rect,
  525. LLUI::sMultiSliderTrackColor %
  526. opacity);
  527. }
  528. // If we are supposed to use a drawn triangle simple GL call for the
  529. // triangle
  530. if (mUseTriangle)
  531. {
  532. for (it = begin; it != end; ++it)
  533. {
  534. gl_triangle_2d(it->second.mLeft - EXTRA_TRIANGLE_WIDTH,
  535. it->second.mTop + EXTRA_TRIANGLE_HEIGHT,
  536. it->second.mRight + EXTRA_TRIANGLE_WIDTH,
  537. it->second.mTop + EXTRA_TRIANGLE_HEIGHT,
  538. it->second.mLeft + it->second.getWidth() / 2,
  539. it->second.mBottom - EXTRA_TRIANGLE_HEIGHT,
  540. LLUI::sMultiSliderTriangleColor, true);
  541. }
  542. }
  543. else if (gFocusMgr.getMouseCapture() == this)
  544. {
  545. // Draw drag start
  546. LLUIImage::sRoundedSquare->drawSolid(mDragStartThumbRect,
  547. LLUI::sMultiSliderThumbCenterColor %
  548. 0.3f);
  549. // Draw the highlight
  550. if (hasFocus())
  551. {
  552. LLUIImage::sRoundedSquare->drawBorder(mThumbRects[mCurSlider],
  553. gFocusMgr.getFocusColor(),
  554. gFocusMgr.getFocusFlashWidth());
  555. }
  556. // Draw the thumbs
  557. cur_sldr_it = end;
  558. // Choose the color
  559. LLColor4 cur_thumb_col = LLUI::sMultiSliderThumbCenterColor;
  560. for (it = begin; it != end; ++it)
  561. {
  562. if (it->first == mCurSlider)
  563. {
  564. // Do not draw now, draw last
  565. cur_sldr_it = it;
  566. continue;
  567. }
  568. // The draw command
  569. LLUIImage::sRoundedSquare->drawSolid(it->second, cur_thumb_col);
  570. }
  571. // Draw current slider last
  572. if (cur_sldr_it != end)
  573. {
  574. LLUIImage::sRoundedSquare->drawSolid(cur_sldr_it->second,
  575. LLUI::sMultiSliderThumbCenterSelectedColor);
  576. }
  577. }
  578. else
  579. {
  580. // Draw highlight
  581. if (hasFocus())
  582. {
  583. LLUIImage::sRoundedSquare->drawBorder(mThumbRects[mCurSlider],
  584. gFocusMgr.getFocusColor(),
  585. gFocusMgr.getFocusFlashWidth());
  586. }
  587. // Draw thumbs
  588. cur_sldr_it = end;
  589. // Choose the color
  590. LLColor4 cur_thumb_col = LLUI::sMultiSliderThumbCenterColor % opacity;
  591. for (it = begin; it != end; ++it)
  592. {
  593. if (it->first == mCurSlider)
  594. {
  595. cur_sldr_it = it;
  596. continue;
  597. }
  598. LLUIImage::sRoundedSquare->drawSolid(it->second, cur_thumb_col);
  599. }
  600. // Draw current slider last
  601. if (cur_sldr_it != end)
  602. {
  603. LLUIImage::sRoundedSquare->drawSolid(cur_sldr_it->second,
  604. LLUI::sMultiSliderThumbCenterSelectedColor %
  605. opacity);
  606. }
  607. }
  608. LLUICtrl::draw();
  609. }
  610. ///////////////////////////////////////////////////////////////////////////////
  611. // LLMultiSliderCtrl class proper
  612. ///////////////////////////////////////////////////////////////////////////////
  613. LLMultiSliderCtrl::LLMultiSliderCtrl(const std::string& name,
  614. const LLRect& rect,
  615. const std::string& label,
  616. const LLFontGL* fontp,
  617. S32 label_width, S32 text_left,
  618. bool show_text, bool can_edit_text,
  619. void (*commit_callback)(LLUICtrl*, void*),
  620. void* callback_user_data,
  621. F32 initial_value, F32 min_value,
  622. F32 max_value, F32 increment,
  623. S32 max_sliders, F32 overlap_threshold,
  624. bool allow_overlap, bool loop_overlap,
  625. bool draw_track, bool use_triangle,
  626. bool vertical, const char* ctrl_name)
  627. : LLUICtrl(name, rect, true, commit_callback, callback_user_data),
  628. mFont(fontp),
  629. mShowText(show_text),
  630. mCanEditText(can_edit_text),
  631. mPrecision(3),
  632. mLabelBox(NULL),
  633. mLabelWidth(label_width),
  634. mEditor(NULL),
  635. mTextBox(NULL),
  636. mTextEnabledColor(LLUI::sColorsGroup->getColor("LabelTextColor")),
  637. mTextDisabledColor(LLUI::sColorsGroup->getColor("LabelDisabledColor")),
  638. mSliderMouseUpCallback(NULL),
  639. mSliderMouseDownCallback(NULL)
  640. {
  641. S32 top = getRect().getHeight();
  642. S32 bottom = 0;
  643. S32 left = 0;
  644. // Label
  645. if (!label.empty())
  646. {
  647. if (label_width == 0)
  648. {
  649. label_width = fontp->getWidth(label);
  650. }
  651. LLRect label_rect(left, top, label_width, bottom);
  652. mLabelBox = new LLTextBox("MultiSliderCtrl Label", label_rect, label,
  653. fontp);
  654. addChild(mLabelBox);
  655. }
  656. S32 slider_right = getRect().getWidth();
  657. if (show_text)
  658. {
  659. slider_right = text_left - MULTI_SLIDERCTRL_SPACING;
  660. }
  661. S32 slider_left = label_width ? label_width + MULTI_SLIDERCTRL_SPACING : 0;
  662. LLRect slider_rect(slider_left, top, slider_right, bottom);
  663. mMultiSlider = new LLMultiSlider(LL_MULTI_SLIDER_CTRL_TAG, slider_rect,
  664. LLMultiSliderCtrl::onSliderCommit, this,
  665. initial_value, min_value, max_value,
  666. increment, max_sliders, overlap_threshold,
  667. allow_overlap, loop_overlap, draw_track,
  668. use_triangle, vertical, ctrl_name);
  669. addChild(mMultiSlider);
  670. mCurValue = mMultiSlider->getCurSliderValue();
  671. if (show_text)
  672. {
  673. LLRect text_rect(text_left, top, getRect().getWidth(), bottom);
  674. if (can_edit_text)
  675. {
  676. mEditor = new LLLineEditor("MultiSliderCtrl Editor", text_rect,
  677. LLStringUtil::null, fontp,
  678. MAX_STRING_LENGTH,
  679. &LLMultiSliderCtrl::onEditorCommit,
  680. NULL, NULL, this,
  681. &LLLineEditor::prevalidateFloat);
  682. mEditor->setFollowsLeft();
  683. mEditor->setFollowsBottom();
  684. mEditor->setFocusReceivedCallback(&LLMultiSliderCtrl::onEditorGainFocus,
  685. this);
  686. mEditor->setIgnoreTab(true);
  687. #if 0 // Do not do this, as selecting the entire text is single clicking
  688. // in some cases and double clicking in others:
  689. mEditor->setSelectAllonFocusReceived(true);
  690. #endif
  691. addChild(mEditor);
  692. }
  693. else
  694. {
  695. mTextBox = new LLTextBox("MultiSliderCtrl Text", text_rect,
  696. LLStringUtil::null, fontp);
  697. mTextBox->setFollowsLeft();
  698. mTextBox->setFollowsBottom();
  699. addChild(mTextBox);
  700. }
  701. }
  702. updateText();
  703. }
  704. //virtual
  705. LLMultiSliderCtrl::~LLMultiSliderCtrl()
  706. {
  707. // Children all cleaned up by default view destructor.
  708. }
  709. //static
  710. void LLMultiSliderCtrl::onEditorGainFocus(LLFocusableElement* caller,
  711. void* userdata)
  712. {
  713. LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata;
  714. llassert(caller == self->mEditor);
  715. self->onFocusReceived();
  716. }
  717. //virtual
  718. void LLMultiSliderCtrl::setValue(const LLSD& value)
  719. {
  720. mMultiSlider->setValue(value);
  721. mCurValue = mMultiSlider->getCurSliderValue();
  722. updateText();
  723. }
  724. void LLMultiSliderCtrl::setSliderValue(const std::string& name, F32 v,
  725. bool from_event)
  726. {
  727. mMultiSlider->setSliderValue(name, v, from_event);
  728. mCurValue = mMultiSlider->getCurSliderValue();
  729. updateText();
  730. }
  731. void LLMultiSliderCtrl::setCurSlider(const std::string& name)
  732. {
  733. mMultiSlider->setCurSlider(name);
  734. mCurValue = mMultiSlider->getCurSliderValue();
  735. }
  736. void LLMultiSliderCtrl::setLabel(const std::string& label)
  737. {
  738. if (mLabelBox)
  739. {
  740. mLabelBox->setText(label);
  741. }
  742. }
  743. //virtual
  744. bool LLMultiSliderCtrl::setLabelArg(const std::string& key,
  745. const std::string& text)
  746. {
  747. bool res = false;
  748. if (mLabelBox)
  749. {
  750. res = mLabelBox->setTextArg(key, text);
  751. if (res && mLabelWidth == 0)
  752. {
  753. S32 label_width = mFont->getWidth(mLabelBox->getText());
  754. LLRect rect = mLabelBox->getRect();
  755. S32 prev_right = rect.mRight;
  756. rect.mRight = rect.mLeft + label_width;
  757. mLabelBox->setRect(rect);
  758. S32 delta = rect.mRight - prev_right;
  759. rect = mMultiSlider->getRect();
  760. S32 left = rect.mLeft + delta;
  761. left = llclamp(left, 0, rect.mRight - MULTI_SLIDERCTRL_SPACING);
  762. rect.mLeft = left;
  763. mMultiSlider->setRect(rect);
  764. }
  765. }
  766. return res;
  767. }
  768. const std::string& LLMultiSliderCtrl::addSlider()
  769. {
  770. const std::string& name = mMultiSlider->addSlider();
  771. // If it returns null, pass it on
  772. if (name == LLStringUtil::null)
  773. {
  774. return LLStringUtil::null;
  775. }
  776. // Otherwise, update stuff
  777. mCurValue = mMultiSlider->getCurSliderValue();
  778. updateText();
  779. return name;
  780. }
  781. const std::string& LLMultiSliderCtrl::addSlider(F32 val)
  782. {
  783. const std::string& name = mMultiSlider->addSlider(val);
  784. // If it returns empty, pass it on
  785. if (name.empty())
  786. {
  787. return LLStringUtil::null;
  788. }
  789. // Otherwise, update stuff
  790. mCurValue = mMultiSlider->getCurSliderValue();
  791. updateText();
  792. return name;
  793. }
  794. bool LLMultiSliderCtrl::addSlider(F32 val, const std::string& name)
  795. {
  796. bool res = mMultiSlider->addSlider(val, name);
  797. if (res)
  798. {
  799. mCurValue = mMultiSlider->getCurSliderValue();
  800. updateText();
  801. }
  802. return res;
  803. }
  804. void LLMultiSliderCtrl::deleteSlider(const std::string& name)
  805. {
  806. mMultiSlider->deleteSlider(name);
  807. mCurValue = mMultiSlider->getCurSliderValue();
  808. updateText();
  809. }
  810. //virtual
  811. void LLMultiSliderCtrl::clear()
  812. {
  813. setCurSliderValue(0.f);
  814. if (mEditor)
  815. {
  816. mEditor->setText("");
  817. }
  818. if (mTextBox)
  819. {
  820. mTextBox->setText("");
  821. }
  822. // Get rid of sliders
  823. mMultiSlider->clear();
  824. }
  825. bool LLMultiSliderCtrl::isMouseHeldDown()
  826. {
  827. return gFocusMgr.getMouseCapture() == mMultiSlider;
  828. }
  829. void LLMultiSliderCtrl::updateText()
  830. {
  831. if (mEditor || mTextBox)
  832. {
  833. LLLocale locale(LLLocale::USER_LOCALE);
  834. // Do not display very small negative values as -0.000
  835. F32 displayed_value = floorf(getCurSliderValue() *
  836. powf(10.f, mPrecision) + 0.5f) /
  837. powf(10.f, mPrecision);
  838. std::string format = llformat("%%.%df", mPrecision);
  839. std::string text = llformat(format.c_str(), displayed_value);
  840. if (mEditor)
  841. {
  842. mEditor->setText(text);
  843. }
  844. else
  845. {
  846. mTextBox->setText(text);
  847. }
  848. }
  849. }
  850. //static
  851. void LLMultiSliderCtrl::onEditorCommit(LLUICtrl* caller, void* userdata)
  852. {
  853. LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata;
  854. llassert(caller == self->mEditor);
  855. F32 val = self->mCurValue;
  856. F32 saved_val = self->mCurValue;
  857. bool success = false;
  858. std::string text = self->mEditor->getText();
  859. if (LLLineEditor::postvalidateFloat(text))
  860. {
  861. LLLocale locale(LLLocale::USER_LOCALE);
  862. val = (F32) atof(text.c_str());
  863. if (self->mMultiSlider->getMinValue() <= val &&
  864. val <= self->mMultiSlider->getMaxValue())
  865. {
  866. if (self->mValidateCallback)
  867. {
  868. // Set the value temporarily so that the callback can retrieve
  869. // it:
  870. self->setCurSliderValue(val);
  871. if (self->mValidateCallback(self, self->mCallbackUserData))
  872. {
  873. success = true;
  874. }
  875. }
  876. else
  877. {
  878. self->setCurSliderValue(val);
  879. success = true;
  880. }
  881. }
  882. }
  883. if (success)
  884. {
  885. self->onCommit();
  886. }
  887. else
  888. {
  889. if (self->getCurSliderValue() != saved_val)
  890. {
  891. self->setCurSliderValue(saved_val);
  892. }
  893. self->reportInvalidData();
  894. }
  895. self->updateText();
  896. }
  897. //static
  898. void LLMultiSliderCtrl::onSliderCommit(LLUICtrl* caller, void* userdata)
  899. {
  900. LLMultiSliderCtrl* self = (LLMultiSliderCtrl*)userdata;
  901. F32 saved_val = self->mCurValue;
  902. F32 new_val = self->mMultiSlider->getCurSliderValue();
  903. bool success = false;
  904. if (self->mValidateCallback)
  905. {
  906. // Set the value temporarily so that the callback can retrieve it:
  907. self->mCurValue = new_val;
  908. if (self->mValidateCallback(self, self->mCallbackUserData))
  909. {
  910. success = true;
  911. }
  912. }
  913. else
  914. {
  915. self->mCurValue = new_val;
  916. success = true;
  917. }
  918. if (success)
  919. {
  920. self->onCommit();
  921. }
  922. else
  923. {
  924. if (self->mCurValue != saved_val)
  925. {
  926. self->setCurSliderValue(saved_val);
  927. }
  928. self->reportInvalidData();
  929. }
  930. self->updateText();
  931. }
  932. //virtual
  933. void LLMultiSliderCtrl::setEnabled(bool b)
  934. {
  935. LLUICtrl::setEnabled(b);
  936. if (mLabelBox)
  937. {
  938. mLabelBox->setColor(b ? mTextEnabledColor : mTextDisabledColor);
  939. }
  940. mMultiSlider->setEnabled(b);
  941. if (mEditor)
  942. {
  943. mEditor->setEnabled(b);
  944. }
  945. if (mTextBox)
  946. {
  947. mTextBox->setColor(b ? mTextEnabledColor : mTextDisabledColor);
  948. }
  949. }
  950. //virtual
  951. void LLMultiSliderCtrl::setTentative(bool b)
  952. {
  953. if (mEditor)
  954. {
  955. mEditor->setTentative(b);
  956. }
  957. LLUICtrl::setTentative(b);
  958. }
  959. //virtual
  960. void LLMultiSliderCtrl::onCommit()
  961. {
  962. setTentative(false);
  963. if (mEditor)
  964. {
  965. mEditor->setTentative(false);
  966. }
  967. LLUICtrl::onCommit();
  968. }
  969. void LLMultiSliderCtrl::setPrecision(S32 precision)
  970. {
  971. if (precision < 0 || precision > 10)
  972. {
  973. S32 p = precision;
  974. precision = llclamp(precision, 0, 10);
  975. llwarns << "Precision out of range: " << p << " - clamped to: "
  976. << precision << llendl;
  977. }
  978. mPrecision = precision;
  979. updateText();
  980. }
  981. void LLMultiSliderCtrl::setSliderMouseDownCallback(void (*cb)(S32, S32,
  982. void* userdata))
  983. {
  984. mSliderMouseDownCallback = cb;
  985. mMultiSlider->setMouseDownCallback(LLMultiSliderCtrl::onSliderMouseDown);
  986. }
  987. //static
  988. void LLMultiSliderCtrl::onSliderMouseDown(S32 x, S32 y, void* userdata)
  989. {
  990. LLMultiSliderCtrl* self = (LLMultiSliderCtrl*)userdata;
  991. if (self->mSliderMouseDownCallback)
  992. {
  993. self->mSliderMouseDownCallback(x, y, self->mCallbackUserData);
  994. }
  995. }
  996. void LLMultiSliderCtrl::setSliderMouseUpCallback(void (*cb)(S32, S32,
  997. void* userdata))
  998. {
  999. mSliderMouseUpCallback = cb;
  1000. mMultiSlider->setMouseUpCallback(LLMultiSliderCtrl::onSliderMouseUp);
  1001. }
  1002. //static
  1003. void LLMultiSliderCtrl::onSliderMouseUp(S32 x, S32 y, void* userdata)
  1004. {
  1005. LLMultiSliderCtrl* self = (LLMultiSliderCtrl*)userdata;
  1006. if (self->mSliderMouseUpCallback)
  1007. {
  1008. self->mSliderMouseUpCallback(x, y, self->mCallbackUserData);
  1009. }
  1010. }
  1011. //virtual
  1012. void LLMultiSliderCtrl::onTabInto()
  1013. {
  1014. if (mEditor)
  1015. {
  1016. mEditor->onTabInto();
  1017. }
  1018. }
  1019. void LLMultiSliderCtrl::reportInvalidData()
  1020. {
  1021. make_ui_sound("UISndBadKeystroke");
  1022. }
  1023. //virtual
  1024. const std::string& LLMultiSliderCtrl::getControlName() const
  1025. {
  1026. return mMultiSlider->getControlName();
  1027. }
  1028. //virtual
  1029. void LLMultiSliderCtrl::setControlName(const char* ctr_name, LLView* context)
  1030. {
  1031. mMultiSlider->setControlName(ctr_name, context);
  1032. }
  1033. //virtual
  1034. const std::string& LLMultiSliderCtrl::getTag() const
  1035. {
  1036. return LL_MULTI_SLIDER_CTRL_TAG;
  1037. }
  1038. //virtual
  1039. LLXMLNodePtr LLMultiSliderCtrl::getXML(bool save_children) const
  1040. {
  1041. LLXMLNodePtr node = LLUICtrl::getXML();
  1042. node->setName(LL_MULTI_SLIDER_CTRL_TAG);
  1043. node->createChild("show_text", true)->setBoolValue(mShowText);
  1044. node->createChild("can_edit_text", true)->setBoolValue(mCanEditText);
  1045. node->createChild("decimal_digits", true)->setIntValue(mPrecision);
  1046. if (mLabelBox)
  1047. {
  1048. node->createChild("label", true)->setStringValue(mLabelBox->getText());
  1049. }
  1050. // TomY TODO: Do we really want to export the transient state of the
  1051. // slider ?
  1052. node->createChild("value", true)->setFloatValue(mCurValue);
  1053. node->createChild("initial_val",
  1054. true)->setFloatValue(mMultiSlider->getInitialValue());
  1055. node->createChild("min_val",
  1056. true)->setFloatValue(mMultiSlider->getMinValue());
  1057. node->createChild("max_val",
  1058. true)->setFloatValue(mMultiSlider->getMaxValue());
  1059. node->createChild("increment",
  1060. true)->setFloatValue(mMultiSlider->getIncrement());
  1061. node->createChild("max_sliders",
  1062. true)->setFloatValue(mMultiSlider->mMaxNumSliders);
  1063. if (mMultiSlider->mOverlapThreshold)
  1064. {
  1065. F32 actual = mMultiSlider->mOverlapThreshold +
  1066. mMultiSlider->getIncrement();
  1067. node->createChild("overlap_threshold", true)->setFloatValue(actual);
  1068. }
  1069. if (mMultiSlider->mAllowOverlap)
  1070. {
  1071. node->createChild("allow_overlap", true)->setBoolValue(true);
  1072. }
  1073. if (mMultiSlider->mLoopOverlap)
  1074. {
  1075. node->createChild("loop_overlap", true)->setBoolValue(true);
  1076. }
  1077. if (!mMultiSlider->mDrawTrack)
  1078. {
  1079. node->createChild("draw_track", true)->setBoolValue(false);
  1080. }
  1081. if (mMultiSlider->mUseTriangle)
  1082. {
  1083. node->createChild("use_triangle", true)->setBoolValue(true);
  1084. }
  1085. if (mMultiSlider->mVertical)
  1086. {
  1087. node->createChild("orientation", true)->setStringValue("vertical");
  1088. }
  1089. addColorXML(node, mTextEnabledColor, "text_enabled_color",
  1090. "LabelTextColor");
  1091. addColorXML(node, mTextDisabledColor, "text_disabled_color",
  1092. "LabelDisabledColor");
  1093. return node;
  1094. }
  1095. //virtual
  1096. LLView* LLMultiSliderCtrl::fromXML(LLXMLNodePtr node, LLView* parent,
  1097. LLUICtrlFactory* factory)
  1098. {
  1099. std::string name = LL_MULTI_SLIDER_CTRL_TAG;
  1100. node->getAttributeString("name", name);
  1101. std::string label;
  1102. node->getAttributeString("label", label);
  1103. LLRect rect;
  1104. createRect(node, rect, parent, LLRect());
  1105. LLFontGL* fontp = LLView::selectFont(node);
  1106. // *HACK: font might not be specified.
  1107. if (!fontp)
  1108. {
  1109. fontp = LLFontGL::getFontSansSerifSmall();
  1110. }
  1111. S32 label_width = 0;
  1112. node->getAttributeS32("label_width", label_width);
  1113. bool show_text = true;
  1114. node->getAttributeBool("show_text", show_text);
  1115. bool can_edit_text = false;
  1116. node->getAttributeBool("can_edit_text", can_edit_text);
  1117. F32 overlap_threshold = 0.f;
  1118. node->getAttributeF32("overlap_threshold", overlap_threshold);
  1119. bool allow_overlap = false;
  1120. node->getAttributeBool("allow_overlap", allow_overlap);
  1121. bool loop_overlap = false;
  1122. node->getAttributeBool("loop_overlap", loop_overlap);
  1123. bool draw_track = true;
  1124. node->getAttributeBool("draw_track", draw_track);
  1125. bool use_triangle = false;
  1126. node->getAttributeBool("use_triangle", use_triangle);
  1127. F32 initial_value = 0.f;
  1128. node->getAttributeF32("initial_val", initial_value);
  1129. F32 min_value = 0.f;
  1130. node->getAttributeF32("min_val", min_value);
  1131. F32 max_value = 1.f;
  1132. node->getAttributeF32("max_val", max_value);
  1133. F32 increment = 0.1f;
  1134. node->getAttributeF32("increment", increment);
  1135. U32 precision = 3;
  1136. node->getAttributeU32("decimal_digits", precision);
  1137. S32 max_sliders = 1;
  1138. node->getAttributeS32("max_sliders", max_sliders);
  1139. std::string orientation;
  1140. node->getAttributeString("orientation", orientation);
  1141. S32 text_left = 0;
  1142. if (show_text)
  1143. {
  1144. // Calculate the size of the text box (log max_value is number of
  1145. // digits - 1 so plus 1)
  1146. if (max_value)
  1147. {
  1148. text_left = fontp->getWidth("0") *
  1149. (static_cast<S32>(log10f(max_value)) + precision + 1);
  1150. }
  1151. if (increment < 1.f)
  1152. {
  1153. // (mostly) take account of decimal point in value
  1154. text_left += fontp->getWidth(".");
  1155. }
  1156. if (min_value < 0.f || max_value < 0.f)
  1157. {
  1158. // (mostly) take account of minus sign
  1159. text_left += fontp->getWidth("-");
  1160. }
  1161. // Padding to make things look nicer
  1162. text_left += 8;
  1163. }
  1164. LLUICtrlCallback callback = NULL;
  1165. if (label.empty())
  1166. {
  1167. label.assign(node->getTextContents());
  1168. }
  1169. LLMultiSliderCtrl* slider =
  1170. new LLMultiSliderCtrl(name, rect, label, fontp, label_width,
  1171. rect.getWidth() - text_left, show_text,
  1172. can_edit_text, callback, NULL, initial_value,
  1173. min_value, max_value, increment, max_sliders,
  1174. overlap_threshold, allow_overlap, loop_overlap,
  1175. draw_track, use_triangle,
  1176. orientation == "vertical");
  1177. slider->setPrecision(precision);
  1178. slider->initFromXML(node, parent);
  1179. slider->updateText();
  1180. return slider;
  1181. }