/** * @file llcombobox.cpp * @brief LLComboBox base class * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ // A control that displays the name of the chosen item, which when // clicked shows a scrolling box of options. #include "linden_common.h" // file includes #include "llcombobox.h" // common includes #include "llstring.h" // newview includes #include "llbutton.h" #include "llkeyboard.h" #include "llscrolllistctrl.h" #include "llwindow.h" #include "llfloater.h" #include "llscrollbar.h" #include "llscrolllistcell.h" #include "llscrolllistitem.h" #include "llcontrol.h" #include "llfocusmgr.h" #include "lllineeditor.h" #include "v2math.h" #include "lluictrlfactory.h" #include "lltooltip.h" // Globals S32 MAX_COMBO_WIDTH = 500; static LLDefaultChildRegistry::Register register_combo_box("combo_box"); void LLComboBox::PreferredPositionValues::declareValues() { declare("above", ABOVE); declare("below", BELOW); } LLComboBox::ItemParams::ItemParams() : label("label") { } LLComboBox::Params::Params() : allow_text_entry("allow_text_entry", false), allow_new_values("allow_new_values", false), show_text_as_tentative("show_text_as_tentative", true), max_chars("max_chars", 20), list_position("list_position", BELOW), items("item"), combo_button("combo_button"), combo_list("combo_list"), combo_editor("combo_editor"), drop_down_button("drop_down_button") { addSynonym(items, "combo_item"); } LLComboBox::LLComboBox(const LLComboBox::Params& p) : LLUICtrl(p), mTextEntry(NULL), mTextEntryTentative(p.show_text_as_tentative), mHasAutocompletedText(false), mAllowTextEntry(p.allow_text_entry), mAllowNewValues(p.allow_new_values), mMaxChars(p.max_chars), mPrearrangeCallback(p.prearrange_callback()), mTextEntryCallback(p.text_entry_callback()), mTextChangedCallback(p.text_changed_callback()), mListPosition(p.list_position), mLastSelectedIndex(-1), mLabel(p.label) { // Text label button LLButton::Params button_params = (mAllowTextEntry ? p.combo_button : p.drop_down_button); button_params.mouse_down_callback.function( boost::bind(&LLComboBox::onButtonMouseDown, this)); button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_BOTTOM|FOLLOWS_RIGHT); button_params.rect(p.rect); if (mAllowTextEntry) { button_params.pad_right(2); } mArrowImage = button_params.image_unselected; if (mArrowImage.notNull()) { mImageLoadedConnection = mArrowImage->addLoadedCallback(boost::bind(&LLComboBox::imageLoaded, this)); } mButton = LLUICtrlFactory::create(button_params); if (mAllowTextEntry) { //redo to compensate for button hack that leaves space for a character //unless it is a "minimal combobox"(drop down) mButton->setRightHPad(2); } addChild(mButton); LLScrollListCtrl::Params params = p.combo_list; params.name("ComboBox"); params.commit_callback.function(boost::bind(&LLComboBox::onItemSelected, this, _2)); params.visible(false); params.commit_on_keyboard_movement(false); mList = LLUICtrlFactory::create(params); addChild(mList); // Mouse-down on button will transfer mouse focus to the list // Grab the mouse-up event and make sure the button state is correct mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this)); for (LLComboBox::ItemParams item_params : p.items) { if (item_params.label.isProvided()) { item_params.columns.add().value(item_params.label()); } mList->addRow(item_params); } createLineEditor(p); mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this)); } void LLComboBox::initFromParams(const LLComboBox::Params& p) { LLUICtrl::initFromParams(p); if (!acceptsTextInput() && mLabel.empty()) { selectFirstItem(); } } // virtual bool LLComboBox::postBuild() { if (mControlVariable) { setValue(mControlVariable->getValue()); // selects the appropriate item } return true; } LLComboBox::~LLComboBox() { // children automatically deleted, including mMenu, mButton // explicitly disconect this signal, since base class destructor might fire top lost mTopLostSignalConnection.disconnect(); mImageLoadedConnection.disconnect(); LLUI::getInstance()->removePopup(this); } void LLComboBox::clear() { if (mTextEntry) { mTextEntry->setText(LLStringUtil::null); } mButton->setLabelSelected(LLStringUtil::null); mButton->setLabelUnselected(LLStringUtil::null); mList->deselectAllItems(); mLastSelectedIndex = -1; } void LLComboBox::onCommit() { if (LLScrollListItem* item = mList->getFirstSelected()) { if (mAllowTextEntry && mTextEntry) { // we have selected an existing item, blitz the manual text entry with // the properly capitalized item LLSD label = item->getColumn(0)->getValue(); mTextEntry->setValue(label); mTextEntry->setTentative(false); } setControlValue(item->getValue()); } else if (mAllowTextEntry) { setControlValue(mTextEntry->getValue()); } else { setControlValue(LLSD()); } LLUICtrl::onCommit(); } // virtual bool LLComboBox::isDirty() const { bool grubby = false; if (mList) { grubby = mList->isDirty(); } return grubby; } // virtual Clear dirty state void LLComboBox::resetDirty() { if (mList) { mList->resetDirty(); } } bool LLComboBox::itemExists(const std::string& name) { return mList->getItemByLabel(name); } std::vector LLComboBox::getAllData() const { return mList->getAllData(); } // add item "name" to menu LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, bool enabled) { LLScrollListItem* item = mList->addSimpleElement(name, pos); item->setEnabled(enabled); if (!mAllowTextEntry && mLabel.empty()) { if (mControlVariable) { setValue(mControlVariable->getValue()); // selects the appropriate item } else { selectFirstItem(); } } return item; } // add item "name" with a unique id to menu LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAddPosition pos, bool enabled ) { LLScrollListItem* item = mList->addSimpleElement(name, pos, id); item->setEnabled(enabled); if (!mAllowTextEntry && mLabel.empty()) { if (mControlVariable) { setValue(mControlVariable->getValue()); // selects the appropriate item } else { selectFirstItem(); } } return item; } // add item "name" with attached userdata LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddPosition pos, bool enabled ) { LLScrollListItem* item = mList->addSimpleElement(name, pos); item->setEnabled(enabled); item->setUserdata(userdata); if (!mAllowTextEntry && mLabel.empty()) { if (mControlVariable) { setValue(mControlVariable->getValue()); // selects the appropriate item } else { selectFirstItem(); } } return item; } // add item "name" with attached generic data LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosition pos, bool enabled ) { LLScrollListItem* item = mList->addSimpleElement(name, pos, value); item->setEnabled(enabled); if (!mAllowTextEntry && mLabel.empty()) { if (mControlVariable) { setValue(mControlVariable->getValue()); // selects the appropriate item } else { selectFirstItem(); } } return item; } LLScrollListItem* LLComboBox::addSeparator(EAddPosition pos) { return mList->addSeparator(pos); } void LLComboBox::sortByName(bool ascending) { mList->sortOnce(0, ascending); } // Choose an item with a given name in the menu. // Returns true if the item was found. bool LLComboBox::setSimple(const LLStringExplicit& name) { bool found = mList->selectItemByLabel(name, false); if (found) { setLabel(name); mLastSelectedIndex = mList->getFirstSelectedIndex(); } return found; } // virtual void LLComboBox::setValue(const LLSD& value) { if (LLScrollListItem* item = mList->getFirstSelected()) { LLSD item_value = item->getValue(); if (item_value.asString() == value.asString()) return; } if (mList->selectByValue(value)) { if (mList->getFirstSelected()) { updateLabel(); } mLastSelectedIndex = mList->getFirstSelectedIndex(); } else { mLastSelectedIndex = -1; } } const std::string LLComboBox::getSimple() const { const std::string res = getSelectedItemLabel(); if (res.empty() && mAllowTextEntry) { return mTextEntry->getText(); } return res; } const std::string LLComboBox::getSelectedItemLabel(S32 column) const { return mList->getSelectedItemLabel(column); } // virtual LLSD LLComboBox::getValue() const { LLScrollListItem* item = mList->getFirstSelected(); if(item) { return item->getValue(); } if (mAllowTextEntry) { return mTextEntry->getValue(); } return LLSD(); } void LLComboBox::setLabel(const LLStringExplicit& name) { if (mTextEntry) { mTextEntry->setText(name); if (mList->selectItemByLabel(name, false)) { mTextEntry->setTentative(false); mLastSelectedIndex = mList->getFirstSelectedIndex(); } else { mTextEntry->setTentative(mTextEntryTentative); } } if (!mAllowTextEntry) { mButton->setLabel(name); } } void LLComboBox::updateLabel() { // Update the combo editor with the selected // item label. if (mTextEntry) { mTextEntry->setText(getSelectedItemLabel()); mTextEntry->setTentative(false); } // If combo box doesn't allow text entry update // the combo button label. if (!mAllowTextEntry) { mButton->setLabel(getSelectedItemLabel()); } } bool LLComboBox::remove(const std::string& name) { bool found = mList->selectItemByLabel(name); if (found) { LLScrollListItem* item = mList->getFirstSelected(); if (item) { mList->deleteSingleItem(mList->getItemIndex(item)); } mLastSelectedIndex = mList->getFirstSelectedIndex(); } return found; } bool LLComboBox::remove(S32 index) { if (index < mList->getItemCount()) { mList->deleteSingleItem(index); setLabel(getSelectedItemLabel()); return true; } return false; } // Keyboard focus lost. void LLComboBox::onFocusLost() { hideList(); // if valid selection if (mAllowTextEntry && getCurrentIndex() != -1) { mTextEntry->selectAll(); } mButton->setForcePressedState(false); LLUICtrl::onFocusLost(); } void LLComboBox::setButtonVisible(bool visible) { mButton->setVisible(visible); if (mTextEntry) { LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); if (visible) { S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; } //mTextEntry->setRect(text_entry_rect); mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), true); } } bool LLComboBox::setCurrentByIndex(S32 index) { if (LLScrollListItem* item = mList->getItemByIndex(index)) { if (item->getEnabled()) { mList->selectItem(item, -1, true); LLSD::String label = item->getColumn(0)->getValue().asString(); if (mTextEntry) { mTextEntry->setText(label); mTextEntry->setTentative(false); } if (!mAllowTextEntry) { mButton->setLabel(label); } mLastSelectedIndex = index; return true; } } return false; } S32 LLComboBox::getCurrentIndex() const { if (LLScrollListItem* item = mList->getFirstSelected()) { return mList->getItemIndex(item); } return -1; } bool LLComboBox::selectNextItem() { S32 last_index = getItemCount() - 1; if (last_index < 0) return false; S32 current_index = getCurrentIndex(); if (current_index >= last_index) return false; S32 new_index = llmax(current_index, -1); while (++new_index <= last_index) { if (setCurrentByIndex(new_index)) return true; } return false; } bool LLComboBox::selectPrevItem() { S32 last_index = getItemCount() - 1; if (last_index < 0) return false; S32 current_index = getCurrentIndex(); if (!current_index) return false; S32 new_index = current_index > 0 ? current_index : last_index + 1; while (--new_index >= 0) { if (setCurrentByIndex(new_index)) return true; } return false; } void LLComboBox::setEnabledByValue(const LLSD& value, bool enabled) { if (LLScrollListItem* found = mList->getItem(value)) { found->setEnabled(enabled); } } void LLComboBox::createLineEditor(const LLComboBox::Params& p) { LLRect rect = getLocalRect(); if (mAllowTextEntry) { S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; S32 shadow_size = BTN_DROP_SHADOW; mButton->setRect(LLRect( getRect().getWidth() - llmax(8,arrow_width) - 2 * shadow_size, rect.mTop, rect.mRight, rect.mBottom)); mButton->setTabStop(false); mButton->setHAlign(LLFontGL::HCENTER); LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; // clear label on button std::string cur_label = mButton->getLabelSelected(); LLLineEditor::Params params = p.combo_editor; params.rect(text_entry_rect); params.default_text(LLStringUtil::null); params.max_length.bytes(mMaxChars); params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2)); params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1)); params.commit_on_focus_lost(false); params.follows.flags(FOLLOWS_ALL); params.label(mLabel); mTextEntry = LLUICtrlFactory::create (params); mTextEntry->setText(cur_label); mTextEntry->setIgnoreTab(true); addChild(mTextEntry); // clear label on button setLabel(LLStringUtil::null); mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT); } else { mButton->setRect(rect); mButton->setLabel(mLabel.getString()); if (mTextEntry) { mTextEntry->setVisible(false); } } } void LLComboBox::setLeftTextPadding(S32 pad) { S32 left_pad, right_pad; mTextEntry->getTextPadding(&left_pad, &right_pad); mTextEntry->setTextPadding(pad, right_pad); } void* LLComboBox::getCurrentUserdata() { LLScrollListItem* item = mList->getFirstSelected(); if (item) { return item->getUserdata(); } return NULL; } void LLComboBox::showList() { // Make sure we don't go off top of screen. LLCoordWindow window_size; getWindow()->getSize(&window_size); //HACK: shouldn't have to know about scale here mList->fitContents( 192, llfloor((F32)window_size.mY / LLUI::getScaleFactor().mV[VY]) - 50 ); // Make sure that we can see the whole list LLRect root_view_local; LLView* root_view = getRootView(); root_view->localRectToOtherView(root_view->getLocalRect(), &root_view_local, this); LLRect rect = mList->getRect(); S32 min_width = getRect().getWidth(); S32 max_width = llmax(min_width, MAX_COMBO_WIDTH); // make sure we have up to date content width metrics S32 list_width = llclamp(mList->calcMaxContentWidth(), min_width, max_width); if (mListPosition == BELOW) { if (rect.getHeight() <= -root_view_local.mBottom) { // Move rect so it hangs off the bottom of this view rect.setLeftTopAndSize(0, 0, list_width, rect.getHeight() ); } else { // stack on top or bottom, depending on which has more room if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight()) { // Move rect so it hangs off the bottom of this view rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight())); } else { // move rect so it stacks on top of this view (clipped to size of screen) rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); } } } else // ABOVE { if (rect.getHeight() <= root_view_local.mTop - getRect().getHeight()) { // move rect so it stacks on top of this view (clipped to size of screen) rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); } else { // stack on top or bottom, depending on which has more room if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight()) { // Move rect so it hangs off the bottom of this view rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight())); } else { // move rect so it stacks on top of this view (clipped to size of screen) rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); } } } mList->setOrigin(rect.mLeft, rect.mBottom); mList->reshape(rect.getWidth(), rect.getHeight()); mList->translateIntoRect(root_view_local); // Make sure we didn't go off bottom of screen S32 x, y; mList->localPointToScreen(0, 0, &x, &y); if (y < 0) { mList->translate(0, -y); } // NB: this call will trigger the focuslost callback which will hide the list, so do it first // before finally showing the list mList->setFocus(true); // Show the list and push the button down mButton->setToggleState(true); mList->setVisible(true); LLUI::getInstance()->addPopup(this); setUseBoundingRect(true); // updateBoundingRect(); } void LLComboBox::hideList() { if (mList->getVisible()) { // assert selection in list if (mAllowNewValues) { // mLastSelectedIndex = -1 means that we entered a new value, don't select // any of existing items in this case. if (mLastSelectedIndex >= 0) { mList->selectNthItem(mLastSelectedIndex); } } else if (mLastSelectedIndex >= 0) { mList->selectNthItem(mLastSelectedIndex); } mButton->setToggleState(false); mList->setVisible(false); mList->mouseOverHighlightNthItem(-1); setUseBoundingRect(false); LLUI::getInstance()->removePopup(this); // updateBoundingRect(); } } void LLComboBox::onButtonMouseDown() { if (!mList->getVisible()) { // this might change selection, so do it first prearrangeList(); // highlight the last selected item from the original selection before potentially selecting a new item // as visual cue to original value of combo box LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); if (last_selected_item) { mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); } if (mList->getItemCount() != 0) { showList(); } setFocus( true ); // pass mouse capture on to list if button is depressed if (mButton->hasMouseCapture()) { gFocusMgr.setMouseCapture(mList); // But keep the "pressed" look, which buttons normally lose when they // lose focus mButton->setForcePressedState(true); } } else { hideList(); } } void LLComboBox::onListMouseUp() { // In some cases this is the termination of a mouse click that started on // the button, so clear its pressed state mButton->setForcePressedState(false); } //------------------------------------------------------------------ // static functions //------------------------------------------------------------------ void LLComboBox::onItemSelected(const LLSD& data) { mLastSelectedIndex = getCurrentIndex(); if (mLastSelectedIndex != -1) { updateLabel(); if (mAllowTextEntry) { gFocusMgr.setKeyboardFocus(mTextEntry); mTextEntry->selectAll(); } } // hiding the list reasserts the old value stored in the text editor/dropdown button hideList(); // commit does the reverse, asserting the value in the list onCommit(); } bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) { std::string tool_tip; if (LLUICtrl::handleToolTip(x, y, mask)) { return true; } tool_tip = getToolTip(); if (tool_tip.empty()) { tool_tip = getSelectedItemLabel(); } if( !tool_tip.empty() ) { LLToolTipMgr::instance().show(LLToolTip::Params() .message(tool_tip) .sticky_rect(calcScreenRect())); } return true; } bool LLComboBox::handleKeyHere(KEY key, MASK mask) { bool result = false; if (hasFocus()) { if (mList->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) { hideList(); return true; } //give list a chance to pop up and handle key LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); if (last_selected_item) { // highlight the original selection before potentially selecting a new item mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); } result = mList->handleKeyHere(key, mask); // will only see return key if it is originating from line editor // since the dropdown button eats the key if (key == KEY_RETURN) { if (mask == MASK_NONE) { mOnReturnSignal(this, getValue()); } // don't show list and don't eat key input when committing // free-form text entry with RETURN since user already knows // what they are trying to select return false; } // if selection has changed, pop open list else if (mList->getLastSelectedItem() != last_selected_item || ((key == KEY_DOWN || key == KEY_UP) && mList->getCanSelect() && !mList->isEmpty())) { showList(); } } return result; } bool LLComboBox::handleUnicodeCharHere(llwchar uni_char) { bool result = false; if (gFocusMgr.childHasKeyboardFocus(this)) { // space bar just shows the list if (' ' != uni_char ) { LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); if (last_selected_item) { // highlight the original selection before potentially selecting a new item mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); } result = mList->handleUnicodeCharHere(uni_char); if (mList->getLastSelectedItem() != last_selected_item) { showList(); } } } return result; } // virtual bool LLComboBox::handleScrollWheel(S32 x, S32 y, S32 clicks) { if (mList->getVisible()) { return mList->handleScrollWheel(x, y, clicks); } if (mAllowTextEntry) // We might be editable { if (!mList->getFirstSelected()) // We aren't in the list, don't kill their text { return false; } } S32 current_index = getCurrentIndex(); if (clicks > 0) { for (S32 i = 0; i < clicks; ++i) { if (!selectNextItem()) break; } } else { for (S32 i = 0; i < -clicks; ++i) { if (!selectPrevItem()) break; } } S32 new_index = getCurrentIndex(); if (new_index != current_index) { prearrangeList(); onCommit(); return true; } return false; } void LLComboBox::setTextEntry(const LLStringExplicit& text) { if (mTextEntry) { mTextEntry->setText(text); mHasAutocompletedText = false; updateSelection(); } } void LLComboBox::setKeystrokeOnEsc(bool enable) { if (mTextEntry) { mTextEntry->setKeystrokeOnEsc(enable); } } void LLComboBox::onTextEntry(LLLineEditor* line_editor) { if (mTextEntryCallback != NULL) { (mTextEntryCallback)(line_editor, LLSD()); } KEY key = gKeyboard->currentKey(); if (key == KEY_BACKSPACE || key == KEY_DELETE) { if (mList->selectItemByLabel(line_editor->getText(), false)) { line_editor->setTentative(false); mLastSelectedIndex = mList->getFirstSelectedIndex(); } else { line_editor->setTentative(mTextEntryTentative); mList->deselectAllItems(); mLastSelectedIndex = -1; } if (mTextChangedCallback != NULL) { (mTextChangedCallback)(line_editor, LLSD()); } return; } if (key == KEY_LEFT || key == KEY_RIGHT) { return; } if (key == KEY_DOWN) { setCurrentByIndex(llmin(getItemCount() - 1, getCurrentIndex() + 1)); if (!mList->getVisible()) { prearrangeList(); if (mList->getItemCount() != 0) { showList(); } } line_editor->selectAll(); line_editor->setTentative(false); } else if (key == KEY_UP) { setCurrentByIndex(llmax(0, getCurrentIndex() - 1)); if (!mList->getVisible()) { prearrangeList(); if (mList->getItemCount() != 0) { showList(); } } line_editor->selectAll(); line_editor->setTentative(false); } else { // RN: presumably text entry updateSelection(); } if (mTextChangedCallback != NULL) { (mTextChangedCallback)(line_editor, LLSD()); } } void LLComboBox::updateSelection() { LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor()); // user-entered portion of string, based on assumption that any selected // text was a result of auto-completion LLWString user_wstring = mHasAutocompletedText ? left_wstring : mTextEntry->getWText(); std::string full_string = mTextEntry->getText(); // go ahead and arrange drop down list on first typed character, even // though we aren't showing it... some code relies on prearrange // callback to populate content if( mTextEntry->getWText().size() == 1 ) { prearrangeList(mTextEntry->getText()); } if (mList->selectItemByLabel(full_string, false)) { mTextEntry->setTentative(false); mLastSelectedIndex = mList->getFirstSelectedIndex(); } else if (mList->selectItemByPrefix(left_wstring, false)) { LLWString selected_item = utf8str_to_wstring(getSelectedItemLabel()); LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); mTextEntry->setText(wstring_to_utf8str(wtext)); mTextEntry->setSelection(static_cast(left_wstring.size()), static_cast(mTextEntry->getWText().size())); mTextEntry->endSelection(); mTextEntry->setTentative(false); mHasAutocompletedText = true; mLastSelectedIndex = mList->getFirstSelectedIndex(); } else // no matching items found { mList->deselectAllItems(); mTextEntry->setText(wstring_to_utf8str(user_wstring)); // removes text added by autocompletion mTextEntry->setTentative(mTextEntryTentative); mHasAutocompletedText = false; mLastSelectedIndex = -1; } } void LLComboBox::onTextCommit(const LLSD& data) { std::string text = mTextEntry->getText(); setSimple(text); onCommit(); mTextEntry->selectAll(); } void LLComboBox::setFocus(bool b) { LLUICtrl::setFocus(b); if (b) { mList->clearSearchString(); if (mList->getVisible()) { mList->setFocus(true); } } } void LLComboBox::prearrangeList(std::string filter) { if (mPrearrangeCallback) { mPrearrangeCallback(this, LLSD(filter)); } } //============================================================================ // ll::ui::SearchableControl functions //virtual std::string LLComboBox::_getSearchText() const { std::string res; if (mList) { // getAllData returns a full copy of content, might be a // better option to implement an mList->getSearchText(column) std::vector data = mList->getAllData(); std::vector::iterator iter = data.begin(); while (iter != data.end()) { LLScrollListCell* cell = (*iter)->getColumn(0); if (cell) { std::string whitelist_url = cell->getValue().asString(); res += cell->getValue().asString(); } iter++; } } return res + getToolTip(); } //virtual void LLComboBox::onSetHighlight() const { if (mButton) { mButton->ll::ui::SearchableControl::setHighlighted(ll::ui::SearchableControl::getHighlighted()); } } void LLComboBox::imageLoaded() { if (mAllowTextEntry) { LLRect rect = getLocalRect(); S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; S32 shadow_size = BTN_DROP_SHADOW; mButton->setRect(LLRect(getRect().getWidth() - llmax(8, arrow_width) - 2 * shadow_size, rect.mTop, rect.mRight, rect.mBottom)); if (mButton->getVisible()) { // recalculate field size if (mTextEntry) { LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); text_entry_rect.mRight -= llmax(8, arrow_width) + 2 * BTN_DROP_SHADOW; mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), true); } } } } //============================================================================ // LLCtrlListInterface functions S32 LLComboBox::getItemCount() const { return mList->getItemCount(); } void LLComboBox::addColumn(const LLSD& column, EAddPosition pos) { mList->clearColumns(); mList->addColumn(column, pos); } void LLComboBox::clearColumns() { mList->clearColumns(); } void LLComboBox::setColumnLabel(const std::string& column, const std::string& label) { mList->setColumnLabel(column, label); } LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata) { return mList->addElement(value, pos, userdata); } LLScrollListItem* LLComboBox::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) { return mList->addSimpleElement(value, pos, id); } void LLComboBox::clearRows() { mList->clearRows(); } void LLComboBox::sortByColumn(const std::string& name, bool ascending) { mList->sortByColumn(name, ascending); } //============================================================================ //LLCtrlSelectionInterface functions bool LLComboBox::setCurrentByID(const LLUUID& id) { bool found = mList->selectByID( id ); if (found) { setLabel(getSelectedItemLabel()); mLastSelectedIndex = mList->getFirstSelectedIndex(); } return found; } LLUUID LLComboBox::getCurrentID() const { return mList->getStringUUIDSelectedItem(); } bool LLComboBox::setSelectedByValue(const LLSD& value, bool selected) { bool found = mList->setSelectedByValue(value, selected); if (found) { setLabel(getSelectedItemLabel()); } return found; } LLSD LLComboBox::getSelectedValue() { return mList->getSelectedValue(); } bool LLComboBox::isSelected(const LLSD& value) const { return mList->isSelected(value); } bool LLComboBox::operateOnSelection(EOperation op) { if (op == OP_DELETE) { mList->deleteSelectedItems(); return true; } return false; } bool LLComboBox::operateOnAll(EOperation op) { if (op == OP_DELETE) { clearRows(); return true; } return false; } bool LLComboBox::selectItemRange( S32 first, S32 last ) { return mList->selectItemRange(first, last); } static LLDefaultChildRegistry::Register register_icons_combo_box("icons_combo_box"); LLIconsComboBox::Params::Params() : icon_column("icon_column", ICON_COLUMN), label_column("label_column", LABEL_COLUMN) {} LLIconsComboBox::LLIconsComboBox(const LLIconsComboBox::Params& p) : LLComboBox(p), mIconColumnIndex(p.icon_column), mLabelColumnIndex(p.label_column) {} const std::string LLIconsComboBox::getSelectedItemLabel(S32 column) const { mButton->setImageOverlay(LLComboBox::getSelectedItemLabel(mIconColumnIndex), mButton->getImageOverlayHAlign()); return LLComboBox::getSelectedItemLabel(mLabelColumnIndex); }