Skip to content
Snippets Groups Projects
lltabcontainer.cpp 59.1 KiB
Newer Older
James Cook's avatar
James Cook committed
/** 
 * @file lltabcontainer.cpp
James Cook's avatar
James Cook committed
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
James Cook's avatar
James Cook committed
 */

#include "linden_common.h"
James Cook's avatar
James Cook committed
#include "lltabcontainer.h"
#include "llviewereventrecorder.h"
James Cook's avatar
James Cook committed
#include "llfocusmgr.h"
James Cook's avatar
James Cook committed
#include "llrect.h"
#include "llresizehandle.h"
#include "lltextbox.h"
#include "llcriticaldamp.h"
#include "lluictrlfactory.h"
#include "llrender.h"

//----------------------------------------------------------------------------

// Implementation Notes:
//  - Each tab points to a LLPanel (see LLTabTuple below)
//  - When a tab is selected, the validation callback
//    (LLUICtrl::mValidateSignal) is called
//  -  If the validation callback returns true (or none is provided),
//     the tab is changed and the commit callback
//     (LLUICtrl::mCommitSignal) is called
//  - Callbacks pass the LLTabContainer as the control,
//    and the NAME of the selected PANEL as the LLSD data

//----------------------------------------------------------------------------
James Cook's avatar
James Cook committed

const F32 SCROLL_STEP_TIME = 0.4f;
const F32 SCROLL_DELAY_TIME = 0.5f;

void LLTabContainer::TabPositions::declareValues()
{
	declare("top", LLTabContainer::TOP);
	declare("bottom", LLTabContainer::BOTTOM);
	declare("left", LLTabContainer::LEFT);
}

//----------------------------------------------------------------------------

// Structure used to map tab buttons to and from tab panels
class LLTabTuple
{
public:
	LLTabTuple( LLTabContainer* c, LLPanel* p, LLButton* b, LLTextBox* placeholder = NULL)
		:
		mTabContainer(c),
		mTabPanel(p),
		mButton(b),
		mOldState(FALSE),
		mPlaceholderText(placeholder),
	{}

	LLTabContainer*  mTabContainer;
	LLPanel*		 mTabPanel;
	LLButton*		 mButton;
	BOOL			 mOldState;
	LLTextBox*		 mPlaceholderText;
	S32				 mPadding;
};

//----------------------------------------------------------------------------

//============================================================================
/*
 * @file lltabcontainer.cpp
 * @brief class implements LLButton with LLIconCtrl on it
 */
class LLCustomButtonIconCtrl : public LLButton
{
public:
	struct Params
	:	public LLInitParam::Block<Params, LLButton::Params>
	{
		// LEFT, RIGHT, TOP, BOTTOM paddings of LLIconCtrl in this class has same value
		Optional<S32>					icon_ctrl_pad;

		Params()
		:	icon_ctrl_pad("icon_ctrl_pad", 1)
		{}
	};

protected:
	friend class LLUICtrlFactory;

	LLCustomButtonIconCtrl(const Params& p)
	:	LLButton(p),
Tofu Linden's avatar
Tofu Linden committed
		mIconAlignment(LLFontGL::HCENTER),

public:

	void updateLayout()
	{
		LLRect button_rect = getRect();
		LLRect icon_rect = mIcon->getRect();

		S32 icon_size = button_rect.getHeight() - 2*mIconCtrlPad;

		switch(mIconAlignment)
		{
		case LLFontGL::LEFT:
			icon_rect.setLeftTopAndSize(button_rect.mLeft + mIconCtrlPad, button_rect.mTop - mIconCtrlPad, 
				icon_size, icon_size);
			setLeftHPad(icon_size + mIconCtrlPad * 2);
			break;
		case LLFontGL::HCENTER:
			icon_rect.setLeftTopAndSize(button_rect.mRight - (button_rect.getWidth() + mIconCtrlPad - icon_size)/2, button_rect.mTop - mIconCtrlPad, 
				icon_size, icon_size);
			setRightHPad(icon_size + mIconCtrlPad * 2);
			break;
		case LLFontGL::RIGHT:
			icon_rect.setLeftTopAndSize(button_rect.mRight - mIconCtrlPad - icon_size, button_rect.mTop - mIconCtrlPad, 
				icon_size, icon_size);
			setRightHPad(icon_size + mIconCtrlPad * 2);
			break;
		default:
			break;
		}
		mIcon->setRect(icon_rect);
	}

	void setIcon(LLIconCtrl* icon, LLFontGL::HAlign alignment = LLFontGL::LEFT)
	{
		if(icon)
		{
			if(mIcon)
			{
				removeChild(mIcon);
				mIcon->die();
			}
			mIcon = icon;
			mIconAlignment = alignment;

			addChild(mIcon);
			updateLayout();
		}
	}


private:
	LLIconCtrl* mIcon;
	LLFontGL::HAlign mIconAlignment;
	S32 mIconCtrlPad;
};
//============================================================================

struct LLPlaceHolderPanel : public LLPanel
{
	// create dummy param block to register with "placeholder" nane
	struct Params : public LLPanel::Params{};
	LLPlaceHolderPanel(const Params& p) : LLPanel(p)
	{}
};
static LLDefaultChildRegistry::Register<LLPlaceHolderPanel> r1("placeholder");
static LLDefaultChildRegistry::Register<LLTabContainer> r2("tab_container");
LLTabContainer::TabParams::TabParams()
:	tab_top_image_unselected("tab_top_image_unselected"),
	tab_top_image_selected("tab_top_image_selected"),
	tab_top_image_flash("tab_top_image_flash"),
	tab_bottom_image_unselected("tab_bottom_image_unselected"),
	tab_bottom_image_selected("tab_bottom_image_selected"),
	tab_bottom_image_flash("tab_bottom_image_flash"),
	tab_left_image_unselected("tab_left_image_unselected"),
	tab_left_image_selected("tab_left_image_selected"),
	tab_left_image_flash("tab_left_image_flash")
LLTabContainer::Params::Params()
:	tab_width("tab_width"),
	tab_min_width("tab_min_width"),
	tab_max_width("tab_max_width"),
	tab_height("tab_height"),
	label_pad_bottom("label_pad_bottom"),
	label_pad_left("label_pad_left"),
	tab_position("tab_position"),
	hide_tabs("hide_tabs", false),
	first_tab("first_tab"),
	middle_tab("middle_tab"),
	last_tab("last_tab"),
	use_custom_icon_ctrl("use_custom_icon_ctrl", false),
	open_tabs_on_drag_and_drop("open_tabs_on_drag_and_drop", false),
	enable_tabs_flashing("enable_tabs_flashing", false),
	tabs_flashing_color("tabs_flashing_color"),
	tab_icon_ctrl_pad("tab_icon_ctrl_pad", 0),

LLTabContainer::LLTabContainer(const LLTabContainer::Params& p)
:	LLPanel(p),
James Cook's avatar
James Cook committed
	mCurrentTabIdx(-1),
	mTabsHidden(p.hide_tabs),
James Cook's avatar
James Cook committed
	mScrolled(FALSE),
	mScrollPos(0),
	mScrollPosPixels(0),
	mMaxScrollPos(0),
	mTitleBox(NULL),
	mTopBorderHeight(LLPANEL_BORDER_WIDTH),
	mMinTabWidth(0),
	mMaxTabWidth(p.tab_max_width),
	mTabHeight(p.tab_height),
	mLabelPadBottom(p.label_pad_bottom),
	mLabelPadLeft(p.label_pad_left),
	mIsVertical( p.tab_position == LEFT ),
	// Horizontal Specific
	mJumpPrevArrowBtn(NULL),
	mJumpNextArrowBtn(NULL),
	mRightTabBtnOffset(p.tab_padding_right),
	mTotalTabWidth(0),
	mTabPosition(p.tab_position),
	mFontHalign(p.font_halign),
	mFirstTabParams(p.first_tab),
	mMiddleTabParams(p.middle_tab),
	mLastTabParams(p.last_tab),
	mCustomIconCtrlUsed(p.use_custom_icon_ctrl),
	mOpenTabsOnDragAndDrop(p.open_tabs_on_drag_and_drop),
	mEnableTabsFlashing(p.enable_tabs_flashing),
	mTabsFlashingColor(p.tabs_flashing_color),
	mUseTabEllipses(p.use_ellipses)
{
	static LLUICachedControl<S32> tabcntr_vert_tab_min_width ("UITabCntrVertTabMinWidth", 0);

	mDragAndDropDelayTimer.stop();

	if (p.tab_width.isProvided())
	{
		mMinTabWidth = p.tab_width;
	}
	else if (!mIsVertical)
		mMinTabWidth = p.tab_min_width;
	else
	{
		// *HACK: support default min width for legacy vertical
		// tab containers
		mMinTabWidth = tabcntr_vert_tab_min_width;
	}

    if (p.tabs_flashing_color.isProvided())
    {
        mEnableTabsFlashing = true;
    }

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(std::string_view name, BOOL recurse) 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);
James Cook's avatar
James Cook committed
			if (child)
			{
				return child;
			}
		}
	}
	return LLView::getChildView(name, recurse);
}

//virtual
LLView* LLTabContainer::findChildView(std::string_view 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->findChildView(name, recurse);
			if (child)
			{
				return child;
			}
		}
	}
	return LLView::findChildView(name, recurse);
bool LLTabContainer::addChild(LLView* view, S32 tab_group)
{
	LLPanel* panelp = dynamic_cast<LLPanel*>(view);

	if (panelp)
	{
		addTabPanel(TabPanelParams().panel(panelp).label(panelp->getLabel()).is_placeholder(dynamic_cast<LLPlaceHolderPanel*>(view) != NULL));
		return true;
	}
	else
	{
		return LLUICtrl::addChild(view, tab_group);
	}
}

BOOL LLTabContainer::postBuild()
{
	selectFirstTab();

	return TRUE;
}

James Cook's avatar
James Cook committed
{
	static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0);
	static LLUICachedControl<S32> tabcntrv_arrow_btn_size ("UITabCntrvArrowBtnSize", 0);
	static LLUICachedControl<S32> tabcntr_tab_h_pad ("UITabCntrTabHPad", 0);
	static LLUICachedControl<S32> tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0);
	static LLUICachedControl<S32> tabcntr_tab_partial_width ("UITabCntrTabPartialWidth", 0);
	S32 cur_scroll_pos = getScrollPos();
James Cook's avatar
James Cook committed
	{
James Cook's avatar
James Cook committed
		{
			target_pixel_scroll = cur_scroll_pos * (BTN_HEIGHT + tabcntrv_pad);
		}
		else
		{
			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)

				if( (*iter)->mVisible )
					target_pixel_scroll += (*iter)->mButton->getRect().getWidth();

			// 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, LLSmoothInterpolation::getInterpolant(0.08f)));
	BOOL has_scroll_arrows = !mHideScrollArrows && !getTabsHidden() && ((mMaxScrollPos > 0) || (mScrollPosPixels > 0));
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
	if (getTabsHidden())
		for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
		{
			LLTabTuple* tuple = *iter;
			tuple->mButton->setVisible( FALSE );
		}
	{
		LLRect clip_rect = getLocalRect();
		clip_rect.mLeft+=(LLPANEL_BORDER_WIDTH + 2);
		clip_rect.mRight-=(LLPANEL_BORDER_WIDTH + 2);
		LLLocalClipRect clip(clip_rect);
		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

		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

			if( !tuple->mVisible )
			{
				tuple->mButton->setVisible( false );
				continue;
			}

			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 );
					}
James Cook's avatar
James Cook committed


		if( mIsVertical && has_scroll_arrows )
		{
			// Redraw the arrows so that they appears on top.
			gGL.pushUIMatrix();
			gGL.translateUI((F32)mPrevArrowBtn->getRect().mLeft, (F32)mPrevArrowBtn->getRect().mBottom, 0.f);
			gGL.pushUIMatrix();
			gGL.translateUI((F32)mNextArrowBtn->getRect().mLeft, (F32)mNextArrowBtn->getRect().mBottom, 0.f);
	mPrevArrowBtn->setFlashing(false);
	mNextArrowBtn->setFlashing(false);
// virtual
BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask )
James Cook's avatar
James Cook committed
{
	static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0);
	BOOL has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0) && !getTabsHidden();
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();
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 );
			S32 index = getCurrentPanelIndex();
			index = llclamp(index, 0, tab_count-1);
			LLButton* tab_button = getTab(index)->mButton;
			tab_button->setFocus(TRUE);
James Cook's avatar
James Cook committed
		}
	}

#if AL_VIEWER_EVENT_RECORDER
	if (handled && LLViewerEventRecorder::getLoggingStatus()) {
		// Note: May need to also capture local coords right here ?
		LLViewerEventRecorder::instance().update_xui(getPathname( ));
	}
// virtual
BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask )
James Cook's avatar
James Cook committed
{
	BOOL has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0) && !getTabsHidden();
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 has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0)  && !getTabsHidden();
James Cook's avatar
James Cook committed

	S32 local_x = x - getRect().mLeft;
	S32 local_y = y - getRect().mBottom;

		if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y))
		{
			local_x = x - mJumpPrevArrowBtn->getRect().mLeft;
			local_y = y - mJumpPrevArrowBtn->getRect().mBottom;
			handled = mJumpPrevArrowBtn->handleMouseUp(local_x, local_y, mask);
		}
		else if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x,	y))
		{
			local_x	= x	- mJumpNextArrowBtn->getRect().mLeft;
			local_y	= y	- mJumpNextArrowBtn->getRect().mBottom;
			handled = mJumpNextArrowBtn->handleMouseUp(local_x,	local_y, mask);
		}
		else if (mPrevArrowBtn && mPrevArrowBtn->getRect().pointInRect(x, y))
		{
			local_x = x - mPrevArrowBtn->getRect().mLeft;
			local_y = y - mPrevArrowBtn->getRect().mBottom;
			handled = mPrevArrowBtn->handleMouseUp(local_x, local_y, mask);
		}
		else if (mNextArrowBtn && mNextArrowBtn->getRect().pointInRect(x, y))
		{
			local_x = x - mNextArrowBtn->getRect().mLeft;
			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
	}

#if AL_VIEWER_EVENT_RECORDER
	if (handled && LLViewerEventRecorder::getLoggingStatus()) {
		// Note: may need to capture local coords here
		LLViewerEventRecorder::instance().update_xui(getPathname( ));
	}
BOOL LLTabContainer::handleToolTip( S32 x, S32 y, MASK mask)
James Cook's avatar
James Cook committed
{
	static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0);
	BOOL handled = LLPanel::handleToolTip( x, y, mask);
	if (!handled && getTabCount() > 0 && !getTabsHidden()) 
James Cook's avatar
James Cook committed
	{
James Cook's avatar
James Cook committed

		BOOL has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0);
James Cook's avatar
James Cook committed
		{
			clip = LLRect(firsttuple->mButton->getRect().mLeft,
						  has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - tabcntrv_pad : mPrevArrowBtn->getRect().mTop,
						  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)
			{
				LLButton* tab_button = (*iter)->mButton;
				if (!tab_button->getVisible()) continue;
				S32 local_x = x - tab_button->getRect().mLeft;
				S32 local_y = y - tab_button->getRect().mBottom;
				handled = tab_button->handleToolTip(local_x, local_y, mask);
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
		}
	}
BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask,	BOOL drop,	EDragAndDropType type, void* cargo_data, EAcceptance *accept, std::string	&tooltip)
James Cook's avatar
James Cook committed
{
	BOOL has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0);
James Cook's avatar
James Cook committed

	if(mOpenTabsOnDragAndDrop && !getTabsHidden())
James Cook's avatar
James Cook committed
	{
		// In that case, we'll open the hovered tab while dragging and dropping items.
		// This allows for drilling through tabs.
		if (mDragAndDropDelayTimer.getStarted())
			if (mDragAndDropDelayTimer.getElapsedTimeF32() > SCROLL_DELAY_TIME)
				if (has_scroll_arrows)
				{
					if (mJumpPrevArrowBtn && 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 && 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();
					}
				}
				// Stop the timer whether successful or not. Don't let it run forever.
		else 
		{
			// Start a timer so we don't open tabs as soon as we hover on them
			mDragAndDropDelayTimer.start();
		}
	return LLView::handleDragAndDrop(x,	y, mask, drop, type, cargo_data,  accept, tooltip);
void LLTabContainer::addTabPanel(LLPanel* panelp)
{
	addTabPanel(TabPanelParams().panel(panelp));
}

void LLTabContainer::update_images(LLTabTuple* tuple, const TabParams& params, LLTabContainer::TabPosition pos)
{
	if (tuple && tuple->mButton)
	{
		if (pos == LLTabContainer::TOP)
		{
			tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_top_image_unselected));
			tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_top_image_selected));
			tuple->mButton->setImageFlash(static_cast<LLUIImage*>(params.tab_top_image_flash));
		}
		else if (pos == LLTabContainer::BOTTOM)
		{
			tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_bottom_image_unselected));
			tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_bottom_image_selected));
			tuple->mButton->setImageFlash(static_cast<LLUIImage*>(params.tab_bottom_image_flash));
		}
		else if (pos == LLTabContainer::LEFT)
		{
			tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_left_image_unselected));
			tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_left_image_selected));
			tuple->mButton->setImageFlash(static_cast<LLUIImage*>(params.tab_left_image_flash));
void LLTabContainer::addTabPanel(const TabPanelParams& panel)
James Cook's avatar
James Cook committed
{
	LLPanel* child = panel.panel();
Tofu Linden's avatar
Tofu Linden committed

	llassert(child);
	if (!child) return;

	const std::string& label = panel.label.isProvided() 
			? panel.label() 
			: panel.panel()->getLabel();
	BOOL select = panel.select_tab(); 
	S32 indent = panel.indent();
	BOOL placeholder = panel.is_placeholder;
	eInsertionPoint insertion_point = panel.insert_at();

	static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0);
	static LLUICachedControl<S32> tabcntr_button_panel_overlap ("UITabCntrButtonPanelOverlap", 0);
	static LLUICachedControl<S32> tab_padding ("UITabPadding", 0);
James Cook's avatar
James Cook committed
	if (child->getParent() == this)
	{
		// already a child of mine
		return;
	}

	// Store the original label for possible xml export.
	child->setLabel(label);
	std::string trimmed_label = label;
	LLStringUtil::trim(trimmed_label);
James Cook's avatar
James Cook committed

	S32 button_width = mMinTabWidth;
	if (!mIsVertical)
	{
		button_width = llclamp(mFont->getWidth(trimmed_label) + tab_padding, mMinTabWidth, mMaxTabWidth);
James Cook's avatar
James Cook committed
	// Tab panel
	S32 tab_panel_top;
	S32 tab_panel_bottom;
Steven Bennetts's avatar
Steven Bennetts committed
	if (!getTabsHidden()) 
James Cook's avatar
James Cook committed
	{
Steven Bennetts's avatar
Steven Bennetts committed
		if( getTabPosition() == LLTabContainer::TOP )
		{
			S32 tab_height = mIsVertical ? BTN_HEIGHT : mTabHeight;
Steven Bennetts's avatar
Steven Bennetts committed
			tab_panel_top = getRect().getHeight() - getTopBorderHeight() - (tab_height - tabcntr_button_panel_overlap);	
			tab_panel_bottom = LLPANEL_BORDER_WIDTH;