/indra/llui/llcombobox.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 1112 lines · 851 code · 147 blank · 114 comment · 138 complexity · 4ffc4f7881f62b99d4c3e918e72e4ccc MD5 · raw file

  1. /**
  2. * @file llcombobox.cpp
  3. * @brief LLComboBox base class
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. // A control that displays the name of the chosen item, which when
  27. // clicked shows a scrolling box of options.
  28. #include "linden_common.h"
  29. // file includes
  30. #include "llcombobox.h"
  31. // common includes
  32. #include "llstring.h"
  33. // newview includes
  34. #include "llbutton.h"
  35. #include "llkeyboard.h"
  36. #include "llscrolllistctrl.h"
  37. #include "llwindow.h"
  38. #include "llfloater.h"
  39. #include "llscrollbar.h"
  40. #include "llscrolllistcell.h"
  41. #include "llscrolllistitem.h"
  42. #include "llcontrol.h"
  43. #include "llfocusmgr.h"
  44. #include "lllineeditor.h"
  45. #include "v2math.h"
  46. #include "lluictrlfactory.h"
  47. #include "lltooltip.h"
  48. // Globals
  49. S32 MAX_COMBO_WIDTH = 500;
  50. static LLDefaultChildRegistry::Register<LLComboBox> register_combo_box("combo_box");
  51. void LLComboBox::PreferredPositionValues::declareValues()
  52. {
  53. declare("above", ABOVE);
  54. declare("below", BELOW);
  55. }
  56. LLComboBox::ItemParams::ItemParams()
  57. : label("label")
  58. {
  59. }
  60. LLComboBox::Params::Params()
  61. : allow_text_entry("allow_text_entry", false),
  62. allow_new_values("allow_new_values", false),
  63. show_text_as_tentative("show_text_as_tentative", true),
  64. max_chars("max_chars", 20),
  65. list_position("list_position", BELOW),
  66. items("item"),
  67. combo_button("combo_button"),
  68. combo_list("combo_list"),
  69. combo_editor("combo_editor"),
  70. drop_down_button("drop_down_button")
  71. {
  72. addSynonym(items, "combo_item");
  73. }
  74. LLComboBox::LLComboBox(const LLComboBox::Params& p)
  75. : LLUICtrl(p),
  76. mTextEntry(NULL),
  77. mTextEntryTentative(p.show_text_as_tentative),
  78. mHasAutocompletedText(false),
  79. mAllowTextEntry(p.allow_text_entry),
  80. mAllowNewValues(p.allow_new_values),
  81. mMaxChars(p.max_chars),
  82. mPrearrangeCallback(p.prearrange_callback()),
  83. mTextEntryCallback(p.text_entry_callback()),
  84. mTextChangedCallback(p.text_changed_callback()),
  85. mListPosition(p.list_position),
  86. mLastSelectedIndex(-1),
  87. mLabel(p.label)
  88. {
  89. // Text label button
  90. LLButton::Params button_params = (mAllowTextEntry ? p.combo_button : p.drop_down_button);
  91. button_params.mouse_down_callback.function(
  92. boost::bind(&LLComboBox::onButtonMouseDown, this));
  93. button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_BOTTOM|FOLLOWS_RIGHT);
  94. button_params.rect(p.rect);
  95. if(mAllowTextEntry)
  96. {
  97. button_params.pad_right(2);
  98. }
  99. mArrowImage = button_params.image_unselected;
  100. mButton = LLUICtrlFactory::create<LLButton>(button_params);
  101. if(mAllowTextEntry)
  102. {
  103. //redo to compensate for button hack that leaves space for a character
  104. //unless it is a "minimal combobox"(drop down)
  105. mButton->setRightHPad(2);
  106. }
  107. addChild(mButton);
  108. LLScrollListCtrl::Params params = p.combo_list;
  109. params.name("ComboBox");
  110. params.commit_callback.function(boost::bind(&LLComboBox::onItemSelected, this, _2));
  111. params.visible(false);
  112. params.commit_on_keyboard_movement(false);
  113. mList = LLUICtrlFactory::create<LLScrollListCtrl>(params);
  114. addChild(mList);
  115. // Mouse-down on button will transfer mouse focus to the list
  116. // Grab the mouse-up event and make sure the button state is correct
  117. mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this));
  118. for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin();
  119. it != p.items.end();
  120. ++it)
  121. {
  122. LLScrollListItem::Params item_params = *it;
  123. if (it->label.isProvided())
  124. {
  125. item_params.columns.add().value(it->label());
  126. }
  127. mList->addRow(item_params);
  128. }
  129. createLineEditor(p);
  130. mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this));
  131. }
  132. void LLComboBox::initFromParams(const LLComboBox::Params& p)
  133. {
  134. LLUICtrl::initFromParams(p);
  135. if (!acceptsTextInput() && mLabel.empty())
  136. {
  137. selectFirstItem();
  138. }
  139. }
  140. // virtual
  141. BOOL LLComboBox::postBuild()
  142. {
  143. if (mControlVariable)
  144. {
  145. setValue(mControlVariable->getValue()); // selects the appropriate item
  146. }
  147. return TRUE;
  148. }
  149. LLComboBox::~LLComboBox()
  150. {
  151. // children automatically deleted, including mMenu, mButton
  152. // explicitly disconect this signal, since base class destructor might fire top lost
  153. mTopLostSignalConnection.disconnect();
  154. }
  155. void LLComboBox::clear()
  156. {
  157. if (mTextEntry)
  158. {
  159. mTextEntry->setText(LLStringUtil::null);
  160. }
  161. mButton->setLabelSelected(LLStringUtil::null);
  162. mButton->setLabelUnselected(LLStringUtil::null);
  163. mList->deselectAllItems();
  164. mLastSelectedIndex = -1;
  165. }
  166. void LLComboBox::onCommit()
  167. {
  168. if (mAllowTextEntry && getCurrentIndex() != -1)
  169. {
  170. // we have selected an existing item, blitz the manual text entry with
  171. // the properly capitalized item
  172. mTextEntry->setValue(getSimple());
  173. mTextEntry->setTentative(FALSE);
  174. }
  175. setControlValue(getValue());
  176. LLUICtrl::onCommit();
  177. }
  178. // virtual
  179. BOOL LLComboBox::isDirty() const
  180. {
  181. BOOL grubby = FALSE;
  182. if ( mList )
  183. {
  184. grubby = mList->isDirty();
  185. }
  186. return grubby;
  187. }
  188. // virtual Clear dirty state
  189. void LLComboBox::resetDirty()
  190. {
  191. if ( mList )
  192. {
  193. mList->resetDirty();
  194. }
  195. }
  196. bool LLComboBox::itemExists(const std::string& name)
  197. {
  198. return mList->getItemByLabel(name);
  199. }
  200. // add item "name" to menu
  201. LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, BOOL enabled)
  202. {
  203. LLScrollListItem* item = mList->addSimpleElement(name, pos);
  204. item->setEnabled(enabled);
  205. if (!mAllowTextEntry && mLabel.empty())
  206. {
  207. selectFirstItem();
  208. }
  209. return item;
  210. }
  211. // add item "name" with a unique id to menu
  212. LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAddPosition pos, BOOL enabled )
  213. {
  214. LLScrollListItem* item = mList->addSimpleElement(name, pos, id);
  215. item->setEnabled(enabled);
  216. if (!mAllowTextEntry && mLabel.empty())
  217. {
  218. selectFirstItem();
  219. }
  220. return item;
  221. }
  222. // add item "name" with attached userdata
  223. LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddPosition pos, BOOL enabled )
  224. {
  225. LLScrollListItem* item = mList->addSimpleElement(name, pos);
  226. item->setEnabled(enabled);
  227. item->setUserdata( userdata );
  228. if (!mAllowTextEntry && mLabel.empty())
  229. {
  230. selectFirstItem();
  231. }
  232. return item;
  233. }
  234. // add item "name" with attached generic data
  235. LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosition pos, BOOL enabled )
  236. {
  237. LLScrollListItem* item = mList->addSimpleElement(name, pos, value);
  238. item->setEnabled(enabled);
  239. if (!mAllowTextEntry && mLabel.empty())
  240. {
  241. selectFirstItem();
  242. }
  243. return item;
  244. }
  245. LLScrollListItem* LLComboBox::addSeparator(EAddPosition pos)
  246. {
  247. return mList->addSeparator(pos);
  248. }
  249. void LLComboBox::sortByName(BOOL ascending)
  250. {
  251. mList->sortOnce(0, ascending);
  252. }
  253. // Choose an item with a given name in the menu.
  254. // Returns TRUE if the item was found.
  255. BOOL LLComboBox::setSimple(const LLStringExplicit& name)
  256. {
  257. BOOL found = mList->selectItemByLabel(name, FALSE);
  258. if (found)
  259. {
  260. setLabel(name);
  261. mLastSelectedIndex = mList->getFirstSelectedIndex();
  262. }
  263. return found;
  264. }
  265. // virtual
  266. void LLComboBox::setValue(const LLSD& value)
  267. {
  268. BOOL found = mList->selectByValue(value);
  269. if (found)
  270. {
  271. LLScrollListItem* item = mList->getFirstSelected();
  272. if (item)
  273. {
  274. updateLabel();
  275. }
  276. mLastSelectedIndex = mList->getFirstSelectedIndex();
  277. }
  278. else
  279. {
  280. mLastSelectedIndex = -1;
  281. }
  282. }
  283. const std::string LLComboBox::getSimple() const
  284. {
  285. const std::string res = getSelectedItemLabel();
  286. if (res.empty() && mAllowTextEntry)
  287. {
  288. return mTextEntry->getText();
  289. }
  290. else
  291. {
  292. return res;
  293. }
  294. }
  295. const std::string LLComboBox::getSelectedItemLabel(S32 column) const
  296. {
  297. return mList->getSelectedItemLabel(column);
  298. }
  299. // virtual
  300. LLSD LLComboBox::getValue() const
  301. {
  302. LLScrollListItem* item = mList->getFirstSelected();
  303. if( item )
  304. {
  305. return item->getValue();
  306. }
  307. else if (mAllowTextEntry)
  308. {
  309. return mTextEntry->getValue();
  310. }
  311. else
  312. {
  313. return LLSD();
  314. }
  315. }
  316. void LLComboBox::setLabel(const LLStringExplicit& name)
  317. {
  318. if ( mTextEntry )
  319. {
  320. mTextEntry->setText(name);
  321. if (mList->selectItemByLabel(name, FALSE))
  322. {
  323. mTextEntry->setTentative(FALSE);
  324. mLastSelectedIndex = mList->getFirstSelectedIndex();
  325. }
  326. else
  327. {
  328. mTextEntry->setTentative(mTextEntryTentative);
  329. }
  330. }
  331. if (!mAllowTextEntry)
  332. {
  333. mButton->setLabel(name);
  334. }
  335. }
  336. void LLComboBox::updateLabel()
  337. {
  338. // Update the combo editor with the selected
  339. // item label.
  340. if (mTextEntry)
  341. {
  342. mTextEntry->setText(getSelectedItemLabel());
  343. mTextEntry->setTentative(FALSE);
  344. }
  345. // If combo box doesn't allow text entry update
  346. // the combo button label.
  347. if (!mAllowTextEntry)
  348. {
  349. mButton->setLabel(getSelectedItemLabel());
  350. }
  351. }
  352. BOOL LLComboBox::remove(const std::string& name)
  353. {
  354. BOOL found = mList->selectItemByLabel(name);
  355. if (found)
  356. {
  357. LLScrollListItem* item = mList->getFirstSelected();
  358. if (item)
  359. {
  360. mList->deleteSingleItem(mList->getItemIndex(item));
  361. }
  362. mLastSelectedIndex = mList->getFirstSelectedIndex();
  363. }
  364. return found;
  365. }
  366. BOOL LLComboBox::remove(S32 index)
  367. {
  368. if (index < mList->getItemCount())
  369. {
  370. mList->deleteSingleItem(index);
  371. setLabel(getSelectedItemLabel());
  372. return TRUE;
  373. }
  374. return FALSE;
  375. }
  376. // Keyboard focus lost.
  377. void LLComboBox::onFocusLost()
  378. {
  379. hideList();
  380. // if valid selection
  381. if (mAllowTextEntry && getCurrentIndex() != -1)
  382. {
  383. mTextEntry->selectAll();
  384. }
  385. LLUICtrl::onFocusLost();
  386. }
  387. void LLComboBox::setButtonVisible(BOOL visible)
  388. {
  389. static LLUICachedControl<S32> drop_shadow_button ("DropShadowButton", 0);
  390. mButton->setVisible(visible);
  391. if (mTextEntry)
  392. {
  393. LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0);
  394. if (visible)
  395. {
  396. S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0;
  397. text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * drop_shadow_button;
  398. }
  399. //mTextEntry->setRect(text_entry_rect);
  400. mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE);
  401. }
  402. }
  403. BOOL LLComboBox::setCurrentByIndex( S32 index )
  404. {
  405. BOOL found = mList->selectNthItem( index );
  406. if (found)
  407. {
  408. setLabel(getSelectedItemLabel());
  409. mLastSelectedIndex = index;
  410. }
  411. return found;
  412. }
  413. S32 LLComboBox::getCurrentIndex() const
  414. {
  415. LLScrollListItem* item = mList->getFirstSelected();
  416. if( item )
  417. {
  418. return mList->getItemIndex( item );
  419. }
  420. return -1;
  421. }
  422. void LLComboBox::createLineEditor(const LLComboBox::Params& p)
  423. {
  424. static LLUICachedControl<S32> drop_shadow_button ("DropShadowButton", 0);
  425. LLRect rect = getLocalRect();
  426. if (mAllowTextEntry)
  427. {
  428. S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0;
  429. S32 shadow_size = drop_shadow_button;
  430. mButton->setRect(LLRect( getRect().getWidth() - llmax(8,arrow_width) - 2 * shadow_size,
  431. rect.mTop, rect.mRight, rect.mBottom));
  432. mButton->setTabStop(FALSE);
  433. mButton->setHAlign(LLFontGL::HCENTER);
  434. LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0);
  435. text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * drop_shadow_button;
  436. // clear label on button
  437. std::string cur_label = mButton->getLabelSelected();
  438. LLLineEditor::Params params = p.combo_editor;
  439. params.rect(text_entry_rect);
  440. params.default_text(LLStringUtil::null);
  441. params.max_length.bytes(mMaxChars);
  442. params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2));
  443. params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1));
  444. params.commit_on_focus_lost(false);
  445. params.follows.flags(FOLLOWS_ALL);
  446. params.label(mLabel);
  447. mTextEntry = LLUICtrlFactory::create<LLLineEditor> (params);
  448. mTextEntry->setText(cur_label);
  449. mTextEntry->setIgnoreTab(TRUE);
  450. addChild(mTextEntry);
  451. // clear label on button
  452. setLabel(LLStringUtil::null);
  453. mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT);
  454. }
  455. else
  456. {
  457. mButton->setRect(rect);
  458. mButton->setLabel(mLabel.getString());
  459. if (mTextEntry)
  460. {
  461. mTextEntry->setVisible(FALSE);
  462. }
  463. }
  464. }
  465. void* LLComboBox::getCurrentUserdata()
  466. {
  467. LLScrollListItem* item = mList->getFirstSelected();
  468. if( item )
  469. {
  470. return item->getUserdata();
  471. }
  472. return NULL;
  473. }
  474. void LLComboBox::showList()
  475. {
  476. // Make sure we don't go off top of screen.
  477. LLCoordWindow window_size;
  478. getWindow()->getSize(&window_size);
  479. //HACK: shouldn't have to know about scale here
  480. mList->fitContents( 192, llfloor((F32)window_size.mY / LLUI::sGLScaleFactor.mV[VY]) - 50 );
  481. // Make sure that we can see the whole list
  482. LLRect root_view_local;
  483. LLView* root_view = getRootView();
  484. root_view->localRectToOtherView(root_view->getLocalRect(), &root_view_local, this);
  485. LLRect rect = mList->getRect();
  486. S32 min_width = getRect().getWidth();
  487. S32 max_width = llmax(min_width, MAX_COMBO_WIDTH);
  488. // make sure we have up to date content width metrics
  489. mList->calcColumnWidths();
  490. S32 list_width = llclamp(mList->getMaxContentWidth(), min_width, max_width);
  491. if (mListPosition == BELOW)
  492. {
  493. if (rect.getHeight() <= -root_view_local.mBottom)
  494. {
  495. // Move rect so it hangs off the bottom of this view
  496. rect.setLeftTopAndSize(0, 0, list_width, rect.getHeight() );
  497. }
  498. else
  499. {
  500. // stack on top or bottom, depending on which has more room
  501. if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight())
  502. {
  503. // Move rect so it hangs off the bottom of this view
  504. rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight()));
  505. }
  506. else
  507. {
  508. // move rect so it stacks on top of this view (clipped to size of screen)
  509. rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight()));
  510. }
  511. }
  512. }
  513. else // ABOVE
  514. {
  515. if (rect.getHeight() <= root_view_local.mTop - getRect().getHeight())
  516. {
  517. // move rect so it stacks on top of this view (clipped to size of screen)
  518. rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight()));
  519. }
  520. else
  521. {
  522. // stack on top or bottom, depending on which has more room
  523. if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight())
  524. {
  525. // Move rect so it hangs off the bottom of this view
  526. rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight()));
  527. }
  528. else
  529. {
  530. // move rect so it stacks on top of this view (clipped to size of screen)
  531. rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight()));
  532. }
  533. }
  534. }
  535. mList->setOrigin(rect.mLeft, rect.mBottom);
  536. mList->reshape(rect.getWidth(), rect.getHeight());
  537. mList->translateIntoRect(root_view_local, FALSE);
  538. // Make sure we didn't go off bottom of screen
  539. S32 x, y;
  540. mList->localPointToScreen(0, 0, &x, &y);
  541. if (y < 0)
  542. {
  543. mList->translate(0, -y);
  544. }
  545. // NB: this call will trigger the focuslost callback which will hide the list, so do it first
  546. // before finally showing the list
  547. mList->setFocus(TRUE);
  548. // Show the list and push the button down
  549. mButton->setToggleState(TRUE);
  550. mList->setVisible(TRUE);
  551. LLUI::addPopup(this);
  552. setUseBoundingRect(TRUE);
  553. // updateBoundingRect();
  554. }
  555. void LLComboBox::hideList()
  556. {
  557. if (mList->getVisible())
  558. {
  559. // assert selection in list
  560. if(mAllowNewValues)
  561. {
  562. // mLastSelectedIndex = -1 means that we entered a new value, don't select
  563. // any of existing items in this case.
  564. if(mLastSelectedIndex >= 0)
  565. mList->selectNthItem(mLastSelectedIndex);
  566. }
  567. else if(mLastSelectedIndex >= 0)
  568. mList->selectNthItem(mLastSelectedIndex);
  569. mButton->setToggleState(FALSE);
  570. mList->setVisible(FALSE);
  571. mList->mouseOverHighlightNthItem(-1);
  572. setUseBoundingRect(FALSE);
  573. LLUI::removePopup(this);
  574. // updateBoundingRect();
  575. }
  576. }
  577. void LLComboBox::onButtonMouseDown()
  578. {
  579. if (!mList->getVisible())
  580. {
  581. // this might change selection, so do it first
  582. prearrangeList();
  583. // highlight the last selected item from the original selection before potentially selecting a new item
  584. // as visual cue to original value of combo box
  585. LLScrollListItem* last_selected_item = mList->getLastSelectedItem();
  586. if (last_selected_item)
  587. {
  588. mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item));
  589. }
  590. if (mList->getItemCount() != 0)
  591. {
  592. showList();
  593. }
  594. setFocus( TRUE );
  595. // pass mouse capture on to list if button is depressed
  596. if (mButton->hasMouseCapture())
  597. {
  598. gFocusMgr.setMouseCapture(mList);
  599. // But keep the "pressed" look, which buttons normally lose when they
  600. // lose focus
  601. mButton->setForcePressedState(true);
  602. }
  603. }
  604. else
  605. {
  606. hideList();
  607. }
  608. }
  609. void LLComboBox::onListMouseUp()
  610. {
  611. // In some cases this is the termination of a mouse click that started on
  612. // the button, so clear its pressed state
  613. mButton->setForcePressedState(false);
  614. }
  615. //------------------------------------------------------------------
  616. // static functions
  617. //------------------------------------------------------------------
  618. void LLComboBox::onItemSelected(const LLSD& data)
  619. {
  620. mLastSelectedIndex = getCurrentIndex();
  621. if (mLastSelectedIndex != -1)
  622. {
  623. updateLabel();
  624. if (mAllowTextEntry)
  625. {
  626. gFocusMgr.setKeyboardFocus(mTextEntry);
  627. mTextEntry->selectAll();
  628. }
  629. }
  630. // hiding the list reasserts the old value stored in the text editor/dropdown button
  631. hideList();
  632. // commit does the reverse, asserting the value in the list
  633. onCommit();
  634. }
  635. BOOL LLComboBox::handleToolTip(S32 x, S32 y, MASK mask)
  636. {
  637. std::string tool_tip;
  638. if(LLUICtrl::handleToolTip(x, y, mask))
  639. {
  640. return TRUE;
  641. }
  642. tool_tip = getToolTip();
  643. if (tool_tip.empty())
  644. {
  645. tool_tip = getSelectedItemLabel();
  646. }
  647. if( !tool_tip.empty() )
  648. {
  649. LLToolTipMgr::instance().show(LLToolTip::Params()
  650. .message(tool_tip)
  651. .sticky_rect(calcScreenRect()));
  652. }
  653. return TRUE;
  654. }
  655. BOOL LLComboBox::handleKeyHere(KEY key, MASK mask)
  656. {
  657. BOOL result = FALSE;
  658. if (hasFocus())
  659. {
  660. if (mList->getVisible()
  661. && key == KEY_ESCAPE && mask == MASK_NONE)
  662. {
  663. hideList();
  664. return TRUE;
  665. }
  666. //give list a chance to pop up and handle key
  667. LLScrollListItem* last_selected_item = mList->getLastSelectedItem();
  668. if (last_selected_item)
  669. {
  670. // highlight the original selection before potentially selecting a new item
  671. mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item));
  672. }
  673. result = mList->handleKeyHere(key, mask);
  674. // will only see return key if it is originating from line editor
  675. // since the dropdown button eats the key
  676. if (key == KEY_RETURN)
  677. {
  678. // don't show list and don't eat key input when committing
  679. // free-form text entry with RETURN since user already knows
  680. // what they are trying to select
  681. return FALSE;
  682. }
  683. // if selection has changed, pop open list
  684. else if (mList->getLastSelectedItem() != last_selected_item
  685. || ((key == KEY_DOWN || key == KEY_UP)
  686. && mList->getCanSelect()
  687. && !mList->isEmpty()))
  688. {
  689. showList();
  690. }
  691. }
  692. return result;
  693. }
  694. BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char)
  695. {
  696. BOOL result = FALSE;
  697. if (gFocusMgr.childHasKeyboardFocus(this))
  698. {
  699. // space bar just shows the list
  700. if (' ' != uni_char )
  701. {
  702. LLScrollListItem* last_selected_item = mList->getLastSelectedItem();
  703. if (last_selected_item)
  704. {
  705. // highlight the original selection before potentially selecting a new item
  706. mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item));
  707. }
  708. result = mList->handleUnicodeCharHere(uni_char);
  709. if (mList->getLastSelectedItem() != last_selected_item)
  710. {
  711. showList();
  712. }
  713. }
  714. }
  715. return result;
  716. }
  717. void LLComboBox::setTextEntry(const LLStringExplicit& text)
  718. {
  719. if (mTextEntry)
  720. {
  721. mTextEntry->setText(text);
  722. mHasAutocompletedText = FALSE;
  723. updateSelection();
  724. }
  725. }
  726. void LLComboBox::onTextEntry(LLLineEditor* line_editor)
  727. {
  728. if (mTextEntryCallback != NULL)
  729. {
  730. (mTextEntryCallback)(line_editor, LLSD());
  731. }
  732. KEY key = gKeyboard->currentKey();
  733. if (key == KEY_BACKSPACE ||
  734. key == KEY_DELETE)
  735. {
  736. if (mList->selectItemByLabel(line_editor->getText(), FALSE))
  737. {
  738. line_editor->setTentative(FALSE);
  739. mLastSelectedIndex = mList->getFirstSelectedIndex();
  740. }
  741. else
  742. {
  743. line_editor->setTentative(mTextEntryTentative);
  744. mList->deselectAllItems();
  745. mLastSelectedIndex = -1;
  746. }
  747. if (mTextChangedCallback != NULL)
  748. {
  749. (mTextChangedCallback)(line_editor, LLSD());
  750. }
  751. return;
  752. }
  753. if (key == KEY_LEFT ||
  754. key == KEY_RIGHT)
  755. {
  756. return;
  757. }
  758. if (key == KEY_DOWN)
  759. {
  760. setCurrentByIndex(llmin(getItemCount() - 1, getCurrentIndex() + 1));
  761. if (!mList->getVisible())
  762. {
  763. prearrangeList();
  764. if (mList->getItemCount() != 0)
  765. {
  766. showList();
  767. }
  768. }
  769. line_editor->selectAll();
  770. line_editor->setTentative(FALSE);
  771. }
  772. else if (key == KEY_UP)
  773. {
  774. setCurrentByIndex(llmax(0, getCurrentIndex() - 1));
  775. if (!mList->getVisible())
  776. {
  777. prearrangeList();
  778. if (mList->getItemCount() != 0)
  779. {
  780. showList();
  781. }
  782. }
  783. line_editor->selectAll();
  784. line_editor->setTentative(FALSE);
  785. }
  786. else
  787. {
  788. // RN: presumably text entry
  789. updateSelection();
  790. }
  791. if (mTextChangedCallback != NULL)
  792. {
  793. (mTextChangedCallback)(line_editor, LLSD());
  794. }
  795. }
  796. void LLComboBox::updateSelection()
  797. {
  798. LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor());
  799. // user-entered portion of string, based on assumption that any selected
  800. // text was a result of auto-completion
  801. LLWString user_wstring = mHasAutocompletedText ? left_wstring : mTextEntry->getWText();
  802. std::string full_string = mTextEntry->getText();
  803. // go ahead and arrange drop down list on first typed character, even
  804. // though we aren't showing it... some code relies on prearrange
  805. // callback to populate content
  806. if( mTextEntry->getWText().size() == 1 )
  807. {
  808. prearrangeList(mTextEntry->getText());
  809. }
  810. if (mList->selectItemByLabel(full_string, FALSE))
  811. {
  812. mTextEntry->setTentative(FALSE);
  813. mLastSelectedIndex = mList->getFirstSelectedIndex();
  814. }
  815. else if (mList->selectItemByPrefix(left_wstring, FALSE))
  816. {
  817. LLWString selected_item = utf8str_to_wstring(getSelectedItemLabel());
  818. LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size());
  819. mTextEntry->setText(wstring_to_utf8str(wtext));
  820. mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size());
  821. mTextEntry->endSelection();
  822. mTextEntry->setTentative(FALSE);
  823. mHasAutocompletedText = TRUE;
  824. mLastSelectedIndex = mList->getFirstSelectedIndex();
  825. }
  826. else // no matching items found
  827. {
  828. mList->deselectAllItems();
  829. mTextEntry->setText(wstring_to_utf8str(user_wstring)); // removes text added by autocompletion
  830. mTextEntry->setTentative(mTextEntryTentative);
  831. mHasAutocompletedText = FALSE;
  832. mLastSelectedIndex = -1;
  833. }
  834. }
  835. void LLComboBox::onTextCommit(const LLSD& data)
  836. {
  837. std::string text = mTextEntry->getText();
  838. setSimple(text);
  839. onCommit();
  840. mTextEntry->selectAll();
  841. }
  842. void LLComboBox::setFocus(BOOL b)
  843. {
  844. LLUICtrl::setFocus(b);
  845. if (b)
  846. {
  847. mList->clearSearchString();
  848. if (mList->getVisible())
  849. {
  850. mList->setFocus(TRUE);
  851. }
  852. }
  853. }
  854. void LLComboBox::prearrangeList(std::string filter)
  855. {
  856. if (mPrearrangeCallback)
  857. {
  858. mPrearrangeCallback(this, LLSD(filter));
  859. }
  860. }
  861. //============================================================================
  862. // LLCtrlListInterface functions
  863. S32 LLComboBox::getItemCount() const
  864. {
  865. return mList->getItemCount();
  866. }
  867. void LLComboBox::addColumn(const LLSD& column, EAddPosition pos)
  868. {
  869. mList->clearColumns();
  870. mList->addColumn(column, pos);
  871. }
  872. void LLComboBox::clearColumns()
  873. {
  874. mList->clearColumns();
  875. }
  876. void LLComboBox::setColumnLabel(const std::string& column, const std::string& label)
  877. {
  878. mList->setColumnLabel(column, label);
  879. }
  880. LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata)
  881. {
  882. return mList->addElement(value, pos, userdata);
  883. }
  884. LLScrollListItem* LLComboBox::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id)
  885. {
  886. return mList->addSimpleElement(value, pos, id);
  887. }
  888. void LLComboBox::clearRows()
  889. {
  890. mList->clearRows();
  891. }
  892. void LLComboBox::sortByColumn(const std::string& name, BOOL ascending)
  893. {
  894. mList->sortByColumn(name, ascending);
  895. }
  896. //============================================================================
  897. //LLCtrlSelectionInterface functions
  898. BOOL LLComboBox::setCurrentByID(const LLUUID& id)
  899. {
  900. BOOL found = mList->selectByID( id );
  901. if (found)
  902. {
  903. setLabel(getSelectedItemLabel());
  904. mLastSelectedIndex = mList->getFirstSelectedIndex();
  905. }
  906. return found;
  907. }
  908. LLUUID LLComboBox::getCurrentID() const
  909. {
  910. return mList->getStringUUIDSelectedItem();
  911. }
  912. BOOL LLComboBox::setSelectedByValue(const LLSD& value, BOOL selected)
  913. {
  914. BOOL found = mList->setSelectedByValue(value, selected);
  915. if (found)
  916. {
  917. setLabel(getSelectedItemLabel());
  918. }
  919. return found;
  920. }
  921. LLSD LLComboBox::getSelectedValue()
  922. {
  923. return mList->getSelectedValue();
  924. }
  925. BOOL LLComboBox::isSelected(const LLSD& value) const
  926. {
  927. return mList->isSelected(value);
  928. }
  929. BOOL LLComboBox::operateOnSelection(EOperation op)
  930. {
  931. if (op == OP_DELETE)
  932. {
  933. mList->deleteSelectedItems();
  934. return TRUE;
  935. }
  936. return FALSE;
  937. }
  938. BOOL LLComboBox::operateOnAll(EOperation op)
  939. {
  940. if (op == OP_DELETE)
  941. {
  942. clearRows();
  943. return TRUE;
  944. }
  945. return FALSE;
  946. }
  947. BOOL LLComboBox::selectItemRange( S32 first, S32 last )
  948. {
  949. return mList->selectItemRange(first, last);
  950. }
  951. static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box");
  952. LLIconsComboBox::Params::Params()
  953. : icon_column("icon_column", ICON_COLUMN),
  954. label_column("label_column", LABEL_COLUMN)
  955. {}
  956. LLIconsComboBox::LLIconsComboBox(const LLIconsComboBox::Params& p)
  957. : LLComboBox(p),
  958. mIconColumnIndex(p.icon_column),
  959. mLabelColumnIndex(p.label_column)
  960. {}
  961. const std::string LLIconsComboBox::getSelectedItemLabel(S32 column) const
  962. {
  963. mButton->setImageOverlay(LLComboBox::getSelectedItemLabel(mIconColumnIndex), mButton->getImageOverlayHAlign());
  964. return LLComboBox::getSelectedItemLabel(mLabelColumnIndex);
  965. }