Skip to content
Snippets Groups Projects
lltabcontainer.cpp 47.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • James Cook's avatar
    James Cook committed
    /** 
     * @file lltabcontainer.cpp
    
    James Cook's avatar
    James Cook committed
     *
    
     * $LicenseInfo:firstyear=2001&license=viewergpl$
     * 
     * Copyright (c) 2001-2007, Linden Research, Inc.
     * 
     * Second Life Viewer Source Code
     * The source code in this file ("Source Code") is provided by Linden Lab
     * to you under the terms of the GNU General Public License, version 2.0
     * ("GPL"), unless you have obtained a separate licensing agreement
     * ("Other License"), formally executed by you and Linden Lab.  Terms of
     * the GPL can be found in doc/GPL-license.txt in this distribution, or
     * online at http://secondlife.com/developers/opensource/gplv2
     * 
     * There are special exceptions to the terms and conditions of the GPL as
     * it is applied to this Source Code. View the full text of the exception
     * in the file doc/FLOSS-exception.txt in this software distribution, or
     * online at http://secondlife.com/developers/opensource/flossexception
     * 
     * By copying, modifying or distributing this software, you acknowledge
     * that you have read and understood your obligations described above,
     * and agree to abide by those obligations.
     * 
     * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
     * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
     * COMPLETENESS OR PERFORMANCE.
     * $/LicenseInfo$
    
    James Cook's avatar
    James Cook committed
     */
    
    #include "linden_common.h"
    #include "lltabcontainer.h"
    #include "llfocusmgr.h"
    #include "llbutton.h"
    #include "llrect.h"
    #include "llresmgr.h"
    #include "llresizehandle.h"
    #include "lltextbox.h"
    #include "llcriticaldamp.h"
    #include "lluictrlfactory.h"
    #include "lltabcontainervertical.h"
    
    #include "llrender.h"
    
    James Cook's avatar
    James Cook committed
    
    const F32 SCROLL_STEP_TIME = 0.4f;
    
    const F32 SCROLL_DELAY_TIME = 0.5f;
    
    James Cook's avatar
    James Cook committed
    const S32 TAB_PADDING = 15;
    const S32 TABCNTR_TAB_MIN_WIDTH = 60;
    
    const S32 TABCNTR_VERT_TAB_MIN_WIDTH = 100;
    
    James Cook's avatar
    James Cook committed
    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;
    
    
    const S32 TABCNTR_CLOSE_BTN_SIZE = 16;
    const S32 TABCNTR_HEADER_HEIGHT = LLPANEL_BORDER_WIDTH + TABCNTR_CLOSE_BTN_SIZE;
    
    James Cook's avatar
    James Cook committed
    
    
    const S32 TABCNTRV_CLOSE_BTN_SIZE = 16;
    const S32 TABCNTRV_HEADER_HEIGHT = LLPANEL_BORDER_WIDTH + TABCNTRV_CLOSE_BTN_SIZE;
    //const S32 TABCNTRV_TAB_WIDTH = 100;
    const S32 TABCNTRV_ARROW_BTN_SIZE = 16;
    const S32 TABCNTRV_PAD = 0;
    
    
    static LLRegisterWidget<LLTabContainer> r("tab_container");
    
    
    LLTabContainer::LLTabContainer(const LLString& name, const LLRect& rect, TabPosition pos,
    							   BOOL bordered, BOOL is_vertical )
    
    James Cook's avatar
    James Cook committed
    	: 
    	LLPanel(name, rect, bordered),
    	mCurrentTabIdx(-1),
    
    	mTabsHidden(FALSE),
    
    James Cook's avatar
    James Cook committed
    	mScrolled(FALSE),
    	mScrollPos(0),
    	mScrollPosPixels(0),
    	mMaxScrollPos(0),
    
    	mCloseCallback( NULL ),
    	mCallbackUserdata( NULL ),
    
    James Cook's avatar
    James Cook committed
    	mTitleBox(NULL),
    	mTopBorderHeight(LLPANEL_BORDER_WIDTH),
    
    	mLockedTabCount(0),
    	mMinTabWidth(TABCNTR_TAB_MIN_WIDTH),
    	mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH),
    	mPrevArrowBtn(NULL),
    	mNextArrowBtn(NULL),
    	mIsVertical(is_vertical),
    	// Horizontal Specific
    	mJumpPrevArrowBtn(NULL),
    	mJumpNextArrowBtn(NULL),
    	mRightTabBtnOffset(0),
    	mTotalTabWidth(0)
    
    James Cook's avatar
    James Cook committed
    { 
    
    	//RN: HACK to support default min width for legacy vertical tab containers
    	if (mIsVertical)
    	{
    		mMinTabWidth = TABCNTR_VERT_TAB_MIN_WIDTH;
    	}
    
    James Cook's avatar
    James Cook committed
    	setMouseOpaque(FALSE);
    
    	mDragAndDropDelayTimer.stop();
    
    LLTabContainer::~LLTabContainer()
    {
    	std::for_each(mTabList.begin(), mTabList.end(), DeletePointer());
    }
    
    James Cook's avatar
    James Cook committed
    
    
    //virtual
    void LLTabContainer::setValue(const LLSD& value)
    
    James Cook's avatar
    James Cook committed
    {
    
    	selectTab((S32) value.asInteger());
    }
    
    //virtual
    void LLTabContainer::reshape(S32 width, S32 height, BOOL called_from_parent)
    
    James Cook's avatar
    James Cook committed
    {
    
    	LLPanel::reshape( width, height, called_from_parent );
    	updateMaxScrollPos();
    
    LLView* LLTabContainer::getChildView(const LLString& name, BOOL recurse, BOOL create_if_missing) const
    
    James Cook's avatar
    James Cook committed
    {
    	tuple_list_t::const_iterator itor;
    	for (itor = mTabList.begin(); itor != mTabList.end(); ++itor)
    	{
    		LLPanel *panel = (*itor)->mTabPanel;
    		if (panel->getName() == name)
    		{
    			return panel;
    		}
    	}
    
    James Cook's avatar
    James Cook committed
    	if (recurse)
    	{
    		for (itor = mTabList.begin(); itor != mTabList.end(); ++itor)
    		{
    			LLPanel *panel = (*itor)->mTabPanel;
    
    			LLView *child = panel->getChildView(name, recurse, FALSE);
    
    James Cook's avatar
    James Cook committed
    			if (child)
    			{
    				return child;
    			}
    		}
    	}
    
    	return LLView::getChildView(name, recurse, create_if_missing);
    
    James Cook's avatar
    James Cook committed
    {
    
    	S32 target_pixel_scroll = 0;
    	S32 cur_scroll_pos = mIsVertical ? 0 : getScrollPos();
    	if (cur_scroll_pos > 0)
    
    James Cook's avatar
    James Cook committed
    	{
    
    		S32 available_width_with_arrows = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE  + TABCNTR_ARROW_BTN_SIZE + 1);
    		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
    
    James Cook's avatar
    James Cook committed
    		{
    
    			if (cur_scroll_pos == 0)
    			{
    				break;
    			}
    			target_pixel_scroll += (*iter)->mButton->getRect().getWidth();
    			cur_scroll_pos--;
    
    James Cook's avatar
    James Cook committed
    		}
    
    		// 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);
    
    James Cook's avatar
    James Cook committed
    	}
    
    
    	setScrollPosPixels((S32)lerp((F32)getScrollPosPixels(), (F32)target_pixel_scroll, LLCriticalDamp::getInterpolant(0.08f)));
    
    
    	BOOL has_scroll_arrows = (mMaxScrollPos > 0) || (mScrollPosPixels > 0);
    	if (!mIsVertical)
    
    James Cook's avatar
    James Cook committed
    	{
    
    		mJumpPrevArrowBtn->setVisible( has_scroll_arrows );
    		mJumpNextArrowBtn->setVisible( has_scroll_arrows );
    	}
    	mPrevArrowBtn->setVisible( has_scroll_arrows );
    	mNextArrowBtn->setVisible( has_scroll_arrows );
    
    James Cook's avatar
    James Cook committed
    
    
    	S32 left = 0, top = 0;
    	if (mIsVertical)
    	{
    		top = getRect().getHeight() - getTopBorderHeight() - LLPANEL_BORDER_WIDTH - 1 - (has_scroll_arrows ? TABCNTRV_ARROW_BTN_SIZE : 0);
    		top += getScrollPosPixels();
    	}
    	else
    	{
    		// Set the leftmost position of the tab buttons.
    		left = LLPANEL_BORDER_WIDTH + (has_scroll_arrows ? (TABCNTR_ARROW_BTN_SIZE * 2) : TABCNTR_TAB_H_PAD);
    		left -= getScrollPosPixels();
    	}
    	
    	// 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();
    
    	// if tabs are hidden, don't draw them and leave them in the invisible state
    	if (!getTabsHidden())
    	{
    		// Show all the buttons
    
    		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
    		{
    			LLTabTuple* tuple = *iter;
    
    			tuple->mButton->setVisible( TRUE );
    
    James Cook's avatar
    James Cook committed
    
    
    		// Draw some of the buttons...
    		LLRect clip_rect = getLocalRect();
    		if (has_scroll_arrows)
    
    			// ...but clip them.
    			if (mIsVertical)
    
    				clip_rect.mBottom = mNextArrowBtn->getRect().mTop + 3*TABCNTRV_PAD;
    				clip_rect.mTop = mPrevArrowBtn->getRect().mBottom - 3*TABCNTRV_PAD;
    
    				clip_rect.mLeft = mPrevArrowBtn->getRect().mRight;
    				clip_rect.mRight = mNextArrowBtn->getRect().mLeft;
    
    		}
    		LLLocalClipRect clip(clip_rect);
    
    James Cook's avatar
    James Cook committed
    
    
    		S32 max_scroll_visible = getTabCount() - getMaxScrollPos() + getScrollPos();
    		S32 idx = 0;
    		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
    		{
    			LLTabTuple* tuple = *iter;
    
    James Cook's avatar
    James Cook committed
    
    
    			tuple->mButton->translate( left ? left - tuple->mButton->getRect().mLeft : 0,
    									   top ? top - tuple->mButton->getRect().mTop : 0 );
    			if (top) top -= BTN_HEIGHT + TABCNTRV_PAD;
    			if (left) left += tuple->mButton->getRect().getWidth();
    
    James Cook's avatar
    James Cook committed
    
    
    			if (!mIsVertical)
    			{
    				if( idx < getScrollPos() )
    
    					if( tuple->mButton->getFlashing() )
    
    						mPrevArrowBtn->setFlashing( TRUE );
    
    				else if( max_scroll_visible < idx )
    
    					if( tuple->mButton->getFlashing() )
    					{
    						mNextArrowBtn->setFlashing( TRUE );
    					}
    
    				LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f);
    				tuple->mButton->draw();
    
    James Cook's avatar
    James Cook committed
    
    
    
    		if( mIsVertical && has_scroll_arrows )
    		{
    			// Redraw the arrows so that they appears on top.
    			gGL.pushMatrix();
    			gGL.translatef((F32)mPrevArrowBtn->getRect().mLeft, (F32)mPrevArrowBtn->getRect().mBottom, 0.f);
    			mPrevArrowBtn->draw();
    			gGL.popMatrix();
    
    			gGL.pushMatrix();
    			gGL.translatef((F32)mNextArrowBtn->getRect().mLeft, (F32)mNextArrowBtn->getRect().mBottom, 0.f);
    			mNextArrowBtn->draw();
    			gGL.popMatrix();
    		}
    
    
    	mPrevArrowBtn->setFlashing(FALSE);
    	mNextArrowBtn->setFlashing(FALSE);
    
    // virtual
    BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask )
    
    James Cook's avatar
    James Cook committed
    {
    
    	BOOL handled = FALSE;
    	BOOL has_scroll_arrows = (getMaxScrollPos() > 0);
    
    James Cook's avatar
    James Cook committed
    
    
    James Cook's avatar
    James Cook committed
    	{
    
    		if (mJumpPrevArrowBtn&& mJumpPrevArrowBtn->getRect().pointInRect(x, y))
    
    James Cook's avatar
    James Cook committed
    		{
    
    			S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft;
    			S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom;
    			handled = mJumpPrevArrowBtn->handleMouseDown(local_x, local_y, mask);
    		}
    		else if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mJumpNextArrowBtn->getRect().mLeft;
    			S32 local_y = y - mJumpNextArrowBtn->getRect().mBottom;
    			handled = mJumpNextArrowBtn->handleMouseDown(local_x, local_y, mask);
    		}
    		else if (mPrevArrowBtn && mPrevArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mPrevArrowBtn->getRect().mLeft;
    			S32 local_y = y - mPrevArrowBtn->getRect().mBottom;
    			handled = mPrevArrowBtn->handleMouseDown(local_x, local_y, mask);
    		}
    		else if (mNextArrowBtn && mNextArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mNextArrowBtn->getRect().mLeft;
    			S32 local_y = y - mNextArrowBtn->getRect().mBottom;
    			handled = mNextArrowBtn->handleMouseDown(local_x, local_y, mask);
    
    James Cook's avatar
    James Cook committed
    		}
    	}
    
    	if (!handled)
    	{
    		handled = LLPanel::handleMouseDown( x, y, mask );
    	}
    
    James Cook's avatar
    James Cook committed
    
    
    	S32 tab_count = getTabCount();
    	if (tab_count > 0)
    
    James Cook's avatar
    James Cook committed
    	{
    
    		LLTabTuple* firsttuple = getTab(0);
    		LLRect tab_rect;
    		if (mIsVertical)
    
    James Cook's avatar
    James Cook committed
    		{
    
    			tab_rect = LLRect(firsttuple->mButton->getRect().mLeft,
    							  has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - TABCNTRV_PAD : mPrevArrowBtn->getRect().mTop,
    							  firsttuple->mButton->getRect().mRight,
    							  has_scroll_arrows ? mNextArrowBtn->getRect().mTop + TABCNTRV_PAD : mNextArrowBtn->getRect().mBottom );
    		}
    		else
    		{
    			tab_rect = LLRect(has_scroll_arrows ? mPrevArrowBtn->getRect().mRight : mJumpPrevArrowBtn->getRect().mLeft,
    							  firsttuple->mButton->getRect().mTop,
    							  has_scroll_arrows ? mNextArrowBtn->getRect().mLeft : mJumpNextArrowBtn->getRect().mRight,
    							  firsttuple->mButton->getRect().mBottom );
    		}
    		if( tab_rect.pointInRect( x, y ) )
    		{
    
    			S32 index = getCurrentPanelIndex();
    			index = llclamp(index, 0, tab_count-1);
    			LLButton* tab_button = getTab(index)->mButton;
    
    			gFocusMgr.setMouseCapture(this);
    			gFocusMgr.setKeyboardFocus(tab_button);
    
    James Cook's avatar
    James Cook committed
    		}
    	}
    
    // virtual
    BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask )
    
    James Cook's avatar
    James Cook committed
    {
    
    	BOOL handled = FALSE;
    	BOOL has_scroll_arrows = (getMaxScrollPos() > 0);
    
    	if (has_scroll_arrows)
    
    James Cook's avatar
    James Cook committed
    	{
    
    		if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y))
    
    James Cook's avatar
    James Cook committed
    		{
    
    			S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft;
    			S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom;
    			handled = mJumpPrevArrowBtn->handleHover(local_x, local_y, mask);
    
    James Cook's avatar
    James Cook committed
    		}
    
    		else if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x, y))
    
    James Cook's avatar
    James Cook committed
    		{
    
    			S32 local_x = x - mJumpNextArrowBtn->getRect().mLeft;
    			S32 local_y = y - mJumpNextArrowBtn->getRect().mBottom;
    			handled = mJumpNextArrowBtn->handleHover(local_x, local_y, mask);
    		}
    		else if (mPrevArrowBtn && mPrevArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mPrevArrowBtn->getRect().mLeft;
    			S32 local_y = y - mPrevArrowBtn->getRect().mBottom;
    			handled = mPrevArrowBtn->handleHover(local_x, local_y, mask);
    		}
    		else if (mNextArrowBtn && mNextArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mNextArrowBtn->getRect().mLeft;
    			S32 local_y = y - mNextArrowBtn->getRect().mBottom;
    			handled = mNextArrowBtn->handleHover(local_x, local_y, mask);
    
    James Cook's avatar
    James Cook committed
    		}
    	}
    
    James Cook's avatar
    James Cook committed
    	{
    
    		handled = LLPanel::handleHover(x, y, mask);
    
    // virtual
    BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask )
    
    James Cook's avatar
    James Cook committed
    {
    
    	BOOL handled = FALSE;
    	BOOL has_scroll_arrows = (getMaxScrollPos() > 0);
    
    James Cook's avatar
    James Cook committed
    
    
    		if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft;
    			S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom;
    			handled = mJumpPrevArrowBtn->handleMouseUp(local_x, local_y, mask);
    		}
    		else if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x,	y))
    		{
    			S32	local_x	= x	- mJumpNextArrowBtn->getRect().mLeft;
    			S32	local_y	= y	- mJumpNextArrowBtn->getRect().mBottom;
    			handled = mJumpNextArrowBtn->handleMouseUp(local_x,	local_y, mask);
    		}
    		else if (mPrevArrowBtn && mPrevArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mPrevArrowBtn->getRect().mLeft;
    			S32 local_y = y - mPrevArrowBtn->getRect().mBottom;
    			handled = mPrevArrowBtn->handleMouseUp(local_x, local_y, mask);
    		}
    		else if (mNextArrowBtn && mNextArrowBtn->getRect().pointInRect(x, y))
    		{
    			S32 local_x = x - mNextArrowBtn->getRect().mLeft;
    			S32 local_y = y - mNextArrowBtn->getRect().mBottom;
    			handled = mNextArrowBtn->handleMouseUp(local_x, local_y, mask);
    		}
    
    James Cook's avatar
    James Cook committed
    	{
    
    		handled = LLPanel::handleMouseUp( x, y, mask );
    
    	commitHoveredButton(x, y);
    	LLPanel* cur_panel = getCurrentPanel();
    	if (hasMouseCapture())
    
    James Cook's avatar
    James Cook committed
    	{
    
    		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
    				getTab(getCurrentPanelIndex())->mButton->setFocus(TRUE);
    			}
    		}
    		gFocusMgr.setMouseCapture(NULL);
    
    James Cook's avatar
    James Cook committed
    	}
    
    // virtual
    BOOL LLTabContainer::handleToolTip( S32 x, S32 y, LLString& msg, LLRect* sticky_rect )
    
    James Cook's avatar
    James Cook committed
    {
    
    	BOOL handled = LLPanel::handleToolTip( x, y, msg, sticky_rect );
    	if (!handled && getTabCount() > 0) 
    
    James Cook's avatar
    James Cook committed
    	{
    
    James Cook's avatar
    James Cook committed
    
    
    		BOOL has_scroll_arrows = (getMaxScrollPos() > 0);
    		LLRect clip;
    		if (mIsVertical)
    
    James Cook's avatar
    James Cook committed
    		{
    
    			clip = LLRect(firsttuple->mButton->getRect().mLeft,
    						  has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - TABCNTRV_PAD : mPrevArrowBtn->getRect().mTop,
    						  firsttuple->mButton->getRect().mRight,
    						  has_scroll_arrows ? mNextArrowBtn->getRect().mTop + TABCNTRV_PAD : mNextArrowBtn->getRect().mBottom );
    
    James Cook's avatar
    James Cook committed
    		}
    
    James Cook's avatar
    James Cook committed
    		{
    
    			clip = LLRect(has_scroll_arrows ? mPrevArrowBtn->getRect().mRight : mJumpPrevArrowBtn->getRect().mLeft,
    						  firsttuple->mButton->getRect().mTop,
    						  has_scroll_arrows ? mNextArrowBtn->getRect().mLeft : mJumpNextArrowBtn->getRect().mRight,
    						  firsttuple->mButton->getRect().mBottom );
    
    James Cook's avatar
    James Cook committed
    		{
    
    			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)
    
    James Cook's avatar
    James Cook committed
    		{
    
    			LLTabTuple* tuple = *iter;
    			tuple->mButton->setVisible( FALSE );
    
    James Cook's avatar
    James Cook committed
    		}
    	}
    
    BOOL LLTabContainer::handleKeyHere(KEY key, MASK mask)
    
    {
    	BOOL handled = FALSE;
    	if (key == KEY_LEFT && mask == MASK_ALT)
    
    James Cook's avatar
    James Cook committed
    	{
    
    		selectPrevTab();
    		handled = TRUE;
    	}
    	else if (key == KEY_RIGHT && mask == MASK_ALT)
    	{
    		selectNextTab();
    		handled = TRUE;
    
    James Cook's avatar
    James Cook committed
    	{
    
    James Cook's avatar
    James Cook committed
    		{
    
    	if (!gFocusMgr.childHasKeyboardFocus(getCurrentPanel()))
    
    James Cook's avatar
    James Cook committed
    	{
    
    		// if child has focus, but not the current panel, focus is on a button
    		if (mIsVertical)
    
    Jon Wolk's avatar
    Jon Wolk committed
    		{
    
    			switch(key)
    			{
    			  case KEY_UP:
    				selectPrevTab();
    				handled = TRUE;
    				break;
    			  case KEY_DOWN:
    				selectNextTab();
    				handled = TRUE;
    				break;
    			  case KEY_LEFT:
    				handled = TRUE;
    				break;
    			  case KEY_RIGHT:
    				if (getTabPosition() == LEFT && getCurrentPanel())
    				{
    					getCurrentPanel()->setFocus(TRUE);
    				}
    				handled = TRUE;
    				break;
    			  default:
    				break;
    			}
    
    Jon Wolk's avatar
    Jon Wolk committed
    		}
    
    James Cook's avatar
    James Cook committed
    		{
    
    			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;
    			}
    
    James Cook's avatar
    James Cook committed
    		}
    	}
    
    // virtual
    LLXMLNodePtr LLTabContainer::getXML(bool save_children) const
    
    James Cook's avatar
    James Cook committed
    {
    
    	LLXMLNodePtr node = LLPanel::getXML();
    	node->createChild("tab_position", TRUE)->setStringValue((getTabPosition() == TOP ? "top" : "bottom"));
    	return node;
    
    // virtual
    BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask,	BOOL drop,	EDragAndDropType type, void* cargo_data, EAcceptance *accept, LLString	&tooltip)
    
    James Cook's avatar
    James Cook committed
    {
    
    	BOOL has_scroll_arrows = (getMaxScrollPos() > 0);
    
    James Cook's avatar
    James Cook committed
    
    
    	if( mDragAndDropDelayTimer.getElapsedTimeF32() > SCROLL_DELAY_TIME )
    
    James Cook's avatar
    James Cook committed
    	{
    
    		if (has_scroll_arrows)
    		{
    			if (mJumpPrevArrowBtn->getRect().pointInRect(x,	y))
    			{
    				S32	local_x	= x	- mJumpPrevArrowBtn->getRect().mLeft;
    				S32	local_y	= y	- mJumpPrevArrowBtn->getRect().mBottom;
    				mJumpPrevArrowBtn->handleHover(local_x,	local_y, mask);
    			}
    			if (mJumpNextArrowBtn->getRect().pointInRect(x,	y))
    			{
    				S32	local_x	= x	- mJumpNextArrowBtn->getRect().mLeft;
    				S32	local_y	= y	- mJumpNextArrowBtn->getRect().mBottom;
    				mJumpNextArrowBtn->handleHover(local_x,	local_y, mask);
    			}
    			if (mPrevArrowBtn->getRect().pointInRect(x,	y))
    			{
    				S32	local_x	= x	- mPrevArrowBtn->getRect().mLeft;
    				S32	local_y	= y	- mPrevArrowBtn->getRect().mBottom;
    				mPrevArrowBtn->handleHover(local_x,	local_y, mask);
    			}
    			else if	(mNextArrowBtn->getRect().pointInRect(x, y))
    			{
    				S32	local_x	= x	- mNextArrowBtn->getRect().mLeft;
    				S32	local_y	= y	- mNextArrowBtn->getRect().mBottom;
    				mNextArrowBtn->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();
    				mDragAndDropDelayTimer.stop();
    			}
    		}
    
    	return LLView::handleDragAndDrop(x,	y, mask, drop, type, cargo_data,  accept, tooltip);
    
    James Cook's avatar
    James Cook committed
    }
    
    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 = LLResMgr::getInstance()->getRes( mIsVertical ? LLFONT_SANSSERIF : LLFONT_SANSSERIF_SMALL );
    
    James Cook's avatar
    James Cook committed
    
    	// Store the original label for possible xml export.
    	child->setLabel(label);
    	LLString trimmed_label = label;
    	LLString::trim(trimmed_label);
    
    
    	S32 button_width = mMinTabWidth;
    	if (!mIsVertical)
    	{
    		button_width = llclamp(font->getWidth(trimmed_label) + TAB_PADDING, mMinTabWidth, mMaxTabWidth);
    	}
    	
    
    James Cook's avatar
    James Cook committed
    	// Tab panel
    	S32 tab_panel_top;
    	S32 tab_panel_bottom;
    
    	if( getTabPosition() == LLTabContainer::TOP )
    
    James Cook's avatar
    James Cook committed
    	{
    
    		S32 tab_height = mIsVertical ? BTN_HEIGHT : TABCNTR_TAB_HEIGHT;
    		tab_panel_top = getRect().getHeight() - getTopBorderHeight() - (tab_height - TABCNTR_BUTTON_PANEL_OVERLAP);	
    
    James Cook's avatar
    James Cook committed
    		tab_panel_bottom = LLPANEL_BORDER_WIDTH;
    	}
    	else
    	{
    
    		tab_panel_top = getRect().getHeight() - getTopBorderHeight();
    
    James Cook's avatar
    James Cook committed
    		tab_panel_bottom = (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP);  // Run to the edge, covering up the border
    	}
    	
    
    	LLRect tab_panel_rect;
    	if (mIsVertical)
    	{
    		tab_panel_rect = LLRect(mMinTabWidth + (LLPANEL_BORDER_WIDTH * 2) + TABCNTRV_PAD, 
    								getRect().getHeight() - LLPANEL_BORDER_WIDTH,
    								getRect().getWidth() - LLPANEL_BORDER_WIDTH,
    								LLPANEL_BORDER_WIDTH);
    	}
    	else
    	{
    		tab_panel_rect = LLRect(LLPANEL_BORDER_WIDTH, 
    								tab_panel_top,
    								getRect().getWidth()-LLPANEL_BORDER_WIDTH,
    								tab_panel_bottom );
    	}
    
    James Cook's avatar
    James Cook committed
    	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 );
    	// 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
    
    
    James Cook's avatar
    James Cook committed
    	{
    
    		btn_rect.setLeftTopAndSize(TABCNTRV_PAD + LLPANEL_BORDER_WIDTH + 2,	// JC - Fudge factor
    								   (getRect().getHeight() - getTopBorderHeight() - LLPANEL_BORDER_WIDTH - 1) - ((BTN_HEIGHT + TABCNTRV_PAD) * getTabCount()),
    								   mMinTabWidth,
    								   BTN_HEIGHT);
    	}
    	else if( getTabPosition() == LLTabContainer::TOP )
    	{
    		btn_rect.setLeftTopAndSize( 0, getRect().getHeight() - getTopBorderHeight() + tab_fudge, button_width, TABCNTR_TAB_HEIGHT );
    
    Jon Wolk's avatar
    Jon Wolk committed
    		tab_img = "tab_top_blue.tga";
    		tab_selected_img = "tab_top_selected_blue.tga";
    
    James Cook's avatar
    James Cook committed
    	}
    	else
    	{
    		btn_rect.setOriginAndSize( 0, 0 + tab_fudge, button_width, TABCNTR_TAB_HEIGHT );
    
    Jon Wolk's avatar
    Jon Wolk committed
    		tab_img = "tab_bottom_blue.tga";
    		tab_selected_img = "tab_bottom_selected_blue.tga";
    
    James Cook's avatar
    James Cook committed
    	if (placeholder)
    	{
    		btn_rect.translate(0, -LLBUTTON_V_PAD-2);
    
    		textbox = new LLTextBox(trimmed_label, btn_rect, trimmed_label, font);
    		
    		btn = new LLButton("", LLRect(0,0,0,0));
    
    James Cook's avatar
    James Cook committed
    	}
    	else
    	{
    
    James Cook's avatar
    James Cook committed
    		{
    
    			btn = new LLButton("vert tab button",
    							   btn_rect,
    							   "",
    							   "", 
    							   "", 
    							   &LLTabContainer::onTabBtn, NULL,
    							   font,
    							   trimmed_label, trimmed_label);
    			btn->setImages("tab_left.tga", "tab_left_selected.tga");
    			btn->setScaleImage(TRUE);
    			btn->setHAlign(LLFontGL::LEFT);
    			btn->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT);
    			btn->setTabStop(FALSE);
    			if (indent)
    			{
    				btn->setLeftHPad(indent);
    			}
    
    James Cook's avatar
    James Cook committed
    		}
    		else
    		{
    
    			LLString tooltip = trimmed_label;
    			tooltip += "\nAlt-Left arrow for previous tab";
    			tooltip += "\nAlt-Right arrow for next tab";
    
    			btn = new LLButton(LLString(child->getName()) + " tab",
    							   btn_rect, 
    							   "", "", "",
    							   &LLTabContainer::onTabBtn, NULL, // set userdata below
    							   font,
    							   trimmed_label, trimmed_label );
    			btn->setVisible( FALSE );
    			btn->setToolTip( tooltip );
    			btn->setScaleImage(TRUE);
    			btn->setImages(tab_img, tab_selected_img);
    
    			// 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( getTabPosition() == TOP )
    			{
    				btn->setFollowsTop();
    			}
    			else
    			{
    				btn->setFollowsBottom();
    			}
    
    James Cook's avatar
    James Cook committed
    		}
    
    	}
    	
    	LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata, textbox );
    	insertTuple( tuple, insertion_point );
    
    James Cook's avatar
    James Cook committed
    
    
    	if (textbox)
    	{
    		textbox->setSaveToXML(false);
    		addChild( textbox, 0 );
    	}
    	if (btn)
    	{
    		btn->setSaveToXML(false);
    
    James Cook's avatar
    James Cook committed
    		btn->setCallbackUserData( tuple );
    		addChild( btn, 0 );
    	}
    
    James Cook's avatar
    James Cook committed
    	if( select )
    	{
    		selectLastTab();
    	}
    
    
    	updateMaxScrollPos();
    }
    
    void LLTabContainer::addPlaceholder(LLPanel* child, const LLString& label)
    {
    	addTabPanel(child, label, FALSE, NULL, NULL, 0, TRUE);
    
    James Cook's avatar
    James Cook committed
    }
    
    void LLTabContainer::removeTabPanel(LLPanel* child)
    {
    
    	if (mIsVertical)
    	{
    		// Fix-up button sizes
    		S32 tab_count = 0;
    		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
    		{
    			LLTabTuple* tuple = *iter;
    			LLRect rect;
    			rect.setLeftTopAndSize(TABCNTRV_PAD + LLPANEL_BORDER_WIDTH + 2,	// JC - Fudge factor
    								   (getRect().getHeight() - LLPANEL_BORDER_WIDTH - 1) - ((BTN_HEIGHT + TABCNTRV_PAD) * (tab_count)),
    								   mMinTabWidth,
    								   BTN_HEIGHT);
    			if (tuple->mPlaceholderText)
    			{
    				tuple->mPlaceholderText->setRect(rect);
    			}
    			else
    			{
    				tuple->mButton->setRect(rect);
    			}
    			tab_count++;
    		}
    	}
    	else
    	{
    		// 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;
    			}
    		}
    	}
    	
    	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)
    
    James Cook's avatar
    James Cook committed
    	{
    		LLTabTuple* tuple = *iter;
    		if( tuple->mTabPanel == child )
    		{
    
     			removeChild( tuple->mButton );
     			delete tuple->mButton;
    
     			removeChild( tuple->mTabPanel );
    // 			delete tuple->mTabPanel;
    			
    			mTabList.erase( iter );
    			delete tuple;
    
    
    	// make sure we don't have more locked tabs than we have tabs
    	mLockedTabCount = llmin(getTabCount(), mLockedTabCount);
    
    	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 LLTabContainer::lockTabs(S32 num_tabs)
    
    James Cook's avatar
    James Cook committed
    {
    
    	// count current tabs or use supplied value and ensure no new tabs get
    	// inserted between them
    	mLockedTabCount = num_tabs > 0 ? llmin(getTabCount(), num_tabs) : getTabCount();
    }
    
    void LLTabContainer::unlockTabs()
    {
    	mLockedTabCount = 0;
    }
    
    void LLTabContainer::enableTabButton(S32 which, BOOL enable)
    {
    	if (which >= 0 && which < (S32)mTabList.size())
    
    James Cook's avatar
    James Cook committed
    	{
    
    		mTabList[which]->mButton->setEnabled(enable);
    
    void LLTabContainer::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;
    
    James Cook's avatar
    James Cook committed
    
    
    		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* LLTabContainer::getCurrentPanel()
    {
    	if (mCurrentTabIdx >= 0 && mCurrentTabIdx < (S32) mTabList.size())
    	{
    		return mTabList[mCurrentTabIdx]->mTabPanel;
    	}
    	return NULL;
    }
    
    S32 LLTabContainer::getCurrentPanelIndex()
    {
    	return mCurrentTabIdx;
    }
    
    S32 LLTabContainer::getTabCount()
    
    James Cook's avatar
    James Cook committed
    {
    
    James Cook's avatar
    James Cook committed
    
    
    LLPanel* LLTabContainer::getPanelByIndex(S32 index)
    {
    	if (index >= 0 && index < (S32)mTabList.size())
    
    James Cook's avatar
    James Cook committed
    	{