/**
 * @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<LLComboBox> 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<LLButton>(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<LLScrollListCtrl>(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 (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin();
        it != p.items.end();
        ++it)
    {
        LLScrollListItem::Params item_params = *it;
        if (it->label.isProvided())
        {
            item_params.columns.add().value(it->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 (mAllowTextEntry && getCurrentIndex() != -1)
    {
        // we have selected an existing item, blitz the manual text entry with
        // the properly capitalized item
        mTextEntry->setValue(getSimple());
        mTextEntry->setTentative(false);
    }
    setControlValue(getValue());
    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);
}

// 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)
{
    bool found = mList->selectByValue(value);
    if (found)
    {
        LLScrollListItem* item = mList->getFirstSelected();
        if (item)
        {
            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();
    }
    else
    {
        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();
    }
    else if (mAllowTextEntry)
    {
        return mTextEntry->getValue();
    }
    else
    {
        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 )
{
    bool found = mList->selectNthItem( index );
    if (found)
    {
        setLabel(getSelectedItemLabel());
        mLastSelectedIndex = index;
    }
    return found;
}

S32 LLComboBox::getCurrentIndex() const
{
    LLScrollListItem* item = mList->getFirstSelected();
    if( item )
    {
        return mList->getItemIndex( item );
    }
    return -1;
}

void LLComboBox::setEnabledByValue(const LLSD& value, bool enabled)
{
    LLScrollListItem *found = mList->getItem(value);
    if (found)
    {
        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<LLLineEditor> (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;
}

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(left_wstring.size(), 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<LLScrollListItem*> data = mList->getAllData();
        std::vector<LLScrollListItem*>::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<LLIconsComboBox> 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);
}