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

#include "linden_common.h"

#include "lltabcontainer.h"

#include "llfocusmgr.h"
#include "llfontgl.h"
#include "llgl.h"

#include "llbutton.h"
#include "llrect.h"
#include "llpanel.h"
#include "llresmgr.h"
#include "llkeyboard.h"
#include "llresizehandle.h"
#include "llui.h"
#include "lltextbox.h"
#include "llcontrol.h"
#include "llcriticaldamp.h"
#include "lluictrlfactory.h"

#include "lltabcontainervertical.h"

#include "llglheaders.h"

const F32 SCROLL_STEP_TIME = 0.4f;
const S32 TAB_PADDING = 15;
const S32 TABCNTR_TAB_MIN_WIDTH = 60;
const S32 TABCNTR_TAB_MAX_WIDTH = 150;
const S32 TABCNTR_TAB_PARTIAL_WIDTH = 12;	// When tabs are parially obscured, how much can you still see.
const S32 TABCNTR_TAB_HEIGHT = 16;
const S32 TABCNTR_ARROW_BTN_SIZE = 16;
const S32 TABCNTR_BUTTON_PANEL_OVERLAP = 1;  // how many pixels the tab buttons and tab panels overlap.
const S32 TABCNTR_TAB_H_PAD = 4;


LLTabContainerCommon::LLTabContainerCommon( 
	const LLString& name, const LLRect& rect, 
	TabPosition pos, 
	void(*close_callback)(void*), void* callback_userdata,
	BOOL bordered )
	: 
	LLPanel(name, rect, bordered),
	mCurrentTabIdx(-1),
	mScrolled(FALSE),
	mScrollPos(0),
	mScrollPosPixels(0),
	mMaxScrollPos(0),
	mCloseCallback( close_callback ),
	mCallbackUserdata( callback_userdata ),
	mTitleBox(NULL),
	mTopBorderHeight(LLPANEL_BORDER_WIDTH),
	mTabPosition(pos)
{ 
	setMouseOpaque(FALSE);
}


LLTabContainerCommon::LLTabContainerCommon( 
	const LLString& name,
	const LLString& rect_control,
	TabPosition pos,
	void(*close_callback)(void*), void* callback_userdata,
	BOOL bordered )
	: 
	LLPanel(name, rect_control, bordered),
	mCurrentTabIdx(-1),
	mScrolled(FALSE),
	mScrollPos(0),
	mScrollPosPixels(0),
	mMaxScrollPos(0),
	mCloseCallback( close_callback ),
	mCallbackUserdata( callback_userdata ),
	mTitleBox(NULL),
	mTopBorderHeight(LLPANEL_BORDER_WIDTH),
	mTabPosition(pos)
{
	setMouseOpaque(FALSE);
}


LLTabContainerCommon::~LLTabContainerCommon()
{
	std::for_each(mTabList.begin(), mTabList.end(), DeletePointer());
}

LLView* LLTabContainerCommon::getChildByName(const LLString& name, BOOL recurse) const
{
	tuple_list_t::const_iterator itor;
	for (itor = mTabList.begin(); itor != mTabList.end(); ++itor)
	{
		LLPanel *panel = (*itor)->mTabPanel;
		if (panel->getName() == name)
		{
			return panel;
		}
	}
	if (recurse)
	{
		for (itor = mTabList.begin(); itor != mTabList.end(); ++itor)
		{
			LLPanel *panel = (*itor)->mTabPanel;
			LLView *child = panel->getChildByName(name, recurse);
			if (child)
			{
				return child;
			}
		}
	}
	return LLView::getChildByName(name, recurse);
}

void LLTabContainerCommon::addPlaceholder(LLPanel* child, const LLString& label)
{
	addTabPanel(child, label, FALSE, NULL, NULL, 0, TRUE);
}

void LLTabContainerCommon::removeTabPanel(LLPanel* child)
{
	BOOL has_focus = gFocusMgr.childHasKeyboardFocus(this);

	// If the tab being deleted is the selected one, select a different tab.
	for(std::vector<LLTabTuple*>::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
	{
		LLTabTuple* tuple = *iter;
		if( tuple->mTabPanel == child )
		{
 			removeChild( tuple->mButton );
 			delete tuple->mButton;

 			removeChild( tuple->mTabPanel );
// 			delete tuple->mTabPanel;
			
			mTabList.erase( iter );
			delete tuple;

			break;
		}
	}
	if (mCurrentTabIdx >= (S32)mTabList.size())
	{
		mCurrentTabIdx = mTabList.size()-1;
	}
	selectTab(mCurrentTabIdx);
	if (has_focus)
	{
		LLPanel* panelp = getPanelByIndex(mCurrentTabIdx);
		if (panelp)
		{
			panelp->setFocus(TRUE);
		}
	}

	updateMaxScrollPos();
}

void LLTabContainerCommon::deleteAllTabs()
{
	// Remove all the tab buttons and delete them.  Also, unlink all the child panels.
	for(std::vector<LLTabTuple*>::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
	{
		LLTabTuple* tuple = *iter;

		removeChild( tuple->mButton );
		delete tuple->mButton;

 		removeChild( tuple->mTabPanel );
// 		delete tuple->mTabPanel;
	}

	// Actually delete the tuples themselves
	std::for_each(mTabList.begin(), mTabList.end(), DeletePointer());
	mTabList.clear();
	
	// And there isn't a current tab any more
	mCurrentTabIdx = -1;
}


LLPanel* LLTabContainerCommon::getCurrentPanel()
{
	if (mCurrentTabIdx < 0 || mCurrentTabIdx >= (S32) mTabList.size()) return NULL;
		
	return mTabList[mCurrentTabIdx]->mTabPanel;
}

LLTabContainerCommon::LLTabTuple* LLTabContainerCommon::getTabByPanel(LLPanel* child)
{
	for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
	{
		LLTabTuple* tuple = *iter;
		if( tuple->mTabPanel == child )
		{
			return tuple;
		}
	}
	return NULL;
}

void LLTabContainerCommon::setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*, bool))
{
	LLTabTuple* tuplep = getTabByPanel(tab);
	if (tuplep)
	{
		tuplep->mOnChangeCallback = on_tab_clicked;
	}
}

void LLTabContainerCommon::setTabUserData(LLPanel* tab, void* userdata)
{
	LLTabTuple* tuplep = getTabByPanel(tab);
	if (tuplep)
	{
		tuplep->mUserData = userdata;
	}
}

S32 LLTabContainerCommon::getCurrentPanelIndex()
{
	return mCurrentTabIdx;
}

S32 LLTabContainerCommon::getTabCount()
{
	return mTabList.size();
}

LLPanel* LLTabContainerCommon::getPanelByIndex(S32 index)
{
	if (index >= 0 && index < (S32)mTabList.size())
	{
		return mTabList[index]->mTabPanel;
	}
	return NULL;
}

S32 LLTabContainerCommon::getIndexForPanel(LLPanel* panel)
{
	for (S32 index = 0; index < (S32)mTabList.size(); index++)
	{
		if (mTabList[index]->mTabPanel == panel)
		{
			return index;
		}
	}
	return -1;
}

S32 LLTabContainerCommon::getPanelIndexByTitle(const LLString& title)
{
	for (S32 index = 0 ; index < (S32)mTabList.size(); index++)
	{
		if (title == mTabList[index]->mButton->getLabelSelected())
		{
			return index;
		}
	}
	return -1;
}

LLPanel *LLTabContainerCommon::getPanelByName(const LLString& name)
{
	for (S32 index = 0 ; index < (S32)mTabList.size(); index++)
	{
		LLPanel *panel = mTabList[index]->mTabPanel;
		if (name == panel->getName())
		{
			return panel;
		}
	}
	return NULL;
}


void LLTabContainerCommon::scrollNext()
{
	// No wrap
	if( mScrollPos < mMaxScrollPos )
	{
		mScrollPos++;
	}
}

void LLTabContainerCommon::scrollPrev()
{
	// No wrap
	if( mScrollPos > 0 )
	{
		mScrollPos--;
	}
}

void LLTabContainerCommon::enableTabButton(S32 which, BOOL enable)
{
	if (which >= 0 && which < (S32)mTabList.size())
	{
		mTabList[which]->mButton->setEnabled(enable);
	}
}

BOOL LLTabContainerCommon::selectTabPanel(LLPanel* child)
{
	S32 idx = 0;
	for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
	{
		LLTabTuple* tuple = *iter;
		if( tuple->mTabPanel == child )
		{
			return selectTab( idx );
		}
		idx++;
	}
	return FALSE;
}

BOOL LLTabContainerCommon::selectTabByName(const LLString& name)
{
	LLPanel* panel = getPanelByName(name);
	if (!panel)
	{
		llwarns << "LLTabContainerCommon::selectTabByName("
			<< name << ") failed" << llendl;
		return FALSE;
	}

	BOOL result = selectTabPanel(panel);
	return result;
}


void LLTabContainerCommon::selectFirstTab()
{
	selectTab( 0 );
}


void LLTabContainerCommon::selectLastTab()
{
	selectTab( mTabList.size()-1 );
}


void LLTabContainerCommon::selectNextTab()
{
	BOOL tab_has_focus = FALSE;
	if (mCurrentTabIdx >= 0 && mTabList[mCurrentTabIdx]->mButton->hasFocus())
	{
		tab_has_focus = TRUE;
	}
	S32 idx = mCurrentTabIdx+1;
	if (idx >= (S32)mTabList.size())
		idx = 0;
	while (!selectTab(idx) && idx != mCurrentTabIdx)
	{
		idx = (idx + 1 ) % (S32)mTabList.size();
	}

	if (tab_has_focus)
	{
		mTabList[idx]->mButton->setFocus(TRUE);
	}
}

void LLTabContainerCommon::selectPrevTab()
{
	BOOL tab_has_focus = FALSE;
	if (mCurrentTabIdx >= 0 && mTabList[mCurrentTabIdx]->mButton->hasFocus())
	{
		tab_has_focus = TRUE;
	}
	S32 idx = mCurrentTabIdx-1;
	if (idx < 0)
		idx = mTabList.size()-1;
	while (!selectTab(idx) && idx != mCurrentTabIdx)
	{
		idx = idx - 1;
		if (idx < 0)
			idx = mTabList.size()-1;
	}
	if (tab_has_focus)
	{
		mTabList[idx]->mButton->setFocus(TRUE);
	}
}	


void LLTabContainerCommon::reshape(S32 width, S32 height, BOOL called_from_parent)
{
	LLPanel::reshape( width, height, called_from_parent );
	updateMaxScrollPos();
}

// static 
void LLTabContainerCommon::onTabBtn( void* userdata )
{
	LLTabTuple* tuple = (LLTabTuple*) userdata;
	LLTabContainerCommon* self = tuple->mTabContainer;
	self->selectTabPanel( tuple->mTabPanel );
	
	if( tuple->mOnChangeCallback )
	{
		tuple->mOnChangeCallback( tuple->mUserData, true );
	}

	tuple->mTabPanel->setFocus(TRUE);
}

// static 
void LLTabContainerCommon::onCloseBtn( void* userdata )
{
	LLTabContainer* self = (LLTabContainer*) userdata;
	if( self->mCloseCallback )
	{
		self->mCloseCallback( self->mCallbackUserdata );
	}
}

// static 
void LLTabContainerCommon::onNextBtn( void* userdata )
{
	// Scroll tabs to the left
	LLTabContainer* self = (LLTabContainer*) userdata;
	if (!self->mScrolled)
	{
		self->scrollNext();
	}
	self->mScrolled = FALSE;
}

// static 
void LLTabContainerCommon::onNextBtnHeld( void* userdata )
{
	LLTabContainer* self = (LLTabContainer*) userdata;
	if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME)
	{
		self->mScrollTimer.reset();
		self->scrollNext();
		self->mScrolled = TRUE;
	}
}

// static 
void LLTabContainerCommon::onPrevBtn( void* userdata )
{
	LLTabContainer* self = (LLTabContainer*) userdata;
	if (!self->mScrolled)
	{
		self->scrollPrev();
	}
	self->mScrolled = FALSE;
}

// static 
void LLTabContainerCommon::onPrevBtnHeld( void* userdata )
{
	LLTabContainer* self = (LLTabContainer*) userdata;
	if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME)
	{
		self->mScrollTimer.reset();
		self->scrollPrev();
		self->mScrolled = TRUE;
	}
}

BOOL LLTabContainerCommon::getTabPanelFlashing(LLPanel *child)
{
	LLTabTuple* tuple = getTabByPanel(child);
	if( tuple )
	{
		return tuple->mButton->getFlashing();
	}
	return FALSE;
}

void LLTabContainerCommon::setTabPanelFlashing(LLPanel* child, BOOL state )
{
	LLTabTuple* tuple = getTabByPanel(child);
	if( tuple )
	{
		tuple->mButton->setFlashing( state );
	}
}

void LLTabContainerCommon::setTitle(const LLString& title)
{
	if (mTitleBox)
	{
		mTitleBox->setText( title );
	}
}

const LLString LLTabContainerCommon::getPanelTitle(S32 index)
{
	if (index >= 0 && index < (S32)mTabList.size())
	{
		LLButton* tab_button = mTabList[index]->mButton;
		return tab_button->getLabelSelected();
	}
	return LLString::null;
}

void LLTabContainerCommon::setTopBorderHeight(S32 height)
{
	mTopBorderHeight = height;
}

// Change the name of the button for the current tab.
void LLTabContainerCommon::setCurrentTabName(const LLString& name)
{
	// Might not have a tab selected
	if (mCurrentTabIdx < 0) return;

	mTabList[mCurrentTabIdx]->mButton->setLabelSelected(name);
	mTabList[mCurrentTabIdx]->mButton->setLabelUnselected(name);
}

LLView* LLTabContainerCommon::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
	LLString name("tab_container");
	node->getAttributeString("name", name);

	// Figure out if we are creating a vertical or horizontal tab container.
	bool is_vertical = false;
	LLTabContainer::TabPosition tab_position = LLTabContainer::TOP;
	if (node->hasAttribute("tab_position"))
	{
		LLString tab_position_string;
		node->getAttributeString("tab_position", tab_position_string);
		LLString::toLower(tab_position_string);

		if ("top" == tab_position_string)
		{
			tab_position = LLTabContainer::TOP;
			is_vertical = false;
		}
		else if ("bottom" == tab_position_string)
		{
			tab_position = LLTabContainer::BOTTOM;
			is_vertical = false;
		}
		else if ("left" == tab_position_string)
		{
			is_vertical = true;
		}
	}
	BOOL border = FALSE;
	node->getAttributeBOOL("border", border);

	// Create the correct container type.
	LLTabContainerCommon* 	tab_container = NULL;

	if (is_vertical)
	{
		// Vertical tabs can specify tab width
		U32 tab_width = TABCNTRV_TAB_WIDTH;
		if (node->hasAttribute("tab_width"))
		{
			node->getAttributeU32("tab_width", tab_width);
		}

		tab_container = new LLTabContainerVertical(name,
				LLRect::null,
				NULL,
				NULL,
				tab_width,
				border);

	}
	else // horizontal tab container
	{
		// Horizontal tabs can have a title (?)
		LLString title(LLString::null);
		if (node->hasAttribute("title"))
		{
			node->getAttributeString("title", title);
		}

		tab_container = new LLTabContainer(name,
				LLRect::null,
				tab_position,
				NULL,
				NULL,
				title,
				border);
		
		if(node->hasAttribute("tab_min_width"))
		{
			S32	minTabWidth=0;
			node->getAttributeS32("tab_min_width",minTabWidth);
			((LLTabContainer*)tab_container)->setMinTabWidth(minTabWidth); 
		}
		if(node->hasAttribute("tab_max_width"))
		{
			S32	maxTabWidth=0;
			node->getAttributeS32("tab_max_width",maxTabWidth);
			((LLTabContainer*)tab_container)->setMaxTabWidth(maxTabWidth); 
		}
	}
	
	tab_container->setPanelParameters(node, parent);

	if (LLFloater::getFloaterHost())
	{
		LLFloater::getFloaterHost()->setTabContainer(tab_container);
	}

	//parent->addChild(tab_container);

	// Add all tab panels.
	LLXMLNodePtr child;
	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
	{
		LLView *control = factory->createCtrlWidget(tab_container, child);
		if (control && control->isPanel())
		{
			LLPanel* panelp = (LLPanel*)control;
			LLString label;
			child->getAttributeString("label", label);
			if (label.empty())
			{
				label = panelp->getLabel();
			}
			BOOL placeholder = FALSE;
			child->getAttributeBOOL("placeholder", placeholder);
			tab_container->addTabPanel(panelp, label.c_str(), false,
									   NULL, NULL, 0, placeholder);
		}
	}

	tab_container->selectFirstTab();

	tab_container->postBuild();

	tab_container->initButtons(); // now that we have the correct rect
	
	return tab_container;
}

void LLTabContainerCommon::insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point)
{
	switch(insertion_point)
	{
	case START:
		// insert the new tab in the front of the list
		mTabList.insert(mTabList.begin(), tuple);
		break;
	case RIGHT_OF_CURRENT:
		// insert the new tab after the current tab
		{
		tuple_list_t::iterator current_iter = mTabList.begin() + mCurrentTabIdx + 1;
		mTabList.insert(current_iter, tuple);
		}
		break;
	case END:
	default:
		mTabList.push_back( tuple );
	}
}


LLTabContainer::LLTabContainer( 
	const LLString& name, const LLRect& rect, TabPosition pos,
	void(*close_callback)(void*), void* callback_userdata,
	const LLString& title, BOOL bordered )
	: 
	LLTabContainerCommon(name, rect, pos, close_callback, callback_userdata, bordered),
	mLeftArrowBtn(NULL),
	mRightArrowBtn(NULL),
	mRightTabBtnOffset(0),
	mMinTabWidth(TABCNTR_TAB_MIN_WIDTH),
	mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH),
	mTotalTabWidth(0)
{
	initButtons( );
}

LLTabContainer::LLTabContainer( 
	const LLString& name, const LLString& rect_control, TabPosition pos,
	void(*close_callback)(void*), void* callback_userdata,
	const LLString& title, BOOL bordered )
	: 
	LLTabContainerCommon(name, rect_control, pos, close_callback, callback_userdata, bordered),
	mLeftArrowBtn(NULL),
	mRightArrowBtn(NULL),
	mRightTabBtnOffset(0),
	mMinTabWidth(TABCNTR_TAB_MIN_WIDTH),
	mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH),
	mTotalTabWidth(0)
{
	initButtons( );
}

void LLTabContainer::initButtons()
{
	// Hack:
	if (mRect.getHeight() == 0 || mLeftArrowBtn)
	{
		return; // Don't have a rect yet or already got called
	}
	
	LLString out_id;
	LLString in_id;

	S32 arrow_fudge = 1;		//  match new art better 

	// tabs on bottom reserve room for resize handle (just in case)
	if (mTabPosition == BOTTOM)
	{
		mRightTabBtnOffset = RESIZE_HANDLE_WIDTH;
	}

	// Left and right scroll arrows (for when there are too many tabs to show all at once).
	S32 btn_top = (mTabPosition == TOP ) ? mRect.getHeight() - mTopBorderHeight : TABCNTR_ARROW_BTN_SIZE + 1;

	LLRect left_arrow_btn_rect;
	left_arrow_btn_rect.setLeftTopAndSize( LLPANEL_BORDER_WIDTH+1, btn_top + arrow_fudge, TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE );

	S32 right_pad = TABCNTR_ARROW_BTN_SIZE + LLPANEL_BORDER_WIDTH + 1;
	LLRect right_arrow_btn_rect;
	right_arrow_btn_rect.setLeftTopAndSize( mRect.getWidth() - mRightTabBtnOffset - right_pad,
											btn_top + arrow_fudge,
											TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE );

	out_id = "UIImgBtnScrollLeftOutUUID";
	in_id = "UIImgBtnScrollLeftInUUID";
	mLeftArrowBtn = new LLButton(
		"Left Arrow", left_arrow_btn_rect,
		out_id, in_id, "",
		&LLTabContainer::onPrevBtn, this, LLFontGL::sSansSerif );
	mLeftArrowBtn->setHeldDownCallback(onPrevBtnHeld);
	mLeftArrowBtn->setFollowsLeft();
	mLeftArrowBtn->setSaveToXML(false);
	mLeftArrowBtn->setTabStop(FALSE);
	addChild(mLeftArrowBtn);

	out_id = "UIImgBtnScrollRightOutUUID";
	in_id = "UIImgBtnScrollRightInUUID";
	mRightArrowBtn = new LLButton(
		"Right Arrow", right_arrow_btn_rect,
		out_id, in_id, "",
		&LLTabContainer::onNextBtn, this,
		LLFontGL::sSansSerif);
	mRightArrowBtn->setFollowsRight();
	mRightArrowBtn->setHeldDownCallback(onNextBtnHeld);
	mRightArrowBtn->setSaveToXML(false);
	mRightArrowBtn->setTabStop(FALSE);
	addChild(mRightArrowBtn);

	if( mTabPosition == TOP )
	{
		mRightArrowBtn->setFollowsTop();
		mLeftArrowBtn->setFollowsTop();
	}
	else
	{
		mRightArrowBtn->setFollowsBottom();
		mLeftArrowBtn->setFollowsBottom();
	}

	// set default tab group to be panel contents
	mDefaultTabGroup = 1;
}

LLTabContainer::~LLTabContainer()
{
}

void LLTabContainer::addTabPanel(LLPanel* child, 
								 const LLString& label, 
								 BOOL select, 
								 void (*on_tab_clicked)(void*, bool), 
								 void* userdata,
								 S32 indent,
								 BOOL placeholder,
								 eInsertionPoint insertion_point)
{
	if (child->getParent() == this)
	{
		// already a child of mine
		return;
	}
	const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL );

	// Store the original label for possible xml export.
	child->setLabel(label);
	LLString trimmed_label = label;
	LLString::trim(trimmed_label);

	S32 button_width = llclamp(font->getWidth(trimmed_label) + TAB_PADDING, mMinTabWidth, mMaxTabWidth);

	// Tab panel
	S32 tab_panel_top;
	S32 tab_panel_bottom;
	if( LLTabContainer::TOP == mTabPosition )
	{
		tab_panel_top = mRect.getHeight() - mTopBorderHeight - (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP);	
		tab_panel_bottom = LLPANEL_BORDER_WIDTH;
	}
	else
	{
		tab_panel_top = mRect.getHeight() - mTopBorderHeight;
		tab_panel_bottom = (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP);  // Run to the edge, covering up the border
	}
	
	LLRect tab_panel_rect(
		LLPANEL_BORDER_WIDTH, 
		tab_panel_top,
		mRect.getWidth()-LLPANEL_BORDER_WIDTH,
		tab_panel_bottom );

	child->setFollowsAll();
	child->translate( tab_panel_rect.mLeft - child->getRect().mLeft, tab_panel_rect.mBottom - child->getRect().mBottom);
	child->reshape( tab_panel_rect.getWidth(), tab_panel_rect.getHeight(), TRUE );
	child->setBackgroundVisible( FALSE );  // No need to overdraw
	// add this child later

	child->setVisible( FALSE );  // Will be made visible when selected

	mTotalTabWidth += button_width;

	// Tab button
	LLRect btn_rect;  // Note: btn_rect.mLeft is just a dummy.  Will be updated in draw().
	LLString tab_img;
	LLString tab_selected_img;
	S32 tab_fudge = 1;		//  To make new tab art look better, nudge buttons up 1 pel

	if( LLTabContainer::TOP == mTabPosition )
	{
		btn_rect.setLeftTopAndSize( 0, mRect.getHeight() - mTopBorderHeight + tab_fudge, button_width, TABCNTR_TAB_HEIGHT );
		tab_img = "UIImgBtnTabTopOutUUID";
		tab_selected_img = "UIImgBtnTabTopInUUID";
	}
	else
	{
		btn_rect.setOriginAndSize( 0, 0 + tab_fudge, button_width, TABCNTR_TAB_HEIGHT );
		tab_img = "UIImgBtnTabBottomOutUUID";
		tab_selected_img = "UIImgBtnTabBottomInUUID";
	}

	if (placeholder)
	{
		//FIXME: wont work for horizontal tabs
		btn_rect.translate(0, -LLBUTTON_V_PAD-2);
		LLString box_label = trimmed_label;
		LLTextBox* text = new LLTextBox(box_label, btn_rect, box_label, font);
		addChild( text, 0 );

		LLButton* btn = new LLButton("", LLRect(0,0,0,0));
		LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata, text );
		addChild( btn, 0 );
		addChild( child, 1 );
		insertTuple(tuple, insertion_point);
	}
	else
	{
		LLString tooltip = trimmed_label;
		tooltip += "\nCtrl-[ for previous tab";
		tooltip += "\nCtrl-] for next tab";

		LLButton* btn = new LLButton(
			LLString(child->getName()) + " tab",
			btn_rect, 
			tab_img, tab_selected_img, "",
			&LLTabContainer::onTabBtn, NULL, // set userdata below
			font,
			trimmed_label, trimmed_label );
		btn->setSaveToXML(false);
		btn->setVisible( FALSE );
		btn->setToolTip( tooltip );
		btn->setScaleImage(TRUE);
		btn->setFixedBorder(14, 14);

		// Try to squeeze in a bit more text
		btn->setLeftHPad( 4 );
		btn->setRightHPad( 2 );
		btn->setHAlign(LLFontGL::LEFT);
		btn->setTabStop(FALSE);
		if (indent)
		{
			btn->setLeftHPad(indent);
		}

		if( mTabPosition == TOP )
		{
			btn->setFollowsTop();
		}
		else
		{
			btn->setFollowsBottom();
		}

		LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata );
		btn->setCallbackUserData( tuple );
		addChild( btn, 0 );
		addChild( child, 1 );
		insertTuple(tuple, insertion_point);
	}

	updateMaxScrollPos();

	if( select )
	{
		selectLastTab();
	}
}

void LLTabContainer::removeTabPanel(LLPanel* child)
{
	// Adjust the total tab width.
	for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
	{
		LLTabTuple* tuple = *iter;
		if( tuple->mTabPanel == child )
		{
			mTotalTabWidth -= tuple->mButton->getRect().getWidth();
			break;
		}
	}

	LLTabContainerCommon::removeTabPanel(child);
}

void LLTabContainer::setPanelTitle(S32 index, const LLString& title)
{
	if (index >= 0 && index < (S32)mTabList.size())
	{
		LLButton* tab_button = mTabList[index]->mButton;
		const LLFontGL* fontp = gResMgr->getRes( LLFONT_SANSSERIF_SMALL );
		mTotalTabWidth -= tab_button->getRect().getWidth();
		tab_button->reshape(llclamp(fontp->getWidth(title) + TAB_PADDING, mMinTabWidth, mMaxTabWidth), tab_button->getRect().getHeight());
		mTotalTabWidth += tab_button->getRect().getWidth();
		tab_button->setLabelSelected(title);
		tab_button->setLabelUnselected(title);
	}
	updateMaxScrollPos();
}


void LLTabContainer::updateMaxScrollPos()
{
	S32 tab_space = 0;
	S32 available_space = 0;
	tab_space = mTotalTabWidth;
	available_space = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_TAB_H_PAD);

	if( tab_space > available_space )
	{
		S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1);
		// subtract off reserved portion on left
		available_width_with_arrows -= TABCNTR_TAB_PARTIAL_WIDTH;

		S32 running_tab_width = 0;
		mMaxScrollPos = mTabList.size();
		for(tuple_list_t::reverse_iterator tab_it = mTabList.rbegin(); tab_it != mTabList.rend(); ++tab_it)
		{
			running_tab_width += (*tab_it)->mButton->getRect().getWidth();
			if (running_tab_width > available_width_with_arrows)
			{
				break;
			}
			mMaxScrollPos--;
		}
		// in case last tab doesn't actually fit on screen, make it the last scrolling position
		mMaxScrollPos = llmin(mMaxScrollPos, (S32)mTabList.size() - 1);
	}
	else
	{
		mMaxScrollPos = 0;
		mScrollPos = 0;
	}
	if (mScrollPos > mMaxScrollPos)
	{
		mScrollPos = mMaxScrollPos;
	}
}

void LLTabContainer::commitHoveredButton(S32 x, S32 y)
{
	if (gFocusMgr.getMouseCapture() == this)
	{
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			tuple->mButton->setVisible( TRUE );
			S32 local_x = x - tuple->mButton->getRect().mLeft;
			S32 local_y = y - tuple->mButton->getRect().mBottom;
			if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible())
			{
				tuple->mButton->onCommit();
			}
		}
	}
}

void LLTabContainer::setMinTabWidth(S32 width)
{
	mMinTabWidth = width;
}

void LLTabContainer::setMaxTabWidth(S32 width)
{
	mMaxTabWidth = width;
}

S32 LLTabContainer::getMinTabWidth() const
{
	return mMinTabWidth;
}

S32 LLTabContainer::getMaxTabWidth() const
{
	return mMaxTabWidth;
}

BOOL LLTabContainer::selectTab(S32 which)
{
	if (which >= (S32)mTabList.size()) return FALSE;
	if (which < 0) return FALSE;

	//if( gFocusMgr.childHasKeyboardFocus( this ) )
	//{
	//	gFocusMgr.setKeyboardFocus( NULL, NULL );
	//}

	LLTabTuple* selected_tuple = mTabList[which];
	if (!selected_tuple)
	{
		return FALSE;
	}
	
	if (mTabList[which]->mButton->getEnabled())
	{
		mCurrentTabIdx = which;

		S32 i = 0;
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			BOOL is_selected = ( tuple == selected_tuple );
			tuple->mTabPanel->setVisible( is_selected );
// 			tuple->mTabPanel->setFocus(is_selected); // not clear that we want to do this here.
			tuple->mButton->setToggleState( is_selected );
			// RN: this limits tab-stops to active button only, which would require arrow keys to switch tabs
			tuple->mButton->setTabStop( is_selected && mTabList.size() > 1 );
			
			if( is_selected && mMaxScrollPos > 0)
			{
				// Make sure selected tab is within scroll region
				if( i < mScrollPos )
				{
					mScrollPos = i;
				}
				else
				{
					S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1);
					S32 running_tab_width = tuple->mButton->getRect().getWidth();
					S32 j = i - 1;
					S32 min_scroll_pos = i;
					if (running_tab_width < available_width_with_arrows)
					{
						while (j >= 0)
						{
							LLTabTuple* other_tuple = mTabList[j];
							running_tab_width += other_tuple->mButton->getRect().getWidth();
							if (running_tab_width > available_width_with_arrows)
							{
								break;
							}
							j--;
						}
						min_scroll_pos = j + 1;
					}
					mScrollPos = llclamp(mScrollPos, min_scroll_pos, i);
					mScrollPos = llmin(mScrollPos, mMaxScrollPos);
				}
			}
			i++;
		}
		if( selected_tuple->mOnChangeCallback )
		{
			selected_tuple->mOnChangeCallback( selected_tuple->mUserData, false );
		}
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

void LLTabContainer::draw()
{
	S32 target_pixel_scroll = 0;
	S32 cur_scroll_pos = mScrollPos;
	if (cur_scroll_pos > 0)
	{
		S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1);
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			if (cur_scroll_pos == 0)
			{
				break;
			}
			target_pixel_scroll += (*iter)->mButton->getRect().getWidth();
			cur_scroll_pos--;
		}

		// Show part of the tab to the left of what is fully visible
		target_pixel_scroll -= TABCNTR_TAB_PARTIAL_WIDTH;
		// clamp so that rightmost tab never leaves right side of screen
		target_pixel_scroll = llmin(mTotalTabWidth - available_width_with_arrows, target_pixel_scroll);
	}

	mScrollPosPixels = (S32)lerp((F32)mScrollPosPixels, (F32)target_pixel_scroll, LLCriticalDamp::getInterpolant(0.08f));
	if( getVisible() )
	{
		BOOL has_scroll_arrows = (mMaxScrollPos > 0) || (mScrollPosPixels > 0);
		mLeftArrowBtn->setVisible( has_scroll_arrows );
		mRightArrowBtn->setVisible( has_scroll_arrows );

		// Set the leftmost position of the tab buttons.
		S32 left = LLPANEL_BORDER_WIDTH + (has_scroll_arrows ? TABCNTR_ARROW_BTN_SIZE : TABCNTR_TAB_H_PAD);
		left -= mScrollPosPixels;

		// Hide all the buttons
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			tuple->mButton->setVisible( FALSE );
		}

		LLPanel::draw();

		// Show all the buttons
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			tuple->mButton->setVisible( TRUE );
		}

		// Draw some of the buttons...

		LLGLEnable scissor_test(has_scroll_arrows ? GL_SCISSOR_TEST : GL_FALSE);
		if( has_scroll_arrows )
		{
			// ...but clip them.
			S32 x1 = mLeftArrowBtn->getRect().mRight;
			S32 y1 = 0;
			S32 x2 = mRightArrowBtn->getRect().mLeft;
			S32 y2 = 1;
			if (mTabList.size() > 0)
			{
				y2 = mTabList[0]->mButton->getRect().mTop;
			}
			LLUI::setScissorRegionLocal(LLRect(x1, y2, x2, y1));
		}

		S32 max_scroll_visible = mTabList.size() - mMaxScrollPos + mScrollPos;
		S32 idx = 0;
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			tuple->mButton->translate( left - tuple->mButton->getRect().mLeft, 0 );
			left += tuple->mButton->getRect().getWidth();

			if( idx < mScrollPos )
			{
				if( tuple->mButton->getFlashing() )
				{
					mLeftArrowBtn->setFlashing( TRUE );
				}
			}
			else
			if( max_scroll_visible < idx )
			{
				if( tuple->mButton->getFlashing() )
				{
					mRightArrowBtn->setFlashing( TRUE );
				}
			}

			LLUI::pushMatrix();
			{
				LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f);
				tuple->mButton->draw();
			}
			LLUI::popMatrix();
		
			idx++;
		}

		mLeftArrowBtn->setFlashing(FALSE);
		mRightArrowBtn->setFlashing(FALSE);
	}
}


void LLTabContainer::setRightTabBtnOffset(S32 offset)
{
	mRightArrowBtn->translate( -offset - mRightTabBtnOffset, 0 );
	mRightTabBtnOffset = offset;
	updateMaxScrollPos();
}

BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask )
{
	BOOL handled = FALSE;
	BOOL has_scroll_arrows = (mMaxScrollPos > 0);

	if (has_scroll_arrows)
	{
		if (mLeftArrowBtn->getRect().pointInRect(x, y))
		{
			S32 local_x = x - mLeftArrowBtn->getRect().mLeft;
			S32 local_y = y - mLeftArrowBtn->getRect().mBottom;
			handled = mLeftArrowBtn->handleMouseDown(local_x, local_y, mask);
		}
		else if (mRightArrowBtn->getRect().pointInRect(x, y))
		{
			S32 local_x = x - mRightArrowBtn->getRect().mLeft;
			S32 local_y = y - mRightArrowBtn->getRect().mBottom;
			handled = mRightArrowBtn->handleMouseDown(local_x, local_y, mask);
		}
	}
	if (!handled)
	{
		handled = LLPanel::handleMouseDown( x, y, mask );
	}

	if (mTabList.size() > 0)
	{
		LLTabTuple* firsttuple = mTabList[0];
		LLRect tab_rect(has_scroll_arrows ? mLeftArrowBtn->getRect().mRight : mLeftArrowBtn->getRect().mLeft,
						firsttuple->mButton->getRect().mTop,
						has_scroll_arrows ? mRightArrowBtn->getRect().mLeft : mRightArrowBtn->getRect().mRight,
						firsttuple->mButton->getRect().mBottom );
		if( tab_rect.pointInRect( x, y ) )
		{
			LLButton* tab_button = mTabList[getCurrentPanelIndex()]->mButton;
			gFocusMgr.setMouseCapture(this, NULL);
			gFocusMgr.setKeyboardFocus(tab_button, NULL);
		}
	}
	return handled;
}

BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask )
{
	BOOL handled = FALSE;
	BOOL has_scroll_arrows = (mMaxScrollPos > 0);

	if (has_scroll_arrows)
	{
		if (mLeftArrowBtn->getRect().pointInRect(x, y))
		{
			S32 local_x = x - mLeftArrowBtn->getRect().mLeft;
			S32 local_y = y - mLeftArrowBtn->getRect().mBottom;
			handled = mLeftArrowBtn->handleHover(local_x, local_y, mask);
		}
		else if (mRightArrowBtn->getRect().pointInRect(x, y))
		{
			S32 local_x = x - mRightArrowBtn->getRect().mLeft;
			S32 local_y = y - mRightArrowBtn->getRect().mBottom;
			handled = mRightArrowBtn->handleHover(local_x, local_y, mask);
		}
	}
	if (!handled)
	{
		handled = LLPanel::handleHover(x, y, mask);
	}

	commitHoveredButton(x, y);
	return handled;
}

BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask )
{
	BOOL handled = FALSE;
	BOOL has_scroll_arrows = (mMaxScrollPos > 0);

	if (has_scroll_arrows)
	{
		if (mLeftArrowBtn->getRect().pointInRect(x, y))
		{
			S32 local_x = x - mLeftArrowBtn->getRect().mLeft;
			S32 local_y = y - mLeftArrowBtn->getRect().mBottom;
			handled = mLeftArrowBtn->handleMouseUp(local_x, local_y, mask);
		}
		else if (mRightArrowBtn->getRect().pointInRect(x, y))
		{
			S32 local_x = x - mRightArrowBtn->getRect().mLeft;
			S32 local_y = y - mRightArrowBtn->getRect().mBottom;
			handled = mRightArrowBtn->handleMouseUp(local_x, local_y, mask);
		}
	}
	if (!handled)
	{
		handled = LLPanel::handleMouseUp( x, y, mask );
	}

	commitHoveredButton(x, y);
	LLPanel* cur_panel = getCurrentPanel();
	if (gFocusMgr.getMouseCapture() == this)
	{
		if (cur_panel)
		{
			if (!cur_panel->focusFirstItem(FALSE))
			{
				// if nothing in the panel gets focus, make sure the new tab does
				// otherwise the last tab might keep focus
				mTabList[getCurrentPanelIndex()]->mButton->setFocus(TRUE);
			}
		}
		gFocusMgr.setMouseCapture(NULL, NULL);
	}
	return handled;
}

BOOL LLTabContainer::handleToolTip( S32 x, S32 y, LLString& msg, LLRect* sticky_rect )
{
	BOOL handled = LLPanel::handleToolTip( x, y, msg, sticky_rect );
	if (!handled && mTabList.size() > 0 && getVisible() && pointInView( x, y ) ) 
	{
		LLTabTuple* firsttuple = mTabList[0];

		BOOL has_scroll_arrows = (mMaxScrollPos > 0);
		LLRect clip(
			has_scroll_arrows ? mLeftArrowBtn->getRect().mRight : mLeftArrowBtn->getRect().mLeft,
			firsttuple->mButton->getRect().mTop,
			has_scroll_arrows ? mRightArrowBtn->getRect().mLeft : mRightArrowBtn->getRect().mRight,
			0 );
		if( clip.pointInRect( x, y ) )
		{
			for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
			{
				LLTabTuple* tuple = *iter;
				tuple->mButton->setVisible( TRUE );
				S32 local_x = x - tuple->mButton->getRect().mLeft;
				S32 local_y = y - tuple->mButton->getRect().mBottom;
				handled = tuple->mButton->handleToolTip( local_x, local_y, msg, sticky_rect );
				if( handled )
				{
					break;
				}
			}
		}

		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			tuple->mButton->setVisible( FALSE );
		}
	}
	return handled;
}

BOOL LLTabContainer::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent)
{
	if (!getEnabled()) return FALSE;

	if (!gFocusMgr.childHasKeyboardFocus(this)) return FALSE;

	BOOL handled = FALSE;
	if (key == '[' && mask == MASK_CONTROL)
	{
		selectPrevTab();
		handled = TRUE;
	}
	else if (key == ']' && mask == MASK_CONTROL)
	{
		selectNextTab();
		handled = TRUE;
	}

	if (handled)
	{
		if (getCurrentPanel())
		{
			getCurrentPanel()->setFocus(TRUE);
		}
	}

	if (!gFocusMgr.childHasKeyboardFocus(getCurrentPanel()))
	{
		// if child has focus, but not the current panel, focus
		// is on a button
		switch(key)
		{
		case KEY_UP:
			if (getTabPosition() == BOTTOM && getCurrentPanel())
			{
				getCurrentPanel()->setFocus(TRUE);
			}
			handled = TRUE;
			break;
		case KEY_DOWN:
			if (getTabPosition() == TOP && getCurrentPanel())
			{
				getCurrentPanel()->setFocus(TRUE);
			}
			handled = TRUE;
			break;
		case KEY_LEFT:
		  	selectPrevTab();
			handled = TRUE;
			break;
		case KEY_RIGHT:
		  	selectNextTab();
			handled = TRUE;
			break;
		default:
			break;
		}
	}
	return handled;
}

// virtual
LLXMLNodePtr LLTabContainer::getXML(bool save_children) const
{
	LLXMLNodePtr node = LLTabContainerCommon::getXML();

	node->createChild("tab_position", TRUE)->setStringValue((mTabPosition == TOP ? "top" : "bottom"));

	return node;
}

BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask,	BOOL drop,	EDragAndDropType type, void* cargo_data, EAcceptance *accept, LLString	&tooltip)
{
	BOOL has_scroll_arrows = (mMaxScrollPos	> 0);

	if (has_scroll_arrows)
	{
		if (mLeftArrowBtn->getRect().pointInRect(x,	y))
		{
			S32	local_x	= x	- mLeftArrowBtn->getRect().mLeft;
			S32	local_y	= y	- mLeftArrowBtn->getRect().mBottom;
			mLeftArrowBtn->handleHover(local_x,	local_y, mask);
		}
		else if	(mRightArrowBtn->getRect().pointInRect(x, y))
		{
			S32	local_x	= x	- mRightArrowBtn->getRect().mLeft;
			S32	local_y	= y	- mRightArrowBtn->getRect().mBottom;
			mRightArrowBtn->handleHover(local_x, local_y, mask);
		}
	}

	for(tuple_list_t::iterator iter	= mTabList.begin();	iter !=	 mTabList.end(); ++iter)
	{
		LLTabTuple*	tuple =	*iter;
		tuple->mButton->setVisible(	TRUE );
		S32	local_x	= x	- tuple->mButton->getRect().mLeft;
		S32	local_y	= y	- tuple->mButton->getRect().mBottom;
		if (tuple->mButton->pointInView(local_x, local_y) &&  tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible())
		{
			tuple->mButton->onCommit();
		}
	}

	return LLView::handleDragAndDrop(x,	y, mask, drop, type, cargo_data,  accept, tooltip);
}

