diff options
Diffstat (limited to 'indra/llui/lltabcontainer.cpp')
-rw-r--r-- | indra/llui/lltabcontainer.cpp | 1469 |
1 files changed, 1469 insertions, 0 deletions
diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp new file mode 100644 index 0000000000..fd85dbb2f4 --- /dev/null +++ b/indra/llui/lltabcontainer.cpp @@ -0,0 +1,1469 @@ +/** + * @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); +} + |