/** * @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" 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 t1("accordion_tab"); class LLAccordionCtrlTab::LLAccordionCtrlTabHeader : public LLUICtrl { public: friend class LLUICtrlFactory; struct Params : public LLInitParam::Block { Params(); }; 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 mImageCollapsed; LLPointer mImageExpanded; LLPointer mImageCollapsedPressed; LLPointer mImageExpandedPressed; // Background images LLPointer mImageHeader; LLPointer mImageHeaderOver; LLPointer mImageHeaderPressed; LLPointer mImageHeaderFocused; // style saved when applying it in setTitleFontStyle 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) { LLTextBox::Params textboxParams; textboxParams.name(DD_TEXTBOX_NAME); textboxParams.initial_value(p.title()); textboxParams.text_color(p.header_text_color()); textboxParams.follows.flags(FOLLOWS_NONE); textboxParams.font( p.font() ); textboxParams.font_shadow(LLFontGL::NO_SHADOW); textboxParams.use_ellipses = true; textboxParams.bg_visible = false; textboxParams.mouse_opaque = false; textboxParams.parse_urls = false; mHeaderTextbox = LLUICtrlFactory::create(textboxParams); addChild(mHeaderTextbox); } LLAccordionCtrlTab::LLAccordionCtrlTabHeader::~LLAccordionCtrlTabHeader() { } bool LLAccordionCtrlTab::LLAccordionCtrlTabHeader::postBuild() { return true; } std::string LLAccordionCtrlTab::LLAccordionCtrlTabHeader::getTitle() { if (mHeaderTextbox) { return mHeaderTextbox->getText(); } return LLStringUtil::null; } void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitle(const std::string& title, const std::string& hl) { if (mHeaderTextbox) { LLTextUtil::textboxSetHighlightedVal( mHeaderTextbox, mStyleParams, title, hl); } } void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitleFontStyle(std::string style) { if (mHeaderTextbox) { std::string text = mHeaderTextbox->getText(); mStyleParams.font(mHeaderTextbox->getFont()); mStyleParams.font.style(style); mHeaderTextbox->setText(text, mStyleParams); } } 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(); gl_rect_2d(0, 0, width - 1, height - 1, mHeaderBGColor.get() % alpha, true); LLAccordionCtrlTab* parent = dynamic_cast(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 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(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; mSkipChangesOnNotifyParent = false; mDropdownBGColor = LLColor4::white; LLAccordionCtrlTabHeader::Params headerParams; headerParams.name(DD_HEADER_NAME); headerParams.title(p.title); mHeader = LLUICtrlFactory::create(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)); } // virtual void LLAccordionCtrlTab::onUpdateScrollToChild(const LLUICtrl *cntrl) { if (mScrollbar && mScrollbar->getVisible()) { LLRect rect; cntrl->localRectToOtherView(cntrl->getLocalRect(), &rect, this); // Translate to parent coordinatess to check if we are in visible rectangle rect.translate(getRect().mLeft, getRect().mBottom); if (!getRect().contains(rect)) { // for accordition's scroll, height is in pixels // Back to local coords and calculate position for scroller S32 bottom = mScrollbar->getDocPos() - rect.mBottom + getRect().mBottom; S32 top = mScrollbar->getDocPos() - rect.mTop + getRect().mTop; S32 scroll_pos = llclamp(mScrollbar->getDocPos(), bottom, // min vertical scroll top); // max vertical scroll mScrollbar->setDocPos(scroll_pos); } } LLUICtrl::onUpdateScrollToChild(cntrl); } 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(); } 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 (auto child : *getChildList()) { if (DD_HEADER_NAME != child->getName() && child->getVisible()) return child; } return nullptr; } 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 scrollbar_size("UIScrollbarSize", 0); LLRect scroll_rect; scroll_rect.setOriginAndSize( getRect().getWidth() - scrollbar_size, 1, scrollbar_size, getRect().getHeight() - 1); mContainerPanel = findContainerView(); if (!mFitPanel) { LLScrollbar::Params sbparams; sbparams.name("scrollable vertical"); sbparams.rect(scroll_rect); sbparams.orientation(LLScrollbar::VERTICAL); sbparams.doc_size(getRect().getHeight()); sbparams.doc_pos(0); sbparams.page_size(getRect().getHeight()); sbparams.step_size(VERTICAL_MULTIPLE); sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); sbparams.change_callback(boost::bind(&LLAccordionCtrlTab::onScrollPosChangeCallback, this, _1, _2)); mScrollbar = LLUICtrlFactory::create(sbparams); LLView::addChild(mScrollbar); mScrollbar->setFollowsRight(); mScrollbar->setFollowsTop(); mScrollbar->setFollowsBottom(); 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() && !mSkipChangesOnNotifyParent) { 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 accordions changed its size if (getParent()) // A parent may not be set if tabs are added dynamically. getParent()->notifyParent(info); return 1; } if (str_action == "select_prev") { showAndFocusHeader(); return 1; } } else if (info.has("scrollToShowRect")) { LLAccordionCtrl* parent = dynamic_cast(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; } if (str_action == "select_last") { if (!getDisplayChildren()) { 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()) { changeOpenClose(getDisplayChildren()); return true; } } if ((key == KEY_SUBTRACT || key == KEY_LEFT) && mask == MASK_NONE) { if (getDisplayChildren()) { changeOpenClose(getDisplayChildren()); return true; } } if (key == KEY_DOWN && mask == MASK_NONE) { // if collapsed go to the next accordion if (!getDisplayChildren()) { // we're 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're 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() { if (!mHeader) { return; } 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 scrollbar_size ("UIScrollbarSize", 0); ctrlSetLeftTopAndSize(mScrollbar, child_rect.getWidth() - scrollbar_size, child_rect.getHeight() - PARENT_BORDER_MARGIN, scrollbar_size, child_rect.getHeight() - PARENT_BORDER_MARGIN * 2); 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()) 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) && sDirtyRect.overlaps(screen_rect)) { 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 scrollbar_size("UIScrollbarSize", 0); if (mScrollbar && mScrollbar->getVisible()) { 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, MASK mask) { //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, mask); return true; } return LLUICtrl::handleToolTip(x, y, mask); } 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; }