/** * @file lltabcontainer.cpp * @brief LLTabContainer class * * $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$ */ #include "linden_common.h" #include "lltabcontainer.h" #include "llviewereventrecorder.h" #include "llfocusmgr.h" #include "lllocalcliprect.h" #include "llrect.h" #include "llresizehandle.h" #include "lltextbox.h" #include "llcriticaldamp.h" #include "lluictrlfactory.h" #include "llrender.h" #include "llfloater.h" #include "lltrans.h" #include "lluiusage.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 //---------------------------------------------------------------------------- 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), mPadding(0), mVisible(true) {} LLTabContainer* mTabContainer; LLPanel* mTabPanel; LLButton* mButton; bool mOldState; LLTextBox* mPlaceholderText; S32 mPadding; mutable bool mVisible; }; //---------------------------------------------------------------------------- //============================================================================ /* * @file lltabcontainer.cpp * @brief class implements LLButton with LLIconCtrl on it */ class LLCustomButtonIconCtrl : public LLButton { public: struct Params : public LLInitParam::Block { // LEFT, RIGHT, TOP, BOTTOM paddings of LLIconCtrl in this class has same value Optional icon_ctrl_pad; Params() : icon_ctrl_pad("icon_ctrl_pad", 1) {} }; protected: friend class LLUICtrlFactory; LLCustomButtonIconCtrl(const Params& p) : LLButton(p), mIcon(NULL), mIconAlignment(LLFontGL::HCENTER), mIconCtrlPad(p.icon_ctrl_pad) {} 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(); } } LLIconCtrl* getIconCtrl() const { return mIcon; } 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 r1("placeholder"); static LLDefaultChildRegistry::Register 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), hide_scroll_arrows("hide_scroll_arrows", false), tab_padding_right("tab_padding_right"), 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), use_ellipses("use_ellipses"), font_halign("halign"), use_tab_offset("use_tab_offset", false) {} LLTabContainer::LLTabContainer(const LLTabContainer::Params& p) : LLPanel(p), mCurrentTabIdx(-1), mTabsHidden(p.hide_tabs), mScrolled(false), mScrollPos(0), mScrollPosPixels(0), mMaxScrollPos(0), mTitleBox(NULL), mTopBorderHeight(LLPANEL_BORDER_WIDTH), mLockedTabCount(0), mMinTabWidth(0), mMaxTabWidth(p.tab_max_width), mTabHeight(p.tab_height), mLabelPadBottom(p.label_pad_bottom), mLabelPadLeft(p.label_pad_left), mPrevArrowBtn(NULL), mNextArrowBtn(NULL), mIsVertical( p.tab_position == LEFT ), mHideScrollArrows(p.hide_scroll_arrows), // Horizontal Specific mJumpPrevArrowBtn(NULL), mJumpNextArrowBtn(NULL), mRightTabBtnOffset(p.tab_padding_right), mTotalTabWidth(0), mTabPosition(p.tab_position), mFontHalign(p.font_halign), mFont(p.font), 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), mTabIconCtrlPad(p.tab_icon_ctrl_pad), mEnableTabsFlashing(p.enable_tabs_flashing), mTabsFlashingColor(p.tabs_flashing_color), mUseTabEllipses(p.use_ellipses), mUseTabOffset(p.use_tab_offset) { static LLUICachedControl 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; } initButtons( ); } LLTabContainer::~LLTabContainer() { std::for_each(mTabList.begin(), mTabList.end(), DeletePointer()); mTabList.clear(); } //virtual void LLTabContainer::setValue(const LLSD& value) { selectTab((S32) value.asInteger()); } //virtual void LLTabContainer::reshape(S32 width, S32 height, bool called_from_parent) { LLPanel::reshape( width, height, called_from_parent ); updateMaxScrollPos(); } //virtual LLView* LLTabContainer::getChildView(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->getChildView(name, recurse); 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(view); if (panelp) { addTabPanel(TabPanelParams().panel(panelp).label(panelp->getLabel()).is_placeholder(dynamic_cast(view) != NULL)); return true; } else { return LLUICtrl::addChild(view, tab_group); } } bool LLTabContainer::postBuild() { selectFirstTab(); return true; } // virtual void LLTabContainer::draw() { static LLUICachedControl tabcntrv_pad ("UITabCntrvPad", 0); static LLUICachedControl tabcntrv_arrow_btn_size ("UITabCntrvArrowBtnSize", 0); static LLUICachedControl tabcntr_tab_h_pad ("UITabCntrTabHPad", 0); static LLUICachedControl tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); static LLUICachedControl tabcntr_tab_partial_width ("UITabCntrTabPartialWidth", 0); S32 target_pixel_scroll = 0; S32 cur_scroll_pos = getScrollPos(); if (cur_scroll_pos > 0) { if (mIsVertical) { 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 (cur_scroll_pos == 0) { break; } if( (*iter)->mVisible ) 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); } } setScrollPosPixels((S32)lerp((F32)getScrollPosPixels(), (F32)target_pixel_scroll, LLSmoothInterpolation::getInterpolant(0.08f))); bool has_scroll_arrows = !mHideScrollArrows && !getTabsHidden() && ((mMaxScrollPos > 0) || (mScrollPosPixels > 0)); if (!mIsVertical) { mJumpPrevArrowBtn->setVisible( has_scroll_arrows ); mJumpNextArrowBtn->setVisible( has_scroll_arrows ); } mPrevArrowBtn->setVisible( has_scroll_arrows ); mNextArrowBtn->setVisible( has_scroll_arrows ); 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 ); } 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; 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(); 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 ); } } } idx++; } 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); mPrevArrowBtn->draw(); gGL.popUIMatrix(); gGL.pushUIMatrix(); gGL.translateUI((F32)mNextArrowBtn->getRect().mLeft, (F32)mNextArrowBtn->getRect().mBottom, 0.f); mNextArrowBtn->draw(); gGL.popUIMatrix(); } } mPrevArrowBtn->setFlashing(false); mNextArrowBtn->setFlashing(false); } // virtual bool LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) { static LLUICachedControl tabcntrv_pad ("UITabCntrvPad", 0); bool handled = false; bool has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0) && !getTabsHidden(); 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; 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); } } if (!handled) { handled = LLPanel::handleMouseDown( x, y, mask ); } S32 tab_count = getTabCount(); if (tab_count > 0 && !getTabsHidden()) { LLTabTuple* firsttuple = getTab(0); LLRect tab_rect; if (mIsVertical) { 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); tab_button->setFocus(true); mMouseDownTimer.start(); } } if (handled) { // Note: May need to also capture local coords right here ? LLViewerEventRecorder::instance().update_xui(getPathname( )); } return handled; } // virtual bool LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) { bool handled = false; bool has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0) && !getTabsHidden(); 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; handled = mJumpPrevArrowBtn->handleHover(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->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); } } if (!handled) { handled = LLPanel::handleHover(x, y, mask); } F32 drag_delay = 0.25f; // filter out clicks from dragging if (mMouseDownTimer.getElapsedTimeF32() > drag_delay) { commitHoveredButton(x, y); } return handled; } // virtual bool LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) { bool handled = false; bool has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0) && !getTabsHidden(); S32 local_x = x - getRect().mLeft; S32 local_y = y - getRect().mBottom; if (has_scroll_arrows) { 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); } } if (!handled) { handled = LLPanel::handleMouseUp( x, y, mask ); } commitHoveredButton(x, y); mMouseDownTimer.stop(); LLPanel* cur_panel = getCurrentPanel(); if (hasMouseCapture()) { 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); } if (handled) { // Note: may need to capture local coords here LLViewerEventRecorder::instance().update_xui(getPathname( )); } return handled; } // virtual bool LLTabContainer::handleToolTip( S32 x, S32 y, MASK mask) { static LLUICachedControl tabcntrv_pad ("UITabCntrvPad", 0); bool handled = LLPanel::handleToolTip( x, y, mask); if (!handled && getTabCount() > 0 && !getTabsHidden()) { LLTabTuple* firsttuple = getTab(0); bool has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0); LLRect clip; if (mIsVertical) { 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 ); } else { 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 ); } if( clip.pointInRect( x, y ) ) { 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); if( handled ) { break; } } } } return handled; } // virtual bool LLTabContainer::handleKeyHere(KEY key, MASK mask) { bool handled = false; if (key == KEY_LEFT && mask == MASK_ALT) { selectPrevTab(); handled = true; } else if (key == KEY_RIGHT && mask == MASK_ALT) { 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 if (mIsVertical) { 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; } } else { 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 bool LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType type, void* cargo_data, EAcceptance *accept, std::string &tooltip) { bool has_scroll_arrows = !mHideScrollArrows && (getMaxScrollPos() > 0); if(mOpenTabsOnDragAndDrop && !getTabsHidden()) { // 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. mDragAndDropDelayTimer.stop(); } } 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)); } // function to update images void LLTabContainer::update_images(LLTabTuple* tuple, TabParams params, LLTabContainer::TabPosition pos) { if (tuple && tuple->mButton) { if (pos == LLTabContainer::TOP) { tuple->mButton->setImageUnselected(static_cast(params.tab_top_image_unselected)); tuple->mButton->setImageSelected(static_cast(params.tab_top_image_selected)); tuple->mButton->setImageFlash(static_cast(params.tab_top_image_flash)); } else if (pos == LLTabContainer::BOTTOM) { tuple->mButton->setImageUnselected(static_cast(params.tab_bottom_image_unselected)); tuple->mButton->setImageSelected(static_cast(params.tab_bottom_image_selected)); tuple->mButton->setImageFlash(static_cast(params.tab_bottom_image_flash)); } else if (pos == LLTabContainer::LEFT) { tuple->mButton->setImageUnselected(static_cast(params.tab_left_image_unselected)); tuple->mButton->setImageSelected(static_cast(params.tab_left_image_selected)); tuple->mButton->setImageFlash(static_cast(params.tab_left_image_flash)); } } } void LLTabContainer::addTabPanel(const TabPanelParams& panel) { LLPanel* child = panel.panel(); 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 tabcntrv_pad ("UITabCntrvPad", 0); static LLUICachedControl tabcntr_button_panel_overlap ("UITabCntrButtonPanelOverlap", 0); static LLUICachedControl tab_padding ("UITabPadding", 0); 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); S32 button_width = mMinTabWidth; if (!mIsVertical) { button_width = llclamp(mFont->getWidth(trimmed_label) + tab_padding, mMinTabWidth, mMaxTabWidth); } // Tab panel S32 tab_panel_top; S32 tab_panel_bottom; if (!getTabsHidden()) { if( getTabPosition() == LLTabContainer::TOP ) { S32 tab_height = mIsVertical ? BTN_HEIGHT : mTabHeight; tab_panel_top = getRect().getHeight() - getTopBorderHeight() - (tab_height - tabcntr_button_panel_overlap); tab_panel_bottom = LLPANEL_BORDER_WIDTH; } else { tab_panel_top = getRect().getHeight() - getTopBorderHeight(); tab_panel_bottom = (mTabHeight - tabcntr_button_panel_overlap); // Run to the edge, covering up the border } } else { // Skip tab button space if tabs are invisible (EXT-576) tab_panel_top = getRect().getHeight(); tab_panel_bottom = LLPANEL_BORDER_WIDTH; } LLRect tab_panel_rect; if (!getTabsHidden() && mIsVertical) { tab_panel_rect = LLRect(mMinTabWidth + mRightTabBtnOffset + (LLPANEL_BORDER_WIDTH * 2) + tabcntrv_pad, getRect().getHeight() - LLPANEL_BORDER_WIDTH, getRect().getWidth() - LLPANEL_BORDER_WIDTH, LLPANEL_BORDER_WIDTH); } else { S32 left_offset = mUseTabOffset ? LLPANEL_BORDER_WIDTH * 3 : LLPANEL_BORDER_WIDTH; S32 right_offset = mUseTabOffset ? LLPANEL_BORDER_WIDTH * 2 : LLPANEL_BORDER_WIDTH; tab_panel_rect = LLRect(left_offset, tab_panel_top, getRect().getWidth() - right_offset, 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 ); // 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(). LLUIImage* tab_img = NULL; LLUIImage* tab_selected_img = NULL; S32 tab_fudge = 1; // To make new tab art look better, nudge buttons up 1 pel if (mIsVertical) { 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, mTabHeight); tab_img = mMiddleTabParams.tab_top_image_unselected; tab_selected_img = mMiddleTabParams.tab_top_image_selected; } else { btn_rect.setOriginAndSize( 0, 0 + tab_fudge, button_width, mTabHeight); tab_img = mMiddleTabParams.tab_bottom_image_unselected; tab_selected_img = mMiddleTabParams.tab_bottom_image_selected; } LLTextBox* textbox = NULL; LLButton* btn = NULL; LLCustomButtonIconCtrl::Params custom_btn_params; { custom_btn_params.icon_ctrl_pad(mTabIconCtrlPad); } LLButton::Params normal_btn_params; if (placeholder) { btn_rect.translate(0, -6); // *TODO: make configurable LLTextBox::Params params; params.name(trimmed_label); params.rect(btn_rect); params.initial_value(trimmed_label); params.font(mFont); textbox = LLUICtrlFactory::create (params); LLButton::Params p; p.name("placeholder"); btn = LLUICtrlFactory::create(p); } else { LLButton::Params& p = (mCustomIconCtrlUsed ? custom_btn_params : normal_btn_params); p.rect(btn_rect); p.font(mFont); p.font_halign = mFontHalign; p.label(trimmed_label); p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); if (indent) { p.pad_left(indent); } p.pad_bottom( mLabelPadBottom ); p.scale_image(true); p.tab_stop(false); p.label_shadow(false); p.follows.flags = FOLLOWS_LEFT; if (mIsVertical) { p.name("vtab_"+std::string(child->getName())); p.image_unselected(mMiddleTabParams.tab_left_image_unselected); p.image_selected(mMiddleTabParams.tab_left_image_selected); p.follows.flags = p.follows.flags() | FOLLOWS_TOP; } else { p.name("htab_"+std::string(child->getName())); p.visible(false); p.image_unselected(tab_img); p.image_selected(tab_selected_img); p.follows.flags = p.follows.flags() | (getTabPosition() == TOP ? FOLLOWS_TOP : FOLLOWS_BOTTOM); // Try to squeeze in a bit more text p.pad_left( mLabelPadLeft ); p.pad_right(2); } // inits flash timer p.button_flash_enable = mEnableTabsFlashing; p.flash_color = mTabsFlashingColor; // *TODO : It seems wrong not to use p in both cases considering the way p is initialized if (mCustomIconCtrlUsed) { btn = LLUICtrlFactory::create(custom_btn_params); } else { btn = LLUICtrlFactory::create(p); } } LLTabTuple* tuple = new LLTabTuple( this, child, btn, textbox ); insertTuple( tuple, insertion_point ); // if new tab was added as a first or last tab, update button image // and update button image of any tab it may have affected if (tuple == mTabList.front()) { update_images(tuple, mFirstTabParams, getTabPosition()); if (mTabList.size() == 2) { update_images(mTabList[1], mLastTabParams, getTabPosition()); } else if (mTabList.size() > 2) { update_images(mTabList[1], mMiddleTabParams, getTabPosition()); } } else if (tuple == mTabList.back()) { update_images(tuple, mLastTabParams, getTabPosition()); if (mTabList.size() > 2) { update_images(mTabList[mTabList.size()-2], mMiddleTabParams, getTabPosition()); } } //Don't add button and textbox if tab buttons are invisible(EXT - 576) if (!getTabsHidden()) { if (textbox) { addChild( textbox, 0 ); } if (btn) { addChild( btn, 0 ); } } else { if (textbox) { LLUICtrl::addChild(textbox, 0); } if (btn) { LLUICtrl::addChild(btn, 0); } } if (child) { LLUICtrl::addChild(child, 1); } sendChildToFront(mPrevArrowBtn); sendChildToFront(mNextArrowBtn); sendChildToFront(mJumpPrevArrowBtn); sendChildToFront(mJumpNextArrowBtn); updateMaxScrollPos(); if( select ) { selectLastTab(); mScrollPos = mMaxScrollPos; } } void LLTabContainer::addPlaceholder(LLPanel* child, const std::string& label) { addTabPanel(TabPanelParams().panel(child).label(label).is_placeholder(true)); } void LLTabContainer::removeTabPanel(LLPanel* child) { static LLUICachedControl tabcntrv_pad ("UITabCntrvPad", 0); 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::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { LLTabTuple* tuple = *iter; if( tuple->mTabPanel == child ) { // update tab button images if removing the first or last tab if ((tuple == mTabList.front()) && (mTabList.size() > 1)) { update_images(mTabList[1], mFirstTabParams, getTabPosition()); } else if ((tuple == mTabList.back()) && (mTabList.size() > 2)) { update_images(mTabList[mTabList.size()-2], mLastTabParams, getTabPosition()); } if (!getTabsHidden()) { // We need to remove tab buttons only if the tabs are not hidden. removeChild( tuple->mButton ); } delete tuple->mButton; tuple->mButton = NULL; removeChild( tuple->mTabPanel ); // delete tuple->mTabPanel; tuple->mTabPanel = NULL; mTabList.erase( iter ); delete tuple; break; } } // make sure we don't have more locked tabs than we have tabs mLockedTabCount = llmin(getTabCount(), mLockedTabCount); if (mCurrentTabIdx >= (S32)mTabList.size()) { mCurrentTabIdx = static_cast(mTabList.size()) - 1; } selectTab(mCurrentTabIdx); if (has_focus) { LLPanel* panelp = getPanelByIndex(mCurrentTabIdx); if (panelp) { panelp->setFocus(true); } } updateMaxScrollPos(); } void LLTabContainer::lockTabs(S32 num_tabs) { // 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()) { mTabList[which]->mButton->setEnabled(enable); } // Stop the DaD timer as it might run forever // enableTabButton() is typically called on refresh and draw when anything changed // in the tab container so it's a good time to reset that. mDragAndDropDelayTimer.stop(); } void LLTabContainer::deleteAllTabs() { // Remove all the tab buttons and delete them. Also, unlink all the child panels. for(std::vector::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { LLTabTuple* tuple = *iter; removeChild( tuple->mButton ); delete tuple->mButton; tuple->mButton = NULL; removeChild( tuple->mTabPanel ); // delete tuple->mTabPanel; tuple->mTabPanel = NULL; } // 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() { return static_cast(mTabList.size()); } LLPanel* LLTabContainer::getPanelByIndex(S32 index) { if (index >= 0 && index < (S32)mTabList.size()) { return mTabList[index]->mTabPanel; } return NULL; } S32 LLTabContainer::getIndexForPanel(LLPanel* panel) { for (S32 index = 0; index < (S32)mTabList.size(); index++) { if (mTabList[index]->mTabPanel == panel) { return index; } } return -1; } S32 LLTabContainer::getPanelIndexByTitle(std::string_view title) { for (S32 index = 0 ; index < (S32)mTabList.size(); index++) { if (title == mTabList[index]->mButton->getLabelSelected()) { return index; } } return -1; } LLPanel* LLTabContainer::getPanelByName(std::string_view name) { for (S32 index = 0 ; index < (S32)mTabList.size(); index++) { LLPanel *panel = mTabList[index]->mTabPanel; if (name == panel->getName()) { return panel; } } return NULL; } // Change the name of the button for the current tab. void LLTabContainer::setCurrentTabName(const std::string& name) { // Might not have a tab selected if (mCurrentTabIdx < 0) return; mTabList[mCurrentTabIdx]->mButton->setLabelSelected(name); mTabList[mCurrentTabIdx]->mButton->setLabelUnselected(name); } void LLTabContainer::selectFirstTab() { selectTab( 0 ); } void LLTabContainer::selectLastTab() { selectTab(static_cast(mTabList.size()) - 1); } void LLTabContainer::selectNextTab() { if (mTabList.size() == 0) { return; } 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 LLTabContainer::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 = static_cast(mTabList.size()) - 1; while (!selectTab(idx) && idx != mCurrentTabIdx) { idx = idx - 1; if (idx < 0) idx = static_cast(mTabList.size()) - 1; } if (tab_has_focus) { mTabList[idx]->mButton->setFocus(true); } } bool LLTabContainer::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 LLTabContainer::selectTab(S32 which) { if (which >= getTabCount() || which < 0) return false; LLTabTuple* selected_tuple = getTab(which); if (!selected_tuple) return false; LLSD cbdata; if (selected_tuple->mTabPanel) cbdata = selected_tuple->mTabPanel->getName(); bool result = false; if (!mValidateSignal || (*mValidateSignal)(this, cbdata)) { result = setTab(which); if (result && mCommitSignal) { (*mCommitSignal)(this, cbdata); } } return result; } // private bool LLTabContainer::setTab(S32 which) { static LLUICachedControl tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); LLTabTuple* selected_tuple = getTab(which); if (!selected_tuple) { return false; } bool is_visible = false; if( selected_tuple->mButton->getEnabled() && selected_tuple->mVisible ) { setCurrentPanelIndex(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 ); // Although the selected tab must be complete, we may have hollow LLTabTuple tucked in the list if (tuple && tuple->mButton) { tuple->mButton->setUseEllipses(mUseTabEllipses); tuple->mButton->setHAlign(mFontHalign); 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 ); } if (tuple && tuple->mTabPanel) { tuple->mTabPanel->setVisible( is_selected ); //tuple->mTabPanel->setFocus(is_selected); // not clear that we want to do this here. } if (is_selected) { LLUIUsage::instance().logPanel(tuple->mTabPanel->getName()); // Make sure selected tab is within scroll region if (mIsVertical) { S32 num_visible = getTabCount() - getMaxScrollPos(); if( i >= getScrollPos() && i <= getScrollPos() + num_visible) { setCurrentPanelIndex(which); is_visible = true; } else { is_visible = false; } } else if (!mHideScrollArrows && getMaxScrollPos() > 0) { if( i < getScrollPos() ) { setScrollPos(i); } else { S32 available_width_with_arrows = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + tabcntr_arrow_btn_size + tabcntr_arrow_btn_size + 1); S32 running_tab_width = (tuple && tuple->mButton ? tuple->mButton->getRect().getWidth() : 0); S32 j = i - 1; S32 min_scroll_pos = i; if (running_tab_width < available_width_with_arrows) { while (j >= 0) { LLTabTuple* other_tuple = getTab(j); running_tab_width += (other_tuple && other_tuple->mButton ? other_tuple->mButton->getRect().getWidth() : 0); if (running_tab_width > available_width_with_arrows) { break; } j--; } min_scroll_pos = j + 1; } setScrollPos(llclamp(getScrollPos(), min_scroll_pos, i)); setScrollPos(llmin(getScrollPos(), getMaxScrollPos())); } is_visible = true; } else { is_visible = true; } } i++; } } if (mIsVertical && getCurrentPanelIndex() >= 0) { LLTabTuple* tuple = getTab(getCurrentPanelIndex()); tuple->mTabPanel->setVisible( true ); tuple->mButton->setToggleState( true ); } return is_visible; } bool LLTabContainer::selectTabByName(std::string_view name) { LLPanel* panel = getPanelByName(name); if (!panel) { LL_WARNS() << "LLTabContainer::selectTabByName(" << name << ") failed" << LL_ENDL; return false; } bool result = selectTabPanel(panel); return result; } bool LLTabContainer::getTabPanelFlashing(LLPanel *child) { LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { return tuple->mButton->getFlashing(); } return false; } void LLTabContainer::setTabPanelFlashing(LLPanel* child, bool state ) { LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { tuple->mButton->setFlashing( state ); } } void LLTabContainer::setTabImage(LLPanel* child, std::string image_name, const LLColor4& color) { LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { tuple->mButton->setImageOverlay(image_name, LLFontGL::LEFT, color); reshapeTuple(tuple); } } void LLTabContainer::setTabImage(LLPanel* child, const LLUUID& image_id, const LLColor4& color) { LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { tuple->mButton->setImageOverlay(image_id, LLFontGL::LEFT, color); reshapeTuple(tuple); } } void LLTabContainer::setTabImage(LLPanel* child, LLIconCtrl* icon) { LLTabTuple* tuple = getTabByPanel(child); LLCustomButtonIconCtrl* button; bool hasButton = false; if(tuple) { button = dynamic_cast(tuple->mButton); if(button) { hasButton = true; button->setIcon(icon); reshapeTuple(tuple); } } if (!hasButton && (icon != NULL)) { // It was assumed that the tab's button would take ownership of the icon pointer. // But since the tab did not have a button, kill the icon to prevent the memory // leak. icon->die(); } } void LLTabContainer::reshapeTuple(LLTabTuple* tuple) { static LLUICachedControl tab_padding ("UITabPadding", 0); if (!mIsVertical) { S32 image_overlay_width = 0; if(mCustomIconCtrlUsed) { LLCustomButtonIconCtrl* button = dynamic_cast(tuple->mButton); LLIconCtrl* icon_ctrl = button ? button->getIconCtrl() : NULL; image_overlay_width = icon_ctrl ? icon_ctrl->getRect().getWidth() : 0; } else { image_overlay_width = tuple->mButton->getImageOverlay().notNull() ? tuple->mButton->getImageOverlay()->getImage()->getWidth(0) : 0; } // remove current width from total tab strip width mTotalTabWidth -= tuple->mButton->getRect().getWidth(); tuple->mPadding = image_overlay_width; tuple->mButton->reshape(llclamp(mFont->getWidth(tuple->mButton->getLabelSelected()) + tab_padding + tuple->mPadding, mMinTabWidth, mMaxTabWidth), tuple->mButton->getRect().getHeight()); // add back in button width to total tab strip width mTotalTabWidth += tuple->mButton->getRect().getWidth(); // tabs have changed size, might need to scroll to see current tab updateMaxScrollPos(); } } void LLTabContainer::setTitle(const std::string& title) { if (mTitleBox) { mTitleBox->setText( title ); } } const std::string LLTabContainer::getPanelTitle(S32 index) { if (index >= 0 && index < (S32)mTabList.size()) { LLButton* tab_button = mTabList[index]->mButton; return tab_button->getLabelSelected(); } return LLStringUtil::null; } void LLTabContainer::setTopBorderHeight(S32 height) { mTopBorderHeight = height; } S32 LLTabContainer::getTopBorderHeight() const { return mTopBorderHeight; } void LLTabContainer::setRightTabBtnOffset(S32 offset) { mNextArrowBtn->translate( -offset - mRightTabBtnOffset, 0 ); mRightTabBtnOffset = offset; updateMaxScrollPos(); } void LLTabContainer::setPanelTitle(S32 index, const std::string& title) { static LLUICachedControl tab_padding ("UITabPadding", 0); if (index >= 0 && index < getTabCount()) { LLTabTuple* tuple = getTab(index); LLButton* tab_button = tuple->mButton; const LLFontGL* fontp = LLFontGL::getFontSansSerifSmall(); mTotalTabWidth -= tab_button->getRect().getWidth(); tab_button->reshape(llclamp(fontp->getWidth(title) + tab_padding + tuple->mPadding, mMinTabWidth, mMaxTabWidth), tab_button->getRect().getHeight()); mTotalTabWidth += tab_button->getRect().getWidth(); tab_button->setLabelSelected(title); tab_button->setLabelUnselected(title); } updateMaxScrollPos(); } void LLTabContainer::onTabBtn( const LLSD& data, LLPanel* panel ) { LLTabTuple* tuple = getTabByPanel(panel); selectTabPanel( panel ); if (tuple) { tuple->mTabPanel->setFocus(true); } } void LLTabContainer::onNextBtn( const LLSD& data ) { if (!mScrolled) { scrollNext(); } mScrolled = false; if(mCurrentTabIdx < mTabList.size()-1) { selectNextTab(); } } void LLTabContainer::onNextBtnHeld( const LLSD& data ) { if (mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) { mScrollTimer.reset(); scrollNext(); if(mCurrentTabIdx < mTabList.size()-1) { selectNextTab(); } mScrolled = true; } } void LLTabContainer::onPrevBtn( const LLSD& data ) { if (!mScrolled) { scrollPrev(); } mScrolled = false; if(mCurrentTabIdx > 0) { selectPrevTab(); } } void LLTabContainer::onJumpFirstBtn( const LLSD& data ) { mScrollPos = 0; } void LLTabContainer::onJumpLastBtn( const LLSD& data ) { mScrollPos = mMaxScrollPos; } void LLTabContainer::onPrevBtnHeld( const LLSD& data ) { if (mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) { mScrollTimer.reset(); scrollPrev(); if(mCurrentTabIdx > 0) { selectPrevTab(); } mScrolled = true; } } // private void LLTabContainer::initButtons() { // Hack: if (getRect().getHeight() == 0 || mPrevArrowBtn) { return; // Don't have a rect yet or already got called } if (mIsVertical) { static LLUICachedControl tabcntrv_arrow_btn_size ("UITabCntrvArrowBtnSize", 0); // Left and right scroll arrows (for when there are too many tabs to show all at once). S32 btn_top = getRect().getHeight(); S32 btn_top_lower = getRect().mBottom+tabcntrv_arrow_btn_size; LLRect up_arrow_btn_rect; up_arrow_btn_rect.setLeftTopAndSize( mMinTabWidth/2 , btn_top, tabcntrv_arrow_btn_size, tabcntrv_arrow_btn_size ); LLRect down_arrow_btn_rect; down_arrow_btn_rect.setLeftTopAndSize( mMinTabWidth/2 , btn_top_lower, tabcntrv_arrow_btn_size, tabcntrv_arrow_btn_size ); LLButton::Params prev_btn_params; prev_btn_params.name(std::string("Up Arrow")); prev_btn_params.rect(up_arrow_btn_rect); prev_btn_params.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); prev_btn_params.image_unselected.name("scrollbutton_up_out_blue.tga"); prev_btn_params.image_selected.name("scrollbutton_up_in_blue.tga"); prev_btn_params.click_callback.function(boost::bind(&LLTabContainer::onPrevBtn, this, _2)); mPrevArrowBtn = LLUICtrlFactory::create(prev_btn_params); LLButton::Params next_btn_params; next_btn_params.name(std::string("Down Arrow")); next_btn_params.rect(down_arrow_btn_rect); next_btn_params.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_LEFT); next_btn_params.image_unselected.name("scrollbutton_down_out_blue.tga"); next_btn_params.image_selected.name("scrollbutton_down_in_blue.tga"); next_btn_params.click_callback.function(boost::bind(&LLTabContainer::onNextBtn, this, _2)); mNextArrowBtn = LLUICtrlFactory::create(next_btn_params); } else // Horizontal { static LLUICachedControl tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); S32 arrow_fudge = 1; // match new art better // Left and right scroll arrows (for when there are too many tabs to show all at once). S32 btn_top = (getTabPosition() == TOP ) ? getRect().getHeight() - getTopBorderHeight() : tabcntr_arrow_btn_size + 1; LLRect left_arrow_btn_rect; left_arrow_btn_rect.setLeftTopAndSize( LLPANEL_BORDER_WIDTH+1+tabcntr_arrow_btn_size, btn_top + arrow_fudge, tabcntr_arrow_btn_size, mTabHeight ); LLRect jump_left_arrow_btn_rect; jump_left_arrow_btn_rect.setLeftTopAndSize( LLPANEL_BORDER_WIDTH+1, btn_top + arrow_fudge, tabcntr_arrow_btn_size, mTabHeight ); S32 right_pad = tabcntr_arrow_btn_size + LLPANEL_BORDER_WIDTH + 1; LLRect right_arrow_btn_rect; right_arrow_btn_rect.setLeftTopAndSize( getRect().getWidth() - mRightTabBtnOffset - right_pad - tabcntr_arrow_btn_size, btn_top + arrow_fudge, tabcntr_arrow_btn_size, mTabHeight ); LLRect jump_right_arrow_btn_rect; jump_right_arrow_btn_rect.setLeftTopAndSize( getRect().getWidth() - mRightTabBtnOffset - right_pad, btn_top + arrow_fudge, tabcntr_arrow_btn_size, mTabHeight ); LLButton::Params p; p.name(std::string("Jump Left Arrow")); p.image_unselected.name("jump_left_out.tga"); p.image_selected.name("jump_left_in.tga"); p.click_callback.function(boost::bind(&LLTabContainer::onJumpFirstBtn, this, _2)); p.rect(jump_left_arrow_btn_rect); p.follows.flags(FOLLOWS_LEFT); mJumpPrevArrowBtn = LLUICtrlFactory::create(p); p = LLButton::Params(); p.name(std::string("Left Arrow")); p.rect(left_arrow_btn_rect); p.follows.flags(FOLLOWS_LEFT); p.image_unselected.name("scrollbutton_left_out_blue.tga"); p.image_selected.name("scrollbutton_left_in_blue.tga"); p.click_callback.function(boost::bind(&LLTabContainer::onPrevBtn, this, _2)); p.mouse_held_callback.function(boost::bind(&LLTabContainer::onPrevBtnHeld, this, _2)); mPrevArrowBtn = LLUICtrlFactory::create(p); p = LLButton::Params(); p.name(std::string("Jump Right Arrow")); p.rect(jump_right_arrow_btn_rect); p.follows.flags(FOLLOWS_RIGHT); p.image_unselected.name("jump_right_out.tga"); p.image_selected.name("jump_right_in.tga"); p.click_callback.function(boost::bind(&LLTabContainer::onJumpLastBtn, this, _2)); mJumpNextArrowBtn = LLUICtrlFactory::create(p); p = LLButton::Params(); p.name(std::string("Right Arrow")); p.rect(right_arrow_btn_rect); p.follows.flags(FOLLOWS_RIGHT); p.image_unselected.name("scrollbutton_right_out_blue.tga"); p.image_selected.name("scrollbutton_right_in_blue.tga"); p.click_callback.function(boost::bind(&LLTabContainer::onNextBtn, this, _2)); p.mouse_held_callback.function(boost::bind(&LLTabContainer::onNextBtnHeld, this, _2)); mNextArrowBtn = LLUICtrlFactory::create(p); if( getTabPosition() == TOP ) { mNextArrowBtn->setFollowsTop(); mPrevArrowBtn->setFollowsTop(); mJumpPrevArrowBtn->setFollowsTop(); mJumpNextArrowBtn->setFollowsTop(); } else { mNextArrowBtn->setFollowsBottom(); mPrevArrowBtn->setFollowsBottom(); mJumpPrevArrowBtn->setFollowsBottom(); mJumpNextArrowBtn->setFollowsBottom(); } } mPrevArrowBtn->setTabStop(false); addChild(mPrevArrowBtn); mNextArrowBtn->setTabStop(false); addChild(mNextArrowBtn); if (mJumpPrevArrowBtn) { mJumpPrevArrowBtn->setTabStop(false); addChild(mJumpPrevArrowBtn); } if (mJumpNextArrowBtn) { mJumpNextArrowBtn->setTabStop(false); addChild(mJumpNextArrowBtn); } // set default tab group to be panel contents setDefaultTabGroup(1); } //this is a work around for the current LLPanel::initFromParams hack //so that it doesn't overwrite the default tab group. //will be removed when LLPanel is fixed soon. void LLTabContainer::initFromParams(const LLPanel::Params& p) { LLPanel::initFromParams(p); setDefaultTabGroup(1); } LLTabTuple* LLTabContainer::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 LLTabContainer::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() + mLockedTabCount, tuple); break; case LEFT_OF_CURRENT: // insert the new tab before the current tab (but not before mLockedTabCount) { tuple_list_t::iterator current_iter = mTabList.begin() + llmax(mLockedTabCount, mCurrentTabIdx); mTabList.insert(current_iter, tuple); } break; case RIGHT_OF_CURRENT: // insert the new tab after the current tab (but not before mLockedTabCount) { tuple_list_t::iterator current_iter = mTabList.begin() + llmax(mLockedTabCount, mCurrentTabIdx + 1); mTabList.insert(current_iter, tuple); } break; case END: default: mTabList.push_back( tuple ); } } void LLTabContainer::updateMaxScrollPos() { static LLUICachedControl tabcntrv_pad ("UITabCntrvPad", 0); bool no_scroll = true; if (mIsVertical) { S32 tab_total_height = (BTN_HEIGHT + tabcntrv_pad) * getTabCount(); S32 available_height = getRect().getHeight() - getTopBorderHeight(); if( tab_total_height > available_height ) { static LLUICachedControl tabcntrv_arrow_btn_size ("UITabCntrvArrowBtnSize", 0); S32 available_height_with_arrows = getRect().getHeight() - 2*(tabcntrv_arrow_btn_size + 3*tabcntrv_pad) - mNextArrowBtn->getRect().mBottom; S32 additional_needed = tab_total_height - available_height_with_arrows; setMaxScrollPos((S32) ceil(additional_needed / float(BTN_HEIGHT + tabcntrv_pad) ) ); no_scroll = false; } } else { static LLUICachedControl tabcntr_tab_h_pad ("UITabCntrTabHPad", 0); static LLUICachedControl tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); static LLUICachedControl tabcntr_tab_partial_width ("UITabCntrTabPartialWidth", 0); S32 tab_space = 0; S32 available_space = 0; tab_space = mTotalTabWidth; available_space = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + tabcntr_tab_h_pad); if( tab_space > available_space ) { S32 available_width_with_arrows = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + tabcntr_arrow_btn_size + tabcntr_arrow_btn_size + 1); // subtract off reserved portion on left available_width_with_arrows -= tabcntr_tab_partial_width; S32 running_tab_width = 0; setMaxScrollPos(getTabCount()); 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; } setMaxScrollPos(getMaxScrollPos()-1); } // in case last tab doesn't actually fit on screen, make it the last scrolling position setMaxScrollPos(llmin(getMaxScrollPos(), getTabCount() - 1)); no_scroll = false; } } if (no_scroll) { setMaxScrollPos(0); setScrollPos(0); } if (getScrollPos() > getMaxScrollPos()) { setScrollPos(getMaxScrollPos()); // maybe just enforce this via limits in setScrollPos instead? } } void LLTabContainer::commitHoveredButton(S32 x, S32 y) { if (!getTabsHidden() && hasMouseCapture()) { for (tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { LLButton* button = (*iter)->mButton; LLPanel* panel = (*iter)->mTabPanel; if (button->getEnabled() && button->getVisible() && !panel->getVisible()) { S32 local_x = x - button->getRect().mLeft; S32 local_y = y - button->getRect().mBottom; if (button->pointInView(local_x, local_y)) { button->onCommit(); break; } } } } } S32 LLTabContainer::getTotalTabWidth() const { return mTotalTabWidth; } void LLTabContainer::setTabVisibility( LLPanel const *aPanel, bool aVisible ) { for( tuple_list_t::const_iterator itr = mTabList.begin(); itr != mTabList.end(); ++itr ) { LLTabTuple const *pTT = *itr; if( pTT->mTabPanel == aPanel ) { pTT->mVisible = aVisible; break; } } bool foundTab( false ); for( tuple_list_t::const_iterator itr = mTabList.begin(); itr != mTabList.end(); ++itr ) { LLTabTuple const *pTT = *itr; if( pTT->mVisible ) { this->selectTab( itr - mTabList.begin() ); foundTab = true; break; } } if( foundTab ) this->setVisible( true ); else this->setVisible( false ); updateMaxScrollPos(); }