1279 lines
32 KiB
C++
1279 lines
32 KiB
C++
/**
|
|
* @file LLAccordionCtrlTab.cpp
|
|
* @brief Collapsible control implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2009&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 "llaccordionctrltab.h"
|
|
#include "llaccordionctrl.h"
|
|
|
|
#include "lllocalcliprect.h"
|
|
#include "llscrollbar.h"
|
|
#include "lltextbox.h"
|
|
//#include "lltextutil.h"
|
|
#include "lluictrl.h"
|
|
#include "lluictrlfactory.h"
|
|
|
|
static const std::string DD_BUTTON_NAME = "dd_button";
|
|
static const std::string DD_TEXTBOX_NAME = "dd_textbox";
|
|
static const std::string DD_HEADER_NAME = "dd_header";
|
|
|
|
static const S32 HEADER_HEIGHT = 23;
|
|
static const S32 HEADER_IMAGE_LEFT_OFFSET = 5;
|
|
static const S32 HEADER_TEXT_LEFT_OFFSET = 30;
|
|
static const F32 AUTO_OPEN_TIME = 1.f;
|
|
static const S32 VERTICAL_MULTIPLE = 16;
|
|
static const S32 PARENT_BORDER_MARGIN = 5;
|
|
|
|
//static LLDefaultChildRegistry::Register<LLAccordionCtrlTab> t1("accordion_tab");
|
|
static LLRegisterWidget<LLAccordionCtrlTab> t1("accordion_tab");
|
|
|
|
class LLAccordionCtrlTab::LLAccordionCtrlTabHeader : public LLUICtrl
|
|
{
|
|
public:
|
|
friend class LLUICtrlFactory;
|
|
|
|
struct Params : public LLAccordionCtrlTab::Params //LLInitParam::Block<Params, LLAccordionCtrlTab::Params>
|
|
{
|
|
Params();
|
|
Params(const LLAccordionCtrlTab::Params& p) : LLAccordionCtrlTab::Params(p) {}
|
|
};
|
|
|
|
LLAccordionCtrlTabHeader(const LLAccordionCtrlTabHeader::Params& p);
|
|
|
|
virtual ~LLAccordionCtrlTabHeader();
|
|
|
|
virtual void draw();
|
|
|
|
virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
|
|
|
|
virtual BOOL postBuild();
|
|
|
|
std::string getTitle();
|
|
void setTitle(const std::string& title, const std::string& hl);
|
|
|
|
void setTitleFontStyle(std::string style);
|
|
|
|
void setTitleColor(LLUIColor);
|
|
|
|
void setSelected(bool is_selected) { mIsSelected = is_selected; }
|
|
|
|
virtual void onMouseEnter(S32 x, S32 y, MASK mask);
|
|
virtual void onMouseLeave(S32 x, S32 y, MASK mask);
|
|
virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent);
|
|
virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
|
|
EDragAndDropType cargo_type,
|
|
void* cargo_data,
|
|
EAcceptance* accept,
|
|
std::string& tooltip_msg);
|
|
private:
|
|
|
|
LLTextBox* mHeaderTextbox;
|
|
|
|
// Overlay images (arrows)
|
|
LLPointer<LLUIImage> mImageCollapsed;
|
|
LLPointer<LLUIImage> mImageExpanded;
|
|
LLPointer<LLUIImage> mImageCollapsedPressed;
|
|
LLPointer<LLUIImage> mImageExpandedPressed;
|
|
|
|
// Background images
|
|
LLPointer<LLUIImage> mImageHeader;
|
|
LLPointer<LLUIImage> mImageHeaderOver;
|
|
LLPointer<LLUIImage> mImageHeaderPressed;
|
|
LLPointer<LLUIImage> mImageHeaderFocused;
|
|
|
|
// style saved when applying it in setTitleFontStyle
|
|
//U8/*LLStyle::Params*/ mStyleParams;
|
|
|
|
LLUIColor mHeaderBGColor;
|
|
|
|
bool mNeedsHighlight;
|
|
bool mIsSelected;
|
|
|
|
LLFrameTimer mAutoOpenTimer;
|
|
};
|
|
|
|
LLAccordionCtrlTab::LLAccordionCtrlTabHeader::Params::Params()
|
|
{
|
|
}
|
|
|
|
LLAccordionCtrlTab::LLAccordionCtrlTabHeader::LLAccordionCtrlTabHeader(
|
|
const LLAccordionCtrlTabHeader::Params& p)
|
|
: LLUICtrl(p)
|
|
, mHeaderBGColor(p.header_bg_color())
|
|
, mNeedsHighlight(false)
|
|
, mIsSelected(false),
|
|
mImageCollapsed(p.header_collapse_img),
|
|
mImageCollapsedPressed(p.header_collapse_img_pressed),
|
|
mImageExpanded(p.header_expand_img),
|
|
mImageExpandedPressed(p.header_expand_img_pressed),
|
|
mImageHeader(p.header_image),
|
|
mImageHeaderOver(p.header_image_over),
|
|
mImageHeaderPressed(p.header_image_pressed),
|
|
mImageHeaderFocused(p.header_image_focused)
|
|
{
|
|
mHeaderTextbox = new LLTextBox(DD_TEXTBOX_NAME, p.title(), 200, p.font(), false);
|
|
mHeaderTextbox->setColor(p.header_text_color());
|
|
mHeaderTextbox->setFollows(FOLLOWS_NONE);
|
|
mHeaderTextbox->setFontShadow(LLFontGL::NO_SHADOW);
|
|
mHeaderTextbox->setUseEllipses(true);
|
|
mHeaderTextbox->setBackgroundVisible(false);
|
|
addChild(mHeaderTextbox);
|
|
}
|
|
|
|
LLAccordionCtrlTab::LLAccordionCtrlTabHeader::~LLAccordionCtrlTabHeader()
|
|
{
|
|
}
|
|
|
|
BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::postBuild()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
std::string LLAccordionCtrlTab::LLAccordionCtrlTabHeader::getTitle()
|
|
{
|
|
if(mHeaderTextbox)
|
|
{
|
|
return mHeaderTextbox->getText();
|
|
}
|
|
else
|
|
{
|
|
return LLStringUtil::null;
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitle(const std::string& title, const std::string& hl)
|
|
{
|
|
if(mHeaderTextbox)
|
|
{
|
|
mHeaderTextbox->setText(title);
|
|
/*LLTextUtil::textboxSetHighlightedVal(
|
|
mHeaderTextbox,
|
|
mStyleParams,
|
|
title,
|
|
hl);*/
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitleFontStyle(std::string style)
|
|
{
|
|
if (mHeaderTextbox)
|
|
{
|
|
mHeaderTextbox->setFontStyle(/*mStyleParams =*/ LLFontGL::getStyleFromString(style));
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitleColor(LLUIColor color)
|
|
{
|
|
if(mHeaderTextbox)
|
|
{
|
|
mHeaderTextbox->setColor(color);
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw()
|
|
{
|
|
S32 width = getRect().getWidth();
|
|
S32 height = getRect().getHeight();
|
|
|
|
//F32 alpha = getCurrentTransparency(); // Singu TODO
|
|
gl_rect_2d(0,0,width - 1 ,height - 1,mHeaderBGColor.get() /*% alpha*/,true);
|
|
|
|
LLAccordionCtrlTab* parent = dynamic_cast<LLAccordionCtrlTab*>(getParent());
|
|
bool collapsible = (parent && parent->getCollapsible());
|
|
bool expanded = (parent && parent->getDisplayChildren());
|
|
|
|
// Handle overlay images, if needed
|
|
// Only show green "focus" background image if the accordion is open,
|
|
// because the user's mental model of focus is that it goes away after
|
|
// the accordion is closed.
|
|
if (getParent()->hasFocus() || mIsSelected
|
|
/*&& !(collapsible && !expanded)*/ // WHY??
|
|
)
|
|
{
|
|
mImageHeaderFocused->draw(0,0,width,height);
|
|
}
|
|
else
|
|
{
|
|
mImageHeader->draw(0,0,width,height);
|
|
}
|
|
|
|
if(mNeedsHighlight)
|
|
{
|
|
mImageHeaderOver->draw(0,0,width,height);
|
|
}
|
|
|
|
|
|
if(collapsible)
|
|
{
|
|
LLPointer<LLUIImage> overlay_image;
|
|
if(expanded)
|
|
{
|
|
overlay_image = mImageExpanded;
|
|
}
|
|
else
|
|
{
|
|
overlay_image = mImageCollapsed;
|
|
}
|
|
overlay_image->draw(HEADER_IMAGE_LEFT_OFFSET,
|
|
(height - overlay_image->getHeight()) / 2);
|
|
}
|
|
|
|
LLUICtrl::draw();
|
|
}
|
|
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
|
|
{
|
|
S32 header_height = mHeaderTextbox->getTextPixelHeight();
|
|
|
|
LLRect textboxRect(HEADER_TEXT_LEFT_OFFSET,(height+header_height)/2 ,width,(height-header_height)/2);
|
|
mHeaderTextbox->reshape(textboxRect.getWidth(), textboxRect.getHeight());
|
|
mHeaderTextbox->setRect(textboxRect);
|
|
|
|
if (mHeaderTextbox->getTextPixelWidth() > mHeaderTextbox->getRect().getWidth())
|
|
{
|
|
setToolTip(mHeaderTextbox->getText());
|
|
}
|
|
else
|
|
{
|
|
setToolTip(LLStringUtil::null);
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::onMouseEnter(S32 x, S32 y, MASK mask)
|
|
{
|
|
LLUICtrl::onMouseEnter(x, y, mask);
|
|
mNeedsHighlight = true;
|
|
}
|
|
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::onMouseLeave(S32 x, S32 y, MASK mask)
|
|
{
|
|
LLUICtrl::onMouseLeave(x, y, mask);
|
|
mNeedsHighlight = false;
|
|
mAutoOpenTimer.stop();
|
|
}
|
|
BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::handleKey(KEY key, MASK mask, BOOL called_from_parent)
|
|
{
|
|
if ( ( key == KEY_LEFT || key == KEY_RIGHT) && mask == MASK_NONE)
|
|
{
|
|
return getParent()->handleKey(key, mask, called_from_parent);
|
|
}
|
|
return LLUICtrl::handleKey(key, mask, called_from_parent);
|
|
}
|
|
BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::handleDragAndDrop(S32 x, S32 y, MASK mask,
|
|
BOOL drop,
|
|
EDragAndDropType cargo_type,
|
|
void* cargo_data,
|
|
EAcceptance* accept,
|
|
std::string& tooltip_msg)
|
|
{
|
|
LLAccordionCtrlTab* parent = dynamic_cast<LLAccordionCtrlTab*>(getParent());
|
|
|
|
if ( parent && !parent->getDisplayChildren() && parent->getCollapsible() && parent->canOpenClose() )
|
|
{
|
|
if (mAutoOpenTimer.getStarted())
|
|
{
|
|
if (mAutoOpenTimer.getElapsedTimeF32() > AUTO_OPEN_TIME)
|
|
{
|
|
parent->changeOpenClose(false);
|
|
mAutoOpenTimer.stop();
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
mAutoOpenTimer.start();
|
|
}
|
|
|
|
return LLUICtrl::handleDragAndDrop(x, y, mask, drop, cargo_type,
|
|
cargo_data, accept, tooltip_msg);
|
|
}
|
|
LLAccordionCtrlTab::Params::Params()
|
|
: title("title")
|
|
,display_children("expanded", true)
|
|
,header_height("header_height", HEADER_HEIGHT),
|
|
min_width("min_width", 0),
|
|
min_height("min_height", 0)
|
|
,collapsible("collapsible", true)
|
|
,header_bg_color("header_bg_color")
|
|
,dropdown_bg_color("dropdown_bg_color")
|
|
,header_visible("header_visible",true)
|
|
,padding_left("padding_left",2)
|
|
,padding_right("padding_right",2)
|
|
,padding_top("padding_top",2)
|
|
,padding_bottom("padding_bottom",2)
|
|
,header_expand_img("header_expand_img")
|
|
,header_expand_img_pressed("header_expand_img_pressed")
|
|
,header_collapse_img("header_collapse_img")
|
|
,header_collapse_img_pressed("header_collapse_img_pressed")
|
|
,header_image("header_image")
|
|
,header_image_over("header_image_over")
|
|
,header_image_pressed("header_image_pressed")
|
|
,header_image_focused("header_image_focused")
|
|
,header_text_color("header_text_color")
|
|
,fit_panel("fit_panel",true)
|
|
,selection_enabled("selection_enabled", false)
|
|
{
|
|
changeDefault(mouse_opaque, false);
|
|
}
|
|
|
|
LLAccordionCtrlTab::LLAccordionCtrlTab(const LLAccordionCtrlTab::Params&p)
|
|
: LLUICtrl(p)
|
|
,mDisplayChildren(p.display_children)
|
|
,mCollapsible(p.collapsible)
|
|
,mExpandedHeight(0)
|
|
,mDropdownBGColor(p.dropdown_bg_color())
|
|
,mHeaderVisible(p.header_visible)
|
|
,mPaddingLeft(p.padding_left)
|
|
,mPaddingRight(p.padding_right)
|
|
,mPaddingTop(p.padding_top)
|
|
,mPaddingBottom(p.padding_bottom)
|
|
,mCanOpenClose(true)
|
|
,mFitPanel(p.fit_panel)
|
|
,mSelectionEnabled(p.selection_enabled)
|
|
,mContainerPanel(NULL)
|
|
,mScrollbar(NULL)
|
|
{
|
|
mStoredOpenCloseState = false;
|
|
mWasStateStored = false;
|
|
|
|
mDropdownBGColor = LLColor4::white;
|
|
LLAccordionCtrlTabHeader::Params headerParams(p);
|
|
headerParams.name(DD_HEADER_NAME);
|
|
headerParams.title(p.title);
|
|
mHeader = LLUICtrlFactory::create<LLAccordionCtrlTabHeader>(headerParams);
|
|
addChild(mHeader, 1);
|
|
|
|
LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLAccordionCtrlTab::selectOnFocusReceived, this));
|
|
|
|
if (!p.selection_enabled)
|
|
{
|
|
LLFocusableElement::setFocusLostCallback(boost::bind(&LLAccordionCtrlTab::deselectOnFocusLost, this));
|
|
}
|
|
|
|
reshape(100, 200,FALSE);
|
|
}
|
|
|
|
LLAccordionCtrlTab::~LLAccordionCtrlTab()
|
|
{
|
|
}
|
|
|
|
|
|
void LLAccordionCtrlTab::setDisplayChildren(bool display)
|
|
{
|
|
mDisplayChildren = display;
|
|
LLRect rect = getRect();
|
|
|
|
rect.mBottom = rect.mTop - (getDisplayChildren() ?
|
|
mExpandedHeight : HEADER_HEIGHT);
|
|
setRect(rect);
|
|
|
|
if(mContainerPanel)
|
|
mContainerPanel->setVisible(getDisplayChildren());
|
|
|
|
if(mDisplayChildren)
|
|
{
|
|
adjustContainerPanel();
|
|
}
|
|
else
|
|
{
|
|
if(mScrollbar)
|
|
mScrollbar->setVisible(false);
|
|
}
|
|
|
|
}
|
|
|
|
void LLAccordionCtrlTab::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
|
|
{
|
|
LLRect headerRect;
|
|
|
|
headerRect.setLeftTopAndSize(
|
|
0,height,width,HEADER_HEIGHT);
|
|
mHeader->setRect(headerRect);
|
|
mHeader->reshape(headerRect.getWidth(), headerRect.getHeight());
|
|
|
|
if(!mDisplayChildren)
|
|
return;
|
|
|
|
LLRect childRect;
|
|
|
|
childRect.setLeftTopAndSize(
|
|
getPaddingLeft(),
|
|
height - getHeaderHeight() - getPaddingTop(),
|
|
width - getPaddingLeft() - getPaddingRight(),
|
|
height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() );
|
|
|
|
adjustContainerPanel(childRect);
|
|
}
|
|
|
|
void LLAccordionCtrlTab::changeOpenClose(bool is_open)
|
|
{
|
|
if(is_open)
|
|
mExpandedHeight = getRect().getHeight();
|
|
|
|
setDisplayChildren(!is_open);
|
|
reshape(getRect().getWidth(), getRect().getHeight(), FALSE);
|
|
if (mCommitSignal)
|
|
{
|
|
(*mCommitSignal)(this, getDisplayChildren());
|
|
}
|
|
}
|
|
|
|
/*void LLAccordionCtrlTab::onVisibilityChange(BOOL new_visibility)
|
|
{
|
|
LLUICtrl::onVisibilityChange(new_visibility);
|
|
|
|
notifyParent(LLSD().with("child_visibility_change", new_visibility));
|
|
}*/
|
|
|
|
BOOL LLAccordionCtrlTab::handleMouseDown(S32 x, S32 y, MASK mask)
|
|
{
|
|
if(mCollapsible && mHeaderVisible && mCanOpenClose)
|
|
{
|
|
if(y >= (getRect().getHeight() - HEADER_HEIGHT) )
|
|
{
|
|
mHeader->setFocus(true);
|
|
changeOpenClose(getDisplayChildren());
|
|
|
|
//reset stored state
|
|
mWasStateStored = false;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return LLUICtrl::handleMouseDown(x,y,mask);
|
|
}
|
|
|
|
BOOL LLAccordionCtrlTab::handleMouseUp(S32 x, S32 y, MASK mask)
|
|
{
|
|
return LLUICtrl::handleMouseUp(x,y,mask);
|
|
}
|
|
|
|
boost::signals2::connection LLAccordionCtrlTab::setDropDownStateChangedCallback(commit_callback_t cb)
|
|
{
|
|
return setCommitCallback(cb);
|
|
}
|
|
|
|
bool LLAccordionCtrlTab::addChild(LLView* child, S32 tab_group)
|
|
{
|
|
if(DD_HEADER_NAME != child->getName())
|
|
{
|
|
reshape(child->getRect().getWidth() , child->getRect().getHeight() + HEADER_HEIGHT );
|
|
mExpandedHeight = getRect().getHeight();
|
|
}
|
|
|
|
bool res = LLUICtrl::addChild(child, tab_group);
|
|
|
|
if(DD_HEADER_NAME != child->getName())
|
|
{
|
|
if(!mCollapsible)
|
|
setDisplayChildren(true);
|
|
else
|
|
setDisplayChildren(getDisplayChildren());
|
|
}
|
|
|
|
if (!mContainerPanel)
|
|
mContainerPanel = findContainerView();
|
|
|
|
return res;
|
|
}
|
|
|
|
void LLAccordionCtrlTab::setAccordionView(LLView* panel)
|
|
{
|
|
addChild(panel,0);
|
|
}
|
|
|
|
std::string LLAccordionCtrlTab::getTitle() const
|
|
{
|
|
if (mHeader)
|
|
{
|
|
return mHeader->getTitle();
|
|
}
|
|
else
|
|
{
|
|
return LLStringUtil::null;
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::setTitle(const std::string& title, const std::string& hl)
|
|
{
|
|
if (mHeader)
|
|
{
|
|
mHeader->setTitle(title, hl);
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::setTitleFontStyle(std::string style)
|
|
{
|
|
if (mHeader)
|
|
{
|
|
mHeader->setTitleFontStyle(style);
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::setTitleColor(LLUIColor color)
|
|
{
|
|
if (mHeader)
|
|
{
|
|
mHeader->setTitleColor(color);
|
|
}
|
|
}
|
|
|
|
boost::signals2::connection LLAccordionCtrlTab::setFocusReceivedCallback(const focus_signal_t::slot_type& cb)
|
|
{
|
|
if (mHeader)
|
|
{
|
|
return mHeader->setFocusReceivedCallback(cb);
|
|
}
|
|
return boost::signals2::connection();
|
|
}
|
|
|
|
boost::signals2::connection LLAccordionCtrlTab::setFocusLostCallback(const focus_signal_t::slot_type& cb)
|
|
{
|
|
if (mHeader)
|
|
{
|
|
return mHeader->setFocusLostCallback(cb);
|
|
}
|
|
return boost::signals2::connection();
|
|
}
|
|
|
|
void LLAccordionCtrlTab::setSelected(bool is_selected)
|
|
{
|
|
if (mHeader)
|
|
{
|
|
mHeader->setSelected(is_selected);
|
|
}
|
|
}
|
|
|
|
LLView* LLAccordionCtrlTab::findContainerView()
|
|
{
|
|
for(child_list_const_iter_t it = getChildList()->begin();
|
|
getChildList()->end() != it; ++it)
|
|
{
|
|
LLView* child = *it;
|
|
if(DD_HEADER_NAME == child->getName())
|
|
continue;
|
|
if(!child->getVisible())
|
|
continue;
|
|
return child;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void LLAccordionCtrlTab::selectOnFocusReceived()
|
|
{
|
|
if (getParent()) // A parent may not be set if tabs are added dynamically.
|
|
getParent()->notifyParent(LLSD().with("action", "select_current"));
|
|
}
|
|
|
|
void LLAccordionCtrlTab::deselectOnFocusLost()
|
|
{
|
|
if(getParent()) // A parent may not be set if tabs are added dynamically.
|
|
{
|
|
getParent()->notifyParent(LLSD().with("action", "deselect_current"));
|
|
}
|
|
|
|
}
|
|
|
|
S32 LLAccordionCtrlTab::getHeaderHeight()
|
|
{
|
|
return mHeaderVisible?HEADER_HEIGHT:0;
|
|
}
|
|
|
|
void LLAccordionCtrlTab::setHeaderVisible(bool value)
|
|
{
|
|
if(mHeaderVisible == value)
|
|
return;
|
|
mHeaderVisible = value;
|
|
if(mHeader)
|
|
mHeader->setVisible(value);
|
|
reshape(getRect().getWidth(), getRect().getHeight(), FALSE);
|
|
};
|
|
|
|
//virtual
|
|
BOOL LLAccordionCtrlTab::postBuild()
|
|
{
|
|
if(mHeader)
|
|
mHeader->setVisible(mHeaderVisible);
|
|
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
|
|
LLRect scroll_rect;
|
|
scroll_rect.setOriginAndSize(
|
|
getRect().getWidth() - scrollbar_size,
|
|
1,
|
|
scrollbar_size,
|
|
getRect().getHeight() - 1);
|
|
|
|
mContainerPanel = findContainerView();
|
|
|
|
if(!mFitPanel)
|
|
{
|
|
mScrollbar = new LLScrollbar("scrollable vertical", scroll_rect, LLScrollbar::VERTICAL, getRect().getHeight(), 0, getRect().getHeight(), boost::bind(&LLAccordionCtrlTab::onScrollPosChangeCallback, this, _1, _2), VERTICAL_MULTIPLE);
|
|
mScrollbar->setFollows(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
|
|
LLView::addChild( mScrollbar );
|
|
mScrollbar->setFollows(FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM);
|
|
|
|
mScrollbar->setVisible(false);
|
|
}
|
|
|
|
if(mContainerPanel)
|
|
mContainerPanel->setVisible(mDisplayChildren);
|
|
|
|
return LLUICtrl::postBuild();
|
|
}
|
|
bool LLAccordionCtrlTab::notifyChildren (const LLSD& info)
|
|
{
|
|
if(info.has("action"))
|
|
{
|
|
std::string str_action = info["action"];
|
|
if(str_action == "store_state")
|
|
{
|
|
storeOpenCloseState();
|
|
return true;
|
|
}
|
|
if(str_action == "restore_state")
|
|
{
|
|
restoreOpenCloseState();
|
|
return true;
|
|
}
|
|
}
|
|
return LLUICtrl::notifyChildren(info);
|
|
}
|
|
|
|
S32 LLAccordionCtrlTab::notifyParent(const LLSD& info)
|
|
{
|
|
if(info.has("action"))
|
|
{
|
|
std::string str_action = info["action"];
|
|
if(str_action == "size_changes")
|
|
{
|
|
//
|
|
S32 height = info["height"];
|
|
height = llmax(height,10) + HEADER_HEIGHT + getPaddingTop() + getPaddingBottom();
|
|
|
|
mExpandedHeight = height;
|
|
|
|
if(isExpanded())
|
|
{
|
|
LLRect panel_rect = getRect();
|
|
panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth(), height);
|
|
reshape(getRect().getWidth(),height);
|
|
setRect(panel_rect);
|
|
}
|
|
|
|
//LLAccordionCtrl should rearrange accordion tab if one of accordion change its size
|
|
if (getParent()) // A parent may not be set if tabs are added dynamically.
|
|
getParent()->notifyParent(info);
|
|
return 1;
|
|
}
|
|
else if(str_action == "select_prev")
|
|
{
|
|
showAndFocusHeader();
|
|
return 1;
|
|
}
|
|
}
|
|
else if (info.has("scrollToShowRect"))
|
|
{
|
|
LLAccordionCtrl* parent = dynamic_cast<LLAccordionCtrl*>(getParent());
|
|
if (parent && parent->getFitParent())
|
|
{
|
|
// EXT-8285 ('No attachments worn' text appears at the bottom of blank 'Attachments' accordion)
|
|
// The problem was in passing message "scrollToShowRect" IN LLAccordionCtrlTab::notifyParent
|
|
// FROM child LLScrollContainer TO parent LLAccordionCtrl with "it_parent" set to true.
|
|
|
|
// It is wrong notification for parent accordion which leads to recursive call of adjustContainerPanel
|
|
// As the result of recursive call of adjustContainerPanel we got LLAccordionCtrlTab
|
|
// that reshaped and re-sized with different rectangles.
|
|
|
|
// LLAccordionCtrl has own scrollContainer and LLAccordionCtrlTab has own scrollContainer
|
|
// both should handle own scroll container's event.
|
|
// So, if parent accordion "fit_parent" accordion tab should handle its scroll container events itself.
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (!getDisplayChildren())
|
|
{
|
|
// Don't pass scrolling event further if our contents are invisible (STORM-298).
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return LLUICtrl::notifyParent(info);
|
|
}
|
|
|
|
S32 LLAccordionCtrlTab::notify(const LLSD& info)
|
|
{
|
|
if(info.has("action"))
|
|
{
|
|
std::string str_action = info["action"];
|
|
if(str_action == "select_first")
|
|
{
|
|
showAndFocusHeader();
|
|
return 1;
|
|
}
|
|
else if( str_action == "select_last" )
|
|
{
|
|
if(getDisplayChildren() == false)
|
|
{
|
|
showAndFocusHeader();
|
|
}
|
|
else
|
|
{
|
|
LLView* view = getAccordionView();
|
|
if(view)
|
|
view->notify(LLSD().with("action","select_last"));
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
BOOL LLAccordionCtrlTab::handleKey(KEY key, MASK mask, BOOL called_from_parent)
|
|
{
|
|
if( !mHeader->hasFocus() )
|
|
return LLUICtrl::handleKey(key, mask, called_from_parent);
|
|
|
|
if ( (key == KEY_RETURN )&& mask == MASK_NONE)
|
|
{
|
|
changeOpenClose(getDisplayChildren());
|
|
return TRUE;
|
|
}
|
|
|
|
if ( (key == KEY_ADD || key == KEY_RIGHT)&& mask == MASK_NONE)
|
|
{
|
|
if(getDisplayChildren() == false)
|
|
{
|
|
changeOpenClose(getDisplayChildren());
|
|
return TRUE;
|
|
}
|
|
}
|
|
if ( (key == KEY_SUBTRACT || key == KEY_LEFT)&& mask == MASK_NONE)
|
|
{
|
|
if(getDisplayChildren() == true)
|
|
{
|
|
changeOpenClose(getDisplayChildren());
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if ( key == KEY_DOWN && mask == MASK_NONE)
|
|
{
|
|
//if collapsed go to the next accordion
|
|
if(getDisplayChildren() == false)
|
|
//we processing notifyParent so let call parent directly
|
|
getParent()->notifyParent(LLSD().with("action","select_next"));
|
|
else
|
|
{
|
|
getAccordionView()->notify(LLSD().with("action","select_first"));
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if ( key == KEY_UP && mask == MASK_NONE)
|
|
{
|
|
//go to the previous accordion
|
|
|
|
//we processing notifyParent so let call parent directly
|
|
getParent()->notifyParent(LLSD().with("action","select_prev"));
|
|
return TRUE;
|
|
}
|
|
|
|
return LLUICtrl::handleKey(key, mask, called_from_parent);
|
|
}
|
|
|
|
void LLAccordionCtrlTab::showAndFocusHeader()
|
|
{
|
|
mHeader->setFocus(true);
|
|
mHeader->setSelected(mSelectionEnabled);
|
|
|
|
LLRect screen_rc;
|
|
LLRect selected_rc = mHeader->getRect();
|
|
localRectToScreen(selected_rc, &screen_rc);
|
|
|
|
// This call to notifyParent() is intended to deliver "scrollToShowRect" command
|
|
// to the parent LLAccordionCtrl so by calling it from the direct parent of this
|
|
// accordion tab (assuming that the parent is an LLAccordionCtrl) the calls chain
|
|
// is shortened and messages from inside the collapsed tabs are avoided.
|
|
// See STORM-536.
|
|
getParent()->notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue()));
|
|
}
|
|
void LLAccordionCtrlTab::storeOpenCloseState()
|
|
{
|
|
if(mWasStateStored)
|
|
return;
|
|
mStoredOpenCloseState = getDisplayChildren();
|
|
mWasStateStored = true;
|
|
}
|
|
|
|
void LLAccordionCtrlTab::restoreOpenCloseState()
|
|
{
|
|
if(!mWasStateStored)
|
|
return;
|
|
if(getDisplayChildren() != mStoredOpenCloseState)
|
|
{
|
|
changeOpenClose(getDisplayChildren());
|
|
}
|
|
mWasStateStored = false;
|
|
}
|
|
|
|
void LLAccordionCtrlTab::adjustContainerPanel ()
|
|
{
|
|
S32 width = getRect().getWidth();
|
|
S32 height = getRect().getHeight();
|
|
|
|
LLRect child_rect;
|
|
child_rect.setLeftTopAndSize(
|
|
getPaddingLeft(),
|
|
height - getHeaderHeight() - getPaddingTop(),
|
|
width - getPaddingLeft() - getPaddingRight(),
|
|
height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() );
|
|
|
|
adjustContainerPanel(child_rect);
|
|
}
|
|
|
|
void LLAccordionCtrlTab::adjustContainerPanel(const LLRect& child_rect)
|
|
{
|
|
if(!mContainerPanel)
|
|
return;
|
|
|
|
if(!mFitPanel)
|
|
{
|
|
show_hide_scrollbar(child_rect);
|
|
updateLayout(child_rect);
|
|
}
|
|
else
|
|
{
|
|
mContainerPanel->reshape(child_rect.getWidth(),child_rect.getHeight());
|
|
mContainerPanel->setRect(child_rect);
|
|
}
|
|
}
|
|
|
|
S32 LLAccordionCtrlTab::getChildViewHeight()
|
|
{
|
|
if(!mContainerPanel)
|
|
return 0;
|
|
return mContainerPanel->getRect().getHeight();
|
|
}
|
|
|
|
void LLAccordionCtrlTab::show_hide_scrollbar(const LLRect& child_rect)
|
|
{
|
|
if(getChildViewHeight() > child_rect.getHeight() )
|
|
showScrollbar(child_rect);
|
|
else
|
|
hideScrollbar(child_rect);
|
|
}
|
|
void LLAccordionCtrlTab::showScrollbar(const LLRect& child_rect)
|
|
{
|
|
if(!mContainerPanel || !mScrollbar)
|
|
return;
|
|
bool was_visible = mScrollbar->getVisible();
|
|
mScrollbar->setVisible(true);
|
|
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
|
|
{
|
|
ctrlSetLeftTopAndSize(mScrollbar,child_rect.getWidth()-scrollbar_size,
|
|
child_rect.getHeight()-PARENT_BORDER_MARGIN,
|
|
scrollbar_size,
|
|
child_rect.getHeight()-2*PARENT_BORDER_MARGIN);
|
|
}
|
|
|
|
LLRect orig_rect = mContainerPanel->getRect();
|
|
|
|
mScrollbar->setPageSize(child_rect.getHeight());
|
|
mScrollbar->setDocParams(orig_rect.getHeight(),mScrollbar->getDocPos());
|
|
|
|
if(was_visible)
|
|
{
|
|
S32 scroll_pos = llmin(mScrollbar->getDocPos(), orig_rect.getHeight() - child_rect.getHeight() - 1);
|
|
mScrollbar->setDocPos(scroll_pos);
|
|
}
|
|
else//shrink child panel
|
|
{
|
|
updateLayout(child_rect);
|
|
}
|
|
|
|
}
|
|
|
|
void LLAccordionCtrlTab::hideScrollbar( const LLRect& child_rect )
|
|
{
|
|
if(!mContainerPanel || !mScrollbar)
|
|
return;
|
|
|
|
if(mScrollbar->getVisible() == false)
|
|
return;
|
|
mScrollbar->setVisible(false);
|
|
mScrollbar->setDocPos(0);
|
|
|
|
//shrink child panel
|
|
updateLayout(child_rect);
|
|
}
|
|
|
|
void LLAccordionCtrlTab::onScrollPosChangeCallback(S32, LLScrollbar*)
|
|
{
|
|
LLRect child_rect;
|
|
|
|
S32 width = getRect().getWidth();
|
|
S32 height = getRect().getHeight();
|
|
|
|
child_rect.setLeftTopAndSize(
|
|
getPaddingLeft(),
|
|
height - getHeaderHeight() - getPaddingTop(),
|
|
width - getPaddingLeft() - getPaddingRight(),
|
|
height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() );
|
|
|
|
updateLayout(child_rect);
|
|
}
|
|
|
|
void LLAccordionCtrlTab::drawChild(const LLRect& root_rect,LLView* child)
|
|
{
|
|
if (child && child->getVisible() && child->getRect().isValid())
|
|
{
|
|
LLRect screen_rect;
|
|
localRectToScreen(child->getRect(),&screen_rect);
|
|
|
|
if ( root_rect.overlaps(screen_rect) /*&& LLUI::sDirtyRect.overlaps(screen_rect)*/)// Singu TODO: LLUI::sDirtyRect
|
|
{
|
|
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
|
LLUI::pushMatrix();
|
|
{
|
|
LLUI::translate((F32)child->getRect().mLeft, (F32)child->getRect().mBottom);
|
|
child->draw();
|
|
|
|
}
|
|
LLUI::popMatrix();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::draw()
|
|
{
|
|
if(mFitPanel)
|
|
LLUICtrl::draw();
|
|
else
|
|
{
|
|
LLRect root_rect = getRootView()->getRect();
|
|
drawChild(root_rect,mHeader);
|
|
drawChild(root_rect,mScrollbar );
|
|
{
|
|
LLRect child_rect;
|
|
|
|
S32 width = getRect().getWidth();
|
|
S32 height = getRect().getHeight();
|
|
|
|
child_rect.setLeftTopAndSize(
|
|
getPaddingLeft(),
|
|
height - getHeaderHeight() - getPaddingTop(),
|
|
width - getPaddingLeft() - getPaddingRight(),
|
|
height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() );
|
|
|
|
LLLocalClipRect clip(child_rect);
|
|
drawChild(root_rect,mContainerPanel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLAccordionCtrlTab::updateLayout ( const LLRect& child_rect )
|
|
{
|
|
LLView* child = getAccordionView();
|
|
if(!mContainerPanel)
|
|
return;
|
|
|
|
S32 panel_top = child_rect.getHeight();
|
|
S32 panel_width = child_rect.getWidth();
|
|
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
if(mScrollbar && mScrollbar->getVisible() != false)
|
|
{
|
|
panel_top+=mScrollbar->getDocPos();
|
|
panel_width-=scrollbar_size;
|
|
}
|
|
|
|
//set sizes for first panels and dragbars
|
|
LLRect panel_rect = child->getRect();
|
|
ctrlSetLeftTopAndSize(mContainerPanel,child_rect.mLeft,panel_top,panel_width,panel_rect.getHeight());
|
|
}
|
|
void LLAccordionCtrlTab::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);
|
|
}
|
|
BOOL LLAccordionCtrlTab::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect)
|
|
{
|
|
//header may be not the first child but we need to process it first
|
|
if(y >= (getRect().getHeight() - HEADER_HEIGHT - HEADER_HEIGHT/2) )
|
|
{
|
|
//inside tab header
|
|
//fix for EXT-6619
|
|
mHeader->handleToolTip(x, y, msg, sticky_rect);
|
|
return TRUE;
|
|
}
|
|
return LLUICtrl::handleToolTip(x, y, msg, sticky_rect);
|
|
}
|
|
BOOL LLAccordionCtrlTab::handleScrollWheel ( S32 x, S32 y, S32 clicks )
|
|
{
|
|
if( LLUICtrl::handleScrollWheel(x,y,clicks))
|
|
{
|
|
return TRUE;
|
|
}
|
|
if( mScrollbar && mScrollbar->getVisible() && mScrollbar->handleScrollWheel( 0, 0, clicks ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//static
|
|
LLView* LLAccordionCtrlTab::fromXML(LLXMLNodePtr node, LLView* parent, LLUICtrlFactory* factory)
|
|
{
|
|
Params p;
|
|
// Singu TODO: Widgets folder for defaults instead of shoving into params here where noted
|
|
if (node->hasAttribute("title"))
|
|
{
|
|
std::string title;
|
|
node->getAttributeString("title", title);
|
|
p.title(title);
|
|
}
|
|
|
|
if (node->hasAttribute("expanded"))
|
|
{
|
|
bool display_children;
|
|
node->getAttribute_bool("expanded", display_children);
|
|
p.display_children(display_children);
|
|
}
|
|
|
|
if (node->hasAttribute("header_height"))
|
|
{
|
|
S32 header_height;
|
|
node->getAttributeS32("header_height", header_height);
|
|
p.header_height(header_height);
|
|
}
|
|
|
|
if (node->hasAttribute("min_width"))
|
|
{
|
|
S32 min_width;
|
|
node->getAttributeS32("min_width", min_width);
|
|
p.min_width(min_width);
|
|
}
|
|
|
|
if (node->hasAttribute("min_width"))
|
|
{
|
|
S32 min_height;
|
|
node->getAttributeS32("min_height", min_height);
|
|
p.min_height(min_height);
|
|
}
|
|
|
|
if (node->hasAttribute("collapsible"))
|
|
{
|
|
bool collapsible;
|
|
node->getAttribute_bool("collapsible", collapsible);
|
|
p.collapsible(collapsible);
|
|
}
|
|
|
|
if (node->hasAttribute("header_bg_color"))
|
|
{
|
|
LLColor4 color;
|
|
node->getAttributeColor("header_bg_color", color);
|
|
p.header_bg_color(color);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_bg_color(LLUI::sColorsGroup->getColor("ButtonUnselectedBgColor")); // was DkGray2
|
|
}
|
|
|
|
if (node->hasAttribute("dropdown_bg_color"))
|
|
{
|
|
LLColor4 color;
|
|
node->getAttributeColor("dropdown_bg_color", color);
|
|
p.dropdown_bg_color(color);
|
|
}
|
|
|
|
if (node->hasAttribute("header_visible"))
|
|
{
|
|
bool header_visible;
|
|
node->getAttribute_bool("header_visible", header_visible);
|
|
p.header_visible(header_visible);
|
|
}
|
|
|
|
if (node->hasAttribute("padding_left"))
|
|
{
|
|
S32 padding_left;
|
|
node->getAttributeS32("padding_left", padding_left);
|
|
p.padding_left(padding_left);
|
|
}
|
|
|
|
if (node->hasAttribute("padding_right"))
|
|
{
|
|
S32 padding_right;
|
|
node->getAttributeS32("padding_right", padding_right);
|
|
p.padding_right(padding_right);
|
|
}
|
|
|
|
if (node->hasAttribute("padding_top"))
|
|
{
|
|
S32 padding_top;
|
|
node->getAttributeS32("padding_top", padding_top);
|
|
p.padding_top(padding_top);
|
|
}
|
|
|
|
if (node->hasAttribute("padding_bottom"))
|
|
{
|
|
S32 padding_bottom;
|
|
node->getAttributeS32("padding_bottom", padding_bottom);
|
|
p.padding_bottom(padding_bottom);
|
|
}
|
|
|
|
if (node->hasAttribute("header_expand_img"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_expand_img", image);
|
|
p.header_expand_img.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_expand_img.name("Accordion_ArrowOpened_Off");
|
|
}
|
|
|
|
if (node->hasAttribute("header_expand_img_pressed"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_expand_img_pressed", image);
|
|
p.header_expand_img_pressed.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_expand_img_pressed.name("Accordion_ArrowOpened_Press");
|
|
}
|
|
|
|
if (node->hasAttribute("header_collapse_img"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_collapse_img", image);
|
|
p.header_collapse_img.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_collapse_img.name("Accordion_ArrowClosed_Off");
|
|
}
|
|
|
|
if (node->hasAttribute("header_collapse_img_pressed"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_collapse_img_pressed", image);
|
|
p.header_collapse_img_pressed.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_collapse_img_pressed.name("Accordion_ArrowClosed_Press");
|
|
}
|
|
|
|
if (node->hasAttribute("header_image"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_image", image);
|
|
p.header_image.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_image.name("Accordion_Off");
|
|
}
|
|
|
|
if (node->hasAttribute("header_image_over"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_image_over", image);
|
|
p.header_image_over.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_image_over.name("Accordion_Over");
|
|
}
|
|
|
|
if (node->hasAttribute("header_image_pressed"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_image_pressed", image);
|
|
p.header_image_pressed.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_image_pressed.name("Accordion_Press");
|
|
}
|
|
|
|
if (node->hasAttribute("header_image_focused"))
|
|
{
|
|
std::string image;
|
|
node->getAttributeString("header_image_focused", image);
|
|
p.header_image_focused.name(image);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_image_focused.name("Accordion_Selected");
|
|
}
|
|
|
|
if (node->hasAttribute("header_text_color"))
|
|
{
|
|
LLColor4 color;
|
|
node->getAttributeColor("header_text_color", color);
|
|
p.header_text_color(color);
|
|
}
|
|
else // widget
|
|
{
|
|
p.header_text_color(LLUI::sColorsGroup->getColor("ButtonLabelColor")); // AccordionHeaderTextColor
|
|
}
|
|
|
|
if (node->hasAttribute("fit_panel"))
|
|
{
|
|
bool fit_panel;
|
|
node->getAttribute_bool("fit_panel", fit_panel);
|
|
p.fit_panel(fit_panel);
|
|
}
|
|
|
|
if (node->hasAttribute("selection_enabled"))
|
|
{
|
|
bool selection_enabled;
|
|
node->getAttribute_bool("selection_enabled", selection_enabled);
|
|
p.selection_enabled(selection_enabled);
|
|
}
|
|
|
|
if (node->hasAttribute("font"))
|
|
{
|
|
std::string font;
|
|
node->getAttributeString("font", font);
|
|
p.font.name(font);
|
|
}
|
|
else // widget
|
|
{
|
|
p.font.name("SansSerif");
|
|
}
|
|
|
|
LLAccordionCtrlTab* ctrl = new LLAccordionCtrlTab(p);
|
|
ctrl->initFromXML(node, parent);
|
|
return ctrl;
|
|
}
|