llcombobox.cpp 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387
  1. /**
  2. * @file llcombobox.cpp
  3. * @brief LLComboBox 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. // A control that displays the name of the chosen item, which when
  33. // clicked shows a scrolling box of options.
  34. #include "linden_common.h"
  35. #include "llcombobox.h"
  36. #include "llbutton.h"
  37. #include "llcontrol.h"
  38. #include "llfloater.h"
  39. #include "llkeyboard.h"
  40. #include "lllineeditor.h"
  41. #include "llscrollbar.h"
  42. #include "llscrolllistctrl.h"
  43. #include "llstring.h"
  44. #include "llwindow.h"
  45. #include "llvector2.h"
  46. // Globals
  47. constexpr S32 MAX_COMBO_WIDTH = 500;
  48. static const std::string LL_COMBO_BOX_TAG = "combo_box";
  49. static LLRegisterWidget<LLComboBox> r03(LL_COMBO_BOX_TAG);
  50. LLComboBox::LLComboBox(const std::string& name,
  51. const LLRect& rect,
  52. const std::string& label,
  53. void (*commit_callback)(LLUICtrl*, void*),
  54. void* callback_userdata)
  55. : LLUICtrl(name, rect, true, commit_callback, callback_userdata,
  56. FOLLOWS_LEFT | FOLLOWS_TOP),
  57. mTextEntry(NULL),
  58. mArrowImage(NULL),
  59. mAllowTextEntry(false),
  60. mMaxChars(20),
  61. mTextEntryTentative(true),
  62. mListPosition(BELOW),
  63. mPrearrangeCallback(NULL),
  64. mTextEntryCallback(NULL),
  65. mSuppressTentative(false),
  66. mLabel(label)
  67. {
  68. // Text label button
  69. mButton = new LLButton(mLabel, LLRect(), NULL, NULL, this);
  70. mButton->setImageUnselected("square_btn_32x128.tga");
  71. mButton->setImageSelected("square_btn_selected_32x128.tga");
  72. mButton->setImageDisabled("square_btn_32x128.tga");
  73. mButton->setImageDisabledSelected("square_btn_selected_32x128.tga");
  74. mButton->setScaleImage(true);
  75. mButton->setMouseDownCallback(onButtonDown);
  76. mButton->setFont(LLFontGL::getFontSansSerifSmall());
  77. mButton->setFollows(FOLLOWS_LEFT | FOLLOWS_BOTTOM | FOLLOWS_RIGHT);
  78. mButton->setHAlign(LLFontGL::LEFT);
  79. mButton->setRightHPad(2);
  80. addChild(mButton);
  81. // Disallow multiple selection
  82. mList = new LLScrollListCtrl("ComboBox", LLRect(),
  83. &LLComboBox::onItemSelected, this, false);
  84. mList->setVisible(false);
  85. mList->setBgWriteableColor(LLColor4(1, 1, 1, 1));
  86. mList->setCommitOnKeyboardMovement(false);
  87. addChild(mList);
  88. mArrowImage = LLUI::getUIImage("combobox_arrow.tga");
  89. mButton->setImageOverlay(mArrowImage, LLFontGL::RIGHT);
  90. updateLayout();
  91. }
  92. LLComboBox::~LLComboBox()
  93. {
  94. // Children automatically deleted, including mMenu, mButton
  95. }
  96. //virtual
  97. const std::string& LLComboBox::getTag() const
  98. {
  99. return LL_COMBO_BOX_TAG;
  100. }
  101. //virtual
  102. LLXMLNodePtr LLComboBox::getXML(bool save_children) const
  103. {
  104. LLXMLNodePtr node = LLUICtrl::getXML();
  105. node->setName(LL_COMBO_BOX_TAG);
  106. // Attributes
  107. node->createChild("allow_text_entry", true)->setBoolValue(mAllowTextEntry);
  108. node->createChild("max_chars", true)->setIntValue(mMaxChars);
  109. // Contents
  110. std::vector<LLScrollListItem*> data_list = mList->getAllData();
  111. std::vector<LLScrollListItem*>::iterator data_itor;
  112. for (data_itor = data_list.begin(); data_itor != data_list.end();
  113. ++data_itor)
  114. {
  115. LLScrollListItem* item = *data_itor;
  116. LLScrollListCell* cell = item->getColumn(0);
  117. if (cell)
  118. {
  119. LLXMLNodePtr item_node = node->createChild("combo_item", false);
  120. LLSD value = item->getValue();
  121. item_node->createChild("value",
  122. true)->setStringValue(value.asString());
  123. item_node->createChild("enabled",
  124. true)->setBoolValue(item->getEnabled());
  125. item_node->setStringValue(cell->getValue().asString());
  126. }
  127. }
  128. return node;
  129. }
  130. //static
  131. LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView* parent,
  132. LLUICtrlFactory* factory)
  133. {
  134. std::string name = LL_COMBO_BOX_TAG;
  135. node->getAttributeString("name", name);
  136. std::string label;
  137. node->getAttributeString("label", label);
  138. LLRect rect;
  139. createRect(node, rect, parent, LLRect());
  140. bool allow_text_entry = false;
  141. node->getAttributeBool("allow_text_entry", allow_text_entry);
  142. S32 max_chars = 20;
  143. node->getAttributeS32("max_chars", max_chars);
  144. LLUICtrlCallback callback = NULL;
  145. LLComboBox* combo_box = new LLComboBox(name, rect, label, callback, NULL);
  146. combo_box->setAllowTextEntry(allow_text_entry, max_chars);
  147. combo_box->initFromXML(node, parent);
  148. const std::string& contents = node->getValue();
  149. if (contents.find_first_not_of(" \n\t") != contents.npos)
  150. {
  151. llerrs << "Deprecated combo box item format used ! Please convert to <combo_item> tags !"
  152. << llendl;
  153. }
  154. else
  155. {
  156. LLXMLNodePtr child;
  157. for (child = node->getFirstChild(); child.notNull();
  158. child = child->getNextSibling())
  159. {
  160. if (child->hasName("combo_item"))
  161. {
  162. std::string label = child->getTextContents();
  163. std::string value = label;
  164. child->getAttributeString("value", value);
  165. LLScrollListItem* item = combo_box->add(label, LLSD(value));
  166. if (item && child->hasAttribute("tool_tip"))
  167. {
  168. std::string tool_tip = label;
  169. child->getAttributeString("tool_tip", tool_tip);
  170. item->setToolTip(tool_tip);
  171. }
  172. }
  173. }
  174. }
  175. // If providing user text entry or descriptive label don't select an item
  176. // under the hood
  177. if (!combo_box->acceptsTextInput() && combo_box->mLabel.empty())
  178. {
  179. combo_box->selectFirstItem();
  180. }
  181. return combo_box;
  182. }
  183. void LLComboBox::setEnabled(bool enabled)
  184. {
  185. LLView::setEnabled(enabled);
  186. mButton->setEnabled(enabled);
  187. }
  188. void LLComboBox::clear()
  189. {
  190. if (mTextEntry)
  191. {
  192. mTextEntry->setText(LLStringUtil::null);
  193. }
  194. mButton->setLabelSelected(LLStringUtil::null);
  195. mButton->setLabelUnselected(LLStringUtil::null);
  196. mButton->setDisabledLabel(LLStringUtil::null);
  197. mButton->setDisabledSelectedLabel(LLStringUtil::null);
  198. mList->deselectAllItems();
  199. }
  200. void LLComboBox::onCommit()
  201. {
  202. if (mAllowTextEntry && getCurrentIndex() != -1)
  203. {
  204. // We have selected an existing item, blitz the manual text entry with
  205. // the properly capitalized item
  206. mTextEntry->setValue(getSimple());
  207. mTextEntry->setTentative(false);
  208. }
  209. setControlValue(getValue());
  210. LLUICtrl::onCommit();
  211. }
  212. //virtual
  213. bool LLComboBox::isDirty() const
  214. {
  215. return mList && mList->isDirty();
  216. }
  217. bool LLComboBox::isTextDirty() const
  218. {
  219. return mTextEntry && mTextEntry->isDirty();
  220. }
  221. // Clears the dirty state
  222. //virtual
  223. void LLComboBox::resetDirty()
  224. {
  225. if (mList)
  226. {
  227. mList->resetDirty();
  228. }
  229. }
  230. void LLComboBox::resetTextDirty()
  231. {
  232. if (mTextEntry)
  233. {
  234. mTextEntry->resetDirty();
  235. }
  236. }
  237. bool LLComboBox::itemExists(const std::string& name)
  238. {
  239. return mList->selectItemByLabel(name);
  240. }
  241. // Adds item "name" to menu
  242. LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos,
  243. bool enabled)
  244. {
  245. LLScrollListItem* item = mList->addSimpleElement(name, pos);
  246. item->setEnabled(enabled);
  247. if (!mAllowTextEntry && mLabel.empty())
  248. {
  249. selectFirstItem();
  250. }
  251. return item;
  252. }
  253. // Adds item "name" with a unique id to menu
  254. LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id,
  255. EAddPosition pos, bool enabled)
  256. {
  257. LLScrollListItem* item = mList->addSimpleElement(name, pos, id);
  258. item->setEnabled(enabled);
  259. if (!mAllowTextEntry && mLabel.empty())
  260. {
  261. selectFirstItem();
  262. }
  263. return item;
  264. }
  265. // Adds item "name" with attached userdata
  266. LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata,
  267. EAddPosition pos, bool enabled)
  268. {
  269. LLScrollListItem* item = mList->addSimpleElement(name, pos);
  270. item->setEnabled(enabled);
  271. item->setUserdata(userdata);
  272. if (!mAllowTextEntry && mLabel.empty())
  273. {
  274. selectFirstItem();
  275. }
  276. return item;
  277. }
  278. // Adds item "name" with attached generic data
  279. LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value,
  280. EAddPosition pos, bool enabled)
  281. {
  282. LLScrollListItem* item = mList->addSimpleElement(name, pos, value);
  283. item->setEnabled(enabled);
  284. if (!mAllowTextEntry && mLabel.empty())
  285. {
  286. selectFirstItem();
  287. }
  288. return item;
  289. }
  290. LLScrollListItem* LLComboBox::addSeparator(EAddPosition pos)
  291. {
  292. return mList->addSeparator(pos);
  293. }
  294. void LLComboBox::sortByName(bool ascending)
  295. {
  296. mList->sortOnce(0, ascending);
  297. }
  298. // Chooses an item with a given name in the menu. Returns true if the item was
  299. // found.
  300. bool LLComboBox::setSimple(const std::string& name)
  301. {
  302. bool found = mList->selectItemByLabel(name, false);
  303. if (found)
  304. {
  305. setLabel(name);
  306. }
  307. return found;
  308. }
  309. //virtual
  310. void LLComboBox::setValue(const LLSD& value)
  311. {
  312. if (mList->selectByValue(value) && mList->getFirstSelected())
  313. {
  314. setLabel(mList->getSelectedItemLabel());
  315. }
  316. }
  317. const std::string LLComboBox::getSimple() const
  318. {
  319. const std::string res = mList->getSelectedItemLabel();
  320. if (res.empty() && mAllowTextEntry)
  321. {
  322. return mTextEntry->getText();
  323. }
  324. else
  325. {
  326. return res;
  327. }
  328. }
  329. const std::string LLComboBox::getSelectedItemLabel(S32 column) const
  330. {
  331. return mList->getSelectedItemLabel(column);
  332. }
  333. //virtual
  334. LLSD LLComboBox::getValue() const
  335. {
  336. LLScrollListItem* item = mList->getFirstSelected();
  337. if (item)
  338. {
  339. return item->getValue();
  340. }
  341. else if (mAllowTextEntry)
  342. {
  343. return mTextEntry->getValue();
  344. }
  345. else
  346. {
  347. return LLSD();
  348. }
  349. }
  350. void LLComboBox::setLabel(const std::string& name)
  351. {
  352. if (mTextEntry)
  353. {
  354. mTextEntry->setText(name);
  355. if (mList->selectItemByLabel(name, false))
  356. {
  357. mTextEntry->setTentative(false);
  358. }
  359. else
  360. {
  361. if (!mSuppressTentative)
  362. {
  363. mTextEntry->setTentative(mTextEntryTentative);
  364. }
  365. }
  366. }
  367. if (!mAllowTextEntry)
  368. {
  369. mButton->setLabelUnselected(name);
  370. mButton->setLabelSelected(name);
  371. mButton->setDisabledLabel(name);
  372. mButton->setDisabledSelectedLabel(name);
  373. }
  374. }
  375. bool LLComboBox::remove(const std::string& name)
  376. {
  377. bool found = mList->selectItemByLabel(name);
  378. if (found)
  379. {
  380. LLScrollListItem* item = mList->getFirstSelected();
  381. if (item)
  382. {
  383. mList->deleteSingleItem(mList->getItemIndex(item));
  384. }
  385. }
  386. return found;
  387. }
  388. bool LLComboBox::remove(S32 index)
  389. {
  390. if (index < mList->getItemCount())
  391. {
  392. mList->deleteSingleItem(index);
  393. return true;
  394. }
  395. return false;
  396. }
  397. // Keyboard focus lost.
  398. void LLComboBox::onFocusLost()
  399. {
  400. hideList();
  401. // if valid selection
  402. if (mAllowTextEntry && getCurrentIndex() != -1)
  403. {
  404. mTextEntry->selectAll();
  405. }
  406. LLUICtrl::onFocusLost();
  407. }
  408. void LLComboBox::onLostTop()
  409. {
  410. hideList();
  411. }
  412. void LLComboBox::setButtonVisible(bool visible)
  413. {
  414. mButton->setVisible(visible);
  415. if (mTextEntry)
  416. {
  417. LLRect text_entry_rect(0, getRect().getHeight(),
  418. getRect().getWidth(), 0);
  419. if (visible)
  420. {
  421. text_entry_rect.mRight -= llmax(8, mArrowImage->getWidth()) +
  422. 2 * LLUI::sDropShadowButton;
  423. }
  424. mTextEntry->reshape(text_entry_rect.getWidth(),
  425. text_entry_rect.getHeight());
  426. }
  427. }
  428. void LLComboBox::draw()
  429. {
  430. mButton->setEnabled(getEnabled() /*&& !mList->isEmpty()*/);
  431. // Draw children normally
  432. LLUICtrl::draw();
  433. }
  434. bool LLComboBox::setCurrentByIndex(S32 index)
  435. {
  436. bool found = mList->selectNthItem(index);
  437. if (found)
  438. {
  439. setLabel(mList->getSelectedItemLabel());
  440. }
  441. return found;
  442. }
  443. S32 LLComboBox::getCurrentIndex() const
  444. {
  445. LLScrollListItem* item = mList->getFirstSelected();
  446. return item ? mList->getItemIndex(item) : -1;
  447. }
  448. void LLComboBox::updateLayout()
  449. {
  450. LLRect rect = getLocalRect();
  451. if (mAllowTextEntry)
  452. {
  453. mButton->setRect(LLRect(getRect().getWidth() -
  454. llmax(8, mArrowImage->getWidth()) -
  455. 2 * LLUI::sDropShadowButton,
  456. rect.mTop, rect.mRight, rect.mBottom));
  457. mButton->setTabStop(false);
  458. if (!mTextEntry)
  459. {
  460. LLRect text_entry_rect(0, getRect().getHeight(),
  461. getRect().getWidth(), 0);
  462. text_entry_rect.mRight -= llmax(8, mArrowImage->getWidth()) +
  463. 2 * LLUI::sDropShadowButton;
  464. // clear label on button
  465. std::string cur_label = mButton->getLabelSelected();
  466. mTextEntry = new LLLineEditor("combo_text_entry", text_entry_rect,
  467. LLStringUtil::null,
  468. LLFontGL::getFontSansSerifSmall(),
  469. mMaxChars, onTextCommit, onTextEntry,
  470. NULL, this);
  471. mTextEntry->setSelectAllonFocusReceived(true);
  472. mTextEntry->setHandleEditKeysDirectly(true);
  473. mTextEntry->setCommitOnFocusLost(false);
  474. mTextEntry->setText(cur_label);
  475. mTextEntry->setIgnoreTab(true);
  476. mTextEntry->setFollowsAll();
  477. addChild(mTextEntry);
  478. }
  479. else
  480. {
  481. mTextEntry->setVisible(true);
  482. mTextEntry->setMaxTextLength(mMaxChars);
  483. }
  484. // Clear label on button
  485. setLabel(LLStringUtil::null);
  486. mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT);
  487. }
  488. else if (!mAllowTextEntry)
  489. {
  490. mButton->setRect(rect);
  491. mButton->setTabStop(true);
  492. if (mTextEntry)
  493. {
  494. mTextEntry->setVisible(false);
  495. }
  496. mButton->setFollowsAll();
  497. }
  498. }
  499. void* LLComboBox::getCurrentUserdata()
  500. {
  501. LLScrollListItem* item = mList->getFirstSelected();
  502. return item ? item->getUserdata() : NULL;
  503. }
  504. void LLComboBox::showList()
  505. {
  506. // Make sure we do not go off top of screen.
  507. LLCoordWindow window_size;
  508. gWindowp->getSize(&window_size);
  509. // *HACK: we should not have to know about scale here
  510. mList->fitContents(192,
  511. llfloor((F32)window_size.mY /
  512. LLUI::sGLScaleFactor.mV[VY]) - 50);
  513. // Make sure that we can see the whole list
  514. LLRect root_view_local;
  515. LLView* root_view = getRootView();
  516. root_view->localRectToOtherView(root_view->getLocalRect(),
  517. &root_view_local, this);
  518. LLRect rect = mList->getRect();
  519. S32 min_width = getRect().getWidth();
  520. S32 max_width = llmax(min_width, MAX_COMBO_WIDTH);
  521. // Make sure we have up to date content width metrics
  522. S32 list_width = llclamp(mList->calcMaxContentWidth(), min_width,
  523. max_width);
  524. if (mListPosition == BELOW)
  525. {
  526. if (rect.getHeight() <= -root_view_local.mBottom)
  527. {
  528. // Move rect so it hangs off the bottom of this view
  529. rect.setLeftTopAndSize(0, 0, list_width, rect.getHeight());
  530. }
  531. else
  532. {
  533. // Stack on top or bottom, depending on which has more room
  534. if (-root_view_local.mBottom >
  535. root_view_local.mTop - getRect().getHeight())
  536. {
  537. // Move rect so it hangs off the bottom of this view
  538. rect.setLeftTopAndSize(0, 0, list_width,
  539. llmin(-root_view_local.mBottom,
  540. rect.getHeight()));
  541. }
  542. else
  543. {
  544. // Move rect so it stacks on top of this view (clipped to size
  545. // of screen)
  546. rect.setOriginAndSize(0, getRect().getHeight(), list_width,
  547. llmin(root_view_local.mTop -
  548. getRect().getHeight(),
  549. rect.getHeight()));
  550. }
  551. }
  552. }
  553. else // ABOVE
  554. {
  555. if (rect.getHeight() <= root_view_local.mTop - getRect().getHeight())
  556. {
  557. // Move rect so it stacks on top of this view (clipped to size of
  558. // screen)
  559. rect.setOriginAndSize(0, getRect().getHeight(), list_width,
  560. llmin(root_view_local.mTop -
  561. getRect().getHeight(),
  562. rect.getHeight()));
  563. }
  564. else
  565. {
  566. // Stack on top or bottom, depending on which has more room
  567. if (-root_view_local.mBottom >
  568. root_view_local.mTop - getRect().getHeight())
  569. {
  570. // Move rect so it hangs off the bottom of this view
  571. rect.setLeftTopAndSize(0, 0, list_width,
  572. llmin(-root_view_local.mBottom,
  573. rect.getHeight()));
  574. }
  575. else
  576. {
  577. // Move rect so it stacks on top of this view (clipped to size
  578. // of screen)
  579. rect.setOriginAndSize(0, getRect().getHeight(), list_width,
  580. llmin(root_view_local.mTop -
  581. getRect().getHeight(),
  582. rect.getHeight()));
  583. }
  584. }
  585. }
  586. mList->setOrigin(rect.mLeft, rect.mBottom);
  587. mList->reshape(rect.getWidth(), rect.getHeight());
  588. mList->translateIntoRect(root_view_local, false);
  589. // Make sure we did not go off bottom of screen
  590. S32 x, y;
  591. mList->localPointToScreen(0, 0, &x, &y);
  592. if (y < 0)
  593. {
  594. mList->translate(0, -y);
  595. }
  596. // NB: this call will trigger the focuslost callback which will hide the
  597. // list, so do it first before finally showing the list
  598. mList->setFocus(true);
  599. // Register ourselves as a "top" control effectively putting us into a
  600. // special draw layer and not affecting the bounding rectangle calculation
  601. gFocusMgr.setTopCtrl(this);
  602. // Show the list and push the button down
  603. mButton->setToggleState(true);
  604. mList->setVisible(true);
  605. setUseBoundingRect(true);
  606. }
  607. void LLComboBox::hideList()
  608. {
  609. #if 0 // Do not do this ! mTextEntry->getText() can be truncated, in which
  610. // case selectItemByLabel fails and this only resets the selection :/
  611. // *HACK: store the original value explicitly somewhere, not just in label
  612. std::string orig_selection = mAllowTextEntry ? mTextEntry->getText()
  613. : mButton->getLabelSelected();
  614. // Assert selection in list
  615. mList->selectItemByLabel(orig_selection, false);
  616. #endif
  617. mButton->setToggleState(false);
  618. mList->setVisible(false);
  619. mList->highlightNthItem(-1);
  620. setUseBoundingRect(false);
  621. if (gFocusMgr.getTopCtrl() == this)
  622. {
  623. gFocusMgr.setTopCtrl(NULL);
  624. }
  625. }
  626. //static
  627. void LLComboBox::onButtonDown(void* userdata)
  628. {
  629. LLComboBox* self = (LLComboBox*)userdata;
  630. if (!self) return;
  631. if (!self->mList->getVisible())
  632. {
  633. LLScrollListItem* last_sel_item = self->mList->getLastSelectedItem();
  634. if (last_sel_item)
  635. {
  636. // highlight the original selection before potentially selecting a
  637. // new item
  638. self->mList->highlightNthItem(self->mList->getItemIndex(last_sel_item));
  639. }
  640. if (self->mPrearrangeCallback)
  641. {
  642. self->mPrearrangeCallback(self, self->mCallbackUserData);
  643. }
  644. if (self->mList->getItemCount() != 0)
  645. {
  646. self->showList();
  647. }
  648. self->setFocus(true);
  649. // pass mouse capture on to list if button is depressed
  650. if (self->mButton->hasMouseCapture())
  651. {
  652. gFocusMgr.setMouseCapture(self->mList);
  653. }
  654. }
  655. else
  656. {
  657. self->hideList();
  658. }
  659. }
  660. //static
  661. void LLComboBox::onItemSelected(LLUICtrl* item, void* userdata)
  662. {
  663. // Note: item is the LLScrollListCtrl
  664. LLComboBox* self = (LLComboBox*)userdata;
  665. if (!self) return;
  666. const std::string name = self->mList->getSelectedItemLabel();
  667. S32 cur_id = self->getCurrentIndex();
  668. if (cur_id != -1)
  669. {
  670. self->setLabel(name);
  671. if (self->mAllowTextEntry)
  672. {
  673. gFocusMgr.setKeyboardFocus(self->mTextEntry);
  674. self->mTextEntry->selectAll();
  675. }
  676. }
  677. // Hiding the list reasserts the old value stored in the text editor/
  678. // dropdown button
  679. self->hideList();
  680. // Commit does the reverse, asserting the value in the list
  681. self->onCommit();
  682. }
  683. bool LLComboBox::handleToolTip(S32 x, S32 y, std::string& msg,
  684. LLRect* sticky_rect_screen)
  685. {
  686. std::string tool_tip;
  687. if (LLUICtrl::handleToolTip(x, y, msg, sticky_rect_screen))
  688. {
  689. return true;
  690. }
  691. if (LLUI::sShowXUINames)
  692. {
  693. tool_tip = getShowNamesToolTip();
  694. }
  695. else
  696. {
  697. tool_tip = getToolTip();
  698. if (tool_tip.empty())
  699. {
  700. tool_tip = getSelectedItemLabel();
  701. }
  702. }
  703. if (!tool_tip.empty())
  704. {
  705. msg = tool_tip;
  706. // Convert rect local to screen coordinates
  707. localPointToScreen(0, 0, &(sticky_rect_screen->mLeft),
  708. &(sticky_rect_screen->mBottom));
  709. localPointToScreen(getRect().getWidth(), getRect().getHeight(),
  710. &(sticky_rect_screen->mRight),
  711. &(sticky_rect_screen->mTop));
  712. }
  713. return true;
  714. }
  715. bool LLComboBox::handleKeyHere(KEY key, MASK mask)
  716. {
  717. bool result = false;
  718. if (hasFocus())
  719. {
  720. if (mList->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE)
  721. {
  722. hideList();
  723. return true;
  724. }
  725. // Give the list a chance to pop up and handle key
  726. LLScrollListItem* last_sel_item = mList->getLastSelectedItem();
  727. if (last_sel_item)
  728. {
  729. // Highlight the original selection before potentially selecting a
  730. // new item
  731. mList->highlightNthItem(mList->getItemIndex(last_sel_item));
  732. }
  733. result = mList->handleKeyHere(key, mask);
  734. // Will only see return key if it is originating from line editor
  735. // since the dropdown button eats the key
  736. if (key == KEY_RETURN)
  737. {
  738. // Do not show list and do not eat key input when committing
  739. // free-form text entry with RETURN since user already knows what
  740. // they are trying to select
  741. return false;
  742. }
  743. // If selection has changed, pop open the list
  744. else if (mList->getLastSelectedItem() != last_sel_item)
  745. {
  746. showList();
  747. }
  748. }
  749. return result;
  750. }
  751. bool LLComboBox::handleUnicodeCharHere(llwchar uni_char)
  752. {
  753. bool result = false;
  754. if (gFocusMgr.childHasKeyboardFocus(this))
  755. {
  756. // Space bar just shows the list
  757. if (uni_char != ' ')
  758. {
  759. LLScrollListItem* last_sel_item = mList->getLastSelectedItem();
  760. if (last_sel_item)
  761. {
  762. // Highlight the original selection before potentially
  763. // selecting a new item
  764. mList->highlightNthItem(mList->getItemIndex(last_sel_item));
  765. }
  766. result = mList->handleUnicodeCharHere(uni_char);
  767. if (mList->getLastSelectedItem() != last_sel_item)
  768. {
  769. showList();
  770. }
  771. }
  772. }
  773. return result;
  774. }
  775. void LLComboBox::setAllowTextEntry(bool allow, S32 max_chars,
  776. bool set_tentative)
  777. {
  778. mAllowTextEntry = allow;
  779. mTextEntryTentative = set_tentative;
  780. mMaxChars = max_chars;
  781. updateLayout();
  782. }
  783. void LLComboBox::setTextEntry(const std::string& text)
  784. {
  785. if (mTextEntry)
  786. {
  787. mTextEntry->setText(text);
  788. updateSelection();
  789. }
  790. }
  791. //static
  792. void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data)
  793. {
  794. LLComboBox* self = (LLComboBox*)user_data;
  795. if (!self || !gKeyboardp) return;
  796. if (self->mTextEntryCallback)
  797. {
  798. (*self->mTextEntryCallback)(line_editor, self->mCallbackUserData);
  799. }
  800. KEY key = gKeyboardp->currentKey();
  801. if (key == KEY_BACKSPACE || key == KEY_DELETE)
  802. {
  803. if (self->mList->selectItemByLabel(line_editor->getText(), false))
  804. {
  805. line_editor->setTentative(false);
  806. }
  807. else
  808. {
  809. if (!self->mSuppressTentative)
  810. {
  811. line_editor->setTentative(self->mTextEntryTentative);
  812. }
  813. self->mList->deselectAllItems();
  814. }
  815. return;
  816. }
  817. if (key == KEY_LEFT || key == KEY_RIGHT)
  818. {
  819. return;
  820. }
  821. if (key == KEY_DOWN)
  822. {
  823. self->setCurrentByIndex(llmin(self->getItemCount() - 1,
  824. self->getCurrentIndex() + 1));
  825. if (!self->mList->getVisible())
  826. {
  827. if (self->mPrearrangeCallback)
  828. {
  829. self->mPrearrangeCallback(self, self->mCallbackUserData);
  830. }
  831. if (self->mList->getItemCount() != 0)
  832. {
  833. self->showList();
  834. }
  835. }
  836. line_editor->selectAll();
  837. line_editor->setTentative(false);
  838. }
  839. else if (key == KEY_UP)
  840. {
  841. self->setCurrentByIndex(llmax(0, self->getCurrentIndex() - 1));
  842. if (!self->mList->getVisible())
  843. {
  844. if (self->mPrearrangeCallback)
  845. {
  846. self->mPrearrangeCallback(self, self->mCallbackUserData);
  847. }
  848. if (self->mList->getItemCount() != 0)
  849. {
  850. self->showList();
  851. }
  852. }
  853. line_editor->selectAll();
  854. line_editor->setTentative(false);
  855. }
  856. else
  857. {
  858. // RN: presumably text entry
  859. self->updateSelection();
  860. }
  861. }
  862. void LLComboBox::updateSelection()
  863. {
  864. LLWString left_wstring =
  865. mTextEntry->getWText().substr(0, mTextEntry->getCursor());
  866. // User-entered portion of string, based on assumption that any selected
  867. // text was a result of auto-completion
  868. LLWString user_wstring =
  869. mTextEntry->hasSelection() ? left_wstring : mTextEntry->getWText();
  870. std::string full_string = mTextEntry->getText();
  871. // Go ahead and arrange drop down list on first typed character, even
  872. // though we are not showing it... Some code relies on prearrange callback
  873. // to populate content
  874. if (mPrearrangeCallback && mTextEntry->getWText().size() == 1)
  875. {
  876. mPrearrangeCallback(this, mCallbackUserData);
  877. }
  878. if (mList->selectItemByLabel(full_string, false))
  879. {
  880. mTextEntry->setTentative(false);
  881. }
  882. else if (!mList->selectItemByPrefix(left_wstring, false))
  883. {
  884. mList->deselectAllItems();
  885. mTextEntry->setText(wstring_to_utf8str(user_wstring));
  886. if (!mSuppressTentative)
  887. {
  888. mTextEntry->setTentative(mTextEntryTentative);
  889. }
  890. }
  891. else
  892. {
  893. LLWString selected_item =
  894. utf8str_to_wstring(mList->getSelectedItemLabel());
  895. LLWString wtext = left_wstring +
  896. selected_item.substr(left_wstring.size(),
  897. selected_item.size());
  898. mTextEntry->setText(wstring_to_utf8str(wtext));
  899. mTextEntry->setSelection(left_wstring.size(),
  900. mTextEntry->getWText().size());
  901. mTextEntry->endSelection();
  902. mTextEntry->setTentative(false);
  903. }
  904. }
  905. //static
  906. void LLComboBox::onTextCommit(LLUICtrl* caller, void* user_data)
  907. {
  908. LLComboBox* self = (LLComboBox*)user_data;
  909. if (self)
  910. {
  911. self->setSimple(self->mTextEntry->getText());
  912. self->onCommit();
  913. self->mTextEntry->selectAll();
  914. }
  915. }
  916. void LLComboBox::setSuppressTentative(bool suppress)
  917. {
  918. mSuppressTentative = suppress;
  919. if (mTextEntry && mSuppressTentative)
  920. {
  921. mTextEntry->setTentative(false);
  922. }
  923. }
  924. void LLComboBox::setFocusText(bool b)
  925. {
  926. LLUICtrl::setFocus(b);
  927. if (b && mTextEntry)
  928. {
  929. if (mTextEntry->getVisible())
  930. {
  931. mTextEntry->setFocus(true);
  932. }
  933. }
  934. }
  935. void LLComboBox::setFocus(bool b)
  936. {
  937. LLUICtrl::setFocus(b);
  938. if (b)
  939. {
  940. mList->clearSearchString();
  941. if (mList->getVisible())
  942. {
  943. mList->setFocus(true);
  944. }
  945. }
  946. }
  947. void LLComboBox::setPrevalidate(bool (*func)(const LLWString&))
  948. {
  949. if (mTextEntry)
  950. {
  951. mTextEntry->setPrevalidate(func);
  952. }
  953. }
  954. //============================================================================
  955. // LLCtrlListInterface functions
  956. S32 LLComboBox::getItemCount() const
  957. {
  958. return mList->getItemCount();
  959. }
  960. void LLComboBox::addColumn(const LLSD& column, EAddPosition pos)
  961. {
  962. mList->clearColumns();
  963. mList->addColumn(column, pos);
  964. }
  965. void LLComboBox::clearColumns()
  966. {
  967. mList->clearColumns();
  968. }
  969. void LLComboBox::setColumnLabel(const std::string& column,
  970. const std::string& label)
  971. {
  972. mList->setColumnLabel(column, label);
  973. }
  974. LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos,
  975. void* userdata)
  976. {
  977. return mList->addElement(value, pos, userdata);
  978. }
  979. LLScrollListItem* LLComboBox::addSimpleElement(const std::string& value,
  980. EAddPosition pos,
  981. const LLSD& id)
  982. {
  983. return mList->addSimpleElement(value, pos, id);
  984. }
  985. void LLComboBox::clearRows()
  986. {
  987. mList->clearRows();
  988. }
  989. void LLComboBox::sortByColumn(const std::string& name, bool ascending)
  990. {
  991. mList->sortByColumn(name, ascending);
  992. }
  993. LLScrollListItem* LLComboBox::getItemByIndex(S32 index) const
  994. {
  995. return mList->getItemByIndex(index);
  996. }
  997. bool LLComboBox::setCurrentByID(const LLUUID& id)
  998. {
  999. bool found = mList->selectByID(id);
  1000. if (found)
  1001. {
  1002. setLabel(mList->getSelectedItemLabel());
  1003. }
  1004. return found;
  1005. }
  1006. LLUUID LLComboBox::getCurrentID() const
  1007. {
  1008. return mList->getStringUUIDSelectedItem();
  1009. }
  1010. bool LLComboBox::setSelectedByValue(const LLSD& value, bool selected)
  1011. {
  1012. bool found = mList->setSelectedByValue(value, selected);
  1013. if (found)
  1014. {
  1015. setLabel(mList->getSelectedItemLabel());
  1016. }
  1017. return found;
  1018. }
  1019. LLSD LLComboBox::getSelectedValue()
  1020. {
  1021. return mList->getSelectedValue();
  1022. }
  1023. bool LLComboBox::isSelected(const LLSD& value) const
  1024. {
  1025. return mList->isSelected(value);
  1026. }
  1027. bool LLComboBox::selectItemRange(S32 first, S32 last)
  1028. {
  1029. return mList->selectItemRange(first, last);
  1030. }
  1031. bool LLComboBox::operateOnSelection(EOperation op)
  1032. {
  1033. if (op == OP_DELETE)
  1034. {
  1035. mList->deleteSelectedItems();
  1036. return true;
  1037. }
  1038. return false;
  1039. }
  1040. bool LLComboBox::operateOnAll(EOperation op)
  1041. {
  1042. if (op == OP_DELETE)
  1043. {
  1044. clearRows();
  1045. return true;
  1046. }
  1047. return false;
  1048. }
  1049. //
  1050. // LLFlyoutButton
  1051. //
  1052. static const std::string LL_FLYOUT_BUTTON_ITEM_TAG = "flyout_button_item";
  1053. static const std::string LL_FLYOUT_BUTTON_TAG = "flyout_button";
  1054. static LLRegisterWidget<LLFlyoutButton> r04(LL_FLYOUT_BUTTON_TAG);
  1055. constexpr S32 FLYOUT_BUTTON_ARROW_WIDTH = 24;
  1056. LLFlyoutButton::LLFlyoutButton(const std::string& name, const LLRect& rect,
  1057. const std::string& label,
  1058. void (*commit_callback)(LLUICtrl*, void*),
  1059. void* callback_userdata)
  1060. : LLComboBox(name, rect, LLStringUtil::null, commit_callback,
  1061. callback_userdata),
  1062. mToggleState(false),
  1063. mActionButton(NULL)
  1064. {
  1065. // Text label button
  1066. mActionButton = new LLButton(label, LLRect(), NULL, NULL, this);
  1067. mActionButton->setScaleImage(true);
  1068. mActionButton->setClickedCallback(onActionButtonClick);
  1069. mActionButton->setFollowsAll();
  1070. mActionButton->setHAlign(LLFontGL::HCENTER);
  1071. mActionButton->setLabel(label);
  1072. addChild(mActionButton);
  1073. mActionButtonImage = LLUI::getUIImage("flyout_btn_left.tga");
  1074. mExpanderButtonImage = LLUI::getUIImage("flyout_btn_right.tga");
  1075. mActionButtonImageSelected = LLUI::getUIImage("flyout_btn_left_selected.tga");
  1076. mExpanderButtonImageSelected = LLUI::getUIImage("flyout_btn_right_selected.tga");
  1077. mActionButtonImageDisabled = LLUI::getUIImage("flyout_btn_left_disabled.tga");
  1078. mExpanderButtonImageDisabled = LLUI::getUIImage("flyout_btn_right_disabled.tga");
  1079. mActionButton->setImageSelected(mActionButtonImageSelected);
  1080. mActionButton->setImageUnselected(mActionButtonImage);
  1081. mActionButton->setImageDisabled(mActionButtonImageDisabled);
  1082. mActionButton->setImageDisabledSelected(LLUIImagePtr(NULL));
  1083. mButton->setImageSelected(mExpanderButtonImageSelected);
  1084. mButton->setImageUnselected(mExpanderButtonImage);
  1085. mButton->setImageDisabled(mExpanderButtonImageDisabled);
  1086. mButton->setImageDisabledSelected(LLUIImagePtr(NULL));
  1087. mButton->setRightHPad(6);
  1088. updateLayout();
  1089. }
  1090. //virtual
  1091. const std::string& LLFlyoutButton::getTag() const
  1092. {
  1093. return LL_FLYOUT_BUTTON_TAG;
  1094. }
  1095. //virtual
  1096. LLXMLNodePtr LLFlyoutButton::getXML(bool save_children) const
  1097. {
  1098. LLXMLNodePtr node = LLComboBox::getXML();
  1099. node->setName(LL_FLYOUT_BUTTON_TAG);
  1100. for (LLXMLNodePtr child = node->getFirstChild(); child.notNull(); )
  1101. {
  1102. if (child->hasName("combo_item"))
  1103. {
  1104. child->setName(LL_FLYOUT_BUTTON_ITEM_TAG);
  1105. // setName() does a delete and add, so we have to start over
  1106. child = node->getFirstChild();
  1107. }
  1108. else
  1109. {
  1110. child = child->getNextSibling();
  1111. }
  1112. }
  1113. return node;
  1114. }
  1115. //static
  1116. LLView* LLFlyoutButton::fromXML(LLXMLNodePtr node, LLView* parent,
  1117. LLUICtrlFactory* factory)
  1118. {
  1119. std::string name = LL_FLYOUT_BUTTON_TAG;
  1120. node->getAttributeString("name", name);
  1121. std::string label;
  1122. node->getAttributeString("label", label);
  1123. LLRect rect;
  1124. createRect(node, rect, parent, LLRect());
  1125. LLUICtrlCallback callback = NULL;
  1126. LLFlyoutButton* flyout_button = new LLFlyoutButton(name, rect, label,
  1127. callback, NULL);
  1128. std::string list_position;
  1129. node->getAttributeString("list_position", list_position);
  1130. if (list_position == "below")
  1131. {
  1132. flyout_button->mListPosition = BELOW;
  1133. }
  1134. else if (list_position == "above")
  1135. {
  1136. flyout_button->mListPosition = ABOVE;
  1137. }
  1138. flyout_button->initFromXML(node, parent);
  1139. for (LLXMLNodePtr child = node->getFirstChild(); child.notNull();
  1140. child = child->getNextSibling())
  1141. {
  1142. if (child->hasName(LL_FLYOUT_BUTTON_ITEM_TAG))
  1143. {
  1144. std::string label = child->getTextContents();
  1145. std::string value = label;
  1146. child->getAttributeString("value", value);
  1147. LLScrollListItem* item = flyout_button->add(label, LLSD(value));
  1148. if (item && child->hasAttribute("tool_tip"))
  1149. {
  1150. std::string tool_tip = label;
  1151. child->getAttributeString("tool_tip", tool_tip);
  1152. item->setToolTip(tool_tip);
  1153. }
  1154. }
  1155. }
  1156. flyout_button->updateLayout();
  1157. return flyout_button;
  1158. }
  1159. void LLFlyoutButton::updateLayout()
  1160. {
  1161. LLComboBox::updateLayout();
  1162. mButton->setOrigin(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH, 0);
  1163. mButton->reshape(FLYOUT_BUTTON_ARROW_WIDTH, getRect().getHeight());
  1164. mButton->setFollows(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
  1165. mButton->setTabStop(false);
  1166. mButton->setImageOverlay(mListPosition == BELOW ? "down_arrow.tga"
  1167. : "up_arrow.tga",
  1168. LLFontGL::RIGHT);
  1169. mActionButton->setOrigin(0, 0);
  1170. mActionButton->reshape(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH,
  1171. getRect().getHeight());
  1172. }
  1173. void LLFlyoutButton::setLabel(const std::string& label)
  1174. {
  1175. mActionButton->setLabel(label);
  1176. }
  1177. //static
  1178. void LLFlyoutButton::onActionButtonClick(void* user_data)
  1179. {
  1180. LLFlyoutButton* buttonp = (LLFlyoutButton*)user_data;
  1181. // remember last list selection ?
  1182. buttonp->mList->deselect();
  1183. buttonp->onCommit();
  1184. }
  1185. void LLFlyoutButton::draw()
  1186. {
  1187. mActionButton->setToggleState(mToggleState);
  1188. mButton->setToggleState(mToggleState);
  1189. // *FIXME: this should be an attribute of comboboxes, whether they have a
  1190. // distinct label or the label reflects the last selected item, for now we
  1191. // have to manually remove the label
  1192. mButton->setLabel(LLStringUtil::null);
  1193. LLComboBox::draw();
  1194. }
  1195. void LLFlyoutButton::setEnabled(bool enabled)
  1196. {
  1197. mActionButton->setEnabled(enabled);
  1198. LLComboBox::setEnabled(enabled);
  1199. }