/** 
 * @file llcombobox.cpp
 * @brief LLComboBox base class
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

// 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 "llcontrol.h"
#include "llfocusmgr.h"
#include "lllineeditor.h"
#include "v2math.h"

// Globals
S32 LLCOMBOBOX_HEIGHT = 0;
S32 LLCOMBOBOX_WIDTH = 0;
 
LLComboBox::LLComboBox(	const LLString& name, const LLRect &rect, const LLString& label,
	void (*commit_callback)(LLUICtrl*,void*),
	void *callback_userdata,
	S32 list_width
	)
:	LLUICtrl(name, rect, TRUE, commit_callback, callback_userdata, 
			 FOLLOWS_LEFT | FOLLOWS_TOP),
	mDrawButton(TRUE),
	mTextEntry(NULL),
	mArrowImage(NULL),
	mAllowTextEntry(FALSE),
	mMaxChars(20),
	mTextEntryTentative(TRUE),
	mPrearrangeCallback( NULL ),
	mTextEntryCallback( NULL ),
	mListWidth(list_width)
{
	// For now, all comboboxes don't take keyboard focus when clicked.
	// This might change if it is part of a modal dialog.
	// mKeyboardFocusOnClick = FALSE;

	// Revert to standard behavior.  When this control's parent is hidden, it needs to
	// hide this ctrl--which won't just happen automatically since when LLComboBox is 
	// showing its list, it's also set to TopView.  When keyboard focus is cleared all
	// controls (including this one) know that they are no longer editing.
	mKeyboardFocusOnClick = TRUE;

	LLRect r;
	r.setOriginAndSize(0, 0, rect.getWidth(), rect.getHeight());

	// Always use text box 
	// Text label button
	mButton = new LLSquareButton("comboxbox button",
								 r, label, NULL, LLString::null,
								 &LLComboBox::onButtonClick, this);
	mButton->setFont(LLFontGL::sSansSerifSmall);
	mButton->setFollows(FOLLOWS_LEFT | FOLLOWS_BOTTOM | FOLLOWS_RIGHT);
	mButton->setHAlign( LLFontGL::LEFT );

	const S32 ARROW_WIDTH = 16;
	mButton->setRightHPad( ARROW_WIDTH );
	addChild(mButton);

	// Default size, will be set by arrange() call in button callback. 
	if (list_width == 0)
	{
		list_width = mRect.getWidth() + SCROLLBAR_SIZE;
	}
	r.setOriginAndSize(0, 16, list_width, 220);

	// disallow multiple selection
	mList = new LLScrollListCtrl(
		"ComboBox", r, 
		&LLComboBox::onItemSelected, this, FALSE);
	mList->setVisible(FALSE);
	mList->setBgWriteableColor( LLColor4(1,1,1,1) );
	mList->setCommitOnKeyboardMovement(FALSE);
	addChild(mList);

	LLRect border_rect(0, mRect.getHeight(), mRect.getWidth(), 0);
	mBorder = new LLViewBorder( "combo border", border_rect );
	addChild( mBorder );
	mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM);

	LLUUID arrow_image_id( LLUI::sAssetsGroup->getString("combobox_arrow.tga") );
	mArrowImage = LLUI::sImageProvider->getUIImageByID(arrow_image_id);
}


LLComboBox::~LLComboBox()
{
	// children automatically deleted, including mMenu, mButton
}

// virtual
LLXMLNodePtr LLComboBox::getXML(bool save_children) const
{
	LLXMLNodePtr node = LLUICtrl::getXML();

	// Attributes

	node->createChild("allow_text_entry", TRUE)->setBoolValue(mAllowTextEntry);

	node->createChild("max_chars", TRUE)->setIntValue(mMaxChars);

	// Contents

	std::vector<LLScrollListItem*> data_list = mList->getAllData();
	std::vector<LLScrollListItem*>::iterator data_itor;
	for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor)
	{
		LLScrollListItem* item = *data_itor;
		LLScrollListCell* cell = item->getColumn(0);
		if (cell)
		{
			LLXMLNodePtr item_node = node->createChild("combo_item", FALSE);
			LLSD value = item->getValue();
			item_node->createChild("value", TRUE)->setStringValue(value.asString());
			item_node->createChild("enabled", TRUE)->setBoolValue(item->getEnabled());
			item_node->setStringValue(cell->getText());
		}
	}

	return node;
}

// static
LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
	LLString name("combo_box");
	node->getAttributeString("name", name);

	LLString label("");
	node->getAttributeString("label", label);

	LLRect rect;
	createRect(node, rect, parent, LLRect());

	BOOL allow_text_entry = FALSE;
	node->getAttributeBOOL("allow_text_entry", allow_text_entry);

	S32 max_chars = 20;
	node->getAttributeS32("max_chars", max_chars);

	LLUICtrlCallback callback = NULL;

	LLComboBox* combo_box = new LLComboBox(name,
							rect, 
							label,
							callback,
							NULL);
	combo_box->setAllowTextEntry(allow_text_entry, max_chars);

	combo_box->initFromXML(node, parent);

	const LLString& contents = node->getValue();

	if (contents.find_first_not_of(" \n\t") != contents.npos)
	{
		llerrs << "Legacy combo box item format used! Please convert to <combo_item> tags!" << llendl;
	}
	else
	{
		LLXMLNodePtr child;
		for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
		{
			if (child->hasName("combo_item"))
			{
				LLString label = child->getTextContents();

				LLString value = label;
				child->getAttributeString("value", value);

				combo_box->add(label, LLSD(value) );
			}
		}
	}

	combo_box->selectFirstItem();

	return combo_box;
}

void LLComboBox::setEnabled(BOOL enabled)
{
	LLUICtrl::setEnabled(enabled);
	mButton->setEnabled(enabled);
}

//FIXME: these are all hacks to support the fact that the combobox has mouse capture
// so we can hide the list when we don't handle the mouse up event
BOOL LLComboBox::handleHover(S32 x, S32 y, MASK mask)
{
	if (mList->getVisible())
	{
		S32 local_x, local_y;
		LLView::localPointToOtherView(x, y, &local_x, &local_y, mList);
		if (mList->pointInView(local_x, local_y))
		{
			return mList->handleHover(local_x, local_y, mask);
		}
	}
	return LLUICtrl::handleHover(x, y, mask);
}

BOOL LLComboBox::handleMouseDown(S32 x, S32 y, MASK mask)
{
	if (mList->getVisible())
	{
		S32 local_x, local_y;
		LLView::localPointToOtherView(x, y, &local_x, &local_y, mList);
		if (mList->pointInView(local_x, local_y))
		{
			return mList->handleMouseDown(local_x, local_y, mask);
		}
	}
	BOOL has_focus_now = hasFocus();
	BOOL handled = LLUICtrl::handleMouseDown(x, y, mask);
	if (handled && !has_focus_now)
	{
		onFocusReceived();
	}

	return handled;
}	

BOOL LLComboBox::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
	if (mList->getVisible())
	{
		S32 local_x, local_y;
		LLView::localPointToOtherView(x, y, &local_x, &local_y, mList);
		if (mList->pointInView(local_x, local_y))
		{
			return mList->handleRightMouseDown(local_x, local_y, mask);
		}
	}
	return LLUICtrl::handleRightMouseDown(x, y, mask);
}

BOOL LLComboBox::handleRightMouseUp(S32 x, S32 y, MASK mask)
{
	if (mList->getVisible())
	{
		S32 local_x, local_y;
		LLView::localPointToOtherView(x, y, &local_x, &local_y, mList);
		if (mList->pointInView(local_x, local_y))
		{
			return mList->handleRightMouseUp(local_x, local_y, mask);
		}
	}
	return LLUICtrl::handleRightMouseUp(x, y, mask);
}

BOOL LLComboBox::handleDoubleClick(S32 x, S32 y, MASK mask)
{
	if (mList->getVisible())
	{
		S32 local_x, local_y;
		LLView::localPointToOtherView(x, y, &local_x, &local_y, mList);
		if (mList->pointInView(local_x, local_y))
		{
			return mList->handleDoubleClick(local_x, local_y, mask);
		}
	}
	return LLUICtrl::handleDoubleClick(x, y, mask);
}

BOOL LLComboBox::handleMouseUp(S32 x, S32 y, MASK mask)
{
	BOOL handled = childrenHandleMouseUp(x, y, mask) != NULL;

	if (!handled && mList->getVisible())
	{
		S32 local_x, local_y;
		LLView::localPointToOtherView(x, y, &local_x, &local_y, mList);
		if (mList->pointInView(local_x, local_y))
		{
			handled = mList->handleMouseUp(local_x, local_y, mask);
		}
	}

	if( !handled && gFocusMgr.getMouseCapture() == this )
	{
		// Mouse events that we didn't handle cause the list to be hidden.
		// Eat mouse event, regardless of where on the screen it happens.
		hideList();
		handled = TRUE;
	}

	return handled;
}

void LLComboBox::clear()
{ 
	if (mTextEntry)
	{
		mTextEntry->setText("");
	}
	mButton->setLabelSelected("");
	mButton->setLabelUnselected("");
	mButton->setDisabledLabel("");
	mButton->setDisabledSelectedLabel("");
	mList->deselectAllItems();
}

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);
	}
	LLUICtrl::onCommit();
}

// add item "name" to menu
void LLComboBox::add(const LLString& name, EAddPosition pos, BOOL enabled)
{
	mList->addSimpleItem(name, pos, enabled);
	mList->selectFirstItem();
}

// add item "name" with a unique id to menu
void LLComboBox::add(const LLString& name, const LLUUID& id, EAddPosition pos, BOOL enabled )
{
	mList->addSimpleItem(name, LLSD(id), pos, enabled);
	mList->selectFirstItem();
}

// add item "name" with attached userdata
void LLComboBox::add(const LLString& name, void* userdata, EAddPosition pos, BOOL enabled )
{
	LLScrollListItem* item = mList->addSimpleItem(name, pos, enabled);
	item->setUserdata( userdata );
	mList->selectFirstItem();
}

// add item "name" with attached generic data
void LLComboBox::add(const LLString& name, LLSD value, EAddPosition pos, BOOL enabled )
{
	mList->addSimpleItem(name, value, pos, enabled);
	mList->selectFirstItem();
}


void LLComboBox::sortByName()
{
	mList->sortByColumn(0, TRUE);
}


// Choose an item with a given name in the menu.
// Returns TRUE if the item was found.
BOOL LLComboBox::setSimple(const LLString& name)
{
	BOOL found = mList->selectSimpleItem(name, FALSE);

	if (found)
	{
		setLabel(name);
	}

	return found;
}

// virtual
void LLComboBox::setValue(const LLSD& value)
{
	BOOL found = mList->selectByValue(value);
	if (found)
	{
		LLScrollListItem* item = mList->getFirstSelected();
		if (item)
		{
			setLabel( mList->getSimpleSelectedItem() );
		}
	}
}

const LLString& LLComboBox::getSimple() const
{
	const LLString& res = mList->getSimpleSelectedItem();
	if (res.empty() && mAllowTextEntry)
	{
		return mTextEntry->getText();
	}
	else
	{
		return res;
	}
}

const LLString& LLComboBox::getSimpleSelectedItem(S32 column) const
{
	return mList->getSimpleSelectedItem(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 LLString& name)
{
	if ( mAllowTextEntry )
	{
		mTextEntry->setText(name);
		if (mList->selectSimpleItem(name, FALSE))
		{
			mTextEntry->setTentative(FALSE);
		}
		else
		{
			mTextEntry->setTentative(mTextEntryTentative);
		}
	}
	else
	{
		mButton->setLabelUnselected(name);
		mButton->setLabelSelected(name);
		mButton->setDisabledLabel(name);
		mButton->setDisabledSelectedLabel(name);
	}
}


BOOL LLComboBox::remove(const LLString& name)
{
	BOOL found = mList->selectSimpleItem(name);

	if (found)
	{
		LLScrollListItem* item = mList->getFirstSelected();
		if (item)
		{
			mList->deleteSingleItem(mList->getItemIndex(item));
		}
	}

	return found;
}

BOOL LLComboBox::remove(S32 index)
{
	if (index < mList->getItemCount())
	{
		mList->deleteSingleItem(index);
		return TRUE;
	}
	return FALSE;
}

// Keyboard focus lost.
void LLComboBox::onFocusLost()
{
	hideList();
	// if valid selection
	if (mAllowTextEntry && getCurrentIndex() != -1)
	{
		mTextEntry->selectAll();
	}
}

void LLComboBox::setButtonVisible(BOOL visible)
{
	mButton->setVisible(visible);
	mDrawButton = visible;
	if (mTextEntry)
	{
		LLRect text_entry_rect(0, mRect.getHeight(), mRect.getWidth(), 0);
		if (visible)
		{
			text_entry_rect.mRight -= mArrowImage->getWidth() + 2 * LLUI::sConfigGroup->getS32("DropShadowButton");
		}
		//mTextEntry->setRect(text_entry_rect);
		mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE);
	}
}

void LLComboBox::draw()
{
	if( getVisible() )
	{
		mBorder->setKeyboardFocusHighlight(hasFocus());

		mButton->setEnabled(mEnabled /*&& !mList->isEmpty()*/);

		// Draw children
		LLUICtrl::draw();

		if (mDrawButton)
		{
			// Paste the graphic on the right edge
			if (!mArrowImage.isNull())
			{
				S32 left = mRect.getWidth() - mArrowImage->getWidth() - LLUI::sConfigGroup->getS32("DropShadowButton");

				gl_draw_image( left, 0, mArrowImage,
					LLColor4::white);
			}
		}
	}
}

BOOL LLComboBox::setCurrentByIndex( S32 index )
{
	BOOL found = mList->selectNthItem( index );
	if (found)
	{
		setLabel(mList->getSimpleSelectedItem());
	}
	return found;
}

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


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->arrange( 192, llfloor((F32)window_size.mY / LLUI::sGLScaleFactor.mV[VY]) - 50 );

	// Move rect so it hangs off the bottom of this view
	LLRect rect = mList->getRect();

	rect.setLeftTopAndSize(0, 0, rect.getWidth(), rect.getHeight() );
	mList->setRect(rect);

	// Make sure that we can see the whole list
	LLRect floater_area_screen;
	LLRect floater_area_local;
	gFloaterView->getParent()->localRectToScreen( gFloaterView->getRect(), &floater_area_screen );
	screenRectToLocal( floater_area_screen, &floater_area_local );
	mList->translateIntoRect( floater_area_local, FALSE );

	// 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);
	}

	gFocusMgr.setMouseCapture( this, LLComboBox::onMouseCaptureLost );
	// NB: this call will trigger the focuslost callback which will hide the list, so do it first
	// before finally showing the list

	if (!mList->getFirstSelected())
	{
		// if nothing is selected, select the first item
		// so that the callback is not immediately triggered on setFocus()
		mList->selectFirstItem();
	}
	gFocusMgr.setKeyboardFocus(mList, onListFocusLost);

	// Show the list and push the button down
	mButton->setToggleState(TRUE);
	mList->setVisible(TRUE);
	
	gFocusMgr.setTopView(mList, LLComboBox::onTopViewLost );

}

void LLComboBox::hideList()
{
	mButton->setToggleState(FALSE);
	mList->setVisible(FALSE);
	mList->highlightNthItem(-1);

	if( gFocusMgr.getTopView() == mList )
	{
		gFocusMgr.setTopView(NULL, NULL);
	}

	if( gFocusMgr.getMouseCapture() == this )
	{
		gFocusMgr.setMouseCapture( NULL, NULL );
	}

	if( gFocusMgr.getKeyboardFocus() == mList )
	{
		if (mAllowTextEntry)
		{
			mTextEntry->setFocus(TRUE);
		}
		else
		{
			setFocus(TRUE);
		}
	}
}



//------------------------------------------------------------------
// static functions
//------------------------------------------------------------------

// static
void LLComboBox::onButtonClick(void *userdata)
{
	LLComboBox *self = (LLComboBox *)userdata;

	if (!self->mList->getVisible())
	{
		LLScrollListItem* last_selected_item = self->mList->getLastSelectedItem();
		if (last_selected_item)
		{
			// highlight the original selection before potentially selecting a new item
			self->mList->highlightNthItem(self->mList->getItemIndex(last_selected_item));
		}

		if( self->mPrearrangeCallback )
		{
			self->mPrearrangeCallback( self, self->mCallbackUserData );
		}

		if (self->mList->getItemCount() != 0)
		{
			self->showList();
		}

		if (self->mKeyboardFocusOnClick && !self->hasFocus())
		{
			self->setFocus( TRUE );
		}
	}
	else
	{
		// hide and release keyboard focus
		self->hideList();

		self->onCommit();
	}
}



// static
void LLComboBox::onItemSelected(LLUICtrl* item, void *userdata)
{
	// Note: item is the LLScrollListCtrl
	LLComboBox *self = (LLComboBox *) userdata;

	const LLString& name = self->mList->getSimpleSelectedItem();

	self->hideList();

	S32 cur_id = self->getCurrentIndex();
	if (cur_id != -1)
	{
		self->setLabel(self->mList->getSimpleSelectedItem());

		if (self->mAllowTextEntry)
		{
			self->mTextEntry->setText(name);
			self->mTextEntry->setTentative(FALSE);
			gFocusMgr.setKeyboardFocus(self->mTextEntry, NULL);
			self->mTextEntry->selectAll();
		}
		else
		{
			self->mButton->setLabelUnselected( name );
			self->mButton->setLabelSelected( name );
			self->mButton->setDisabledLabel( name );
			self->mButton->setDisabledSelectedLabel( name );
		}
	}
	self->onCommit();
}

// static
void LLComboBox::onTopViewLost(LLView* old_focus)
{
	LLComboBox *self = (LLComboBox *) old_focus->getParent();
	self->hideList();
}


// static
void LLComboBox::onMouseCaptureLost(LLMouseHandler*)
{
	// Can't hide the list here.  If the list scrolls off the screen,
	// and you click in the arrow buttons of the scroll bar, they must capture
	// the mouse to handle scrolling-while-mouse-down.
}

BOOL LLComboBox::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
{
	
    LLString tool_tip;

	if (LLUI::sShowXUINames)
	{
		tool_tip = mName;
	}
	else
	{
		tool_tip = mToolTipMsg;
	}

	if( getVisible() && pointInView( x, y ) ) 
	{
		if( !tool_tip.empty() )
		{
			msg = tool_tip;

			// Convert rect local to screen coordinates
			localPointToScreen( 
				0, 0, 
				&(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
			localPointToScreen(
				mRect.getWidth(), mRect.getHeight(),
				&(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) );
		}
		return TRUE;
	}
	return FALSE;
}

BOOL LLComboBox::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent)
{
	BOOL result = FALSE;
	if (gFocusMgr.childHasKeyboardFocus(this))
	{
		//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->highlightNthItem(mList->getItemIndex(last_selected_item));
		}
		result = mList->handleKeyHere(key, mask, FALSE);
		// if selection has changed, pop open list
		if (mList->getLastSelectedItem() != last_selected_item)
		{
			showList();
		}
	}
	return result;
}

BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
{
	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->highlightNthItem(mList->getItemIndex(last_selected_item));
			}
			result = mList->handleUnicodeCharHere(uni_char, called_from_parent);
			if (mList->getLastSelectedItem() != last_selected_item)
			{
				showList();
			}
		}
	}
	return result;
}

void LLComboBox::setAllowTextEntry(BOOL allow, S32 max_chars, BOOL set_tentative)
{
	LLRect rect( 0, mRect.getHeight(), mRect.getWidth(), 0);
	if (allow && !mAllowTextEntry)
	{
		S32 shadow_size = LLUI::sConfigGroup->getS32("DropShadowButton");
		mButton->setRect(LLRect( mRect.getWidth() - mArrowImage->getWidth() - 2 * shadow_size,
								rect.mTop, rect.mRight, rect.mBottom));
		mButton->setTabStop(FALSE);

		// clear label on button
		LLString cur_label = mButton->getLabelSelected();
		setLabel("");
		if (!mTextEntry)
		{
			LLRect text_entry_rect(0, mRect.getHeight(), mRect.getWidth(), 0);
			text_entry_rect.mRight -= mArrowImage->getWidth() + 2 * LLUI::sConfigGroup->getS32("DropShadowButton");
			mTextEntry = new LLLineEditor("combo_text_entry",
										text_entry_rect,
										"",
										LLFontGL::sSansSerifSmall,
										max_chars,
										onTextCommit,
										onTextEntry,
										NULL,
										this,
										NULL,		// prevalidate func
										LLViewBorder::BEVEL_NONE,
										LLViewBorder::STYLE_LINE,
										0);	// no border
			mTextEntry->setSelectAllonFocusReceived(TRUE);
			mTextEntry->setHandleEditKeysDirectly(TRUE);
			mTextEntry->setCommitOnFocusLost(FALSE);
			mTextEntry->setText(cur_label);
			mTextEntry->setIgnoreTab(TRUE);
			addChild(mTextEntry);
			mMaxChars = max_chars;
		}
		else
		{
			mTextEntry->setVisible(TRUE);
		}
	}
	else if (!allow && mAllowTextEntry)
	{
		mButton->setRect(rect);
		mButton->setTabStop(TRUE);

		if (mTextEntry)
		{
			mTextEntry->setVisible(FALSE);
		}
	}
	mAllowTextEntry = allow;
	mTextEntryTentative = set_tentative;	
}

void LLComboBox::setTextEntry(const LLString& text)
{
	if (mTextEntry)
	{
		mTextEntry->setText(text);
		updateSelection();
	}
}

//static 
void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data)
{
	LLComboBox* self = (LLComboBox*)user_data;

	if (self->mTextEntryCallback)
	{
		(*self->mTextEntryCallback)(line_editor, self->mCallbackUserData);
	}

	KEY key = gKeyboard->currentKey();
	if (key == KEY_BACKSPACE || 
		key == KEY_DELETE)
	{
		if (self->mList->selectSimpleItem(line_editor->getText(), FALSE))
		{
			line_editor->setTentative(FALSE);
		}
		else
		{
			line_editor->setTentative(self->mTextEntryTentative);
		}
		return;
	}

	if (key == KEY_LEFT || 
		key == KEY_RIGHT)
	{
		return;
	}

	if (key == KEY_DOWN)
	{
		self->setCurrentByIndex(llmin(self->getItemCount() - 1, self->getCurrentIndex() + 1));
		if (!self->mList->getVisible())
		{
			if( self->mPrearrangeCallback )
			{
				self->mPrearrangeCallback( self, self->mCallbackUserData );
			}

			if (self->mList->getItemCount() != 0)
			{
				self->showList();
			}
		}
		line_editor->selectAll();
		line_editor->setTentative(FALSE);
	}
	else if (key == KEY_UP)
	{
		self->setCurrentByIndex(llmax(0, self->getCurrentIndex() - 1));
		if (!self->mList->getVisible())
		{
			if( self->mPrearrangeCallback )
			{
				self->mPrearrangeCallback( self, self->mCallbackUserData );
			}

			if (self->mList->getItemCount() != 0)
			{
				self->showList();
			}
		}
		line_editor->selectAll();
		line_editor->setTentative(FALSE);
	}
	else
	{
		// RN: presumably text entry
		self->updateSelection();
	}
}

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 = mTextEntry->hasSelection() ? left_wstring : mTextEntry->getWText();
	LLString 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 )
	{
		if (mPrearrangeCallback)
		{
			mPrearrangeCallback( this, mCallbackUserData );
		}
	}

	if (mList->selectSimpleItem(full_string, FALSE))
	{
		mTextEntry->setTentative(FALSE);
	}
	else if (!mList->selectSimpleItemByPrefix(left_wstring, FALSE))
	{
		mList->deselectAllItems();
		mTextEntry->setText(wstring_to_utf8str(user_wstring));
		mTextEntry->setTentative(mTextEntryTentative);
	}
	else
	{
		LLWString selected_item = utf8str_to_wstring(mList->getSimpleSelectedItem());
		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);
	}
}

//static 
void LLComboBox::onTextCommit(LLUICtrl* caller, void* user_data)
{
	LLComboBox* self = (LLComboBox*)user_data;
	LLString text = self->mTextEntry->getText();
	self->setSimple(text);
	self->onCommit();
	self->mTextEntry->selectAll();
}

void LLComboBox::setFocus(BOOL b)
{
	LLUICtrl::setFocus(b);

	if (b)
	{
		mList->clearSearchString();
	}
}

//============================================================================
// 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 LLString& column, const LLString& 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 LLString& value, EAddPosition pos, const LLSD& id)
{
	return mList->addSimpleElement(value, pos, id);
}

void LLComboBox::clearRows()
{
	mList->clearRows();
}

void LLComboBox::sortByColumn(LLString name, BOOL ascending)
{
}

//============================================================================
//LLCtrlSelectionInterface functions

BOOL LLComboBox::setCurrentByID(const LLUUID& id)
{
	BOOL found = mList->selectByID( id );

	if (found)
	{
		setLabel(mList->getSimpleSelectedItem());
	}

	return found;
}

LLUUID LLComboBox::getCurrentID()
{
	return mList->getStringUUIDSelectedItem();
}
BOOL LLComboBox::setSelectedByValue(LLSD value, BOOL selected)
{
	BOOL found = mList->setSelectedByValue(value, selected);
	if (found)
	{
		setLabel(mList->getSimpleSelectedItem());
	}
	return found;
}

LLSD LLComboBox::getSimpleSelectedValue()
{
	return mList->getSimpleSelectedValue();
}

BOOL LLComboBox::isSelected(LLSD value)
{
	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;
}

//static 
void LLComboBox::onListFocusLost(LLUICtrl* old_focus)
{
	// if focus is going to nothing (user hit ESC), take it back
	LLComboBox* combo = (LLComboBox*)old_focus->getParent();
	combo->hideList();
	if (gFocusMgr.getKeyboardFocus() == NULL)
	{
		combo->focusFirstItem();
	}
}
