579 lines
16 KiB
C++
579 lines
16 KiB
C++
/**
|
|
* @file llaccordionctrl.cpp
|
|
* @brief Accordion panel implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2009&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
#include "linden_common.h"
|
|
|
|
#include "llaccordionctrl.h"
|
|
#include "llaccordionctrltab.h"
|
|
|
|
#include "lluictrlfactory.h" // builds floaters from XML
|
|
|
|
#include "llwindow.h"
|
|
#include "llfocusmgr.h"
|
|
#include "lllocalcliprect.h"
|
|
|
|
#include "boost/bind.hpp"
|
|
|
|
static const S32 DRAGGER_BAR_MARGIN = 4;
|
|
static const S32 DRAGGER_BAR_HEIGHT = 5;
|
|
static const S32 BORDER_MARGIN = 2;
|
|
static const S32 PARENT_BORDER_MARGIN = 5;
|
|
|
|
static const S32 panel_delta = DRAGGER_BAR_MARGIN; // Distanse between two panels
|
|
|
|
static const S32 HORIZONTAL_MULTIPLE = 8;
|
|
static const S32 VERTICAL_MULTIPLE = 16;
|
|
static const F32 MIN_AUTO_SCROLL_RATE = 120.f;
|
|
static const F32 MAX_AUTO_SCROLL_RATE = 500.f;
|
|
static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f;
|
|
|
|
|
|
// LLAccordionCtrl =================================================================|
|
|
|
|
static LLDefaultChildRegistry::Register<LLAccordionCtrl> t2("accordion");
|
|
|
|
|
|
LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params)
|
|
, mFitParent(params.fit_parent)
|
|
{
|
|
mSingleExpansion = params.single_expansion;
|
|
if(mFitParent && !mSingleExpansion)
|
|
{
|
|
llinfos << "fit_parent works best when combined with single_expansion" << llendl;
|
|
}
|
|
}
|
|
|
|
LLAccordionCtrl::LLAccordionCtrl() : LLPanel()
|
|
{
|
|
mSingleExpansion = false;
|
|
mFitParent = false;
|
|
LLUICtrlFactory::getInstance()->buildPanel(this, "accordion_parent.xml");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
void LLAccordionCtrl::draw()
|
|
{
|
|
LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0);
|
|
|
|
LLLocalClipRect clip(local_rect);
|
|
|
|
LLPanel::draw();
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------
|
|
BOOL LLAccordionCtrl::postBuild()
|
|
{
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
|
|
LLRect scroll_rect;
|
|
scroll_rect.setOriginAndSize(
|
|
getRect().getWidth() - scrollbar_size,
|
|
1,
|
|
scrollbar_size,
|
|
getRect().getHeight() - 1);
|
|
|
|
|
|
LLScrollbar::Params sbparams;
|
|
sbparams.name("scrollable vertical");
|
|
sbparams.rect(scroll_rect);
|
|
sbparams.orientation(LLScrollbar::VERTICAL);
|
|
sbparams.doc_size(mInnerRect.getHeight());
|
|
sbparams.doc_pos(0);
|
|
sbparams.page_size(mInnerRect.getHeight());
|
|
sbparams.step_size(VERTICAL_MULTIPLE);
|
|
sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
|
|
sbparams.change_callback(boost::bind(&LLAccordionCtrl::onScrollPosChangeCallback, this, _1, _2));
|
|
|
|
mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams);
|
|
LLView::addChild( mScrollbar );
|
|
mScrollbar->setVisible( false );
|
|
mScrollbar->setFollowsRight();
|
|
mScrollbar->setFollowsTop();
|
|
mScrollbar->setFollowsBottom();
|
|
|
|
//if it was created from xml...
|
|
std::vector<LLUICtrl*> accordion_tabs;
|
|
for(child_list_const_iter_t it = getChildList()->begin();
|
|
getChildList()->end() != it; ++it)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(*it);
|
|
if(accordion_tab == NULL)
|
|
continue;
|
|
if(std::find(mAccordionTabs.begin(),mAccordionTabs.end(),accordion_tab) == mAccordionTabs.end())
|
|
{
|
|
accordion_tabs.push_back(accordion_tab);
|
|
}
|
|
}
|
|
|
|
for(std::vector<LLUICtrl*>::reverse_iterator it = accordion_tabs.rbegin();it!=accordion_tabs.rend();++it)
|
|
addCollapsibleCtrl(*it);
|
|
|
|
arrange ();
|
|
|
|
if(mSingleExpansion)
|
|
{
|
|
if(!mAccordionTabs[0]->getDisplayChildren())
|
|
mAccordionTabs[0]->setDisplayChildren(true);
|
|
for(size_t i=1;i<mAccordionTabs.size();++i)
|
|
{
|
|
if(mAccordionTabs[i]->getDisplayChildren())
|
|
mAccordionTabs[i]->setDisplayChildren(false);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------
|
|
LLAccordionCtrl::~LLAccordionCtrl()
|
|
{
|
|
mAccordionTabs.clear();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
|
|
void LLAccordionCtrl::reshape(S32 width, S32 height, BOOL called_from_parent)
|
|
{
|
|
// adjust our rectangle
|
|
LLRect rcLocal = getRect();
|
|
rcLocal.mRight = rcLocal.mLeft + width;
|
|
rcLocal.mTop = rcLocal.mBottom + height;
|
|
|
|
setRect(rcLocal);
|
|
|
|
arrange();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
BOOL LLAccordionCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
|
|
{
|
|
return LLPanel::handleRightMouseDown(x, y, mask);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
void LLAccordionCtrl::shiftAccordionTabs(S16 panel_num, S32 delta)
|
|
{
|
|
for(size_t i = panel_num; i < mAccordionTabs.size(); i++ )
|
|
{
|
|
ctrlShiftVertical(mAccordionTabs[i],delta);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------
|
|
void LLAccordionCtrl::onCollapseCtrlCloseOpen(S16 panel_num)
|
|
{
|
|
if(mSingleExpansion)
|
|
{
|
|
for(size_t i=0;i<mAccordionTabs.size();++i)
|
|
{
|
|
if(i==panel_num)
|
|
continue;
|
|
if(mAccordionTabs[i]->getDisplayChildren())
|
|
mAccordionTabs[i]->setDisplayChildren(false);
|
|
}
|
|
|
|
}
|
|
arrange();
|
|
}
|
|
|
|
void LLAccordionCtrl::show_hide_scrollbar(S32 width, S32 height)
|
|
{
|
|
calcRecuiredHeight();
|
|
if(getRecuiredHeight() > height )
|
|
showScrollbar(width,height);
|
|
else
|
|
hideScrollbar(width,height);
|
|
}
|
|
|
|
void LLAccordionCtrl::showScrollbar(S32 width, S32 height)
|
|
{
|
|
bool was_visible = mScrollbar->getVisible();
|
|
|
|
mScrollbar->setVisible(true);
|
|
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
|
|
ctrlSetLeftTopAndSize(mScrollbar
|
|
,width-scrollbar_size - PARENT_BORDER_MARGIN/2
|
|
,height-PARENT_BORDER_MARGIN
|
|
,scrollbar_size
|
|
,height-2*PARENT_BORDER_MARGIN);
|
|
|
|
mScrollbar->setPageSize(height);
|
|
mScrollbar->setDocParams(mInnerRect.getHeight(),mScrollbar->getDocPos());
|
|
|
|
if(was_visible)
|
|
{
|
|
S32 scroll_pos = llmin(mScrollbar->getDocPos(), getRecuiredHeight() - height - 1);
|
|
mScrollbar->setDocPos(scroll_pos);
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrl::hideScrollbar( S32 width, S32 height )
|
|
{
|
|
if(mScrollbar->getVisible() == false)
|
|
return;
|
|
mScrollbar->setVisible(false);
|
|
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
|
|
S32 panel_width = width - 2*BORDER_MARGIN;
|
|
|
|
//reshape all accordeons and shift all draggers
|
|
for(size_t i=0;i<mAccordionTabs.size();++i)
|
|
{
|
|
LLRect panel_rect = mAccordionTabs[i]->getRect();
|
|
ctrlSetLeftTopAndSize(mAccordionTabs[i],panel_rect.mLeft,panel_rect.mTop,panel_width,panel_rect.getHeight());
|
|
}
|
|
|
|
mScrollbar->setDocPos(0);
|
|
|
|
if(mAccordionTabs.size()>0)
|
|
{
|
|
S32 panel_top = height - BORDER_MARGIN; // Top coordinate of the first panel
|
|
S32 diff = panel_top - mAccordionTabs[0]->getRect().mTop;
|
|
shiftAccordionTabs(0,diff);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------
|
|
S32 LLAccordionCtrl::calcRecuiredHeight()
|
|
{
|
|
S32 rec_height = 0;
|
|
|
|
std::vector<LLAccordionCtrlTab*>::iterator panel;
|
|
for(panel=mAccordionTabs.begin(); panel!=mAccordionTabs.end(); ++panel)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(*panel);
|
|
if(accordion_tab && accordion_tab->getVisible())
|
|
{
|
|
rec_height += accordion_tab->getRect().getHeight();
|
|
}
|
|
}
|
|
|
|
mInnerRect.setLeftTopAndSize(0,rec_height + BORDER_MARGIN*2,getRect().getWidth(),rec_height + BORDER_MARGIN);
|
|
|
|
return mInnerRect.getHeight();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
void LLAccordionCtrl::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height)
|
|
{
|
|
if(!panel)
|
|
return;
|
|
LLRect panel_rect = panel->getRect();
|
|
panel_rect.setLeftTopAndSize( left, top, width, height);
|
|
panel->reshape( width, height, 1);
|
|
panel->setRect(panel_rect);
|
|
}
|
|
|
|
void LLAccordionCtrl::ctrlShiftVertical(LLView* panel,S32 delta)
|
|
{
|
|
if(!panel)
|
|
return;
|
|
panel->translate(0,delta);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
|
|
void LLAccordionCtrl::addCollapsibleCtrl(LLView* view)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(view);
|
|
if(!accordion_tab)
|
|
return;
|
|
if(std::find(getChildList()->begin(),getChildList()->end(),accordion_tab) == getChildList()->end())
|
|
addChild(accordion_tab);
|
|
mAccordionTabs.push_back(accordion_tab);
|
|
|
|
accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, mAccordionTabs.size() - 1) );
|
|
|
|
}
|
|
|
|
|
|
void LLAccordionCtrl::arrange()
|
|
{
|
|
if( mAccordionTabs.size() == 0)
|
|
{
|
|
//We do not arrange if we do not have what should be arranged
|
|
return;
|
|
}
|
|
|
|
//Calculate params
|
|
S32 panel_left = BORDER_MARGIN; // Margin from left side of Splitter
|
|
S32 panel_top = getRect().getHeight() - BORDER_MARGIN; // Top coordinate of the first panel
|
|
S32 panel_width = getRect().getWidth() - 4; // Top coordinate of the first panel
|
|
|
|
|
|
if(mAccordionTabs.size() == 1)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[0]);
|
|
|
|
LLRect panel_rect = accordion_tab->getRect();
|
|
|
|
S32 panel_height = getRect().getHeight() - 2*BORDER_MARGIN;
|
|
|
|
ctrlSetLeftTopAndSize(accordion_tab,panel_rect.mLeft,panel_top,panel_width,panel_height);
|
|
|
|
show_hide_scrollbar(getRect().getWidth(),getRect().getHeight());
|
|
return;
|
|
|
|
}
|
|
|
|
for(size_t i = 0; i < mAccordionTabs.size(); i++ )
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
|
|
|
|
if(accordion_tab->getVisible() == false) //skip hidden accordion tabs
|
|
continue;
|
|
|
|
if(!accordion_tab->isExpanded() )
|
|
{
|
|
ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, accordion_tab->getRect().getHeight());
|
|
panel_top-=mAccordionTabs[i]->getRect().getHeight();
|
|
}
|
|
else
|
|
{
|
|
S32 panel_height = accordion_tab->getRect().getHeight();
|
|
|
|
if(mFitParent)
|
|
{
|
|
// all expanded tabs will have equal height
|
|
panel_height = calcExpandedTabHeight(i, panel_top);
|
|
ctrlSetLeftTopAndSize(accordion_tab, panel_left, panel_top, panel_width, panel_height);
|
|
|
|
// try to make accordion tab fit accordion view height.
|
|
// Accordion View should implement getRequiredRect() and provide valid height
|
|
S32 optimal_height = accordion_tab->getAccordionView()->getRequiredRect().getHeight();
|
|
optimal_height += accordion_tab->getHeaderHeight() + 2 * BORDER_MARGIN;
|
|
if(optimal_height < panel_height)
|
|
{
|
|
panel_height = optimal_height;
|
|
}
|
|
|
|
// minimum tab height is equal to header height
|
|
if(mAccordionTabs[i]->getHeaderHeight() > panel_height)
|
|
{
|
|
panel_height = mAccordionTabs[i]->getHeaderHeight();
|
|
}
|
|
}
|
|
|
|
ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, panel_height);
|
|
panel_top-=panel_height;
|
|
|
|
}
|
|
}
|
|
|
|
show_hide_scrollbar(getRect().getWidth(),getRect().getHeight());
|
|
|
|
updateLayout(getRect().getWidth(),getRect().getHeight());
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
|
|
BOOL LLAccordionCtrl::handleScrollWheel ( S32 x, S32 y, S32 clicks )
|
|
{
|
|
if(LLPanel::handleScrollWheel(x,y,clicks))
|
|
return TRUE;
|
|
if( mScrollbar->getVisible() && mScrollbar->handleScrollWheel( 0, 0, clicks ) )
|
|
return TRUE;
|
|
return false;
|
|
|
|
}
|
|
|
|
BOOL LLAccordionCtrl::handleKeyHere (KEY key, MASK mask)
|
|
{
|
|
if( mScrollbar->getVisible() && mScrollbar->handleKeyHere( key,mask ) )
|
|
return TRUE;
|
|
return LLPanel::handleKeyHere(key,mask);
|
|
}
|
|
|
|
void LLAccordionCtrl::updateLayout (S32 width, S32 height)
|
|
{
|
|
S32 panel_top = height - BORDER_MARGIN ;
|
|
if(mScrollbar->getVisible())
|
|
panel_top+=mScrollbar->getDocPos();
|
|
|
|
S32 panel_width = width - 2*BORDER_MARGIN;
|
|
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
if(mScrollbar->getVisible())
|
|
panel_width-=scrollbar_size;
|
|
|
|
//set sizes for first panels and dragbars
|
|
for(size_t i=0;i<mAccordionTabs.size();++i)
|
|
{
|
|
if(!mAccordionTabs[i]->getVisible())
|
|
continue;
|
|
LLRect panel_rect = mAccordionTabs[i]->getRect();
|
|
ctrlSetLeftTopAndSize(mAccordionTabs[i],panel_rect.mLeft,panel_top,panel_width,panel_rect.getHeight());
|
|
panel_top-=panel_rect.getHeight();
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrl::onScrollPosChangeCallback(S32, LLScrollbar*)
|
|
{
|
|
updateLayout(getRect().getWidth(),getRect().getHeight());
|
|
}
|
|
void LLAccordionCtrl::onOpen (const LLSD& key)
|
|
{
|
|
for(size_t i=0;i<mAccordionTabs.size();++i)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
|
|
LLPanel* panel = dynamic_cast<LLPanel*>(accordion_tab->getAccordionView());
|
|
if(panel!=NULL)
|
|
{
|
|
panel->onOpen(key);
|
|
}
|
|
}
|
|
}
|
|
S32 LLAccordionCtrl::notifyParent(const LLSD& info)
|
|
{
|
|
if(info.has("action"))
|
|
{
|
|
std::string str_action = info["action"];
|
|
if(str_action == "size_changes")
|
|
{
|
|
//
|
|
arrange();
|
|
return 1;
|
|
}
|
|
else if(str_action == "select_next")
|
|
{
|
|
for(size_t i=0;i<mAccordionTabs.size();++i)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
|
|
if(accordion_tab->hasFocus())
|
|
{
|
|
while(++i<mAccordionTabs.size())
|
|
{
|
|
if(mAccordionTabs[i]->getVisible())
|
|
break;
|
|
}
|
|
if(i<mAccordionTabs.size())
|
|
{
|
|
accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
|
|
accordion_tab->notify(LLSD().with("action","select_first"));
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
else if(str_action == "select_prev")
|
|
{
|
|
for(size_t i=0;i<mAccordionTabs.size();++i)
|
|
{
|
|
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
|
|
if(accordion_tab->hasFocus() && i>0)
|
|
{
|
|
while(i>0)
|
|
{
|
|
if(mAccordionTabs[--i]->getVisible())
|
|
break;
|
|
}
|
|
|
|
accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
|
|
accordion_tab->notify(LLSD().with("action","select_last"));
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
else if (info.has("scrollToShowRect"))
|
|
{
|
|
LLRect screen_rc, local_rc;
|
|
screen_rc.setValue(info["scrollToShowRect"]);
|
|
screenRectToLocal(screen_rc, &local_rc);
|
|
|
|
// Translate to parent coordinatess to check if we are in visible rectangle
|
|
local_rc.translate( getRect().mLeft, getRect().mBottom );
|
|
|
|
if ( !getRect().contains (local_rc) )
|
|
{
|
|
// Back to local coords and calculate position for scroller
|
|
S32 bottom = mScrollbar->getDocPos() - local_rc.mBottom + getRect().mBottom;
|
|
S32 top = mScrollbar->getDocPos() - local_rc.mTop + getRect().mTop;
|
|
|
|
S32 scroll_pos = llclamp(mScrollbar->getDocPos(),
|
|
bottom, // min vertical scroll
|
|
top); // max vertical scroll
|
|
|
|
mScrollbar->setDocPos( scroll_pos );
|
|
}
|
|
return 1;
|
|
}
|
|
return LLPanel::notifyParent(info);
|
|
}
|
|
void LLAccordionCtrl::reset ()
|
|
{
|
|
if(mScrollbar)
|
|
mScrollbar->setDocPos(0);
|
|
}
|
|
|
|
S32 LLAccordionCtrl::calcExpandedTabHeight(S32 tab_index /* = 0 */, S32 available_height /* = 0 */)
|
|
{
|
|
if(tab_index < 0)
|
|
{
|
|
return available_height;
|
|
}
|
|
|
|
S32 collapsed_tabs_height = 0;
|
|
S32 num_expanded = 0;
|
|
|
|
for(size_t n = tab_index; n < mAccordionTabs.size(); ++n)
|
|
{
|
|
if(!mAccordionTabs[n]->isExpanded())
|
|
{
|
|
collapsed_tabs_height += mAccordionTabs[n]->getHeaderHeight();
|
|
}
|
|
else
|
|
{
|
|
++num_expanded;
|
|
}
|
|
}
|
|
|
|
if(0 == num_expanded)
|
|
{
|
|
return available_height;
|
|
}
|
|
|
|
S32 expanded_tab_height = available_height - collapsed_tabs_height - BORDER_MARGIN; // top BORDER_MARGIN is added in arrange(), here we add bottom BORDER_MARGIN
|
|
expanded_tab_height /= num_expanded;
|
|
return expanded_tab_height;
|
|
}
|