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

#include <algorithm>

#include "linden_common.h"
#include "llstl.h"
#include "llboost.h"

#include "llscrolllistctrl.h"

#include "indra_constants.h"

#include "llcheckboxctrl.h"
#include "llclipboard.h"
#include "llfocusmgr.h"
#include "llgl.h"
#include "llglheaders.h"
#include "llresmgr.h"
#include "llscrollbar.h"
#include "llstring.h"
#include "llui.h"
#include "lluictrlfactory.h"
#include "llwindow.h"
#include "llcontrol.h"
#include "llkeyboard.h"

const S32 LIST_BORDER_PAD = 2;		// white space inside the border and to the left of the scrollbar

U32 LLScrollListCtrl::sSortColumn = 1;
BOOL LLScrollListCtrl::sSortAscending = TRUE; 

// local structures & classes.
struct SortScrollListItem
{
	SortScrollListItem(const S32 sort_col, BOOL sort_ascending)
	{
		mSortCol = sort_col;
		sSortAscending = sort_ascending;
	}

	bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
	{
		const LLScrollListCell *cell1;
		const LLScrollListCell *cell2;
		
		cell1 = i1->getColumn(mSortCol);
		cell2 = i2->getColumn(mSortCol);
		
		S32 order = 1;
		if (!sSortAscending)
		{
			order = -1;
		}

		BOOL retval = FALSE;

		if (cell1 && cell2)
		{
			retval = ((order * LLString::compareDict(cell1->getText(),	cell2->getText())) < 0);
		}

		return (retval ? TRUE : FALSE);
	}

protected:
	S32 mSortCol;
	S32 sSortAscending;
};



//
// LLScrollListIcon
//
LLScrollListIcon::LLScrollListIcon(LLImageGL* icon, S32 width, LLUUID image_id) :
mIcon(icon), mImageUUID(image_id.getString())
{
	if (width)
	{
		mWidth = width;
	}
	else
	{
		mWidth = icon->getWidth();
	}
}

LLScrollListIcon::~LLScrollListIcon()
{
}

//
// LLScrollListCheck
//
LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width)
{
	mCheckBox = check_box;
	LLRect rect(mCheckBox->getRect());
	if (width)
	{
		
		rect.mRight = rect.mLeft + width;
		mCheckBox->setRect(rect);
		mWidth = width;
	}
	else
	{
		mWidth = rect.getWidth(); //check_box->getWidth();
	}
}

LLScrollListCheck::~LLScrollListCheck()
{
	delete mCheckBox;
}

void LLScrollListCheck::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const
{
	mCheckBox->draw();

}

BOOL LLScrollListCheck::handleClick()
{ 
	if ( mCheckBox->getEnabled() )
	{
		LLCheckBoxCtrl::onButtonPress(mCheckBox); 
	}
	return TRUE; 
}

//
// LLScrollListText
//
U32 LLScrollListText::sCount = 0;

LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLColor4& color, BOOL use_color, BOOL visible)
:	mText( text ),
	mFont( font ),
	mFontStyle( font_style ),
	mWidth( width ),
	mVisible( visible ),
	mHighlightChars( 0 )
{
	if (use_color)
	{
		mColor = new LLColor4();
		mColor->setVec(color);
	}
	else
	{
		mColor = NULL;
	}

	sCount++;

	// initialize rounded rect image
	if (!mRoundedRectImage)
	{
		mRoundedRectImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("rounded_square.tga")));
	}

	// Yes, that's four dots, because we want it to have a little
	// padding, in proportion to the font size.
	mEllipsisWidth = (S32)mFont->getWidth("....");
}

LLScrollListText::~LLScrollListText()
{
	sCount--;
	delete mColor;
}

void LLScrollListText::setText(const LLString& text)
{
	mText = text;
}

void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const
{
	// If the user has specified a small minimum width, use that.
	if (mWidth > 0 && mWidth < width)
	{
		width = mWidth;
	}

	const LLColor4* display_color;
	if (mColor)
	{
		display_color = mColor;
	}
	else
	{
		display_color = &color;
	}

	if (mHighlightChars > 0)
	{
		mRoundedRectImage->bind();
		glColor4fv(highlight_color.mV);
		gl_segmented_rect_2d_tex(-2, 
				llround(mFont->getLineHeight()) + 1, 
				mFont->getWidth(mText.getString(), 0, mHighlightChars) + 1, 
				1, 
				mRoundedRectImage->getWidth(), 
				mRoundedRectImage->getHeight(), 
				16);
	}

	// Try to draw the entire string
	F32 right_x;
	U32 string_chars = mText.length();
	U32 drawn_chars = mFont->render(mText.getWString(), 0, 0, 2,
									*display_color,
									LLFontGL::LEFT,
									LLFontGL::BOTTOM, 
									mFontStyle,
									string_chars, 
									width - mEllipsisWidth,
									&right_x, FALSE);

	// If we didn't get the whole string, abbreviate
	if (drawn_chars < string_chars && drawn_chars)
	{
		mFont->renderUTF8("...", 0, right_x, 0.f, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mFontStyle,
						  S32_MAX, S32_MAX, NULL, FALSE);
	}
}


LLScrollListItem::~LLScrollListItem()
{
	std::for_each(mColumns.begin(), mColumns.end(), DeletePointer());
}

BOOL LLScrollListItem::handleMouseDown(S32 x, S32 y, MASK mask)
{
	BOOL handled = FALSE;

	S32 left = 0;
	S32 right = 0;
	S32 width = 0;

	std::vector<LLScrollListCell *>::iterator iter = mColumns.begin();
	std::vector<LLScrollListCell *>::iterator end = mColumns.end();
	for ( ; iter != end; ++iter)
	{
		width = (*iter)->getWidth();
		right += width;
		if (left <= x && x < right )
		{
			handled = (*iter)->handleClick();
			break;
		}
		
		left += width;
	}
	return handled;
}

void LLScrollListItem::setNumColumns(S32 columns)
{
	S32 prev_columns = mColumns.size();
	if (columns < prev_columns)
	{
		std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer());
	}
	
	mColumns.resize(columns);

	for (S32 col = prev_columns; col < columns; ++col)
	{
		mColumns[col] = NULL;
	}
}

void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell )
{
	if (column < (S32)mColumns.size())
	{
		delete mColumns[column];
		mColumns[column] = cell;
	}
	else
	{
		llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl;
	}
}

LLString LLScrollListItem::getContentsCSV()
{
	LLString ret;

	S32 count = getNumColumns();
	for (S32 i=0; i<count; ++i)
	{
		ret += getColumn(i)->getText();
		if (i < count-1)
		{
			ret += ", ";
		}
	}

	return ret;
}

void LLScrollListItem::setEnabled(BOOL b)
{
	if (b != mEnabled)
	{
		std::vector<LLScrollListCell *>::iterator iter = mColumns.begin();
		std::vector<LLScrollListCell *>::iterator end = mColumns.end();
		for ( ; iter != end; ++iter)
		{
			(*iter)->setEnabled(b);
		}
		mEnabled = b;
	}
}

//---------------------------------------------------------------------------
// LLScrollListCtrl
//---------------------------------------------------------------------------

LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect,
	void (*commit_callback)(LLUICtrl* ctrl, void* userdata),
	void* callback_user_data,
	BOOL allow_multiple_selection,
	BOOL show_border
	)
 :	LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data),
	mLineHeight(0),
	mScrollLines(0),
	mPageLines(0),
	mHeadingHeight(20),
	mMaxSelectable(0),
	mHeadingFont(NULL),
	mAllowMultipleSelection( allow_multiple_selection ),
	mAllowKeyboardMovement(TRUE),
	mCommitOnKeyboardMovement(TRUE),
	mCommitOnSelectionChange(FALSE),
	mSelectionChanged(FALSE),
	mCanSelect(TRUE),
	mDisplayColumnButtons(FALSE),
	mCollapseEmptyColumns(FALSE),
	mIsPopup(FALSE),
	mMaxItemCount(INT_MAX), 
	//mItemCount(0),
	mBackgroundVisible( TRUE ),
	mDrawStripes(TRUE),
	mBgWriteableColor(	LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ),
	mBgReadOnlyColor(	LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ),
	mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ),
	mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ),
	mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ),
	mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ),
	mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ),
	mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ),
	mBorderThickness( 2 ),
	mOnDoubleClickCallback( NULL ),
	mOnMaximumSelectCallback( NULL ),
	mHighlightedItem(-1),
	mBorder(NULL),
	mDefaultColumn("SIMPLE"),
	mSearchColumn(0),
	mNumDynamicWidthColumns(0),
	mTotalStaticColumnWidth(0),
	mDrewSelected(FALSE)
{
	mItemListRect.setOriginAndSize(
		mBorderThickness + LIST_BORDER_PAD,
		mBorderThickness + LIST_BORDER_PAD,
		mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE,
		mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) );

	updateLineHeight();

	mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0;

	// Init the scrollbar
	LLRect scroll_rect;
	scroll_rect.setOriginAndSize( 
		mRect.getWidth() - mBorderThickness - SCROLLBAR_SIZE,
		mItemListRect.mBottom,
		SCROLLBAR_SIZE,
		mItemListRect.getHeight());
	mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect,
		LLScrollbar::VERTICAL,
		getItemCount(),
		mScrollLines,
		mPageLines,
		&LLScrollListCtrl::onScrollChange, this );
	mScrollbar->setFollowsRight();
	mScrollbar->setFollowsTop();
	mScrollbar->setFollowsBottom();
	mScrollbar->setEnabled( TRUE );
	mScrollbar->setVisible( TRUE );
	addChild(mScrollbar);

	// Border
	if (show_border)
	{
		LLRect border_rect( 0, mRect.getHeight(), mRect.getWidth(), 0 );
		mBorder = new LLViewBorder( "dlg border", border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 );
		addChild(mBorder);
	}

	mColumnPadding = 5;

	mLastSelected = NULL;
}

LLScrollListCtrl::~LLScrollListCtrl()
{
	std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());

	if( gEditMenuHandler == this )
	{
		gEditMenuHandler = NULL;
	}
}


BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count)
{
	if (max_count >= getItemCount())
	{
		mMaxItemCount = max_count;
	}
	return (max_count == mMaxItemCount);
}

S32 LLScrollListCtrl::isEmpty() const
{
	return mItemList.empty();
}

S32 LLScrollListCtrl::getItemCount() const
{
	return mItemList.size();
}

// virtual LLScrolListInterface function (was deleteAllItems)
void LLScrollListCtrl::clearRows()
{
	std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
	mItemList.clear();
	//mItemCount = 0;

	// Scroll the bar back up to the top.
	mScrollbar->setDocParams(0, 0);

	mScrollLines = 0;
	mLastSelected = NULL;
}


LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
{
	item_list::const_iterator iter;
	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item  = *iter;
		if (item->getSelected())
		{
			return item;
		}
	}
	return NULL;
}

std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
{
	std::vector<LLScrollListItem*> ret;
	item_list::const_iterator iter;
	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item  = *iter;
		if (item->getSelected())
		{
			ret.push_back(item);
		}
	}
	return ret;
}

S32 LLScrollListCtrl::getFirstSelectedIndex()
{
	S32 CurSelectedIndex = 0;
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item  = *iter;
		if (item->getSelected())
		{
			return CurSelectedIndex;
		}
		CurSelectedIndex++;
	}

	return -1;
}


LLScrollListItem* LLScrollListCtrl::getFirstData() const
{
	if (mItemList.size() == 0)
	{
		return NULL;
	}
	return mItemList[0];
}

std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
{
	std::vector<LLScrollListItem*> ret;
	item_list::const_iterator iter;
	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item  = *iter;
		ret.push_back(item);
	}
	return ret;
}


void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
{
	LLUICtrl::reshape( width, height, called_from_parent );

	S32 heading_size = (mDisplayColumnButtons ? mHeadingHeight : 0);

	mItemListRect.setOriginAndSize(
		mBorderThickness + LIST_BORDER_PAD,
		mBorderThickness + LIST_BORDER_PAD,
		mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE,
		mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) - heading_size );

	mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0;
	mScrollbar->setVisible(mPageLines < getItemCount());
	mScrollbar->setPageSize( mPageLines );

	updateColumns();
	updateColumnButtons();
}


// Attempt to size the control to show all items.
// Do not make larger than width or height.
void LLScrollListCtrl::arrange(S32 max_width, S32 max_height)
{
	S32 height = mLineHeight * (getItemCount() + 1);
	height = llmin( height, max_height );

	S32 width = mRect.getWidth();

	reshape( width, height );
}


LLRect LLScrollListCtrl::getRequiredRect()
{
	S32 height = mLineHeight * (getItemCount() + 1);
	S32 width = mRect.getWidth();

	return LLRect(0, height, width, 0);
}


BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos )
{
	BOOL not_too_big = getItemCount() < mMaxItemCount;
	if (not_too_big)
	{
		switch( pos )
		{
		case ADD_TOP:
			mItemList.push_front(item);
			break;
	
		case ADD_SORTED:
			LLScrollListCtrl::sSortColumn = 0;
			LLScrollListCtrl::sSortAscending = TRUE;
			mItemList.push_back(item);
			std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending));
			break;
	
		case ADD_BOTTOM:
			mItemList.push_back(item);
			break;
	
		default:
			llassert(0);
			mItemList.push_back(item);
			break;
		}
	
		updateLineHeight();
		mPageLines = mLineHeight ? mItemListRect.getHeight() / mLineHeight : 0;
		mScrollbar->setVisible(mPageLines < getItemCount());
		mScrollbar->setPageSize( mPageLines );
	
		mScrollbar->setDocSize( getItemCount() );
	}
	return not_too_big;
}


// Line height is the max height of all the cells in all the items.
void LLScrollListCtrl::updateLineHeight()
{
	const S32 ROW_PAD = 2;

	mLineHeight = 0;
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem *itemp = *iter;
		S32 num_cols = itemp->getNumColumns();
		S32 i = 0;
		for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
		{
			mLineHeight = llmax( mLineHeight, cell->getHeight() + ROW_PAD );
		}
	}
}

void LLScrollListCtrl::updateColumns()
{
	mColumnsIndexed.resize(mColumns.size());

	std::map<LLString, LLScrollListColumn>::iterator column_itor;
	for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
	{
		LLScrollListColumn *column = &column_itor->second;
		if (column->mRelWidth >= 0)
		{
			column->mWidth = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
		}
		else if (column->mDynamicWidth)
		{
			column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
 
		}
		mColumnsIndexed[column_itor->second.mIndex] = column;
	}
}

void LLScrollListCtrl::updateColumnButtons()
{
	std::map<LLString, LLScrollListColumn>::iterator column_itor;
	for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
	{
		LLScrollListColumn* column = &column_itor->second;
		LLButton *button = column->mButton;

		if (button)
		{
			mColumnsIndexed[column->mIndex] = column;

			S32 top = mItemListRect.mTop;
			S32 left = mItemListRect.mLeft;
			{
				std::map<LLString, LLScrollListColumn>::iterator itor;
				for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
				{
					if (itor->second.mIndex < column->mIndex &&
						itor->second.mWidth > 0)
					{
						left += itor->second.mWidth + mColumnPadding;
					}
				}
			}
			S32 right = left+column->mWidth;
			if (column->mIndex != (S32)mColumns.size()-1)
			{
				right += mColumnPadding;
			}
			LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
			button->setRect(temp_rect);
			button->setFont(mHeadingFont);
			button->setVisible(mDisplayColumnButtons);
		}
	}
}

void LLScrollListCtrl::setDisplayHeading(BOOL display)
{
	mDisplayColumnButtons = display;

	updateColumns();

	setHeadingHeight(mHeadingHeight);
}

void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
{
	mHeadingHeight = heading_height;

	reshape(mRect.getWidth(), mRect.getHeight());

	// Resize
	mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight());

	updateColumnButtons();
}

void LLScrollListCtrl::setHeadingFont(const LLFontGL* heading_font)
{
	mHeadingFont = heading_font;
	updateColumnButtons();
}

void LLScrollListCtrl::setCollapseEmptyColumns(BOOL collapse)
{
	mCollapseEmptyColumns = collapse;
}

BOOL LLScrollListCtrl::selectFirstItem()
{
	BOOL success = FALSE;

	// our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
	BOOL first_item = TRUE;

	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem *itemp = *iter;
		if( first_item && itemp->getEnabled() )
		{
			if (!itemp->getSelected())
			{
				selectItem(itemp);
			}
			success = TRUE;
		}
		else
		{
			deselectItem(itemp);
		}
		first_item = FALSE;
	}
	if (mCommitOnSelectionChange)
	{
		commitIfChanged();
	}
	return success;
}


BOOL LLScrollListCtrl::selectNthItem( S32 target_index )
{
	// Deselects all other items
	BOOL success = FALSE;
	S32 index = 0;

	target_index = llclamp(target_index, 0, (S32)mItemList.size() - 1);

	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem *itemp = *iter;
		if( target_index == index )
		{
			if( itemp->getEnabled() )
			{
				selectItem(itemp);
				success = TRUE;
			}
		}
		else
		{
			deselectItem(itemp);
		}
		index++;
	}

	if (mCommitOnSelectionChange)
	{
		commitIfChanged();
	}

	mSearchString.clear();

	return success;
}


void LLScrollListCtrl::swapWithNext(S32 index)
{
	if (index >= ((S32)mItemList.size() - 1))
	{
		// At end of list, doesn't do anything
		return;
	}
	LLScrollListItem *cur_itemp = mItemList[index];
	mItemList[index] = mItemList[index + 1];
	mItemList[index + 1] = cur_itemp;
}


void LLScrollListCtrl::swapWithPrevious(S32 index)
{
	if (index <= 0)
	{
		// At beginning of list, don't do anything
	}

	LLScrollListItem *cur_itemp = mItemList[index];
	mItemList[index] = mItemList[index - 1];
	mItemList[index - 1] = cur_itemp;
}


void LLScrollListCtrl::deleteSingleItem(S32 target_index)
{
	if (target_index >= (S32)mItemList.size())
	{
		return;
	}

	LLScrollListItem *itemp;
	itemp = mItemList[target_index];
	if (itemp == mLastSelected)
	{
		mLastSelected = NULL;
	}
	delete itemp;
	mItemList.erase(mItemList.begin() + target_index);
}

void LLScrollListCtrl::deleteSelectedItems()
{
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter < mItemList.end(); )
	{
		LLScrollListItem* itemp = *iter;
		if (itemp->getSelected())
		{
			delete itemp;
			iter = mItemList.erase(iter);
		}
		else
		{
			iter++;
		}
	}
	mLastSelected = NULL;
}

void LLScrollListCtrl::highlightNthItem(S32 target_index)
{
	if (mHighlightedItem != target_index)
	{
		mHighlightedItem = target_index;
	}
}

S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item )
{
	S32 index = 0;
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem *itemp = *iter;
		if (target_item == itemp)
		{
			return index;
		}
		index++;
	}
	return -1;
}

S32 LLScrollListCtrl::getItemIndex( LLUUID& target_id )
{
	S32 index = 0;
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem *itemp = *iter;
		if (target_id == itemp->getUUID())
		{
			return index;
		}
		index++;
	}
	return -1;
}

void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
{
	LLScrollListItem* prev_item = NULL;

	if (!getFirstSelected())
	{
		selectFirstItem();
	}
	else
	{
		item_list::iterator iter;
		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
		{
			LLScrollListItem* cur_item = *iter;

			if (cur_item->getSelected())
			{
				if (prev_item)
				{
					selectItem(prev_item, !extend_selection);
				}
				else
				{
					reportInvalidInput();
				}
				break;
			}

			prev_item = cur_item;
		}
	}

	if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
	{
		commitIfChanged();
	}

	mSearchString.clear();
}


void LLScrollListCtrl::selectNextItem( BOOL extend_selection)
{
	if (!getFirstSelected())
	{
		selectFirstItem();
	}
	else
	{
		item_list::iterator iter;
		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
		{
			LLScrollListItem* item = *iter;
			if (item->getSelected())
			{
				if (++iter != mItemList.end())
				{
					LLScrollListItem *next_item = *iter;
					if (next_item)
					{
						selectItem(next_item, !extend_selection);
					}
					else
					{
						reportInvalidInput();
					}
				}
				break;
			}
		}
	}

	if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
	{
		onCommit();
	}

	mSearchString.clear();
}



void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change)
{
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item = *iter;
		deselectItem(item);
	}

	if (mCommitOnSelectionChange && !no_commit_on_change)
	{
		commitIfChanged();
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// "Simple" interface: use this when you're creating a list that contains only unique strings, only
// one of which can be selected at a time.

LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, EAddPosition pos, BOOL enabled)
{
	LLScrollListItem* item = NULL;
	if (getItemCount() < mMaxItemCount)
	{
		// simple items have their LLSD data set to their label
		item = new LLScrollListItem( LLSD(item_text) );
		item->setEnabled(enabled);
		item->addColumn( item_text, gResMgr->getRes( LLFONT_SANSSERIF_SMALL ) );
		addItem( item, pos );
	}
	return item;
}


// Selects first enabled item of the given name.
// Returns false if item not found.
BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensitive)
{
	//RN: assume no empty items
	if (label.empty())
	{
		return FALSE;
	}

	LLString target_text = label;
	if (!case_sensitive)
	{
		LLString::toLower(target_text);
	}

	BOOL found = FALSE;

	item_list::iterator iter;
	S32 index = 0;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item = *iter;
		// Only select enabled items with matching names
		LLString item_text = item->getColumn(0)->getText();
		if (!case_sensitive)
		{
			LLString::toLower(item_text);
		}
		BOOL select = !found && item->getEnabled() && item_text == target_text;
		if (select)
		{
			selectItem(item);
		}
		found = found || select;
		index++;
	}

	if (mCommitOnSelectionChange)
	{
		commitIfChanged();
	}

	return found;
}


BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive)
{
	return selectSimpleItemByPrefix(utf8str_to_wstring(target), case_sensitive);
}

// Selects first enabled item that has a name where the name's first part matched the target string.
// Returns false if item not found.
BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive)
{
	BOOL found = FALSE;

	LLWString target_trimmed( target );
	S32 target_len = target_trimmed.size();
	
	if( 0 == target_len )
	{
		// Is "" a valid choice?
		item_list::iterator iter;
		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
		{
			LLScrollListItem* item = *iter;
			// Only select enabled items with matching names
			LLScrollListCell* cellp = item->getColumn(mSearchColumn);
			BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getText()[0]) : FALSE;
			if (select)
			{
				selectItem(item);
				found = TRUE;
				break;
			}
		}
	}
	else
	{
		if (!case_sensitive)
		{
			// do comparisons in lower case
			LLWString::toLower(target_trimmed);
		}

		for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
		{
			LLScrollListItem* item = *iter;

			// Only select enabled items with matching names
			LLScrollListCell* cellp = item->getColumn(mSearchColumn);
			if (!cellp)
			{
				continue;
			}
			LLWString item_label = utf8str_to_wstring(cellp->getText());
			if (!case_sensitive)
			{
				LLWString::toLower(item_label);
			}
			
			BOOL select = item->getEnabled() && !item_label.compare(0, target_len, target_trimmed);

			if (select)
			{
				selectItem(item);
				found = TRUE;
				break;
			}
		}
	}

	if (mCommitOnSelectionChange)
	{
		commitIfChanged();
	}

	return found;
}

const LLString& LLScrollListCtrl::getSimpleSelectedItem(S32 column) const
{
	LLScrollListItem* item;

	item = getFirstSelected();
	if (item)
	{
		return item->getColumn(column)->getText();
	}

	return LLString::null;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
// has an associated, unique UUID, and only one of which can be selected at a time.

LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const LLString& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled, S32 column_width)
{
	LLScrollListItem* item = NULL;
	if (getItemCount() < mMaxItemCount)
	{
		item = new LLScrollListItem( enabled, NULL, id );
		item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width);
		addItem( item, pos );
	}
	return item;
}

LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos, BOOL enabled, S32 column_width)
{
	LLScrollListItem* item = NULL;
	if (getItemCount() < mMaxItemCount)
	{
		item = new LLScrollListItem( sd );
		item->setEnabled(enabled);
		item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width);
		addItem( item, pos );
	}
	return item;
}


// Select the line or lines that match this UUID
BOOL LLScrollListCtrl::selectByID( const LLUUID& id )
{
	return selectByValue( LLSD(id) );
}

BOOL LLScrollListCtrl::setSelectedByValue(LLSD value, BOOL selected)
{
	BOOL found = FALSE;

	if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE);

	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item = *iter;
		if (item->getEnabled() && (item->getValue().asString() == value.asString()))
		{
			if (selected)
			{
				selectItem(item);
			}
			else
			{
				deselectItem(item);
			}
			found = TRUE;
			break;
		}
	}

	if (mCommitOnSelectionChange)
	{
		commitIfChanged();
	}

	return found;
}

BOOL LLScrollListCtrl::isSelected(LLSD value)
{
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item = *iter;
		if (item->getValue().asString() == value.asString())
		{
			return item->getSelected();
		}
	}
	return FALSE;
}

LLUUID LLScrollListCtrl::getStringUUIDSelectedItem()
{
	LLScrollListItem* item = getFirstSelected();

	if (item)
	{
		return item->getUUID();
	}

	return LLUUID::null;
}

LLSD LLScrollListCtrl::getSimpleSelectedValue()
{
	LLScrollListItem* item = getFirstSelected();

	if (item)
	{
		return item->getValue();
	}
	else
	{
		return LLSD();
	}
}

void LLScrollListCtrl::drawItems()
{
	S32 x = mItemListRect.mLeft;
	S32 y = mItemListRect.mTop - mLineHeight;

	S32 num_page_lines = mPageLines;

	LLRect item_rect;

	LLGLSUIDefault gls_ui;
	
	{
	
		S32 cur_x = x;
		S32 cur_y = y;
		
		mDrewSelected = FALSE;

		S32 line = 0;
		LLColor4 color;
		S32 max_columns = 0;

		item_list::iterator iter;
		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
		{
			LLScrollListItem* item = *iter;
			
			item_rect.setOriginAndSize( 
				cur_x, 
				cur_y, 
				mScrollbar->getVisible() ? mItemListRect.getWidth() : mItemListRect.getWidth() + mScrollbar->getRect().getWidth(),
				mLineHeight );

			lldebugs << mItemListRect.getWidth() << llendl;

			if (item->getSelected())
			{
				mDrewSelected = TRUE;
			}

			max_columns = llmax(max_columns, item->getNumColumns());

			LLRect bg_rect = item_rect;
			// pad background rectangle to separate it from contents
			bg_rect.stretch(LIST_BORDER_PAD, 0);

			if( mScrollLines <= line && line < mScrollLines + num_page_lines )
			{
				if( item->getSelected() && mCanSelect)
				{
					// Draw background of selected item
					LLGLSNoTexture no_texture;
					glColor4fv(mBgSelectedColor.mV);
					gl_rect_2d( bg_rect );
                    
					color = mFgSelectedColor;
				}
				else if (mHighlightedItem == line && mCanSelect)
				{
					LLGLSNoTexture no_texture;
					glColor4fv(mHighlightedColor.mV);
					gl_rect_2d( bg_rect );
					color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor);
				}
				else 
				{
					color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor);
					if (mDrawStripes && (line%2 == 0) && (max_columns > 1))
					{
						LLGLSNoTexture no_texture;
						glColor4fv(mBgStripeColor.mV);
						gl_rect_2d( bg_rect );
					}
				}

				S32 line_x = cur_x;
				{
					S32 num_cols = item->getNumColumns();
					S32 cur_col = 0;
					S32 dynamic_width = 0;
					S32 dynamic_remainder = 0;
					if(mNumDynamicWidthColumns > 0)
					{
						dynamic_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
						dynamic_remainder = (mItemListRect.getWidth() - mTotalStaticColumnWidth) % mNumDynamicWidthColumns;
					}
					for (LLScrollListCell* cell = item->getColumn(0); cur_col < num_cols; cell = item->getColumn(++cur_col))
					{
						S32 cell_width = cell->getWidth();
						if(mColumnsIndexed.size() > (U32)cur_col && mColumnsIndexed[cur_col] && mColumnsIndexed[cur_col]->mDynamicWidth)
						{							
							cell_width = dynamic_width + (--dynamic_remainder ? 1 : 0);
							cell->setWidth(cell_width);
						}
						// Two ways a cell could be hidden
						if (cell_width < 0
							|| !cell->getVisible()) continue;
						LLUI::pushMatrix();
						LLUI::translate((F32) cur_x, (F32) cur_y, 0.0f);
						S32 space_left = mItemListRect.mRight - cur_x;
						LLColor4 highlight_color = LLColor4::white;
						F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout");

						highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
						cell->drawToWidth( space_left, color, highlight_color );
						LLUI::popMatrix();
						
						cur_x += cell_width + mColumnPadding;

					}
				} 
				cur_x = line_x;			
				cur_y -= mLineHeight;
			}
			line++;
		}
	}
}


void LLScrollListCtrl::draw()
{
	if( getVisible() )
	{
		LLRect background(0, mRect.getHeight(), mRect.getWidth(), 0);
		// Draw background
		if (mBackgroundVisible)
		{
			LLGLSNoTexture no_texture;
			glColor4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV );
			gl_rect_2d(background);
		}

		drawItems();

		if (mBorder)
		{
			mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this);
		}

		LLUICtrl::draw();
	}
}

void LLScrollListCtrl::setEnabled(BOOL enabled)
{
	mCanSelect = enabled;
	setTabStop(enabled);
	mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
}

BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
	BOOL handled = FALSE;
	// Pretend the mouse is over the scrollbar
	handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
	return handled;
}


BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
{
	BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;

	if( !handled && mCanSelect)
	{
		LLScrollListItem* hit_item = hitItem(x, y);
		if( hit_item )
		{
			if( mAllowMultipleSelection )
			{
				if (mask & MASK_SHIFT)
				{
					if (mLastSelected == NULL)
					{
						selectItem(hit_item);
					}
					else
					{
						// Select everthing between mLastSelected and hit_item
						bool selecting = false;
						item_list::iterator itor;
						// If we multiselect backwards, we'll stomp on mLastSelected,
						// meaning that we never stop selecting until hitting max or
						// the end of the list.
						LLScrollListItem* lastSelected = mLastSelected;
						for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
						{
							if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
							{
								if(mOnMaximumSelectCallback)
								{
									mOnMaximumSelectCallback(mCallbackUserData);
								}
								break;
							}
							LLScrollListItem *item = *itor;
                            if (item == hit_item || item == lastSelected)
							{
								selectItem(item, FALSE);
								selecting = !selecting;
							}
							if (selecting)
							{
								selectItem(item, FALSE);
							}
						}
					}
				}
				else if (mask & MASK_CONTROL)
				{
					if (hit_item->getSelected())
					{
						deselectItem(hit_item);
					}
					else
					{
						if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
						{
							selectItem(hit_item, FALSE);
						}
						else
						{
							if(mOnMaximumSelectCallback)
							{
								mOnMaximumSelectCallback(mCallbackUserData);
							}
						}
					}
				}
				else
				{
					deselectAllItems(TRUE);
					selectItem(hit_item);
				}
			}
			else
			{
				selectItem(hit_item);
			}

			hit_item->handleMouseDown(x - mBorderThickness - LIST_BORDER_PAD, 
									  1, mask);
			// always commit on mousedown
			onCommit();
			mSelectionChanged = FALSE;

			// clear search string on mouse operations
			mSearchString.clear();
		}
		else
		{
			mLastSelected = NULL;
		}
	}

	gFocusMgr.setKeyboardFocus(this, NULL);

	return TRUE;
}

BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
{
	//BOOL handled = FALSE;
	if(getVisible())
	{
		// Offer the click to the children, even if we aren't enabled
		// so the scroll bars will work.
		if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
		{
			if( mCanSelect && mOnDoubleClickCallback )
			{
				mOnDoubleClickCallback( mCallbackUserData );
			}
		}
	}
	return TRUE;
}

LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
{
	// Excludes disabled items.
	LLScrollListItem* hit_item = NULL;

	LLRect item_rect;
	item_rect.setLeftTopAndSize( 
		mItemListRect.mLeft,
		mItemListRect.mTop,
		mItemListRect.getWidth(),
		mLineHeight );

	int num_page_lines = mPageLines;

	S32 line = 0;
	item_list::iterator iter;
	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem* item  = *iter;
		if( mScrollLines <= line && line < mScrollLines + num_page_lines )
		{
			if( item->getEnabled() && item_rect.pointInRect( x, y ) )
			{
				hit_item = item;
				break;
			}

			item_rect.translate(0, -mLineHeight);
		}
		line++;
	}

	return hit_item;
}


BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
{
	BOOL	handled = FALSE;

	if(getVisible())
	{
		if (mCanSelect)
		{
			LLScrollListItem* item = hitItem(x, y);
			if (item)
			{
				highlightNthItem(getItemIndex(item));
			}
			else
			{
				highlightNthItem(-1);
			}
		}

		handled = LLView::handleHover( x, y, mask );

		if( !handled )
		{
			// Opaque
			getWindow()->setCursor(UI_CURSOR_ARROW);
			lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl;		
			handled = TRUE;
		}
	}
	return handled;
}


BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent )
{
	BOOL handled = FALSE;

	// not called from parent means we have keyboard focus or a child does
	if (mCanSelect && !called_from_parent) 
	{
		// Ignore capslock
		mask = mask;

		if (mask == MASK_NONE)
		{
			switch(key)
			{
			case KEY_UP:
				if (mAllowKeyboardMovement || hasFocus())
				{
					// commit implicit in call
					selectPrevItem(FALSE);
					scrollToShowSelected();
					handled = TRUE;
				}
				break;
			case KEY_DOWN:
				if (mAllowKeyboardMovement || hasFocus())
				{
					// commit implicit in call
					selectNextItem(FALSE);
					scrollToShowSelected();
					handled = TRUE;
				}
				break;
			case KEY_PAGE_UP:
				if (mAllowKeyboardMovement || hasFocus())
				{
					selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
					scrollToShowSelected();
					if (mCommitOnKeyboardMovement
						&& !mCommitOnSelectionChange) 
					{
						onCommit();
					}
					handled = TRUE;
				}
				break;
			case KEY_PAGE_DOWN:
				if (mAllowKeyboardMovement || hasFocus())
				{
					selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
					scrollToShowSelected();
					if (mCommitOnKeyboardMovement
						&& !mCommitOnSelectionChange) 
					{
						onCommit();
					}
					handled = TRUE;
				}
				break;
			case KEY_HOME:
				if (mAllowKeyboardMovement || hasFocus())
				{
					selectFirstItem();
					scrollToShowSelected();
					if (mCommitOnKeyboardMovement
						&& !mCommitOnSelectionChange) 
					{
						onCommit();
					}
					handled = TRUE;
				}
				break;
			case KEY_END:
				if (mAllowKeyboardMovement || hasFocus())
				{
					selectNthItem(getItemCount() - 1);
					scrollToShowSelected();
					if (mCommitOnKeyboardMovement
						&& !mCommitOnSelectionChange) 
					{
						onCommit();
					}
					handled = TRUE;
				}
				break;
			case KEY_RETURN:
				// JC - Special case: Only claim to have handled it
				// if we're the special non-commit-on-move
				// type. AND we are visible
			  	if (!mCommitOnKeyboardMovement && mask == MASK_NONE && getVisible())
				{
					onCommit();
					mSearchString.clear();
					handled = TRUE;
				}
				break;
			case KEY_BACKSPACE:
				mSearchTimer.reset();
				if (mSearchString.size())
				{
					mSearchString.erase(mSearchString.size() - 1, 1);
				}
				if (mSearchString.empty())
				{
					if (getFirstSelected())
					{
						LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn);
						if (cellp)
						{
							cellp->highlightText(0);
						}
					}
				}
				else if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
				{
					// update search string only on successful match
					mSearchTimer.reset();

					// highlight current search on matching item
					LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn);
					if (cellp)
					{
						cellp->highlightText(mSearchString.size());
					}

					if (mCommitOnKeyboardMovement
						&& !mCommitOnSelectionChange) 
					{
						onCommit();
					}
				}
				break;
			default:
				break;
			}
		}
		// TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all
	}

	return handled;
}

BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
{
	if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
	{
		return FALSE;
	}

	// perform incremental search based on keyboard input
	if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout"))
	{
		mSearchString.clear();
	}

	// type ahead search is case insensitive
	uni_char = LLStringOps::toLower((llwchar)uni_char);

	if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
	{
		// update search string only on successful match
		mSearchString += uni_char;
		mSearchTimer.reset();

		// highlight current search on matching item
		LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn);
		if (cellp)
		{
			cellp->highlightText(mSearchString.size());
		}

		if (mCommitOnKeyboardMovement
			&& !mCommitOnSelectionChange) 
		{
			onCommit();
		}
	}
	// handle iterating over same starting character
	else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty())
	{
		// start from last selected item, in case we previously had a successful match against
		// duplicated characters ('AA' matches 'Aaron')
		item_list::iterator start_iter = mItemList.begin();
		S32 first_selected = getFirstSelectedIndex();

		// if we have a selection (> -1) then point iterator at the selected item
		if (first_selected > 0)
		{
			// point iterator to first selected item
			start_iter += first_selected;
		}

		// start search at first item after current selection
		item_list::iterator iter = start_iter;
		++iter;
		if (iter == mItemList.end())
		{
			iter = mItemList.begin();
		}

		// loop around once, back to previous selection
		while(iter != start_iter)
		{
			LLScrollListItem* item = *iter;

			LLScrollListCell* cellp = item->getColumn(mSearchColumn);
			if (cellp)
			{
				// Only select enabled items with matching first characters
				LLWString item_label = utf8str_to_wstring(cellp->getText());
				if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
				{
					selectItem(item);
					cellp->highlightText(1);
					mSearchTimer.reset();

					if (mCommitOnKeyboardMovement
						&& !mCommitOnSelectionChange) 
					{
						onCommit();
					}

					break;
				}
			}

			++iter;
			if (iter == mItemList.end())
			{
				iter = mItemList.begin();
			}
		}
	}

	// make sure selected item is on screen
	scrollToShowSelected();
	return TRUE;
}


void LLScrollListCtrl::reportInvalidInput()
{
	make_ui_sound("UISndBadKeystroke");
}

BOOL LLScrollListCtrl::isRepeatedChars(const LLWString& string) const
{
	if (string.empty())
	{
		return FALSE;
	}

	llwchar first_char = string[0];

	for (U32 i = 0; i < string.size(); i++)
	{
		if (string[i] != first_char)
		{
			return FALSE;
		}
	}

	return TRUE;
}

void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_item)
{
	if (!itemp) return;

	if (!itemp->getSelected())
	{
		if (mLastSelected)
		{
			LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn);
			if (cellp)
			{
				cellp->highlightText(0);
			}
		}
		if (select_single_item)
		{
			deselectAllItems(TRUE);
		}
		itemp->setSelected(TRUE);
		mLastSelected = itemp;
		mSelectionChanged = TRUE;
	}
}

void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
{
	if (!itemp) return;

	if (itemp->getSelected())
	{
		if (mLastSelected == itemp)
		{
			mLastSelected = NULL;
		}

		itemp->setSelected(FALSE);
		LLScrollListCell* cellp = itemp->getColumn(mSearchColumn);
		if (cellp)
		{
			cellp->highlightText(0);
		}
		mSelectionChanged = TRUE;
	}
}

void LLScrollListCtrl::commitIfChanged()
{
	if (mSelectionChanged)
	{
		mSelectionChanged = FALSE;
		onCommit();
	}
}

// Called by scrollbar
//static
void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata )
{
	LLScrollListCtrl* self = (LLScrollListCtrl*) userdata;
	self->mScrollLines = new_pos;
}


// First column is column 0
void  LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending)
{
	LLScrollListCtrl::sSortColumn = column;
	LLScrollListCtrl::sSortAscending = ascending;
	std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending));
}

void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending)
{
	std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(name);
	if (itor != mColumns.end())
	{
		sortByColumn((*itor).second.mIndex, ascending);
	}
}

S32 LLScrollListCtrl::getScrollPos()
{
	return mScrollbar->getDocPos();
}


void LLScrollListCtrl::setScrollPos( S32 pos )
{
	mScrollbar->setDocPos( pos );

	onScrollChange(mScrollbar->getDocPos(), mScrollbar, this);
}


void LLScrollListCtrl::scrollToShowSelected()
{
	S32 index = getFirstSelectedIndex();
	if (index < 0)
	{
		return;
	}

	LLScrollListItem* item = mItemList[index];
	if (!item)
	{
		// I don't THINK this should ever happen.
		return;
	}

	S32 lowest = mScrollLines;
	S32 highest = mScrollLines + mPageLines;

	if (index < lowest)
	{
		// need to scroll to show item
		setScrollPos(index);
	}
	else if (highest <= index)
	{
		setScrollPos(index - mPageLines + 1);
	}
}

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

	// Attributes

	node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection);

	node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL));

	node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnButtons);

	node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible);

	node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes);

	node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding);

	addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor");
	addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor");
	addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor");
	addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor");
	addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor");
	addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor");
	addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor");
	addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor");

	// Contents

	std::map<LLString, LLScrollListColumn>::const_iterator itor;
	std::vector<const LLScrollListColumn*> sorted_list;
	sorted_list.resize(mColumns.size());
	for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
	{
		sorted_list[itor->second.mIndex] = &itor->second;
	}

	std::vector<const LLScrollListColumn*>::iterator itor2;
	for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2)
	{
		LLXMLNodePtr child_node = node->createChild("column", FALSE);
		const LLScrollListColumn *column = *itor2;

		child_node->createChild("name", TRUE)->setStringValue(column->mName);
		child_node->createChild("label", TRUE)->setStringValue(column->mLabel);
		child_node->createChild("width", TRUE)->setIntValue(column->mWidth);
	}

	return node;
}

void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node)
{
	// James: This is not a good way to do colors. We need a central "UI style"
	// manager that sets the colors for ALL scroll lists, buttons, etc.

	LLColor4 color;
	if(node->hasAttribute("fg_unselected_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color);
		setFgUnselectedColor(color);
	}
	if(node->hasAttribute("fg_selected_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color);
		setFgSelectedColor(color);
	}
	if(node->hasAttribute("bg_selected_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color);
		setBgSelectedColor(color);
	}
	if(node->hasAttribute("fg_disable_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color);
		setFgDisableColor(color);
	}
	if(node->hasAttribute("bg_writeable_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color);
		setBgWriteableColor(color);
	}
	if(node->hasAttribute("bg_read_only_color"))
	{
		LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color);
		setReadOnlyBgColor(color);
	}
	if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color))
	{
		setBgStripeColor(color);
	}
	if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color))
	{
		setHighlightedColor(color);
	}

	if(node->hasAttribute("background_visible"))
	{
		BOOL background_visible;
		node->getAttributeBOOL("background_visible", background_visible);
		setBackgroundVisible(background_visible);
	}

	if(node->hasAttribute("draw_stripes"))
	{
		BOOL draw_stripes;
		node->getAttributeBOOL("draw_stripes", draw_stripes);
		setDrawStripes(draw_stripes);
	}
	
	if(node->hasAttribute("column_padding"))
	{
		S32 column_padding;
		node->getAttributeS32("column_padding", column_padding);
		setColumnPadding(column_padding);
	}
}

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

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

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

	BOOL draw_border = TRUE;
	node->getAttributeBOOL("draw_border", draw_border);

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

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

	S32 search_column = 0;
	node->getAttributeS32("search_column", search_column);

	LLUICtrlCallback callback = NULL;

	LLScrollListCtrl* scroll_list = new LLScrollListCtrl(
		name,
		rect,
		callback,
		NULL,
		multi_select,
		draw_border);
	
	scroll_list->setDisplayHeading(draw_heading);
	if (node->hasAttribute("heading_height"))
	{
		S32 heading_height;
		node->getAttributeS32("heading_height", heading_height);
		scroll_list->setHeadingHeight(heading_height);
	}
	if (node->hasAttribute("heading_font"))
	{
		LLString heading_font("");
		node->getAttributeString("heading_font", heading_font);
		LLFontGL* gl_font = LLFontGL::fontFromName(heading_font.c_str());
		scroll_list->setHeadingFont(gl_font);
	}
	scroll_list->setCollapseEmptyColumns(collapse_empty_columns);

	scroll_list->setScrollListParameters(node);

	scroll_list->initFromXML(node, parent);

	scroll_list->setSearchColumn(search_column);

	LLSD columns;
	S32 index = 0;
	LLXMLNodePtr child;
	S32 total_static = 0, num_dynamic = 0;
	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
	{
		if (child->hasName("column"))
		{
			LLString labelname("");
			child->getAttributeString("label", labelname);

			LLString columnname(labelname);
			child->getAttributeString("name", columnname);

			LLString sortname(columnname);
			child->getAttributeString("sort", sortname);

			LLString imagename;
			child->getAttributeString("image", imagename);

			BOOL columndynamicwidth = FALSE;
			child->getAttributeBOOL("dynamicwidth", columndynamicwidth);

			S32 columnwidth = -1;
			child->getAttributeS32("width", columnwidth);	

			if(!columndynamicwidth) total_static += columnwidth;
			else ++num_dynamic;

			F32 columnrelwidth = 0.f;
			child->getAttributeF32("relwidth", columnrelwidth);


			columns[index]["name"] = columnname;
			columns[index]["sort"] = sortname;
			columns[index]["image"] = imagename;
			columns[index]["label"] = labelname;
			columns[index]["width"] = columnwidth;
			columns[index]["relwidth"] = columnrelwidth;
			columns[index]["dynamicwidth"] = columndynamicwidth;
			index++;
		}
	}
	scroll_list->setNumDynamicColumns(num_dynamic);
	scroll_list->setTotalStaticColumnWidth(total_static);
	scroll_list->setColumnHeadings(columns);

	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
	{
		if (child->hasName("row"))
		{
			LLUUID id;
			child->getAttributeUUID("id", id);

			LLSD row;

			row["id"] = id;

			S32 column_idx = 0;
			LLXMLNodePtr row_child;
			for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling())
			{
				if (row_child->hasName("column"))
				{
					LLString value = row_child->getTextContents();

					LLString columnname("");
					row_child->getAttributeString("name", columnname);

					LLString font("");
					row_child->getAttributeString("font", font);

					LLString font_style("");
					row_child->getAttributeString("font-style", font_style);

					row["columns"][column_idx]["column"] = columnname;
					row["columns"][column_idx]["value"] = value;
					row["columns"][column_idx]["font"] = font;
					row["columns"][column_idx]["font-style"] = font_style;
					column_idx++;
				}
			}
			scroll_list->addElement(row);
		}
	}

	LLString contents = node->getTextContents();
	if (!contents.empty())
	{
		typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
		boost::char_separator<char> sep("\t\n");
		tokenizer tokens(contents, sep);
		tokenizer::iterator token_iter = tokens.begin();

		while(token_iter != tokens.end())
		{
			const char* line = token_iter->c_str();
			scroll_list->addSimpleItem(line);
			++token_iter;
		}
	}
	
	return scroll_list;
}

// LLEditMenuHandler functions

// virtual
void	LLScrollListCtrl::copy()
{
	LLString buffer;

	std::vector<LLScrollListItem*> items = getAllSelected();
	std::vector<LLScrollListItem*>::iterator itor;
	for (itor = items.begin(); itor != items.end(); ++itor)
	{
		buffer += (*itor)->getContentsCSV() + "\n";
	}
	gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length());
}

// virtual
BOOL	LLScrollListCtrl::canCopy()
{
	return (getFirstSelected() != NULL);
}

// virtual
void	LLScrollListCtrl::cut()
{
	copy();
	doDelete();
}

// virtual
BOOL	LLScrollListCtrl::canCut()
{
	return canCopy() && canDoDelete();
}

// virtual
void	LLScrollListCtrl::doDelete()
{
	// Not yet implemented
}

// virtual
BOOL	LLScrollListCtrl::canDoDelete()
{
	// Not yet implemented
	return FALSE;
}

// virtual
void	LLScrollListCtrl::selectAll()
{
	// Deselects all other items
	item_list::iterator iter;
	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
	{
		LLScrollListItem *itemp = *iter;
		if( itemp->getEnabled() )
		{
			selectItem(itemp, FALSE);
		}
	}

	if (mCommitOnSelectionChange)
	{
		commitIfChanged();
	}
}

// virtual
BOOL	LLScrollListCtrl::canSelectAll()
{
	return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable);
}

// virtual
void	LLScrollListCtrl::deselect()
{
	deselectAllItems();
}

// virtual
BOOL	LLScrollListCtrl::canDeselect()
{
	return getCanSelect();
}

void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
{
	LLString name = column["name"].asString();
	if (mColumns.size() == 0)
	{
		mDefaultColumn = 0;
	}
	if (mColumns.find(name) == mColumns.end())
	{
		// Add column
		mColumns[name] = LLScrollListColumn(column);
		LLScrollListColumn* new_column = &mColumns[name];
		new_column->mParentCtrl = this;
		new_column->mIndex = mColumns.size()-1;

		// Add button
		if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
		{
			if (new_column->mRelWidth >= 0)
			{
				new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth());
			}
			else if(new_column->mDynamicWidth)
			{
				new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
			}
			S32 top = mItemListRect.mTop;
			S32 left = mItemListRect.mLeft;
			{
				std::map<LLString, LLScrollListColumn>::iterator itor;
				for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
				{
					if (itor->second.mIndex < new_column->mIndex &&
						itor->second.mWidth > 0)
					{
						left += itor->second.mWidth + mColumnPadding;
					}
				}
			}
			LLString button_name = "btn_" + name;
			S32 right = left+new_column->mWidth;
			if (new_column->mIndex != (S32)mColumns.size()-1)
			{
				right += mColumnPadding;
			}
			LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
			new_column->mButton = new LLSquareButton(button_name, temp_rect, "", mHeadingFont, "", onClickColumn, NULL); 
			if(column["image"].asString() != "")
			{
				//new_column->mButton->setScaleImage(false);
				new_column->mButton->setImageSelected(column["image"].asString());				
				new_column->mButton->setImageUnselected(column["image"].asString());
			}
			else
			{
				new_column->mButton->setLabelSelected(new_column->mLabel);
				new_column->mButton->setLabelUnselected(new_column->mLabel);
			}
			//RN: although it might be useful to change sort order with the keyboard,
			// mixing tab stops on child items along with the parent item is not supported yet
			new_column->mButton->setTabStop(FALSE);
			addChild(new_column->mButton);
			new_column->mButton->setVisible(mDisplayColumnButtons);


			// Move scroll to front
			removeChild(mScrollbar);
			addChild(mScrollbar);
			
			new_column->mButton->setCallbackUserData(new_column);
		}
	}
	updateColumns();
}

// static
void LLScrollListCtrl::onClickColumn(void *userdata)
{
	LLScrollListColumn *info = (LLScrollListColumn*)userdata;
	if (!info) return;

	U32 column_index = info->mIndex;

	LLScrollListColumn* column = info->mParentCtrl->mColumnsIndexed[info->mIndex];
	if (column->mSortingColumn != column->mName)
	{
		if (info->mParentCtrl->mColumns.find(column->mSortingColumn) != info->mParentCtrl->mColumns.end())
		{
			LLScrollListColumn& info_redir = info->mParentCtrl->mColumns[column->mSortingColumn];
			column_index = info_redir.mIndex;
		}
	}

	// TomY TODO: shouldn't these be non-static members?
	bool ascending = true;
	if (column_index == LLScrollListCtrl::sSortColumn)
	{
		ascending = !LLScrollListCtrl::sSortAscending;
	}

	info->mParentCtrl->sortByColumn(column_index, ascending);
}

void LLScrollListCtrl::clearColumns()
{
	std::map<LLString, LLScrollListColumn>::iterator itor;
	for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
	{
		LLButton *button = itor->second.mButton;
		if (button)
		{
			removeChild(button);
			delete button;
		}
	}
	mColumns.clear();
}

void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label)
{
	std::map<LLString, LLScrollListColumn>::iterator itor = mColumns.find(column);
	if (itor != mColumns.end())
	{
		itor->second.mLabel = label;
		if (itor->second.mButton)
		{
			itor->second.mButton->setLabelSelected(label);
			itor->second.mButton->setLabelUnselected(label);
		}
	}
}

void LLScrollListCtrl::setColumnHeadings(LLSD headings)
{
	mColumns.clear();
	LLSD::array_const_iterator itor;
	for (itor = headings.beginArray(); itor != headings.endArray(); ++itor)
	{
		addColumn(*itor);
	}
}

LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata)
{
	// ID
	LLSD id = value["id"];

	LLScrollListItem *new_item = new LLScrollListItem(id, userdata);
	if (value.has("enabled"))
	{
		new_item->setEnabled( value["enabled"].asBoolean() );
	}

	new_item->setNumColumns(mColumns.size());

	// Add any columns we don't already have
	LLSD columns = value["columns"];
	LLSD::array_const_iterator itor;
	for (itor = columns.beginArray(); itor != columns.endArray(); ++itor)
	{
		LLString column = (*itor)["column"].asString();

		if (mColumns.size() == 0)
		{
			mDefaultColumn = 0;
		}
		std::map<LLString, LLScrollListColumn>::iterator column_itor = mColumns.find(column);
		if (column_itor == mColumns.end())
		{
			LLSD new_column;
			new_column["name"] = column;
			new_column["label"] = column;
			new_column["width"] = 0;
			addColumn(new_column);
			column_itor = mColumns.find(column);
			new_item->setNumColumns(mColumns.size());
		}

		S32 index = column_itor->second.mIndex;
		S32 width = column_itor->second.mWidth;

		LLSD value = (*itor)["value"];
		LLString fontname = (*itor)["font"].asString();
		LLString fontstyle = (*itor)["font-style"].asString();
		LLString type = (*itor)["type"].asString();

		const LLFontGL *font = gResMgr->getRes(fontname);
		if (!font)
		{
			font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL );
		}
		U8 font_style = LLFontGL::getStyleFromString(fontstyle);

		if (type == "icon")
		{
			LLUUID image_id = value.asUUID();
			LLImageGL* icon = LLUI::sImageProvider->getUIImageByID(image_id);
			new_item->setColumn(index, new LLScrollListIcon(icon, width, image_id));
		}
		else if (type == "checkbox")
		{
			LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(value.asString(),
														LLRect(0, 0, width, width), "label");
			new_item->setColumn(index, new LLScrollListCheck(ctrl,width));
		}
		else
		{
			new_item->setColumn(index, new LLScrollListText(value.asString(), font, width, font_style));
		}
	}

	S32 num_columns = mColumns.size();
	for (S32 column = 0; column < num_columns; ++column)
	{
		if (new_item->getColumn(column) == NULL)
		{
			LLScrollListColumn* column_ptr = mColumnsIndexed[column];
			new_item->setColumn(column, new LLScrollListText("", gResMgr->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL));
		}
	}

	addItem(new_item, pos);

	return new_item;
}

LLScrollListItem* LLScrollListCtrl::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id)
{
	LLSD entry_id = id;

	if (id.isUndefined())
	{
		entry_id = value;
	}

	LLScrollListItem *new_item = new LLScrollListItem(entry_id);

	const LLFontGL *font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL );

	new_item->addColumn(value, font, getRect().getWidth());

	addItem(new_item, pos);
	return new_item;
}

void LLScrollListCtrl::setValue(const LLSD& value )
{
	LLSD::array_const_iterator itor;
	for (itor = value.beginArray(); itor != value.endArray(); ++itor)
	{
		addElement(*itor);
	}
}

LLSD LLScrollListCtrl::getValue() const
{
	LLScrollListItem *item = getFirstSelected();
	if (!item) return LLSD();
	return item->getValue();
}

BOOL LLScrollListCtrl::operateOnSelection(EOperation op)
{
	if (op == OP_DELETE)
	{
		deleteSelectedItems();
		return TRUE;
	}
	else if (op == OP_DESELECT)
	{
		deselectAllItems();
	}
	return FALSE;
}

BOOL LLScrollListCtrl::operateOnAll(EOperation op)
{
	if (op == OP_DELETE)
	{
		clearRows();
		return TRUE;
	}
	else if (op == OP_DESELECT)
	{
		deselectAllItems();
	}
	else if (op == OP_SELECT)
	{
		selectAll();
	}
	return FALSE;
}
//virtual 
void LLScrollListCtrl::setFocus(BOOL b)
{
	mSearchString.clear();
	// for tabbing into pristine scroll lists (Finder)
	if (!getFirstSelected())
	{
		selectFirstItem();
		onCommit();
	}
	LLUICtrl::setFocus(b);
}
//virtual 
void LLScrollListCtrl::onFocusLost()
{
	if (mIsPopup)
	{
		if (getParent())
		{
			getParent()->onFocusLost();
		}
	}
}
